Skip to content

Commit

Permalink
Add support for supplying supported curves to TLSConfiguration (#485)
Browse files Browse the repository at this point in the history
This PR adds the support to specify what ECs the handler supports when
doing the handshake.

```swift
var config = TLSConfiguration.makeClientConfiguration()
config.curves = [.x25519]
```
  • Loading branch information
mads-frameo authored Oct 18, 2024
1 parent d7ceaf0 commit 27b25e2
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 2 deletions.
15 changes: 14 additions & 1 deletion Sources/NIOSSL/SSLContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,20 @@ public final class NIOSSLContext {
// Cipher suites. We just pass this straight to BoringSSL.
returnCode = CNIOBoringSSL_SSL_CTX_set_cipher_list(context, configuration.cipherSuites)
precondition(1 == returnCode)


// Curves list.
if let curves = configuration.curves {
returnCode = curves
.map { $0.rawValue }
.withUnsafeBufferPointer { algo in
CNIOBoringSSL_SSL_CTX_set1_group_ids(context, algo.baseAddress, algo.count)
}
if returnCode != 1 {
let errorStack = BoringSSLError.buildErrorStack()
throw BoringSSLError.unknownError(errorStack)
}
}

// Set the PSK Client Configuration callback.
if let pskClientConfigurationsCallback = configuration._pskClientIdentityProvider {
self.pskClientConfigurationCallback = pskClientConfigurationsCallback
Expand Down
28 changes: 28 additions & 0 deletions Sources/NIOSSL/TLSConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,29 @@ public struct NIOTLSCipher: RawRepresentable, Hashable, Sendable {
}
}

/// Available curves to use for TLS.
public struct NIOTLSCurve: RawRepresentable, Hashable, Sendable {
/// Construct a ``NIOTLSCurve`` from the RFC code point for that curve.
public init(rawValue: UInt16) {
self.rawValue = rawValue
}

/// Construct a ``NIOTLSCurve`` from the RFC code point for that curve.
public init(_ rawValue: RawValue) {
self.rawValue = rawValue
}

/// The RFC code point for the given curve.
public var rawValue: UInt16
public typealias RawValue = UInt16

public static let secp256r1 = NIOTLSCurve(rawValue: 0x17)
public static let secp384r1 = NIOTLSCurve(rawValue: 0x18)
public static let secp521r1 = NIOTLSCurve(rawValue: 0x19)
public static let x25519 = NIOTLSCurve(rawValue: 0x1D)
public static let x448 = NIOTLSCurve(rawValue: 0x1E)
}

/// Formats NIOSSL supports for serializing keys and certificates.
public enum NIOSSLSerializationFormats: Sendable {
case pem
Expand Down Expand Up @@ -253,6 +276,9 @@ public struct TLSConfiguration {
/// TLS 1.3 cipher suites cannot be configured.
public var cipherSuites: String = defaultCipherSuites

/// TLS curves supported by this handler. Passing `nil` means that a built-in set of curves will be used.
public var curves: [NIOTLSCurve]?

/// Public property used to set the internal ``cipherSuites`` from ``NIOTLSCipher``.
public var cipherSuiteValues: [NIOTLSCipher] {
get {
Expand Down Expand Up @@ -466,6 +492,7 @@ extension TLSConfiguration {
return self.minimumTLSVersion == comparing.minimumTLSVersion &&
self.maximumTLSVersion == comparing.maximumTLSVersion &&
self.cipherSuites == comparing.cipherSuites &&
self.curves == comparing.curves &&
self.verifySignatureAlgorithms == comparing.verifySignatureAlgorithms &&
self.signingSignatureAlgorithms == comparing.signingSignatureAlgorithms &&
self.certificateVerification == comparing.certificateVerification &&
Expand Down Expand Up @@ -493,6 +520,7 @@ extension TLSConfiguration {
hasher.combine(minimumTLSVersion)
hasher.combine(maximumTLSVersion)
hasher.combine(cipherSuites)
hasher.combine(curves)
hasher.combine(verifySignatureAlgorithms)
hasher.combine(signingSignatureAlgorithms)
hasher.combine(certificateVerification)
Expand Down
63 changes: 62 additions & 1 deletion Tests/NIOSSLTests/TLSConfigurationTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,66 @@ class TLSConfigurationTest: XCTestCase {
XCTAssertFalse(config.bestEffortEquals(differentConfig))
}

func testCompatibleCurves() throws {
var clientConfig = TLSConfiguration.makeClientConfiguration()
clientConfig.curves = [.x25519]
clientConfig.cipherSuiteValues = [.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384]
clientConfig.maximumTLSVersion = .tlsv12
clientConfig.certificateVerification = .noHostnameVerification
clientConfig.trustRoots = .certificates([TLSConfigurationTest.cert1])
clientConfig.renegotiationSupport = .none

var serverConfig = TLSConfiguration.makeServerConfiguration(
certificateChain: [.certificate(TLSConfigurationTest.cert1)],
privateKey: .privateKey(TLSConfigurationTest.key1)
)
serverConfig.cipherSuiteValues = [.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384]
serverConfig.curves = [.x25519]
serverConfig.maximumTLSVersion = .tlsv12
serverConfig.certificateVerification = .none
try assertHandshakeSucceeded(withClientConfig: clientConfig, andServerConfig: serverConfig)
}

func testMultipleCompatibleCurves() throws {
var clientConfig = TLSConfiguration.makeClientConfiguration()
clientConfig.curves = [.x25519]
clientConfig.cipherSuiteValues = [.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384]
clientConfig.maximumTLSVersion = .tlsv12
clientConfig.certificateVerification = .noHostnameVerification
clientConfig.trustRoots = .certificates([TLSConfigurationTest.cert1])
clientConfig.renegotiationSupport = .none

var serverConfig = TLSConfiguration.makeServerConfiguration(
certificateChain: [.certificate(TLSConfigurationTest.cert1)],
privateKey: .privateKey(TLSConfigurationTest.key1)
)
serverConfig.curves = [.x25519, .secp256r1]
serverConfig.cipherSuiteValues = [.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384]
serverConfig.maximumTLSVersion = .tlsv12
serverConfig.certificateVerification = .none
try assertHandshakeSucceeded(withClientConfig: clientConfig, andServerConfig: serverConfig)
}

func testNonCompatibleCurves() throws {
var clientConfig = TLSConfiguration.makeClientConfiguration()
clientConfig.curves = [.secp521r1]
clientConfig.cipherSuiteValues = [.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384]
clientConfig.maximumTLSVersion = .tlsv12
clientConfig.certificateVerification = .noHostnameVerification
clientConfig.trustRoots = .certificates([TLSConfigurationTest.cert1])
clientConfig.renegotiationSupport = .none

var serverConfig = TLSConfiguration.makeServerConfiguration(
certificateChain: [.certificate(TLSConfigurationTest.cert1)],
privateKey: .privateKey(TLSConfigurationTest.key1)
)
serverConfig.curves = [.x25519]
serverConfig.cipherSuiteValues = [.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384]
serverConfig.maximumTLSVersion = .tlsv12
serverConfig.certificateVerification = .none
try assertHandshakeError(withClientConfig: clientConfig, andServerConfig: serverConfig, errorTextContains: "ALERT_HANDSHAKE_FAILURE")
}

func testCompatibleCipherSuite() throws {
// ECDHE_RSA is used here because the public key in .cert1 is derived from a RSA private key.
// These could also be RSA based, but cannot be ECDHE_ECDSA.
Expand Down Expand Up @@ -1072,7 +1132,7 @@ class TLSConfigurationTest: XCTestCase {
func testBestEffortEquatableHashableDifferences() {
// If this assertion fails, DON'T JUST CHANGE THE NUMBER HERE! Make sure you've added any appropriate transforms below
// so that we're testing these best effort functions.
XCTAssertEqual(MemoryLayout<TLSConfiguration>.size, 226, "TLSConfiguration has changed size: you probably need to update this test!")
XCTAssertEqual(MemoryLayout<TLSConfiguration>.size, 234, "TLSConfiguration has changed size: you probably need to update this test!")

let first = TLSConfiguration.makeClientConfiguration()

Expand Down Expand Up @@ -1110,6 +1170,7 @@ class TLSConfigurationTest: XCTestCase {
{ $0.minimumTLSVersion = .tlsv13 },
{ $0.maximumTLSVersion = .tlsv12 },
{ $0.cipherSuites = "AES" },
{ $0.curves = [.x25519] },
{ $0.cipherSuiteValues = [.TLS_RSA_WITH_AES_256_CBC_SHA] },
{ $0.verifySignatureAlgorithms = [.ed25519] },
{ $0.signingSignatureAlgorithms = [.ed25519] },
Expand Down

0 comments on commit 27b25e2

Please sign in to comment.