Skip to content

Instantly share code, notes, and snippets.

@blochberger
Last active January 14, 2024 14:12
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save blochberger/8e98f768502283dccb245f7ca81a79f8 to your computer and use it in GitHub Desktop.
Save blochberger/8e98f768502283dccb245f7ca81a79f8 to your computer and use it in GitHub Desktop.
macOS/iOS TLS 1.3 Support

macOS/iOS TLS 1.3 Support

There appears to be a bug (rdar://50887327) in macOS and iOS that prevents to harden the App Transport Security (ATS) configuration to restrict connections to TLS 1.3. This script can be used to test the behavior with specific configurations.

This issue is resolved with macOS 11 (Big Sur). Validated with macOS 11.0.1 (20B50).

⚠️ Security Advice

For

  • macOS < 10.15
  • iOS < 13.0

To secure your connections with the latest security standards, increase the maximum supported TLS version manually:

let configuration = URLSessionConfiguration.ephemeral
configuration.tlsMaximumSupportedProtocol = .tlsProtocolMaxSupported // <- IMPORTANT
let session = URLSession(configuration: configuration)

Default ATS Configuration: NSExceptionMinimumTLSVersion = TLSv1.2

When creating a new macOS or iOS application without specifying any ATS settings, TLS 1.3 connections will not be established unless the maximum supported TLS version is increased. However, TLS 1.3 cannot be enforced.

With macOS 10.15 Beta and iOS 13.0 Beta, the default ATS setting is still TLSv1.2. However, the maximum supported TLS version was increased to TLSv1.3, hence the advised work-around is no longer required and the enum is marked as deprecated.

Tested on

  • macOS 10.14.4
  • macOS 10.14.5
  • macOS 10.15 Beta (19A487m)
  • macOS 10.15 Beta (19A512f)
  • iOS 12.2
  • iOS 12.3
  • iOS 13.0 Beta (Simulator)
check(minimumSupportedTlsProtocol: nil, maximumSupportedTlsProtocol: nil)
// -> TLS 1.2

check(minimumSupportedTlsProtocol: nil, maximumSupportedTlsProtocol: .tlsProtocol13)
// -> TLS 1.3

check(minimumSupportedTlsProtocol: nil, maximumSupportedTlsProtocol: .tlsProtocolMaxSupported)
// -> TLS 1.3

check(minimumSupportedTlsProtocol: .tlsProtocol13, maximumSupportedTlsProtocol: .tlsProtocolMaxSupported)
// -> An SSL error has occurred and a secure connection to the server cannot be made.

Hardened ATS Configuration: NSExceptionMinimumTLSVersion = TLSv1.3

When creating a new macOS or iOS application and enforce TLS 1.3 in ATS, no connections can be established.

Tested on

  • macOS 10.14.4
  • macOS 10.14.5
  • macOS 10.15 Beta (19A487m)
  • macOS 10.15 Beta (19A512f)
  • iOS 12.2
  • iOS 12.3
  • iOS 13.0 Beta (Simulator)
check(minimumSupportedTlsProtocol: nil, maximumSupportedTlsProtocol: nil)
// -> An SSL error has occurred and a secure connection to the server cannot be made.

check(minimumSupportedTlsProtocol: nil, maximumSupportedTlsProtocol: .tlsProtocol13)
// -> An SSL error has occurred and a secure connection to the server cannot be made.

check(minimumSupportedTlsProtocol: nil, maximumSupportedTlsProtocol: .tlsProtocolMaxSupported)
// -> An SSL error has occurred and a secure connection to the server cannot be made.

check(minimumSupportedTlsProtocol: .tlsProtocol13, maximumSupportedTlsProtocol: .tlsProtocolMaxSupported)
// -> An SSL error has occurred and a secure connection to the server cannot be made.

Disabled ATS: macOS Command Line

When using the macOS command line, you can execute the file by calling:

swift check_tls13.swift

In contrast to ATS-enabled applications, when using the command line TLS 1.3 connections will be established by default.

Tested on

  • macOS 10.14.5
  • macOS 10.15 Beta (19A487m)
  • macOS 10.15 Beta (19A512f)
check(minimumSupportedTlsProtocol: nil, maximumSupportedTlsProtocol: nil, exitOnError: true)
// -> TLS 1.3

check(minimumSupportedTlsProtocol: .tlsProtocol13, maximumSupportedTlsProtocol: nil, exitOnError: true)
// -> An SSL error has occurred and a secure connection to the server cannot be made.
import Foundation
enum FetchError: Error {
case httpError(status_code: Int)
case noContent
case unexpectedContent
}
func handle_response(_ optionalData: Data?, _ optionalResponse: URLResponse?, _ optionalError: Error?) -> Result<String, Error> {
guard let response = optionalResponse as? HTTPURLResponse, optionalError == nil else {
return .failure(optionalError!)
}
guard response.statusCode < 400 else {
return .failure(FetchError.httpError(status_code: response.statusCode))
}
guard let data = optionalData, !data.isEmpty else {
return .failure(FetchError.noContent)
}
guard let json = try? JSONSerialization.jsonObject(with: data) as? NSDictionary as? [String: Any] else {
return .failure(FetchError.unexpectedContent)
}
guard let tlsVersion = json["tls_version"] as? String else {
return .failure(FetchError.unexpectedContent)
}
return .success(tlsVersion)
}
func fetch_check(minimumSupportedTlsProtocol: SSLProtocol? = nil, maximumSupportedTlsProtocol: SSLProtocol? = nil) -> Result<String, Error> {
let url = URL(string: "https://www.howsmyssl.com/a/check")!
let configuration = URLSessionConfiguration.ephemeral
// Setting this to `.tlsProtocol13` is like setting `NSExceptionMinimumTLSVersion`
// in `Info.plist` to `TLSv1.3`.
// However, a bug prevents TLS 1.3 connections from being enforced, regardless of
// the value set in `maximumSupportedTlsProtocol`.
if let minimumSupportedTlsProtocol = minimumSupportedTlsProtocol {
configuration.tlsMinimumSupportedProtocol = minimumSupportedTlsProtocol
}
// When using an ATS context, i.e., an application, only a TLS 1.2 connections
// are established, although setting this to `.tlsProtocol13` or `.tlsProtocolMaxSupported`
// will successfully establish TLS 1.3 connections.
if let maximumSupportedTlsProtocol = maximumSupportedTlsProtocol {
configuration.tlsMaximumSupportedProtocol = maximumSupportedTlsProtocol
}
let session = URLSession(configuration: configuration)
var result: Result<String, Error>! = nil
let semaphore = DispatchSemaphore(value: 0)
let task = session.dataTask(with: url) {
optionalData, optionalResponse, optionalError in
result = handle_response(optionalData, optionalResponse, optionalError)
semaphore.signal()
}
task.resume()
semaphore.wait()
return result
}
func check(minimumSupportedTlsProtocol: SSLProtocol? = nil, maximumSupportedTlsProtocol: SSLProtocol? = nil, exitOnError: Bool = false) {
switch fetch_check(minimumSupportedTlsProtocol: minimumSupportedTlsProtocol, maximumSupportedTlsProtocol: maximumSupportedTlsProtocol) {
case .success(let tlsVersion):
print(tlsVersion)
case .failure(let error):
FileHandle.standardError.write(Data("\(error.localizedDescription)\n".utf8))
if exitOnError {
exit(1)
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>www.howsmyssl.com</key>
<dict>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.3</string>
</dict>
</dict>
</dict>
</dict>
</plist>
@travisgriggs
Copy link

I tried to use this to get my app running on iOS 12.5 to be able to access by NGINX server which is configured with:

    ssl_protocols TLSv1.3;
    ssl_prefer_server_ciphers off;

But it still didn't work. I still get boring SSL errors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment