首页 > 解决方案 > 为什么 CFStream 不使用 TLS 1.3?

问题描述

我试图弄清楚为什么运行 iOS 14 的 iPhone 不使用 TLS 1.3 连接到兼容的网络服务器。

相关代码为:

- (void) streamOpened:(NSStream *)stream {
    NSDictionary *settings = @{
                               (__bridge NSString *)kCFStreamSSLValidatesCertificateChain: (__bridge NSNumber *)kCFBooleanFalse
                               };
    CFReadStreamSetProperty((CFReadStreamRef)inputStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
    CFWriteStreamSetProperty((CFWriteStreamRef)outputStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
}

完整的源代码可以在这里看到:https ://github.com/tls-inspector/tls-inspector/blob/app-store/CertificateKit/Getters/CKAppleCertificateChainGetter.m

我尝试使用kCFStreamSSLLevelset to指定 SSL 级别,kCFStreamSocketSecurityLevelTLSv1_3但没有做任何事情。

如果我使用 OpenSSL 进行连接,它使用 TLS 1.3,我可以通过数据包捕获验证这一点,但使用 CFStream 它坚持使用 1.2。

标签: objective-cssl

解决方案


简短的回答是您最终使用了不支持 TLS 1.3 的已弃用 API。

下面给出了详细说明潜在解决方案的长答案。

我试图解决这个问题,CFStream但没有成功。

这可能是可能的。问题是你最终会像你一样在低级别使用SSLConnectionRef和朋友,在更高级别使用NSInputStreamNSOutputStream和朋友,在某些时候你会遇到这个https://developer.apple.com/documentation/security/ secure_transport?language=objc旧版 API。

在该页面上,它提到该 API 已被 Network 框架取代,这正是我建议您应该使用的。我希望也能快速实施一个解决方案,但它需要比 SO 更多的返工,所以我把它留在那里。

但是,这里有一些建议,基本上是我希望实施的想法。

和以前一样,有两个级别。

在较低级别,您最终会使用nw_and 系列,但我会说不要那样做。某些特殊需求可能需要它,并且您的应用程序可能属于该类别,但请注意,更高级别是建立在此之上的。

在更高的层次上,这就是我认为你的解决方案所在的地方,你最终会使用NSURL和朋友。你这里的 goto 人可能是NSURLSession,但实施将决定这一点。我试图给出一个大纲,您可以查看我的代码以获取更多详细信息,但我认为从这里开始您将处于更好的实施位置。

我希望将您的流代码连接到 NSURLSession 但是当失败时我停止了。这可能有点乐观,我认为它需要更认真的返工,但各种代表(NSURLSessionDelegate、NSURLSessionTaskDelegate、NSURLSessionStreamDelegate 等)似乎已经准备好等待您的解决方案,我认为这不是很多实际工作。

我的尝试中最相关的代码片段如下。这会打开 TLS 1.3,我试图围绕这个实现。

    // Some configuration
    NSURLSessionConfiguration * config = NSURLSessionConfiguration.ephemeralSessionConfiguration;

    // Note this one!!!
    config.TLSMaximumSupportedProtocol = kTLSProtocolMaxSupported;
        
    NSURLSession * urlSession = [NSURLSession sessionWithConfiguration:config
                                  delegate:self
                                 delegateQueue:NSOperationQueue.currentQueue];

从那时起,我尝试重用您的流代码,但老实说,我认为最终的解决方案甚至可能不再使用流,而是仅依赖于使用正确的委托。请原谅我在这一点上感到兴奋,但我怀疑您将能够因此大大简化您的代码。

我喜欢你的应用程序 - 它非常精美,我期待你解决这个问题。我也喜欢玩弄它,但现在这是我的贡献。

第一次尝试

我看了你的代码——它需要对 TLS 1.3 进行一些修改。我试图这样做,但我现在正在重写该课程,所以我停止了。你可以做到,但是,我不能保证它会起作用!

无论如何,这里有一些想法。

首先是现有代码。只是一些 - 咳咳 - 观察,没什么大不了的......

请注意,streamOpened无论触发哪个流,您都将应用设置。我相信,委托将两次发送消息,一次用于输入,一次用于输出流。虽然在这里看起来你应该小心并不重要,因为这可能会在另一种情况下引入一些严重的错误。

另外,我认为您需要在打开流之前对其进行配置,但这没有任何区别。如果您在打开流之前performTaskForURL或之后在其中配置流,streamOpened则无关紧要。

我玩了一下配置。您无需在输出流上设置一个,只需在输入上设置。唯一需要的键是您已经设置的键。无论我做什么,我都无法得到任何区别。

其次,我 - 嗯 -认为的解决方案将在这里工作。

您需要配置 URL 会话。所以我做的是以下

- (void) performTaskForURL:(NSURL *)url{

    queryURL = url;

    // Some configuration
    NSURLSessionConfiguration * config = NSURLSessionConfiguration.ephemeralSessionConfiguration;

    // Note this one!!!
    config.TLSMaximumSupportedProtocol = kTLSProtocolMaxSupported;
        
    NSURLSession * urlSession = [NSURLSession sessionWithConfiguration:config
                                  delegate:self
                                 delegateQueue:NSOperationQueue.currentQueue];

    // Just code to test the idea, not production ready I know
    NSURLSessionStreamTask * streamTask = [urlSession streamTaskWithHostName:url.host
                                        port:443];
    [streamTask captureStreams];
}

- (void)URLSession:(NSURLSession *)session
    streamTask:(NSURLSessionStreamTask *)streamTask
didBecomeInputStream:(NSInputStream *)inputStreamUrl
      outputStream:(NSOutputStream *)outputStreamUrl
{
    // I was hoping to get away with this,
    // just setting your streams equal to the
    // URL stream task streams but it did not
    // work ... problem is you need more of the
    // stream task delegate methods I believe
    inputStream = inputStreamUrl;
    outputStream = outputStreamUrl;

    // This is some left over code from your performTaskForURL message
    // Here you can see how I toyed with the stream configuration

    // Configure here before it is opened
    // Pretty much your current streamOpened message
    // Note only input needs be configured (fwiw)
    [self configureStream:inputStream];

    inputStream.delegate = self;
    outputStream.delegate = self;

    [outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    [inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

    [outputStream open];
    [inputStream open];

    // I was hoping this would just work but I think it needs more work
}

这里的想法是配置一个 URL 会话,kTLSProtocolMaxSupported然后创建一个流任务。现在这就是我想停下来并认为你更擅长走得更远的地方。我希望我可以获取流并将它们返回到您的代码中,但它不起作用,现在我不会更进一步。

我无法测试我的想法,因为我需要将更多的东西连接到NSURLSessionStreamDelegate你的类现在也实现的东西上。我认为你已经在课堂上做了同样的事情,但是对于流。

现在,再次,我可能是错的,并且想在发布之前进行测试,但是要么看看这是否可行,然后我认为您需要例如实现委托方法,例如URLSession:task:didReceiveChallenge:completionHandler:.

我认为你已经在你的代码中做了这样的事情,但这就是我在这里停下来的原因,因为我认为你会更好地判断这个想法。

我已经玩弄了一些但没有成功 - 但我认为你需要在这里使用 NSURLSession ,甚至可能代替套接字。


推荐阅读