The goal we’re trying to accomplish is to load a local video asset (stored on disk) into a WKWebView instance, to be used as a texture in WebGL. Up until now, we have been accomplishing this using a server bound to localhost (GCDWebServer), and loading the local source code as an HTML string with (in this example,) baseURL:"http://localhost:8989/", and then playing the video with the following line of code:
<video src="test.mp4" width="320" height="240" preload="auto" playsinline autoplay muted></video>
However, with imminent changes to Apple’s ATS policy, we now need this to happen over HTTPS. Our new server implementation is based on OpenSSL and is included below:
#import "SSLServer.h"
#import "Logging.h"
#import "Util.h"
#import "SSLServerResponse.h"
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <resolv.h>
#include "openssl/ssl.h"
#include "openssl/err.h"
#define FAIL -1
#define SSL_SERVER_UPDATE_INTERVAL 0.0f
@interface SSLServer ()
{
BOOL _keepAlive;
NSUInteger _port;
NSString* _directoryPath;
NSString* _certFilepath;
SSLServerStartCompletionHandler _startCompletionHandler;
}
@end
@implementation SSLServer
-(instancetype)initWithPort:(NSUInteger)port directoryPath:(NSString*)directoryPath andCertFilepath:(NSString*)certFilepath {
self = [super init];
if (self) {
_keepAlive = YES;
_port = 8989; //port;
_certFilepath = certFilepath ? certFilepath : @"";
_directoryPath = directoryPath;
}
return self;
}
-(void)startWithCompletionHandler:(SSLServerStartCompletionHandler)handler {
_startCompletionHandler = handler;
SSL_CTX *ctx;
int server;
SSL_library_init();
//Initialize SSL
ctx = [self initServerCTX];
if (ctx == NULL) {
LogInfoPrivate(@"[SSLServer] : Failed to create SSL context for some reason");
return;
}
//Load certs
[self loadCertificates:ctx certFile:_certFilepath keyFile:_certFilepath];
//Create server socket
server = [self openListener:_port];
if (server == -1) {
_startCompletionHandler(NO, (NSUInteger)_port);
} else {
_startCompletionHandler(YES, (NSUInteger)_port);
}
_startCompletionHandler = nil;
while (_keepAlive) {
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
SSL *ssl;
LogInfoPrivate(@"[SSLServer] : Listening on port: %lu", (unsigned long)_port);
//Accept connection as usual
int client = accept(server, (struct sockaddr*)&addr, &len);
LogInfoPrivate(@"[SSLServer] : Connection: %s:%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
LogInfoPrivate(@"[SSLServer] : Port:%lu\n", (unsigned long)_port);
//Get new SSL state with context
ssl = SSL_new(ctx);
//Set connection socket to SSL state
SSL_set_fd(ssl, client);
//Service connection
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self servlet:ssl directoryPath:_directoryPath];
});
[NSThread sleepForTimeInterval:SSL_SERVER_UPDATE_INTERVAL];
}
//Close server socket
close(server);
//Release context
SSL_CTX_free(ctx);
}
-(int)openListener:(NSUInteger)port {
int sock;
struct sockaddr_in addr;
sock = socket(PF_INET, SOCK_STREAM, 0);
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons((unsigned long)port);
addr.sin_addr.s_addr = INADDR_ANY;
socklen_t len = sizeof(addr);
if (bind(sock, (struct sockaddr*)&addr, len) != 0) {
LogErrorPrivate(@"[SSLServer] : Can't bind port");
return -1;
}
if (listen(sock, 10) != 0) {
LogErrorPrivate(@"[SSLServer] : Can't configure listening port");
return -1;
}
// when sin_port is init'd as '0', socket lib will randomize the port, so, we grab the bound port here.
if (port == 0) {
if (getsockname(sock, (struct sockaddr *)&addr, &len) == -1) {
LogErrorPrivate(@"[SSLServer] : Could not retrieve random port number");
return -1;
} else {
_port = ntohs(addr.sin_port);
}
}
return sock;
}
-(SSL_CTX*)initServerCTX {
const SSL_METHOD *method;
SSL_CTX *ctx;
//Load & register all cryptos, etc.
OpenSSL_add_all_algorithms();
//Load all error 4messages
SSL_load_error_strings();
//Create new server-method instance
method = TLSv1_2_server_method();
//Create new context from method
ctx = SSL_CTX_new(method);
if (ctx == NULL) {
ERR_print_errors_fp(stderr);
return NULL;
}
return ctx;
}
-(void)loadCertificates:(SSL_CTX*)ctx certFile:(NSString*)certFile keyFile:(NSString*)keyFile {
//Set the local certificate from CertFile
if (SSL_CTX_use_certificate_file(ctx, [certFile UTF8String], SSL_FILETYPE_PEM) <= 0 ) {
ERR_print_errors_fp(stderr);
return;
}
//Set the private key from KeyFile (may be the same as CertFile)
if (SSL_CTX_use_PrivateKey_file(ctx, [keyFile UTF8String], SSL_FILETYPE_PEM) <= 0 ) {
ERR_print_errors_fp(stderr);
return;
}
//Verify private key
if (!SSL_CTX_check_private_key(ctx) ) {
LogErrorPrivate(@"[SSLServer] : Private key does not match the public certificate");
return;
}
}
-(void)showCerts:(SSL*)ssl {
X509 *cert;
char *line;
//Get certificates (if available)
cert = SSL_get_peer_certificate(ssl);
if (cert != NULL) {
LogInfoPrivate(@"[SSLServer] : Server certificates:");
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
LogInfoPrivate(@"[SSLServer] : Subject: %s", line);
free(line);
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
LogInfoPrivate(@"[SSLServer] : Issuer: %s", line);
free(line);
X509_free(cert);
} else {
LogInfoPrivate(@"[SSLServer] : No certificates.");
}
}
//Serve the connection
-(void)servlet:(SSL*)ssl directoryPath:(NSString*)directoryPath {
char buf[1024];
int sd, bytes;
//Do SSL-protocol accept
if (SSL_accept(ssl) == FAIL) {
ERR_print_errors_fp(stderr);
} else {
//Get any certificates
[self showCerts:ssl];
//Get request
bytes = SSL_read(ssl, buf, sizeof(buf));
if (bytes > 0) {
buf[bytes] = 0;
LogInfoPrivate(@"[SSLServer] Client msg: \"%s\"", buf);
NSArray* requestElements = [[NSString stringWithUTF8String:buf] componentsSeparatedByString:@" "];
NSString* resource = [requestElements[1] lastPathComponent];
NSString* resourcePath = [directoryPath stringByAppendingPathComponent:resource];
LogInfoPrivate(@"[SSLServer] Resource path: %@", resource);
//Configure the response
SSLServerResponse* response = [[SSLServerResponse alloc] initWithResourcePath:resourcePath chunked:YES];
[response configure];
//Write the response
for (NSData* data in response.payload) {
SSL_write(ssl, (const char*)[data bytes], (int)[data length]);
}
} else {
LogInfoPrivate(@"[SSLServer] : Nothing to send back");
ERR_print_errors_fp(stderr);
}
}
//Get socket connection
sd = SSL_get_fd(ssl);
//Release SSL state
SSL_free(ssl);
//Close connection
close(sd);
}
-(void)finish {
_keepAlive = NO;
}
@end
The problem we’re encountering is that images (.png) and text are served correctly; however, audio and video are not. We’ve been monitoring the network traffic in Charles and no requests are even coming out of the web view. More specifically, apart from GET requests for images and javascript, no communication occurs on the socket. For images and text, we see the requests in Charles and the web view is getting the authentication challenge callback. We have configured our WKWebViewConfiguration by setting ‘allowsInlineMediaPlayback’ to ‘YES’ and ‘mediaPlaybackRequiresUserAction’ to ‘NO’. Can someone please explain why only some requests are being fired from the web view?