I myself use a special certificate to verify several servers used by our messaging application in development mode.
If you have access to the p12 file (the included private key and therefore the signed identifier), you can check the server certificate with kCFStreamSSLCertificates
Otherwise (in the case of only the public key) you have the opportunity to check through the same name kCFStreamSSLPeerName.
In your piece of code, one thing you are doing wrong is how you deliver certificates to the GCDAsyncSocket module. and therefore find the error you were talking about.
The correct way is as follows:
NSArray *myCerts = [[NSArray alloc] initWithObjects:(__bridge id)identity1, (__bridge id)myReturnedCertificate1, nil]; [settings setObject:myCerts forKey:(NSString *)kCFStreamSSLCertificates];
According to the documentation ID, Apple is required when using kCFStreamSSLCertificates:
You must specify a secIdentityRef object in certRefs [0] that identifies the certificate of the sheet and its corresponding private key. Specifying a root certificate is optional;
Detailed information:
The following are the steps to follow if you are using custom CA certificate certificates. Please note: the example is based on GCDAsyncSocket
- Store your public part certificate in the application resource pack.
- Read the above certificate and add the certificate to the keychain
- Implement delegate function
(void) socket: (GCDAsyncSocket *) sock didConnectToHost: (NSString *) host port: (uint16_t) port;
Inside this function, specify your certificate in GCDAsyncSocket
NSArray *myCerts = [[NSArray alloc] initWithObjects:(__bridge id)identity1, (__bridge id)myReturnedCertificate1, nil]; [settings setObject:myCerts forKey:(NSString *)kCFStreamSSLCertificates];
Use YES (not recommended) or NO below, based on the fact that you want to check trust manually?
[settings setObject:[NSNumber numberWithBool:YES] forKey:GCDAsyncSocketManuallyEvaluateTrust];
- If you decide to manually verify trust, override the following delegation method.
(void) socket: (GCDAsyncSocket *) sock didReceiveTrust: (SecTrustRef) trust completeHandler: (void (^) (BOOL shouldTrustPeer)) completeHandler
As part of this function, you should read all the certificates from the trust and try to reconcile with the certificate that you provided to the application.
Code example:
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port; { // Configure SSL/TLS settings NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:3]; // get the certificates as data for further operations SecIdentityRef identity1 = nil; SecTrustRef trust1 = nil; NSData *certData1 = [[NSData alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"[Dev] InHouse_Certificates" ofType:@"p12"]]; CFDataRef myCertData1 = (__bridge_retained CFDataRef)(certData1); [self extractIdentityAndTrust:myCertData1 withIdentity:&identity1 withTrust:&trust1 withPassword:CFSTR("1234")]; NSString* summaryString1 = [self copySummaryString:&identity1]; SecIdentityRef identity2 = nil; SecTrustRef trust2 = nil; NSData *certData2 = [[NSData alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"[Dis] InHouse_Certificates" ofType:@"p12"]]; CFDataRef myCertData2 = (__bridge_retained CFDataRef)(certData2); [self extractIdentityAndTrust:myCertData2 withIdentity:&identity2 withTrust:&trust2 withPassword:CFSTR("1234")]; NSString* summaryString2 = [self copySummaryString:&identity2]; // if data exists, use it if(myCertData1 && myCertData2) { //Delete if already exist. Just temporary SecItemDelete((__bridge CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys: (__bridge id)(kSecClassKey), kSecClass, (__bridge id)kSecAttrKeyTypeRSA, kSecAttrKeyType, (__bridge id)kSecAttrKeyClassPublic, kSecAttrKeyClass, kCFBooleanTrue, kSecAttrIsPermanent, [summaryString1 dataUsingEncoding:NSUTF8StringEncoding], kSecAttrApplicationTag, certData1, kSecValueData, kCFBooleanTrue, kSecReturnPersistentRef, nil]); OSStatus status1 = SecItemAdd((__bridge CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys: (__bridge id)(kSecClassKey), kSecClass, (__bridge id)kSecAttrKeyTypeRSA, kSecAttrKeyType, (__bridge id)kSecAttrKeyClassPublic, kSecAttrKeyClass, kCFBooleanTrue, kSecAttrIsPermanent, [summaryString1 dataUsingEncoding:NSUTF8StringEncoding], kSecAttrApplicationTag, certData1, kSecValueData, kCFBooleanTrue, kSecReturnPersistentRef, nil], NULL); //don't need public key ref // Setting "cer" is successfully and delivers "noErr" in first run, then "errKCDuplicateItem" NSLog(@"evaluate with status %d", (int)status1); //Delete if already exist. Just temporary SecItemDelete((__bridge CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys: (__bridge id)(kSecClassKey), kSecClass, (__bridge id)kSecAttrKeyTypeRSA, kSecAttrKeyType, (__bridge id)kSecAttrKeyClassPublic, kSecAttrKeyClass, kCFBooleanTrue, kSecAttrIsPermanent, [summaryString2 dataUsingEncoding:NSUTF8StringEncoding], kSecAttrApplicationTag, certData2, kSecValueData, kCFBooleanTrue, kSecReturnPersistentRef, nil]); //NSString *name2 = [NSString stringWithUTF8String:CFStringGetCStringPtr(SecCertificateCopySubjectSummary(cert2), kCFStringEncodingUTF8)]; OSStatus status2 = SecItemAdd((__bridge CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys: (__bridge id)(kSecClassKey), kSecClass, (__bridge id)kSecAttrKeyTypeRSA, kSecAttrKeyType, (__bridge id)kSecAttrKeyClassPublic, kSecAttrKeyClass, kCFBooleanTrue, kSecAttrIsPermanent, [summaryString2 dataUsingEncoding:NSUTF8StringEncoding], kSecAttrApplicationTag, certData2, kSecValueData, kCFBooleanTrue, kSecReturnPersistentRef, nil], NULL); //don't need public key ref NSLog(@"evaluate with status %d", (int)status2); SecCertificateRef myReturnedCertificate1 = NULL; OSStatus status3 = SecIdentityCopyCertificate (identity1, &myReturnedCertificate1); SecCertificateRef myReturnedCertificate2 = NULL; OSStatus status4 = SecIdentityCopyCertificate (identity2, &myReturnedCertificate2); NSArray *myCerts = [[NSArray alloc] initWithObjects:(__bridge id)identity1, (__bridge id)myReturnedCertificate1, nil]; [settings setObject:myCerts forKey:(NSString *)kCFStreamSSLCertificates]; // Allow self-signed certificates [settings setObject:[NSNumber numberWithBool:YES] forKey:GCDAsyncSocketManuallyEvaluateTrust]; [sock startTLS:settings]; } }
If for some reason you decide to evaluate trust manually.
- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler { dispatch_queue_t bgQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(bgQueue, ^{ // This is where you would (eventually) invoke SecTrustEvaluate. SecIdentityRef identity1 = nil; SecTrustRef trust1 = nil; NSData *certData1 = [[NSData alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"[Dev] InHouse_Certificates" ofType:@"p12"]]; CFDataRef myCertData1 = (__bridge_retained CFDataRef)(certData1); [self extractIdentityAndTrust:myCertData1 withIdentity:&identity1 withTrust:&trust1 withPassword:CFSTR("1234")]; SecIdentityRef identity2 = nil; SecTrustRef trust2 = nil; NSData *certData2 = [[NSData alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"[Dis] InHouse_Certificates" ofType:@"p12"]]; CFDataRef myCertData2 = (__bridge_retained CFDataRef)(certData2); [self extractIdentityAndTrust:myCertData2 withIdentity:&identity2 withTrust:&trust2 withPassword:CFSTR("1234")]; if(myCertData1 && myCertData2) { CFArrayRef arrayRefTrust = SecTrustCopyProperties(trust); SecTrustResultType result = kSecTrustResultUnspecified; // usualy should work already here OSStatus status = SecTrustEvaluate(trust, &result); NSLog(@"evaluate with result %d and status %d", result, (int)status); NSLog(@"trust properties: %@", arrayRefTrust); /* log: evaluate with result 5 and status 0 trust properties: ( { type = error; value = "Root certificate is not trusted."; // expected, when top part was not working } */ SecCertificateRef myReturnedCertificate1 = NULL; OSStatus status3 = SecIdentityCopyCertificate (identity1, &myReturnedCertificate1); SecCertificateRef myReturnedCertificate2 = NULL; OSStatus status4 = SecIdentityCopyCertificate (identity2, &myReturnedCertificate2); const void *ref[] = {myReturnedCertificate1}; CFIndex count = SecTrustGetCertificateCount(trust); // CFMutableArrayRef aryRef = CFArrayCreateMutable(NULL, count + 1, NULL); // CFArrayAppendValue(aryRef, ref); CFArrayCreate(NULL, ref, 2, NULL); //
Update:
According to Apple documentation:
You must specify a secIdentityRef object in certRefs [0] that identifies the certificate of the sheet and its corresponding private key. Specifying a root certificate is optional;
As suggested by Apple, if you use the in.cer certificate format, you must map both certificates using a peer domain name (fully qualified domain name).
You can use this function to check the common field name in peers. If you call this function and the common name in the certificate does not match the value you specified in the peerName parameter, then the confirmation message is not executed and returns errSSLXCertChainInvalid. Using this function is optional.