Feng's Notes

周海锋的个人博客

0%

基于GCDAsynSocket 实现 SSL 通信

GCDAsynSocket

GCDAsynSocket
是一个开源的基于GCD的异步的socket库。它支持IPV4和IPV6地址,SSL/TLS协议。同时它支持iOS端和Mac端。本篇主要介绍一下 GCDAsynSocket 中的 SSL/TLS 双向手动验证的实现

证书

  • p12证书:

    // 通过 pem 证书转换而来
    openssl pkcs12 -export -in my_cer.pem -out my_cer.p12
  • cer证书:

/*
// 方法一:
// 服务器自签名证书:
// openssl req -new -x509 -nodes -days 365 -newkey rsa:1024  -out my_cer.crt -keyout my_cer.key

// 需要cer格式证书,分发到终端后需要转换一下
// openssl x509 -outform der -in my_cer.crt -out my_cer.der

// 后缀名改成 cer 即可

//方法二:
// 1.获取根证书p12文件 
// 2.导入钥匙串
// 3.从钥匙串导出根证书cer文件即可 

*/

SSL/TLS

- (void)addSecurtyTransport {
    NSMutableDictionary *settings = [NSMutableDictionary dictionary];
    // SSL 证书
    NSData *pkcs12data = [[NSData alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"my_gateway" ofType:@"p12"]];
    CFDataRef inPKCS12Data = (CFDataRef)CFBridgingRetain(pkcs12data);
    // c语言字符串
    CFStringRef password = CFSTR("123456");

    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)
    ZQLog(@"Success opening p12 certificate.");

    CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
    SecIdentityRef myIdent = (SecIdentityRef)CFDictionaryGetValue(identityDict,
    kSecImportItemIdentity);
    SecIdentityRef certArray[1] = { myIdent };
    CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL);

    //开始手动SSL证书验证,必定要设置此key
    [settings setObject:(id)CFBridgingRelease(myCerts) forKey:(NSString *)kCFStreamSSLCertificates];
    [settings setObject:@(YES) forKey:GCDAsyncSocketManuallyEvaluateTrust];
    [settings setObject:@(NO) forKey:GCDAsyncSocketUseCFStreamForTLS];

    [self.socketClient startTLS:settings];
    [self.socketClient readDataWithTimeout:-1 tag:0];
}

#progma mark - Delegate

// # socket已连接,开启SSL
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
    if (self.useTLS) {
        [self addSecurtyTransport];
    } 
}

// # SSL连接已建立
- (void)socketDidSecure:(GCDAsyncSocket *)sock {
    if (self.useTLS) {
        _socketClient = sock;
    }
}

// # 手动验证服务端证书
- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust completionHandler:(void (^)(BOOL))completionHandler {
    NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"my_gateway" ofType:@"cer"];
    NSData *cerData = [NSData dataWithContentsOfFile:cerPath];

    OSStatus status = -1;
    SecTrustResultType result = kSecTrustResultDeny;
    if (cerData) {
        SecCertificateRef cert1;
        // 将DER encoded X.509转换成 SecCertificateRef
        cert1 = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) cerData);
        NSArray *caArray = [NSArray arrayWithObjects:(__bridge id)(cert1), nil];
        // 设置证书用于验证
        SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)caArray);
        // 同步验证服务器证书和本地证书是否匹配,会一直阻塞验证
        status = SecTrustEvaluate(trust, &result);
        CFRelease(cert1);
    } else {
        NSLog(@"local certificate could't be loaded!");
        completionHandler(NO);
    }

    if ((status == noErr &&
    (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified))) {
        NSLog(@"成功通过验证,证书可信");
        completionHandler(YES);
    } else {
        CFArrayRef arrayRefTrust = SecTrustCopyProperties(trust);
        NSLog(@"error in connection occured\n %@", arrayRefTrust);
        completionHandler(NO);
    }
}

自签名证书

// 自签名证书需要设置
[settings setObject:@(YES) forKey:GCDAsyncSocketUseCFStreamForTLS];