Skip to content

Commit

Permalink
Rationalise TLSConfiguration construction.
Browse files Browse the repository at this point in the history
Motivation:

Currently whenever we add new config to TLSConfiguration, we add a new
static factory function that needs to provide the field. This is because
we currently allow configuration primarily by way of initializers. This
is increasingly not scaling, leading to a proliferation of almost
identical static factory functions.

We can replace this proliferation by having only "default" constructions
that default basically everything, and then having users use
getters/setters to initialize things. This also works well when we have
multiple ways of interacting with config, e.g. with the various cipher
suite operations.

Modifications:

- Deprecate all existing factory functions.
- Replace with two: forClient() and
  forServer(certificateChain:privateKey:). These configure only the
  mandatory things for each mode.
- Replace all uses of the deprecated initializers with the new ones,
  configuring fields manually as needed.

Result:

More consistent configuration objects.
  • Loading branch information
Lukasa committed May 13, 2021
1 parent 33a4041 commit 865fbf3
Show file tree
Hide file tree
Showing 16 changed files with 571 additions and 363 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@ import NIO
import NIOSSL

func run(identifier: String) {
let serverContext = try! NIOSSLContext(configuration: .forServer(certificateChain: [.certificate(.forTesting())], privateKey: .privateKey(.forTesting())))
let clientContext = try! NIOSSLContext(configuration: .forClient(trustRoots: .certificates([.forTesting()])))
let serverContext = try! NIOSSLContext(configuration: .makeServerConfiguration(
certificateChain: [.certificate(.forTesting())],
privateKey: .privateKey(.forTesting())
))

var clientConfig = TLSConfiguration.makeClientConfiguration()
clientConfig.trustRoots = .certificates([.forTesting()])
let clientContext = try! NIOSSLContext(configuration: clientConfig)

let dummyAddress = try! SocketAddress(ipAddress: "1.2.3.4", port: 5678)
let backToBack = BackToBackEmbeddedChannel()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@ import NIO
import NIOSSL

func run(identifier: String) {
let serverContext = try! NIOSSLContext(configuration: .forServer(certificateChain: [.certificate(.forTesting())], privateKey: .privateKey(.forTesting())))
let clientContext = try! NIOSSLContext(configuration: .forClient(trustRoots: .certificates([.forTesting()])))
let serverContext = try! NIOSSLContext(configuration: .makeServerConfiguration(
certificateChain: [.certificate(.forTesting())],
privateKey: .privateKey(.forTesting())
))

var clientConfig = TLSConfiguration.forClient()
clientConfig.trustRoots = .certificates([.forTesting()])
let clientContext = try! NIOSSLContext(configuration: clientConfig)

let dummyAddress = try! SocketAddress(ipAddress: "1.2.3.4", port: 5678)

Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ To secure a server connection, you will need a X.509 certificate chain in a file
For example:

```swift
let configuration = TLSConfiguration.forServer(certificateChain: try NIOSSLCertificate.fromPEMFile("cert.pem").map { .certificate($0) },
privateKey: .file("key.pem"))
let configuration = TLSConfiguration.makeServerConfiguration(
certificateChain: try NIOSSLCertificate.fromPEMFile("cert.pem").map { .certificate($0) },
privateKey: .file("key.pem")
)
let sslContext = try NIOSSLContext(configuration: configuration)

let server = ServerBootstrap(group: group)
Expand All @@ -35,7 +37,7 @@ let server = ServerBootstrap(group: group)
For clients, it is a bit simpler as there is no need to have a certificate chain or private key (though clients *may* have these things). Setup for clients may be done like this:

```swift
let configuration = TLSConfiguration.forClient()
let configuration = TLSConfiguration.makeClientConfiguration()
let sslContext = try NIOSSLContext(configuration: configuration)

let client = ClientBootstrap(group: group)
Expand Down
6 changes: 4 additions & 2 deletions Sources/NIOSSL/SSLPKCS12Bundle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ import NIO
/// If you have a PKCS12 bundle, you configure a `TLSConfiguration` like this:
///
/// let p12Bundle = NIOSSLPKCS12Bundle(file: pathToMyP12)
/// let config = TLSConfiguration.forServer(certificateChain: p12Bundle.certificateChain,
/// privateKey: p12Bundle.privateKey)
/// let config = TLSConfiguration.makeServerConfiguration(
/// certificateChain: p12Bundle.certificateChain,
/// privateKey: p12Bundle.privateKey
/// )
///
/// The created `TLSConfiguration` can then be safely used for your endpoint.
public struct NIOSSLPKCS12Bundle {
Expand Down
196 changes: 129 additions & 67 deletions Sources/NIOSSL/TLSConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ internal func decodeALPNIdentifier(identifier: [UInt8]) -> String {
/// Manages configuration of TLS for SwiftNIO programs.
public struct TLSConfiguration {
/// A default TLS configuration for client use.
public static let clientDefault = TLSConfiguration.forClient()
public static let clientDefault = TLSConfiguration.makeClientConfiguration()

/// The minimum TLS version to allow in negotiation. Defaults to tlsv1.
public var minimumTLSVersion: TLSVersion
Expand Down Expand Up @@ -337,11 +337,120 @@ public struct TLSConfiguration {
self.cipherSuiteValues = cipherSuiteValues
}
}
}

// MARK: BestEffortHashable
extension TLSConfiguration {
/// Returns a best effort result of whether two `TLSConfiguration` objects are equal.
///
/// The "best effort" stems from the fact that we are checking the pointer to the `keyLogCallback` closure.
///
/// - warning: You should probably not use this function. This function can return false-negatives, but not false-positives.
public func bestEffortEquals(_ comparing: TLSConfiguration) -> Bool {
let isKeyLoggerCallbacksEqual = withUnsafeBytes(of: self.keyLogCallback) { callbackPointer1 in
return withUnsafeBytes(of: comparing.keyLogCallback) { callbackPointer2 in
return callbackPointer1.elementsEqual(callbackPointer2)
}
}

return self.minimumTLSVersion == comparing.minimumTLSVersion &&
self.maximumTLSVersion == comparing.maximumTLSVersion &&
self.cipherSuites == comparing.cipherSuites &&
self.verifySignatureAlgorithms == comparing.verifySignatureAlgorithms &&
self.signingSignatureAlgorithms == comparing.signingSignatureAlgorithms &&
self.certificateVerification == comparing.certificateVerification &&
self.trustRoots == comparing.trustRoots &&
self.additionalTrustRoots == comparing.additionalTrustRoots &&
self.certificateChain == comparing.certificateChain &&
self.privateKey == comparing.privateKey &&
self.encodedApplicationProtocols == comparing.encodedApplicationProtocols &&
self.shutdownTimeout == comparing.shutdownTimeout &&
isKeyLoggerCallbacksEqual &&
self.renegotiationSupport == comparing.renegotiationSupport
}

/// Returns a best effort hash of this TLS configuration.
///
/// The "best effort" stems from the fact that we are hashing the pointer bytes of the `keyLogCallback` closure.
///
/// - warning: You should probably not use this function. This function can return false-negatives, but not false-positives.
public func bestEffortHash(into hasher: inout Hasher) {
hasher.combine(minimumTLSVersion)
hasher.combine(maximumTLSVersion)
hasher.combine(cipherSuites)
hasher.combine(verifySignatureAlgorithms)
hasher.combine(signingSignatureAlgorithms)
hasher.combine(certificateVerification)
hasher.combine(trustRoots)
hasher.combine(additionalTrustRoots)
hasher.combine(certificateChain)
hasher.combine(privateKey)
hasher.combine(encodedApplicationProtocols)
hasher.combine(shutdownTimeout)
withUnsafeBytes(of: keyLogCallback) { closureBits in
hasher.combine(bytes: closureBits)
}
hasher.combine(renegotiationSupport)
}

/// Creates a TLS configuration for use with client-side contexts.
///
/// This provides sensible defaults, and can be used without customisation. For server-side
/// contexts, you should use `makeServerConfiguration` instead.
///
/// For customising fields, modify the returned TLSConfiguration object.
public static func makeClientConfiguration() -> TLSConfiguration {
return TLSConfiguration(cipherSuites: defaultCipherSuites,
verifySignatureAlgorithms: nil,
signingSignatureAlgorithms: nil,
minimumTLSVersion: .tlsv1,
maximumTLSVersion: nil,
certificateVerification: .fullVerification,
trustRoots: .default,
certificateChain: [],
privateKey: nil,
applicationProtocols: [],
shutdownTimeout: .seconds(5),
keyLogCallback: nil,
renegotiationSupport: .none,
additionalTrustRoots: [])
}

/// Create a TLS configuration for use with server-side contexts.
///
/// This provides sensible defaults while requiring that you provide any data that is necessary
/// for server-side function. For client use, try `makeClientConfiguration` instead.
///
/// For customising fields, modify the returned TLSConfiguration object.
public static func makeServerConfiguration(
certificateChain: [NIOSSLCertificateSource],
privateKey: NIOSSLPrivateKeySource
) -> TLSConfiguration {
return TLSConfiguration(cipherSuites: defaultCipherSuites,
verifySignatureAlgorithms: nil,
signingSignatureAlgorithms: nil,
minimumTLSVersion: .tlsv1,
maximumTLSVersion: nil,
certificateVerification: .none,
trustRoots: .default,
certificateChain: certificateChain,
privateKey: privateKey,
applicationProtocols: [],
shutdownTimeout: .seconds(5),
keyLogCallback: nil,
renegotiationSupport: .none,
additionalTrustRoots: [])
}
}

// MARK: Deprecated constructors.

extension TLSConfiguration {
/// Create a TLS configuration for use with server-side contexts. This allows setting the `NIOTLSCipher` property specifically.
///
/// This provides sensible defaults while requiring that you provide any data that is necessary
/// for server-side function. For client use, try `forClient` instead.
/// for server-side function. For client use, try `makeClientConfiguration` instead.
@available(*, deprecated, renamed: "makeServerConfiguration(certificateChain:privateKey:)")
public static func forServer(certificateChain: [NIOSSLCertificateSource],
privateKey: NIOSSLPrivateKeySource,
cipherSuites: [NIOTLSCipher],
Expand Down Expand Up @@ -374,7 +483,8 @@ public struct TLSConfiguration {
/// Create a TLS configuration for use with server-side contexts.
///
/// This provides sensible defaults while requiring that you provide any data that is necessary
/// for server-side function. For client use, try `forClient` instead.
/// for server-side function. For client use, try `makeClientConfiguration` instead.
@available(*, deprecated, renamed: "makeServerConfiguration(certificateChain:privateKey:)")
public static func forServer(certificateChain: [NIOSSLCertificateSource],
privateKey: NIOSSLPrivateKeySource,
cipherSuites: String = defaultCipherSuites,
Expand Down Expand Up @@ -404,7 +514,8 @@ public struct TLSConfiguration {
/// Create a TLS configuration for use with server-side contexts.
///
/// This provides sensible defaults while requiring that you provide any data that is necessary
/// for server-side function. For client use, try `forClient` instead.
/// for server-side function. For client use, try `makeClientConfiguration` instead.
@available(*, deprecated, renamed: "makeServerConfiguration(certificateChain:privateKey:)")
public static func forServer(certificateChain: [NIOSSLCertificateSource],
privateKey: NIOSSLPrivateKeySource,
cipherSuites: String = defaultCipherSuites,
Expand Down Expand Up @@ -436,7 +547,8 @@ public struct TLSConfiguration {
/// Create a TLS configuration for use with server-side contexts.
///
/// This provides sensible defaults while requiring that you provide any data that is necessary
/// for server-side function. For client use, try `forClient` instead.
/// for server-side function. For client use, try `makeClientConfiguration` instead.
@available(*, deprecated, renamed: "makeServerConfiguration(certificateChain:privateKey:)")
public static func forServer(certificateChain: [NIOSSLCertificateSource],
privateKey: NIOSSLPrivateKeySource,
cipherSuites: String = defaultCipherSuites,
Expand Down Expand Up @@ -465,11 +577,12 @@ public struct TLSConfiguration {
renegotiationSupport: .none, // Servers never support renegotiation: there's no point.
additionalTrustRoots: additionalTrustRoots)
}

/// Creates a TLS configuration for use with client-side contexts. This allows setting the `NIOTLSCipher` property specifically.
///
/// This provides sensible defaults, and can be used without customisation. For server-side
/// contexts, you should use `forServer` instead.
/// contexts, you should use `makeServerConfiguration` instead.
@available(*, deprecated, renamed: "makeClientConfiguration()")
public static func forClient(cipherSuites: [NIOTLSCipher],
verifySignatureAlgorithms: [SignatureAlgorithm]? = nil,
signingSignatureAlgorithms: [SignatureAlgorithm]? = nil,
Expand Down Expand Up @@ -503,7 +616,8 @@ public struct TLSConfiguration {
/// Creates a TLS configuration for use with client-side contexts.
///
/// This provides sensible defaults, and can be used without customisation. For server-side
/// contexts, you should use `forServer` instead.
/// contexts, you should use `makeServerConfiguration` instead.
@available(*, deprecated, renamed: "makeClientConfiguration()")
public static func forClient(cipherSuites: String = defaultCipherSuites,
minimumTLSVersion: TLSVersion = .tlsv1,
maximumTLSVersion: TLSVersion? = nil,
Expand Down Expand Up @@ -534,7 +648,8 @@ public struct TLSConfiguration {
/// Creates a TLS configuration for use with client-side contexts.
///
/// This provides sensible defaults, and can be used without customisation. For server-side
/// contexts, you should use `forServer` instead.
/// contexts, you should use `makeServerConfiguration` instead.
@available(*, deprecated, renamed: "makeClientConfiguration()")
public static func forClient(cipherSuites: String = defaultCipherSuites,
minimumTLSVersion: TLSVersion = .tlsv1,
maximumTLSVersion: TLSVersion? = nil,
Expand All @@ -561,11 +676,12 @@ public struct TLSConfiguration {
renegotiationSupport: renegotiationSupport,
additionalTrustRoots: [])
}

/// Creates a TLS configuration for use with client-side contexts.
///
/// This provides sensible defaults, and can be used without customisation. For server-side
/// contexts, you should use `forServer` instead.
/// contexts, you should use `makeServerConfiguration` instead.
@available(*, deprecated, renamed: "makeClientConfiguration()")
public static func forClient(cipherSuites: String = defaultCipherSuites,
verifySignatureAlgorithms: [SignatureAlgorithm]? = nil,
signingSignatureAlgorithms: [SignatureAlgorithm]? = nil,
Expand Down Expand Up @@ -598,7 +714,8 @@ public struct TLSConfiguration {
/// Creates a TLS configuration for use with client-side contexts.
///
/// This provides sensible defaults, and can be used without customisation. For server-side
/// contexts, you should use `forServer` instead.
/// contexts, you should use `makeServerConfiguration` instead.
@available(*, deprecated, renamed: "makeClientConfiguration()")
public static func forClient(cipherSuites: String = defaultCipherSuites,
verifySignatureAlgorithms: [SignatureAlgorithm]? = nil,
signingSignatureAlgorithms: [SignatureAlgorithm]? = nil,
Expand Down Expand Up @@ -629,58 +746,3 @@ public struct TLSConfiguration {
additionalTrustRoots: additionalTrustRoots)
}
}

// MARK: BestEffortHashable
extension TLSConfiguration {
/// Returns a best effort result of whether two `TLSConfiguration` objects are equal.
///
/// The "best effort" stems from the fact that we are checking the pointer to the `keyLogCallback` closure.
///
/// - warning: You should probably not use this function. This function can return false-negatives, but not false-positives.
public func bestEffortEquals(_ comparing: TLSConfiguration) -> Bool {
let isKeyLoggerCallbacksEqual = withUnsafeBytes(of: self.keyLogCallback) { callbackPointer1 in
return withUnsafeBytes(of: comparing.keyLogCallback) { callbackPointer2 in
return callbackPointer1.elementsEqual(callbackPointer2)
}
}

return self.minimumTLSVersion == comparing.minimumTLSVersion &&
self.maximumTLSVersion == comparing.maximumTLSVersion &&
self.cipherSuites == comparing.cipherSuites &&
self.verifySignatureAlgorithms == comparing.verifySignatureAlgorithms &&
self.signingSignatureAlgorithms == comparing.signingSignatureAlgorithms &&
self.certificateVerification == comparing.certificateVerification &&
self.trustRoots == comparing.trustRoots &&
self.additionalTrustRoots == comparing.additionalTrustRoots &&
self.certificateChain == comparing.certificateChain &&
self.privateKey == comparing.privateKey &&
self.encodedApplicationProtocols == comparing.encodedApplicationProtocols &&
self.shutdownTimeout == comparing.shutdownTimeout &&
isKeyLoggerCallbacksEqual &&
self.renegotiationSupport == comparing.renegotiationSupport
}

/// Returns a best effort hash of this TLS configuration.
///
/// The "best effort" stems from the fact that we are hashing the pointer bytes of the `keyLogCallback` closure.
///
/// - warning: You should probably not use this function. This function can return false-negatives, but not false-positives.
public func bestEffortHash(into hasher: inout Hasher) {
hasher.combine(minimumTLSVersion)
hasher.combine(maximumTLSVersion)
hasher.combine(cipherSuites)
hasher.combine(verifySignatureAlgorithms)
hasher.combine(signingSignatureAlgorithms)
hasher.combine(certificateVerification)
hasher.combine(trustRoots)
hasher.combine(additionalTrustRoots)
hasher.combine(certificateChain)
hasher.combine(privateKey)
hasher.combine(encodedApplicationProtocols)
hasher.combine(shutdownTimeout)
withUnsafeBytes(of: keyLogCallback) { closureBits in
hasher.combine(bytes: closureBits)
}
hasher.combine(renegotiationSupport)
}
}
2 changes: 1 addition & 1 deletion Sources/NIOSSL/UniversalBootstrapSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import NIO
/// Example:
///
/// // TLS setup.
/// let configuration = TLSConfiguration.forClient()
/// let configuration = TLSConfiguration.makeClientConfiguration()
/// let sslContext = try NIOSSLContext(configuration: configuration)
///
/// // Creating the "universal bootstrap" with the `NIOSSLClientTLSProvider`.
Expand Down
7 changes: 6 additions & 1 deletion Sources/NIOSSLHTTP1Client/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,12 @@ defer {
try! eventLoopGroup.syncShutdownGracefully()
}

let tlsConfiguration = TLSConfiguration.forClient(trustRoots: trustRoot, certificateChain: cert, privateKey: key, renegotiationSupport: .once)
var tlsConfiguration = TLSConfiguration.makeClientConfiguration()
tlsConfiguration.trustRoots = trustRoot
tlsConfiguration.certificateChain = cert
tlsConfiguration.privateKey = key
tlsConfiguration.renegotiationSupport = .once

let sslContext = try! NIOSSLContext(configuration: tlsConfiguration)

let bootstrap = ClientBootstrap(group: eventLoopGroup)
Expand Down
Loading

0 comments on commit 865fbf3

Please sign in to comment.