How to create an HTTPS server on IOS using cocoaHTTPServer?

I made a javascript cloud app that runs on a webpage in a webview on my iPad app that communicates via WebSocket connection but it only works when im on my http site and not https or else I get an CFNetwork SSLHandshake failed (-9806) error in Xcode and on the website it says time out during handshake.

Is this because the webserver on the iPad is running on HTTP instead of HTTPS?

JAVASCRIPT CLOUD APP

This part in the cloud is working for HTTP when connecting to the web server on the iPad.

var protocol = "ws"; 
if (this.useSecureConnection) 
protocol = "wss"; 
var url = protocol+'://localhost:'+this.port+'/service';
this.connection = new WebSocket(url);

Xcode iOS iPad App (Objective-C)

I thought that was the issue so I tried to enable HTTPS but I am not sure what to create for the "sslIdentityAndCertificates" method.



- (BOOL)isSecureServer { 
    HTTPLogTrace();
    // Override me to create an https server...    
    return YES;      
} 
/* 
 * This method is expected to returns an array appropriate for use in kCFStreamSSLCertificates SSL Settings.
 * It should be an array of SecCertificateRefs except for the first element in the array, which is a SecIdentityRef. 
**/ 
- (NSArray *)sslIdentityAndCertificates{ 
    HTTPLogTrace(); 
    return nil; 
}



Some of the other posts I have seen use APIs that are only available on Mac and not iOS.

I tried several combinations of ATS permissions as well. All resulted in HTTPS not allowing for WebSocket connection.

Any help is greatly appreciated! 🙂



More Info:


The cloud hosted webapp was built to be used on different devices as a webpage but we needed to add support for bluetooth to connect to a 3rd party hardware. To do that we needed to create a native "wrapper" for the webapp that would get bluetooth messages and process/send messages to the webapp in the webview via webSocket. This allows for the web app to use the bluetooth tool.

Accepted Reply

You’re not going to be able to use a secure WebSocket connection to a local HTTPS server; the sticking point is that WKWebView provides no way to override the default (RFC 2818) TLS server trust evaluation it does for secure WebSocket connection, and you can’t get a system-trusted certificate for

localhost
. We already have a bug on file about this (r. 25491679) but you should feel free to file your own describing how this is affecting your app.

This is especially irksome because pages loaded over HTTPS must use a secure WebSocket.

My recommended way to get around this is to avoid WebSocket and instead use WKWebView’s extensive JavaScript-to-native integration technologies (namely WKUserScript and WKScriptMessage).

Finally, I have some other stuff I’d like to discuss privately; please drop me a line at my individual email address, quoting this DevForums thread for context.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

You’re not going to be able to use a secure WebSocket connection to a local HTTPS server; the sticking point is that WKWebView provides no way to override the default (RFC 2818) TLS server trust evaluation it does for secure WebSocket connection, and you can’t get a system-trusted certificate for

localhost
. We already have a bug on file about this (r. 25491679) but you should feel free to file your own describing how this is affecting your app.

This is especially irksome because pages loaded over HTTPS must use a secure WebSocket.

My recommended way to get around this is to avoid WebSocket and instead use WKWebView’s extensive JavaScript-to-native integration technologies (namely WKUserScript and WKScriptMessage).

Finally, I have some other stuff I’d like to discuss privately; please drop me a line at my individual email address, quoting this DevForums thread for context.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Dear Quinn,


We also have an iOS App that uses ws and have this problem. Do you have any information as to when this issue might be resolved ? This would help us decide if we need to change the implementation from wss to use the WKScriptMessage technology or not.


KRgds

ActimageDE

Do you have any information as to when this issue might be resolved?

No. As per standard Apple policy, I can’t talk about The Future™.

This would help us decide if we need to change the implementation from wss to use the WKScriptMessage technology or not.

Do you plan to ship in the next year or so? If so, it’s likely that iOS 10 will be a relevant target platform for you, and thus you need to worry about this issue.

Personally I think WebSockets is the wrong answer to the problem of web-to-native communication regardless of this issue. That’s because:

  • As WebSockets are a network technology, you have to deal with all the various errors that can crop up in networking code. WKScriptMessage does not go via the network, which means those problems simply can’t occur.

  • Networking technologies also have to worry about monitoring via the networking stack’s packet trace support (see the discussion of RVI in QA1176). You can get around this by using a secure WebSocket but, alas, that’s simply not possible as things currently stand. So, if you want to maintain a similar level of security, you’ll have to implement your own security on top of an insecure WebSocket.

    WKScriptMessage avoids this whole issue because it doesn’t communicate via the network, and thus is not seen by packet trace technologies.

  • You have to serialise and deserialise your requests to transfer them over the ‘wire’. Likewise, you have to frame and unframe the requests. WKScriptMessage makes the first problem significantly easier and avoids the second problem entirely.

    Note Framing is an absolute requirement if multiple requests go over the same WebSocket connection. It’s not necessarily required if you use a connection per request, but even there it’s a good idea (to detect truncation problems).

  • You have to maintain a WebSocket server on the native side, which is a bunch of complex code that you just don’t need with WKScriptMessage.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Dear Eskimo,


I was able to create an HTTPS server using https://github.com/robbiehanson/CocoaHTTPServer

How I resolve this certificate issue is

1. I created an SSL certificate using keychain access

2. That is successfully added to the keychain

3. I explicitly marked that certificate as trusted for this account so I can see the plus symbol on that certificate in the keychain

4. I exported certificate and named as TestCertificate.p12 and included in my project's bundle


5. I have made the changes in the code as to mentioned this as secure server

- (BOOL)isSecureServer
{
   return YES;
}


6. Change the method to provide this certificate



