我正在尝试使用TLS over TCP/IP将iOS应用程序连接到Windows C#服务器。
TLS连接使用不受信任的证书,这些证书是使用不受信任的CA根证书使用makecert实用程序创建的。
为了测试这些证书,我创建了一个简单的C#客户端,并使用这些证书成功地连接并与服务器通信。
我不擅长iOS开发,但我找到了一些代码,以以下方式将我连接到服务器:
-(bool)CreateAndConnect:(NSString *) remoteHost withPort:(NSInteger) serverPort
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)(remoteHost),
serverPort, &readStream, &writeStream);
CFReadStreamSetProperty(readStream, kCFStreamPropertySocketSecurityLevel,
kCFStreamSocketSecurityLevelNegotiatedSSL);
NSInputStream *inputStream = (__bridge_transfer NSInputStream *)readStream;
NSOutputStream *outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[inputStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey];
// load certificate from servers exported p12 file
NSArray *certificates = [[NSArray alloc] init];
[self loadClientCertificates:certificates];
NSDictionary *sslSettings = [NSDictionary dictionaryWithObjectsAndKeys:
(id)kCFBooleanFalse, (id)kCFStreamSSLValidatesCertificateChain,
certificates,(id)kCFStreamSSLCertificates,
nil];
[inputStream setProperty:sslSettings forKey:(__bridge NSString *)kCFStreamPropertySSLSettings];
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
CFReadStreamOpen(readStream);
CFWriteStreamOpen(writeStream);
return true;
}
该代码似乎也在进行某种形式的TLS协商,因为如果未将p12证书作为NSStream设置的一部分提供,C#服务器将拒绝连接。
因此,看起来TLS协商的第一阶段正在工作。
为了验证服务器证书,我有这个函数,该函数由NSStream代理在NSStreamEventHasSpaceAvailable事件上调用:
// return YES if certificate verification is successful, otherwise NO
-(BOOL) VerifyCertificate:(NSStream *)stream
{
NSData *trustedCertData = nil;
BOOL result = NO;
SecTrustRef trustRef = NULL;
NSString *root_certificate_name = @"reference_cert";
NSString *root_certificate_extension = @"der";
/* Load reference cetificate */
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
trustedCertData = [NSData dataWithContentsOfFile:[bundle pathForResource: root_certificate_name ofType: root_certificate_extension]];
/* get trust object */
/* !!!!! error is here as trustRef is NULL !!!! */
trustRef = (__bridge SecTrustRef)[stream propertyForKey:(__bridge id)kCFStreamPropertySSLPeerTrust];
/* loacate the reference certificate */
NSInteger numCerts = SecTrustGetCertificateCount(trustRef);
for (NSInteger i = 0; i < numCerts; i++) {
SecCertificateRef secCertRef = SecTrustGetCertificateAtIndex(trustRef, i);
NSData *certData = CFBridgingRelease(SecCertificateCopyData(secCertRef));
if ([trustedCertData isEqualToData: certData]) {
result = YES;
break;
}
}
return result;
}
现在的问题是,无论我尝试什么,trustRef对象始终为空。
其中引用了这句话,表明不应该出现这种情况:
当您的流委托事件处理程序被调用以表示套接字上有可用空间时,操作系统已经构建了TLS通道,从连接的另一端获取了证书链,并创建了一个信任对象来评估它。
有没有关于如何解决这个问题的提示?
我怎样才能访问NSStream中的trustRef对象?
编辑:
感谢100phole的回复。
为了让这个工作起来,我认为这可能与问题有关,于是在我的多次尝试中,我将所有与套接字相关的项目都移动到了一个类中:
类似于这样的:
@interface Socket
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
NSInputStream *inputStream;
NSOutputStream *outputStream;
@end
但是结果还是一样的:(
我之所以回到了上面显示的版本,是因为根据我的谷歌搜索,那似乎是一个相当常见的代码模式。
例如,即使是来自苹果开发者网站的这段代码也使用了非常类似的风格:
正如我之前提到的,我不是Objective-C方面的专家(离得很远),所以我可能错了,但从我看到的情况来看,将这些项移动到一个类中并使它们持续存在似乎没有任何区别。
/* 存储输入输出流的引用,以防它们消失.... */
。 - Wain