iOS SecTrustRef 始终为 NULL

40

我正在尝试使用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对象始终为空。

这个苹果开发者链接中有这样一句话:https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html

其中引用了这句话,表明不应该出现这种情况:

当您的流委托事件处理程序被调用以表示套接字上有可用空间时,操作系统已经构建了TLS通道,从连接的另一端获取了证书链,并创建了一个信任对象来评估它。

有没有关于如何解决这个问题的提示?

我怎样才能访问NSStream中的trustRef对象?

编辑:

感谢100phole的回复。

为了让这个工作起来,我认为这可能与问题有关,于是在我的多次尝试中,我将所有与套接字相关的项目都移动到了一个类中:

类似于这样的:

@interface Socket
    CFReadStreamRef readStream;
    CFWriteStreamRef writeStream;

    NSInputStream *inputStream;
    NSOutputStream *outputStream;
@end

但是结果还是一样的:(

我之所以回到了上面显示的版本,是因为根据我的谷歌搜索,那似乎是一个相当常见的代码模式。

例如,即使是来自苹果开发者网站的这段代码也使用了非常类似的风格:

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Streams/Articles/NetworkStreams.html#//apple_ref/doc/uid/20002277-BCIDFCDI

正如我之前提到的,我不是Objective-C方面的专家(离得很远),所以我可能错了,但从我看到的情况来看,将这些项移动到一个类中并使它们持续存在似乎没有任何区别。


2
你的流似乎是方法中的局部变量,这意味着当方法返回时它们将被销毁。 - l00phole
只看了一下你的代码,就想问一句...为什么不使用内置的NSURL会话和连接类呢?连接设置和身份验证握手都已经为您处理好了(并提供必要时进行干预的钩子)。 - Jody Hagins
你在所引用的网页中看到了这样的注释:/* 存储输入输出流的引用,以防它们消失.... */ - Wain
1
你正在测试哪个版本的iOS? - Nicolas S
2
如果您正在低级别连接,这可能不会生效,但iOS 9使用应用程序传输安全性(Application Transport Security),您可能希望禁用或将服务器列入白名单以进行TLS。我建议尝试完全禁用它,以确保它不会影响您。http://stackoverflow.com/questions/32892121/xcode-7-1-beta-2-disable-ats/32894812#32894812 - Nicolas S
显示剩余5条评论
2个回答

0

0

由于这个问题似乎引起了一些兴趣,我决定更新问题并提供详细的答案,说明最终是如何解决这个问题的。

首先,一些背景。我从之前的开发人员那里继承了这段代码,我的角色是让破损的代码正常工作。

我花了很多时间编写和重写连接代码,使用了苹果iOS开发者网页上的详细信息,但似乎什么都不起作用。

最后,我决定更仔细地查看这个函数,这是我继承的代码,错误地认为它已经在工作:

[self loadClientCertificates:certificates];

乍一看,代码看起来没问题。该函数只是从文件中加载证书。但仔细检查后发现,虽然代码正确地加载了证书,但它没有将这些证书返回给调用者!!!

修复代码以正确返回证书后,连接代码正常工作,SecTrustRef不再为NULL。

总之:

1)尽管缺乏良好的示例,但Apple文档似乎是准确的。

2) SecTrustRef为空的原因是在连接协商阶段找不到有效证书,因为由于前面提到的编码错误,没有证书可用于连接API。

3)如果您遇到类似的错误,我的建议是检查和反复检查您的代码,因为预期的iOS方程式的工作方式已记录在文档中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接