我自己使用自定义证书来验证我们的消息应用程序在开发模式下使用的多个服务器。
如果您有权访问 p12(包括私钥和签名身份)文件,您可以使用 kCFStreamSSLCertificates 验证服务器证书
否则(如果只是公钥)您可以选择通过对等名称 kCFStreamSSLPeerName 进行验证。
在您的代码片段中,您做错的一件事是如何向 GCDAsyncSocket 模块提供证书。从而找到您提到的错误。
正确的方法如下:
NSArray *myCerts = [[NSArray alloc] initWithObjects:(__bridge id)identity1, (__bridge id)myReturnedCertificate1, nil];
[settings setObject:myCerts forKey:(NSString *)kCFStreamSSLCertificates];
根据 Apple 文档,使用 kCFStreamSSLCertificates 时身份是强制性的:
您必须在 certRefs[0] 中放置一个 SecIdentityRef 对象来标识
叶证书及其相应的私钥。指定一个
根证书是可选的;
完整详细信息:
如果您使用自定义签名的 CA 证书,请遵循以下步骤。
请注意:示例基于 GCDAsyncSocket
- 将您的公共部分证书保存在应用程序资源包中。
- 读取上面的证书并将证书添加到钥匙串中
- 实现委托功能-
(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host
端口:(uint16_t)端口;
在此函数中,向 GCDAsyncSocket 提供您的证书
NSArray *myCerts = [[NSArray alloc] initWithObjects:(__bridge id)identity1, (__bridge id)myReturnedCertificate1, nil];
[settings setObject:myCerts forKey:(NSString *)kCFStreamSSLCertificates];
根据您是否要手动验证信任,在下面使用“是”(不推荐)或“否”?
[settings setObject:[NSNumber numberWithBool:YES]
forKey:GCDAsyncSocketManuallyEvaluateTrust];
- 如果您选择手动验证信任,请覆盖以下委托方法。
(void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust
完成处理程序:(void (^)(BOOL shouldTrustPeer))completionHandler
在此功能中,您应该读取来自信任的所有证书,并尝试与您随应用程序提供的证书进行匹配。
示例代码:
- (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];
}
}
如果由于某种原因您决定手动评估信任。
- (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);
// # # # #
// so check one by one...
BOOL isMatching = NO;
for (int i = 0; i < count; i++)
{
SecCertificateRef certRef = SecTrustGetCertificateAtIndex(trust, i);
NSString *name = [NSString stringWithUTF8String:CFStringGetCStringPtr(SecCertificateCopySubjectSummary(certRef), kCFStringEncodingUTF8)];
NSLog(@"remote cert at index %d is '%@'", i, name);
const void *ref[] = {certRef, myReturnedCertificate1};
CFArrayRef aryCheck = CFArrayCreate(NULL, ref, 2, NULL);
SecTrustRef trustManual;
OSStatus certStatus = SecTrustCreateWithCertificates(aryCheck, SecPolicyCreateBasicX509(), &trustManual);
// certStatus always noErr
NSLog(@"certStatus: %d", (int)certStatus);
SecTrustResultType result;
OSStatus status = SecTrustEvaluate(trustManual, &result);
CFArrayRef arrayRef = SecTrustCopyProperties(trustManual);
NSLog(@"evaluate with result %d and status %d", result, (int)status);
NSLog(@"trust properties: %@", arrayRef);
/* log:
evaluate with result 5 and status 0
trust properties: (
{
type = error;
value = "Root certificate is not trusted.";
}
*/
if (status == noErr && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified))
{
isMatching = YES;
NSLog(@"certificates matches");
}
else
{
NSLog(@"certificates differs");
}
}
if (isMatching || (status == noErr && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)))
{
completionHandler(YES);
}
else
{
completionHandler(NO);
}
}
completionHandler(NO);
});
}
Update:
根据苹果文档:
您必须在 certRefs[0] 中放置一个 SecIdentityRef 对象来标识
叶证书及其相应的私钥。指定一个
根证书是可选的;
根据 Apple 的建议,如果您使用 .cer 格式的证书,则应使用对等域名(完全限定域名)来匹配两个证书。
您可以使用此功能来验证通用名称字段
同行的证书。如果你调用这个函数和通用名称
该证书与您在 peerName 中指定的值不匹配
参数,则握手失败并返回 errSSLXCertChainInvalid。
该函数的使用是可选的。