- (NSArray *)sslIdentityAndCertificates
{
    SecIdentityRef identityRef = NULL;
    SecCertificateRef certificateRef = NULL;
    SecTrustRef trustRef = NULL;

    NSString *thePath = [[NSBundle mainBundle] pathForResource:@"TestCertificate" ofType:@"p12"];
    NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:thePath];
    CFDataRef inPKCS12Data = (CFDataRef)CFBridgingRetain(PKCS12Data);
    CFStringRef password = CFSTR("test123");
    const void *keys[] = { kSecImportExportPassphrase };
    const void *values[] = { password };
    CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);

    OSStatus securityError = errSecSuccess;
    securityError =  SecPKCS12Import(inPKCS12Data, optionsDictionary, &items);
    if (securityError == 0) {
        CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex (items, 0);
        const void *tempIdentity = NULL;
        tempIdentity = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemIdentity);
        identityRef = (SecIdentityRef)tempIdentity;
        const void *tempTrust = NULL;
        tempTrust = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust);
        trustRef = (SecTrustRef)tempTrust;
    } else {
        NSLog(@"Failed with error code %d",(int)securityError);
        return nil;
    }

    SecIdentityCopyCertificate(identityRef, &certificateRef);
    NSArray *result = [[NSArray alloc] initWithObjects:(id)CFBridgingRelease(identityRef), (id)CFBridgingRelease(certificateRef), nil];

    return result;
}


7. When the HTTPS is started the connection the certificate included in the bundle will be used as below



- (void)startConnection
{
  /
  /
  /

  HTTPLogTrace();

  if ([self isSecureServer])
  {
  /
  /

  NSArray *certificates = [self sslIdentityAndCertificates];

  if ([certificates count] > 0)
  {
  /
  NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:3];

  /
  [settings setObject:[NSNumber numberWithBool:YES]
  forKey:(NSString *)kCFStreamSSLIsServer];

  [settings setObject:certificates
  forKey:(NSString *)kCFStreamSSLCertificates];

  /
  [settings setObject:(NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL
  forKey:(NSString *)kCFStreamSSLLevel];

  [asyncSocket startTLS:settings];
  }
  }

  [self startReadingRequest];
}


8. It is working as expected and I was able to serve the requests coming from my WKwebview

9. This certificate is only for development. For production, I will create the SSL certificate from my developer account which will be verified by Apple.

10. My question is, Will Apple approve this when I ship my app to Appstore?

I was able to create an HTTPS server using


(https://github.com/robbiehanson/CocoaHTTPServer)

I faced the same issue related to a certificate and I have resolved below but I want to know it will not be rejected in the Appstore.


1. Created an SSL certificate using keychain access

2. That is successfully added in the keychain

3. I explicitly marked that certificate as trusted one fro this account so I can see the plus symbol on that certificate

4. I exported this certificate from keychain and named as TestCertificate.p12 and included in the app’s bundle

5. I have made the changes in the code as below to mentioned this as secured server

6. Changed the method to provide the certificate

* Note: In order to support secure connections, the sslIdentityAndCertificates method must be implemented.

**/
- (BOOL)isSecureServer
{
  HTTPLogTrace();

  return YES;
}

7. Changed this method to provide the certificate for the secured connection which is available in the app’s bundle named “TestCertificate”

8. When the HTTPS server is started the certificate will be used as below

- (NSArray *)sslIdentityAndCertificates
{
    SecIdentityRef identityRef = NULL;
    SecCertificateRef certificateRef = NULL;
    SecTrustRef trustRef = NULL;

    NSString *thePath = [[NSBundle mainBundle] pathForResource:@"TestCertificate" ofType:@"p12"];
    NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:thePath];
    CFDataRef inPKCS12Data = (CFDataRef)CFBridgingRetain(PKCS12Data);
    CFStringRef password = CFSTR("test123");
    const void *keys[] = { kSecImportExportPassphrase };
    const void *values[] = { password };
    CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);

    OSStatus securityError = errSecSuccess;
    securityError =  SecPKCS12Import(inPKCS12Data, optionsDictionary, &items);
    if (securityError == 0) {
        CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex (items, 0);
        const void *tempIdentity = NULL;
        tempIdentity = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemIdentity);
        identityRef = (SecIdentityRef)tempIdentity;
        const void *tempTrust = NULL;
        tempTrust = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust);
        trustRef = (SecTrustRef)tempTrust;
    } else {
        NSLog(@"Failed with error code %d",(int)securityError);
        return nil;
    }

    SecIdentityCopyCertificate(identityRef, &certificateRef);
    NSArray *result = [[NSArray alloc] initWithObjects:(id)CFBridgingRelease(identityRef), (id)CFBridgingRelease(certificateRef), nil];

    return result;
}

9. Server started successfully and when I start the request from web view to my local HTTPS server I received the authentication challenge I resolved this as below

- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    /
    SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
    CFDataRef exceptions = SecTrustCopyExceptions (serverTrust);
    SecTrustSetExceptions (serverTrust, exceptions);
    CFRelease (exceptions);
    completionHandler (NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:serverTrust]);
}

10. My questions are below

> Does Apple reject the app which runs an HTTPS server inside of the application?

> If Apple doesn’t reject then can I use the COCOAHTTPServer library ?(https://github.com/robbiehanson/CocoaHTTPServer)

> In the above implementation, Whether the SSL certificate enough for production?

> If not, then can I use any development/ Appstore certificate created in member centre?

> If not then how can I get a certificate in order to run the HTTPS server on the iPhone?

There is LocalCan app that will create certificates for you. You only need to trust the Root Certificate by either dragging it onto iOS simulator or AirDrop to your iOS device. It works with WSS.