通过 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 握手的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 create-react-app 提供 SSL 证书?