KattenFodder
KattenFodder

Reputation: 11

Ocj-C Secure Sockets Server with real cert - SSL Protocol Error (GCDAsyncSockets based)

I've been racking over an odd issue - something that appears to be unspoken of (or termed quite differently). Suprisingly, I cannot find a HTTPS example - everything I find has this 'todo'!

In brief:

I'm trying to create a 'server' that uses TLS, using a valid cert.

Issue:

Once I import certificate successfully, setup settings for GCDAsyncSockets, then call startTLS, the connection will immediately drop with 'SecureTransport error -9800' - Chrome calls this 'ERR_SSL_PROTOCOL_ERROR'

Test:

  1. Run server.
  2. Connect via Chrome or Safari (https://localhost or https://localhost:somefreeport).
  3. Try to observe page request from data received.
  4. Observe failure at handshake stage.

Chrome is my chosen test-bed as this exercise will later handle WebSocket traffic - thus I use a valid cert, not self-signed.

Long desc:

Using GCDAsyncSocket to listen on a port, then once I receive a connection, I take that socket and 'startTLS' to handle it. (I'm not exclusive to using GCDAsyncSocket, it's simply that I'm familiar with it in client socket use cleartext & TLS, and server cleartext - it's only server TLS getting me stumped).

The certificate is a signed cert with a known public CA (Comodo or GoDaddy) - I use it already successfully in .Net The source below will successfully open the cert and report back the peername. .Net however, simplifies a lot of this, so not trivial to compare. (load cert with pass into an x509 object, then apply to the new socket. Whereas here, I have to define ciphers and SSL versions too).

Simplified code example:

- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{   
    //Get certificate
    NSString* p12Path = [[NSBundle bundleForClass:[self class]] pathForResource:@"mySignedCert"
                                                                         ofType:@"pfx"];
    CFDataRef inPKCS12Data = (CFDataRef)CFBridgingRetain([[NSData alloc] initWithContentsOfFile:p12Path]);

    CFStringRef password = CFSTR("myCertPassword"); //Cert Password
    const void *keys[] = { kSecImportExportPassphrase };
    const void *values[] = { password };
    CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);

    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);

    OSStatus securityError = SecPKCS12Import(inPKCS12Data, options, &items);
    CFRelease(options);
    CFRelease(password);

    if(securityError == errSecSuccess)
        NSLog(@"Success opening p12 certificate."); //All good!
    else
        return nil;

    CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
    SecIdentityRef myIdent = (SecIdentityRef)CFDictionaryGetValue(identityDict,
                                                                  kSecImportItemIdentity);

    SecIdentityRef  certArray[1] = { myIdent };
    CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL);

    //Build TLS settings dictionary
    [self willSecureWithSettings:settings]; //This just uses SSLGetNumberSupportedCiphers & SSLGetSupportedCiphers to create a cipher array and appends to settings
    settings[(NSString *)kCFStreamSSLCertificates] = (__bridge id)(myCerts);
    settings[(NSString *)kCFStreamSSLPeerName] = @"my.peer.address";

    /* Valid values for protocol types
     kSSLProtocolUnknown = 0,               no protocol negotiated/specified; use default
    kSSLProtocol3       = 2,                SSL 3.0
    kTLSProtocol1       = 4,                TLS 1.0
    kTLSProtocol11      = 7,                TLS 1.1
    kTLSProtocol12      = 8,                TLS 1.2
    kDTLSProtocol1      = 9,                DTLS 1.0
     */

    //Tried 0,2,4,7,8 - 9 throws another error, possibly unsupported.
    settings[GCDAsyncSocketSSLProtocolVersionMin] = [NSNumber numberWithInteger:8];
    settings[GCDAsyncSocketSSLProtocolVersionMax] = [NSNumber numberWithInteger:8];

    [newSocket startTLS:settings];
    [newSocket readDataWithTimeout:-1 tag:0];
}

I've found many old examples, none are applicable anymore (without using an older GCDAsyncSocket or older Xcode). The closest I found made mention of the later 'GCDAsyncSocketSSLProtocolVersionMin/Max' set to 2 which is unsupported in Chrome for example.

Has anyone been successful and willing to share some pointers?

Kind regards, KF.

Upvotes: 0

Views: 526

Answers (1)

KattenFodder
KattenFodder

Reputation: 11

Wow - I spent a decent time working this out, then reached out here for tips.

Figured it 10 mins after!

My changes:

//THIS! Tried prior, but GCDAsyncSocket docs stated not-needed, plus was getting different handshake errors.
settings[(NSString *)kCFStreamSSLIsServer] = [NSNumber numberWithBool:YES];

Other changes that other SO users may find useful:

//Apparently prefereable
settings[(NSString*)kCFStreamPropertyShouldCloseNativeSocket] = [NSNumber numberWithBool:YES];
//Above example was both matching for test. 4-8 was always preferable.
settings[GCDAsyncSocketSSLProtocolVersionMin] = [NSNumber numberWithInteger:4];
settings[GCDAsyncSocketSSLProtocolVersionMax] = [NSNumber numberWithInteger:8];

Thanks SO - even if you were for Rubber Ducking!

Upvotes: 1

Related Questions