Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ Package.resolved
**/xcuserdata
**/*.xcuserdata
xcuserdata
**/xcshareddata
**/*.xcshareddata
xcshareddata
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove these 3 lines from .gitignore, we actually do, in some cases, want to commit files in Xcode shared data (Not routinely, but in specific situations.)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines removed


# Codegen build products
build/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import AwsCommonRuntimeKit
import Smithy
import SmithyHTTPClientAPI

public struct CRTClientTLSOptions: TLSConfiguration, Sendable {

Expand All @@ -25,6 +26,9 @@ public struct CRTClientTLSOptions: TLSConfiguration, Sendable {
/// Optional PKCS#12 password
public var pkcs12Password: String?

/// Optional Minimum TLS version
public var minimumTLSVersion: SmithyHTTPClientAPI.TLSVersion?

/// Information is provided to use custom trust store
public var useSelfSignedCertificate: Bool {
return certificateDir != nil && certificate != nil
Expand All @@ -41,13 +45,15 @@ public struct CRTClientTLSOptions: TLSConfiguration, Sendable {
certificate: String? = nil, // .cer
pkcs12Path: String? = nil, // .p12 PEM
pkcs12Password: String? = nil,
privateKey: String? = nil
privateKey: String? = nil,
minimumTLSVersion: SmithyHTTPClientAPI.TLSVersion? = nil
) {
self.certificateDir = certificateDir
self.certificate = certificate
self.pkcs12Path = pkcs12Path
self.pkcs12Password = pkcs12Password
self.privateKey = privateKey
self.minimumTLSVersion = minimumTLSVersion
}
}

Expand Down Expand Up @@ -81,6 +87,19 @@ extension CRTClientTLSOptions {
if self.useSelfSignedCertificate, let certPath = certificateDir, let certFilename = certificate {
try tlsOptions.overrideDefaultTrustStore(caPath: certPath, caFile: certFilename)
}
// Set minimum TLS version if specified
if let minVersion = minimumTLSVersion {
switch minVersion {
case .tls10:
tlsOptions.setMinimumTLSVersion(AwsCommonRuntimeKit.TLSVersion.TLSv1)
case .tls11:
tlsOptions.setMinimumTLSVersion(AwsCommonRuntimeKit.TLSVersion.TLSv1_1)
case .tls12:
tlsOptions.setMinimumTLSVersion(AwsCommonRuntimeKit.TLSVersion.TLSv1_2)
case .tls13:
tlsOptions.setMinimumTLSVersion(AwsCommonRuntimeKit.TLSVersion.TLSv1_3)
}
}

tlsContext = try TLSContext(options: tlsOptions, mode: .client)
} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,21 @@ public final class URLSessionHTTPClient: HTTPClient, @unchecked Sendable {
self.connectionTimeout = httpClientConfiguration.connectTimeout ?? 60.0
var urlsessionConfiguration = URLSessionConfiguration.default
urlsessionConfiguration = URLSessionConfiguration.from(httpClientConfiguration: httpClientConfiguration)

if let tlsConfig = tlsConfiguration, let minVersion = tlsConfig.minimumTLSVersion {
switch minVersion {
case .tls10,
.tls11:
// Enforce a secure minimum; do not allow TLS 1.0 or 1.1, they have been deprecated.
logger.error("This `minimumTLSVersion` (\(minVersion)) is not supported. Falling back to TLS 1.2.")
urlsessionConfiguration.tlsMinimumSupportedProtocolVersion = .TLSv12
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the minimum TLS 1.2 enforced on the URLSession-based client but not on the CRT-based client?

Also, we should provide some sort of notice to the customer that their TLS setting is not supported when they choose a version before TLS 1.2. Logging would probably be the best choice.

Copy link
Copy Markdown
Author

@AntAmazonian AntAmazonian Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the Logger.error now, as for the enforcement difference between URLSession and CRT, my reasoning was that URLSession uses a security protocol version (from Apple I suspect) that already enforces it for macOS 12.0 so your systems platform should be able to trust but if you're using a macOS before 12.0 then this should stop the user as an added safety measure. CRT on the other hand does not set any minimum because it trust your OS set TLS.

case .tls12:
urlsessionConfiguration.tlsMinimumSupportedProtocolVersion = .TLSv12
case .tls13:
urlsessionConfiguration.tlsMinimumSupportedProtocolVersion = .TLSv13
}
}

self.session = SessionType.init(configuration: urlsessionConfiguration, delegate: delegate, delegateQueue: nil)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// SPDX-License-Identifier: Apache-2.0
//

import SmithyHTTPClientAPI

public struct URLSessionTLSOptions: TLSConfiguration, Sendable {

/// Filename of the turst certificate file in main bundle (.cer)
Expand All @@ -22,6 +24,9 @@ public struct URLSessionTLSOptions: TLSConfiguration, Sendable {
/// Optional PKCS#12 password
public var pkcs12Password: String?

/// Optional Minimum TLS version
public var minimumTLSVersion: TLSVersion?

/// Information is provided to use custom trust store
public var useSelfSignedCertificate: Bool {
return certificate != nil
Expand All @@ -35,10 +40,12 @@ public struct URLSessionTLSOptions: TLSConfiguration, Sendable {
public init(
certificate: String? = nil, // .cer
pkcs12Path: String? = nil, // .p12
pkcs12Password: String? = nil
pkcs12Password: String? = nil,
minimumTLSVersion: TLSVersion? = nil
) {
self.certificate = certificate
self.pkcs12Path = pkcs12Path
self.pkcs12Password = pkcs12Password
self.minimumTLSVersion = minimumTLSVersion
}
}
10 changes: 10 additions & 0 deletions Sources/SmithyHTTPClientAPI/TLSConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,14 @@ public protocol TLSConfiguration: Sendable {

// Optional PKCS#12 password
var pkcs12Password: String? { get set }

// Optional Minimum TLS Version
var minimumTLSVersion: TLSVersion? { get set }
}

public enum TLSVersion: String, Sendable {
case tls10 = "TLSv1_0"
case tls11 = "TLSv1_1"
case tls12 = "TLSv1_2"
case tls13 = "TLSv1_3"
}
7 changes: 6 additions & 1 deletion Sources/SmithySwiftNIO/SwiftNIOHTTPClientTLSOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public struct SwiftNIOHTTPClientTLSOptions: SmithyHTTPClientAPI.TLSConfiguration
/// Optional PKCS#12 password
public var pkcs12Password: String?

/// Optional Minimum TLS version
public var minimumTLSVersion: SmithyHTTPClientAPI.TLSVersion?

/// Information is provided to use custom trust store
public var useSelfSignedCertificate: Bool {
return certificate != nil || certificateDir != nil
Expand All @@ -42,12 +45,14 @@ public struct SwiftNIOHTTPClientTLSOptions: SmithyHTTPClientAPI.TLSConfiguration
certificateDir: String? = nil,
privateKey: String? = nil,
pkcs12Path: String? = nil,
pkcs12Password: String? = nil
pkcs12Password: String? = nil,
minimumTLSVersion: SmithyHTTPClientAPI.TLSVersion? = nil
) {
self.certificate = certificate
self.certificateDir = certificateDir
self.privateKey = privateKey
self.pkcs12Path = pkcs12Path
self.pkcs12Password = pkcs12Password
self.minimumTLSVersion = minimumTLSVersion
}
}
13 changes: 13 additions & 0 deletions Sources/SmithySwiftNIO/SwiftNIOHTTPClientTLSResolverUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ extension SwiftNIOHTTPClientTLSOptions {
}
}

if let minVersion = minimumTLSVersion {
switch minVersion {
case .tls10:
tlsConfig.minimumTLSVersion = .tlsv1
case .tls11:
tlsConfig.minimumTLSVersion = .tlsv11
case .tls12:
tlsConfig.minimumTLSVersion = .tlsv12
case .tls13:
tlsConfig.minimumTLSVersion = .tlsv13
}
}

return tlsConfig
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation
import XCTest
@testable import ClientRuntime
import SmithyHTTPClientAPI
import AwsCommonRuntimeKit

class CRTClientTLSOptionsTests: XCTestCase {

override func setUp() {
super.setUp()
CommonRuntimeKit.initialize()
}

func testMinimumTLSVersionInitialization() {
let tlsOptions = CRTClientTLSOptions(minimumTLSVersion: .tls12)
XCTAssertEqual(tlsOptions.minimumTLSVersion, .tls12)
}

func testMinimumTLSVersionDefaultsToNil() {
let tlsOptions = CRTClientTLSOptions()
XCTAssertNil(tlsOptions.minimumTLSVersion)
}

func testAllTLSVersionValues() {
let tls10 = CRTClientTLSOptions(minimumTLSVersion: .tls10)
let tls11 = CRTClientTLSOptions(minimumTLSVersion: .tls11)
let tls12 = CRTClientTLSOptions(minimumTLSVersion: .tls12)
let tls13 = CRTClientTLSOptions(minimumTLSVersion: .tls13)

XCTAssertEqual(tls10.minimumTLSVersion, .tls10)
XCTAssertEqual(tls11.minimumTLSVersion, .tls11)
XCTAssertEqual(tls12.minimumTLSVersion, .tls12)
XCTAssertEqual(tls13.minimumTLSVersion, .tls13)
}

func testResolveContextSucceedsWithMinimumTLSVersion() {
let tlsOptions = CRTClientTLSOptions(minimumTLSVersion: .tls12)

// The resolveContext() method should succeed without throwing
let context = tlsOptions.resolveContext()

// Verify a context was created
XCTAssertNotNil(context)
}

func testResolveContextSucceedsWithAllTLSVersions() {
#if os(Linux)
let versions: [SmithyHTTPClientAPI.TLSVersion] = [.tls12]
#else
let versions: [SmithyHTTPClientAPI.TLSVersion] = [.tls10, .tls11, .tls12, .tls13]
#endif

for version in versions {
let tlsOptions = CRTClientTLSOptions(minimumTLSVersion: version)
let context = tlsOptions.resolveContext()

XCTAssertNotNil(context, "Failed to create context for TLS version: \(version)")
}
}

func testResolveContextSucceedsWithoutMinimumTLSVersion() {
let tlsOptions = CRTClientTLSOptions()

let context = tlsOptions.resolveContext()

XCTAssertNotNil(context)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation
import XCTest
@testable import ClientRuntime
import SmithyHTTPClientAPI

class URLSessionTLSOptionsTests: XCTestCase {

func testMinimumTLSVersionInitialization() {
let tlsOptions = URLSessionTLSOptions(minimumTLSVersion: .tls12)
XCTAssertEqual(tlsOptions.minimumTLSVersion, .tls12)
}

func testMinimumTLSVersionDefaultsToNil() {
let tlsOptions = URLSessionTLSOptions()
XCTAssertNil(tlsOptions.minimumTLSVersion)
}

func testAllTLSVersionValues() {
let tls10 = URLSessionTLSOptions(minimumTLSVersion: .tls10)
let tls11 = URLSessionTLSOptions(minimumTLSVersion: .tls11)
let tls12 = URLSessionTLSOptions(minimumTLSVersion: .tls12)
let tls13 = URLSessionTLSOptions(minimumTLSVersion: .tls13)

XCTAssertEqual(tls10.minimumTLSVersion, .tls10)
XCTAssertEqual(tls11.minimumTLSVersion, .tls11)
XCTAssertEqual(tls12.minimumTLSVersion, .tls12)
XCTAssertEqual(tls13.minimumTLSVersion, .tls13)
}

#if os(iOS) || os(macOS) || os(watchOS) || os(tvOS) || os(visionOS)

func testMinimumTLSVersionAppliedToURLSessionConfiguration() {
let tlsOptions = URLSessionTLSOptions(minimumTLSVersion: .tls12)
let httpConfig = HttpClientConfiguration(tlsConfiguration: tlsOptions)
let client = URLSessionHTTPClient(httpClientConfiguration: httpConfig)

// Access the URLSession's configuration to verify TLS version was set
XCTAssertEqual(
client.session.configuration.tlsMinimumSupportedProtocolVersion,
.TLSv12
)
}

func testMinimumTLSVersionTLS13AppliedToURLSessionConfiguration() {
let tlsOptions = URLSessionTLSOptions(minimumTLSVersion: .tls13)
let httpConfig = HttpClientConfiguration(tlsConfiguration: tlsOptions)
let client = URLSessionHTTPClient(httpClientConfiguration: httpConfig)

XCTAssertEqual(
client.session.configuration.tlsMinimumSupportedProtocolVersion,
.TLSv13
)
}

func testNoMinimumTLSVersionUsesDefault() {
let tlsOptions = URLSessionTLSOptions()
let httpConfig = HttpClientConfiguration(tlsConfiguration: tlsOptions)
let client = URLSessionHTTPClient(httpClientConfiguration: httpConfig)

// When not set, should use system default (typically .TLSv12)
XCTAssertNotNil(client.session.configuration.tlsMinimumSupportedProtocolVersion)
}

#endif

}
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,47 @@ class SwiftNIOHTTPClientTLSOptionsTests: XCTestCase {
XCTAssertTrue(tlsOptions.useSelfSignedCertificate)
XCTAssertTrue(tlsOptions.useProvidedKeystore)
}

func testMinimumTLSVersionInitialization() {
let tlsOptions = SwiftNIOHTTPClientTLSOptions(minimumTLSVersion: .tls12)
XCTAssertEqual(tlsOptions.minimumTLSVersion, .tls12)
}

func testMinimumTLSVersionDefaultsToNil() {
let tlsOptions = SwiftNIOHTTPClientTLSOptions()
XCTAssertNil(tlsOptions.minimumTLSVersion)
}

func testMinimumTLSVersionTLS10AppliedToConfiguration() throws {
let tlsOptions = SwiftNIOHTTPClientTLSOptions(minimumTLSVersion: .tls10)
let config = try tlsOptions.makeNIOSSLConfiguration()
XCTAssertEqual(config.minimumTLSVersion, .tlsv1)
}

func testMinimumTLSVersionTLS11AppliedToConfiguration() throws {
let tlsOptions = SwiftNIOHTTPClientTLSOptions(minimumTLSVersion: .tls11)
let config = try tlsOptions.makeNIOSSLConfiguration()
XCTAssertEqual(config.minimumTLSVersion, .tlsv11)
}

func testMinimumTLSVersionTLS12AppliedToConfiguration() throws {
let tlsOptions = SwiftNIOHTTPClientTLSOptions(minimumTLSVersion: .tls12)
let config = try tlsOptions.makeNIOSSLConfiguration()
XCTAssertEqual(config.minimumTLSVersion, .tlsv12)
}

func testMinimumTLSVersionTLS13AppliedToConfiguration() throws {
let tlsOptions = SwiftNIOHTTPClientTLSOptions(minimumTLSVersion: .tls13)
let config = try tlsOptions.makeNIOSSLConfiguration()
XCTAssertEqual(config.minimumTLSVersion, .tlsv13)
}

func testMinimumTLSVersionNotSetWhenNil() throws {
let tlsOptions = SwiftNIOHTTPClientTLSOptions()
let config = try tlsOptions.makeNIOSSLConfiguration()
// When nil, it should use the default from makeClientConfiguration()
// The default is typically TLS 1.2, but we just verify it doesn't crash
XCTAssertNotNil(config.minimumTLSVersion)
}

}
Loading