通过 NSStream 与我的证书进行 SSL 握手

Posted

技术标签:

【中文标题】通过 NSStream 与我的证书进行 SSL 握手【英文标题】:SSL Handshake with my certificate by NSStream 【发布时间】:2015-04-09 10:30:15 【问题描述】:

我正在为 ios 编写一个客户端,以通过 SSL/TLS 连接到我的服务器。我决定使用 NSStream 实现。现在我的项目由于 SSL 握手而停止了,我不知道如何使用流处理它,也找不到任何代表的例子。据我了解,证书传递应该在 NSStreamEventHasSpaceAvailable 中,但实际上我不明白如何。我需要通过我的证书(他没有安装在设备上)。有人可以帮我吗?

此代码会因此崩溃:

2015-02-09 18:58:28.902 Test CFNetwork SSLHandshake failed (-9807)
2015-02-09 18:58:28.903 Test unexpected NSStreamEventErrorOccurred: Error Domain=NSOSStatusErrorDomain Code=-9807 "The operation couldn’t be completed. (OSStatus error -9807.)"
2015-02-09 18:58:28.903 Test unexpected NSStreamEventErrorOccurred: Error Domain=NSOSStatusErrorDomain Code=-9807 "The operation couldn’t be completed. (OSStatus error -9807.)"

我有什么:

@interface NSStream (FSNetworkAdditions)

+ (void)qNetworkAdditions_getStreamsToHostNamed:(NSString *)hostName
                                           port:(NSInteger)port
                                    inputStream:(out NSInputStream **)inputStreamPtr
                                   outputStream:(out NSOutputStream **)outputStreamPtr;

@end

@implementation NSStream (FSNetworkAdditions)

+ (void)qNetworkAdditions_getStreamsToHostNamed:(NSString *)hostName
                                           port:(NSInteger)port
                                    inputStream:(out NSInputStream **)inputStreamPtr
                                   outputStream:(out NSOutputStream **)outputStreamPtr

    CFReadStreamRef     readStream;
    CFWriteStreamRef    writeStream;

    assert(hostName != nil);
    assert( (port > 0) && (port < 65536) );
    assert( (inputStreamPtr != NULL) || (outputStreamPtr != NULL) );

    readStream = NULL;
    writeStream = NULL;

    CFStreamCreatePairWithSocketToHost(
                                       NULL,
                                       (__bridge CFStringRef) hostName,
                                       port,
                                       ((inputStreamPtr  != NULL) ? &readStream : NULL),
                                       ((outputStreamPtr != NULL) ? &writeStream : NULL)
                                       );


    if (inputStreamPtr != NULL) 
        *inputStreamPtr  = CFBridgingRelease(readStream);
    
    if (outputStreamPtr != NULL) 
        *outputStreamPtr = CFBridgingRelease(writeStream);
    




- (void)loadHostName:(NSString *)hostName onPort:(NSInteger)portNumber 

        NSString* filePath = [[NSBundle mainBundle] pathForResource:@"ca" ofType:@"crt"];

        NSData *iosTrustedCertDerData = [NSData dataWithContentsOfFile:filePath];
        certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) iosTrustedCertDerData);

    NSInputStream   *inputStream;
    NSOutputStream  *outputStream;
    [NSStream qNetworkAdditions_getStreamsToHostNamed:hostName
                                                 port:portNumber
                                          inputStream:&inputStream
                                         outputStream:&outputStream];


    [inputStream setProperty:NSStreamSocketSecurityLevelTLSv1 forKey:NSStreamSocketSecurityLevelKey];
    [outputStream setProperty:NSStreamSocketSecurityLevelTLSv1 forKey:NSStreamSocketSecurityLevelKey];

    inputStream.delegate  = self;
    outputStream.delegate = self;


    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                               forMode:NSDefaultRunLoopMode];
    [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                                forMode:NSDefaultRunLoopMode];


    [inputStream open];
    [outputStream open];

还有这个代表:

- (void)stream:(NSStream *)aStream
   handleEvent:(NSStreamEvent)eventCode 
    SecPolicyRef policy;
    switch (eventCode) 
        case NSStreamEventNone:
            break;
        case NSStreamEventOpenCompleted:
            break;
        case NSStreamEventHasBytesAvailable:
        
            int stop = 1;
            break;
        
        case NSStreamEventHasSpaceAvailable:
        
            // #1
            // NO for client, YES for server.  In this example, we are a client
            // replace "localhost" with the name of the server to which you are connecting
            policy = SecPolicyCreateSSL(NO, CFSTR("192.168.178.14"));
            SecTrustRef trust = NULL;
            // #2
            CFArrayRef streamCertificates =
            (__bridge CFArrayRef)([aStream propertyForKey:(NSString *) kCFStreamPropertySSLPeerCertificates]);
            // #3
            SecTrustCreateWithCertificates(streamCertificates,
                                           policy,
                                           &trust);
            // #4
            SecTrustSetAnchorCertificates(trust,
                                          (__bridge CFArrayRef) [NSArray arrayWithObject:(__bridge id)certificate]);            // #5
            SecTrustResultType trustResultType = kSecTrustResultInvalid;
            OSStatus status = SecTrustEvaluate(trust, &trustResultType);
            if (status == errSecSuccess) 
                // expect trustResultType == kSecTrustResultUnspecified
                // until my cert exists in the keychain see technote for more detail.
                if (trustResultType == kSecTrustResultUnspecified) 
                    NSLog(@"We can trust this certificate! TrustResultType: %d", trustResultType);
                 else 
                    NSLog(@"Cannot trust certificate. TrustResultType: %d", trustResultType);
                
             else 
                NSLog(@"Creating trust failed: %d", status);
                [aStream close];
            
            if (trust) 
                CFRelease(trust);
            
            if (policy) 
                CFRelease(policy);
            
            break;
        
        case NSStreamEventErrorOccurred:
            NSLog(@"unexpected NSStreamEventErrorOccurred: %@", [aStream streamError]);
            break;
        case NSStreamEventEndEncountered:
            break;
        default:
            break;
    

【问题讨论】:

【参考方案1】:

效果很好:

    NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:1];
    [settings setObject:(NSString *)NSStreamSocketSecurityLevelTLSv1 forKey:(NSString *)kCFStreamSSLLevel];
    [settings setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCFStreamSSLAllowsAnyRoot];
    [settings setObject:hostName forKey:(NSString *)kCFStreamSSLPeerName];
    [settings setObject:NSStreamSocketSecurityLevelTLSv1 forKey:NSStreamSocketSecurityLevelKey];
    inputStream.delegate  = self;
    outputStream.delegate = self;

    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                               forMode:NSDefaultRunLoopMode];
    [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]

    CFReadStreamSetProperty((CFReadStreamRef)inputStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
    CFWriteStreamSetProperty((CFWriteStreamRef)outputStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);

【讨论】:

值得注意的是 kCFStreamSSLAllowsAnyRoot 已被弃用,但它为我指明了正确的方向:kCFStreamSSLValidatesCertificateChain 可以设置为 @NO 以在流的 SSL 协商中获得类似的效果。

以上是关于通过 NSStream 与我的证书进行 SSL 握手的主要内容,如果未能解决你的问题,请参考以下文章

使用 HttpClient 允许不受信任的 SSL 证书

允许使用HttpClient的不受信任的SSL证书

SSL四次握手

如何使用 create-react-app 提供 SSL 证书?

Bluemix 应用程序上的自定义域的 SSL 证书传播问题

在ubuntu服务器上使用nginx从namecheap获得ssl证书的问题