iOS SecTrustRef 始终为 NULL

iOS SecTrustRef Always NULL

提问人:jussij 提问时间:10/16/2015 最后编辑:idzjussij 更新时间:12/4/2022 访问量:1359

问:

我正在尝试使用 TLS over TCP/IP 将 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 协商,因为如果 p12 证书未作为 NSStream 设置的一部分提供,则 C# 服务器会拒绝连接。

因此,TLS协商的第一阶段似乎正在发挥作用。

为了验证服务器证书,我有这个函数,它由 NSStreamEventHasSpaceAvailable 事件的 NSStream 委托调用:

// 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 对象?

编辑:

感谢您的回复 100phole。

在尝试让它工作时,我认为这可能与问题有关,在我的许多尝试之一中,我将所有这些与套接字相关的项目移动到一个类中:

像这样的东西:

@interface Socket
    CFReadStreamRef readStream;
    CFWriteStreamRef writeStream;

    NSInputStream *inputStream;
    NSOutputStream *outputStream;
@end

但这得出了相同的结果:(

我只恢复到上面显示的版本,因为根据我的 Google 搜索,这似乎是一个相当代码常见的模式。

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

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

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

IOS系统 Objective-C SSL TCP

评论

2赞 l00phole 10/16/2015
您的流似乎是方法中的局部变量,这意味着当方法返回时,它们将被销毁。
0赞 Jody Hagins 11/9/2015
看了你的代码,只问了足够长的时间......为什么不使用内置的 NSURL 会话和连接类?连接设置和身份验证握手都为您处理(必要时使用钩子进行插入)。
0赞 Wain 11/10/2015
您会在引用的网页中看到注释,上面写着/* Store a reference to the input and output streams so that they don't go away.... */
1赞 Nicolas S 11/11/2015
您正在测试哪个版本的 iOS?
2赞 Nicolas S 11/13/2015
如果在低级别连接,这可能不起作用,但 iOS 9 使用应用程序传输安全性,由于 TLS,您可能希望禁用该安全性或将服务器列入白名单。我会尝试完全禁用它,以确保它不会打扰您。stackoverflow.com/questions/32892121/......

答:

0赞 jussij 4/24/2016 #1

由于似乎对这个问题有一些兴趣,我决定用答案来更新这个问题,并详细说明这个问题最终是如何解决的。

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

我花了很多时间使用 Apple iOS 开发人员网页上的详细信息来编写和重写连接代码,但似乎没有任何效果。

我最终决定仔细研究一下这个函数,我继承了代码并错误地认为它正在工作:

[self loadClientCertificates:certificates];

乍一看,代码看起来不错。该函数只不过是从文件加载证书。但仔细检查后,虽然代码正确加载了证书,但它并没有将这些证书返回给调用方!!

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

综上所述:

1)苹果文档虽然缺乏好的例子,但似乎确实是准确的。

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

3)如果您看到类似的错误,我的建议是检查并仔细检查您的代码,因为正如预期的那样,等式的iOS端按文档说明工作。

0赞 ewizard 5/1/2020 #2

我最近创建了一个 Obj-C 包来处理 TLS,同时考虑到 Apple 施加的最新限制。正确获得证书是非常重要的一步。

https://github.com/eamonwhiter73/IOSObjCWebSockets