SSL Socket connection iOS

2019-03-11 08:46发布

问题:

I am trying to setup a secure connection to a java run SSLServerSocket.

I have created my own root CA, and have signed the certificate that the Java SSLServerSocket using this certificate.

I want to add this root certificate to my app so that any certificate signed by the root certificate will work.

So far I have the secure connection working fine by setting the read and write stream properties to this:

NSDictionary *settings = [[NSDictionary alloc] initWithObjectsAndKeys:
(id)kCFStreamSocketSecurityLevelNegotiatedSSL, kCFStreamPropertySocketSecurityLevel,
[NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates,
[NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredRoots,
[NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain,nil];

I add the certificate to the keychain like this:

-(void)addRootCert{
NSString* rootCertPath = [[NSBundle mainBundle] pathForResource:@"rootCA" ofType:@"der"];
NSData* rootCertData = [NSData dataWithContentsOfFile:rootCertPath];

OSStatus err = noErr;
SecCertificateRef rootCert = SecCertificateCreateWithData(kCFAllocatorDefault, (__bridge CFDataRef)rootCertData);
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge_transfer id)kSecClassCertificate, kSecClass, rootCert, kSecValueRef, nil];

err = SecItemAdd((__bridge CFDictionaryRef) dict, NULL);
if (err == noErr) {
    NSLog(@"Sucessfully added root certificate");
}else if (err == errSecDuplicateItem){
    NSLog(@"Root certificate already exists");
}else{
    NSLog(@"Root certificate add failed");
}
}

This is fine but I want to validate the certificate chain, so that my app only accepts certificates signed by my CA (or the default trusted ones)

How can I do this?

If I set kCFStreamSSLValidatesCertificateChain to yes, I get the error: CFNetwork SSLHandshake failed (-9807) but if it's no, it doesn't matter who signed the server certificate, it will connect regardless (I assume that's right?)

Thanks!

回答1:

Technote 2232, "HTTPS Server Trust Evaluation", should have all the answers you need. There is documentation and several examples of how to evaluate server trust.



回答2:

I had this same problem, and while adding the certificate manually does fix the issue, that also means having to update the app every time the certificate on the server changes (such as when it expires, which, in my case, was going to happen in a matter of days).

If you're using an IP address to connect to the socket, and the certificate is for the FQDN (fully qualified domain name, i.e. subdomain.example.com), then when the operating system checks the certificate, it is going to look at the IP address you're connecting to, compare it to the name in the certificate, and think the two are different, causing the chain to fail validation.

So for anyone else who runs into this problem, I recommend using the FQDN in the second parameter to CFStreamCreatePairWithSocketToHost rather than the IP address. After that, it should work without having to include the certificate in the bundle and manually validate.