OpenSSL server not receiving requests for media (a

2019-09-19 05:52发布

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?

1条回答
迷人小祖宗
2楼-- · 2019-09-19 06:22

We had a httpListener on "http://+:13333", but nothings happen when we load our htmlString in WKWebView with this content "http://localhost:13333".

Our solution was to replace the "localhost" with "127.0.0.1".

Maybe it is a genaral problem by WKWebView?!

查看更多
登录 后发表回答