我正在尝试使用TCP/IP上的TLS将iOS应用程序连接到Windows C#服务器。
TLS连接使用的是使用 makecert 实用程序从 * 不可信CA根证书 * 创建的 * 不可信证书 *。
为了测试这些证书,我创建了一个简单的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协商,因为如果NSStream设置中未提供p12证书,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对象总是null。
从这个Apple开发人员链接:https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
有这样一句话,表明这不应该是这种情况:
当呼叫数据流委派的事件行程常式以指示通信端上有可用的空间时,操作系统已经建构TLS通道、从连接的另一端取得凭证链接,并建立信任对象来评估它。
有任何关于如何解决此问题的提示吗?
如何访问NSStream的 trustRef 对象?
编辑:
感谢您的回复100 phole.
在尝试让它工作时,我想这可能与问题有关,在我的多次尝试中,我将所有与套接字相关的项移到了一个类中:
大概是这样的:
@interface Socket
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
NSInputStream *inputStream;
NSOutputStream *outputStream;
@end
但那得出了同样的结果:(
我只是恢复到上面显示的版本,因为根据我的谷歌搜索,这似乎是一个相当常见的代码模式。
例如,甚至来自Apple Developer站点的以下代码也使用了非常相似的样式:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Streams/Articles/NetworkStreams.html#//apple_ref/doc/uid/20002277-BCIDFCDI
正如我前面提到的,我不是Objective-C的Maven(远非如此),所以我可能错了,但从我所看到的来看,将这些项移到一个类中并使它们持久化似乎没有任何区别。
3条答案
按热度按时间agxfikkp1#
由于似乎有一些兴趣在这个问题我决定更新问题的答案与细节这个问题是如何最终解决
首先是一些背景知识。我从以前的开发人员那里继承了这段代码,我的角色是让被破坏的代码工作。
我花了很多时间使用苹果iOS开发者网页上的细节来编写和重写连接代码,但似乎什么都不起作用。
最后我决定仔细看看这个函数,我继承的代码错误地认为它正在工作:
乍一看,代码看起来不错。该函数只不过是从文件加载证书。但仔细检查,虽然代码正确地加载了证书,但它并没有将这些证书返回给调用者!!!
修复该代码以便正确返回证书后,连接代码工作正常,并且 SecTrustRef 不再为NULL。
总而言之:
**1)**Apple文档虽然缺乏好的示例,但看起来确实是准确的。
**2)**SecTrustRef 为NULL的原因是找不到连接协商阶段的有效证书,这是因为由于前面提到的编码错误,连接API没有可用的证书。
**3)**如果您看到类似的错误,我的建议是检查并仔细检查您的代码,因为正如所料,等式的iOS方面如文档所示工作。
ssgvzors2#
我最近创建了一个Obj-C包来处理TLS,考虑到了苹果公司最新的限制。
https://github.com/eamonwhiter73/IOSObjCWebSockets
7ajki6be3#
问题似乎是
kCFStreamPropertySSLPeerTrust
属性在NSStream
对象上未设置,直到TLS握手完成。这意味着trustRef变量的值将为NULL,直到TLS握手完成。修复此问题的一种方法是将对
VerifyCertificate
的调用移到其他委托方法,如stream:handleEvent:
,该方法在TLS握手完成后调用。可以检查NSStreamEvent
参数的值以确定握手是否已完成,然后在必要时调用VerifyCertificate
。下面是一个示例,说明如何执行此操作: