diff --git a/Sources/NIOSSL/TLSConfiguration.swift b/Sources/NIOSSL/TLSConfiguration.swift index 07f3b318..d56b487a 100644 --- a/Sources/NIOSSL/TLSConfiguration.swift +++ b/Sources/NIOSSL/TLSConfiguration.swift @@ -449,9 +449,9 @@ public struct TLSConfiguration { self.signingSignatureAlgorithms = signingSignatureAlgorithms self.minimumTLSVersion = minimumTLSVersion self.maximumTLSVersion = maximumTLSVersion - self.certificateVerification = certificateVerification self.trustRoots = trustRoots self.additionalTrustRoots = additionalTrustRoots + self.certificateVerification = certificateVerification self.certificateChain = certificateChain self.privateKey = privateKey self.encodedApplicationProtocols = [] @@ -642,6 +642,47 @@ extension TLSConfiguration { pskHint: nil ) } + + /// Create a TLS configuration for use with server-side contexts that expect to validate a client + /// certificate (often called mTLS). + /// + /// This provides sensible defaults while requiring that you provide any data that is necessary + /// for server-side function. For servers that don't need mTLS, try + /// ``TLSConfiguration/makeServerConfiguration(certificateChain:privateKey:)`` instead. + /// + /// This configuration is very similar to ``TLSConfiguration/makeServerConfiguration(certificateChain:privateKey:)`` but + /// adds a `trustRoots` requirement. These roots will be used to validate the certificate + /// presented by the peer. It also sets the ``certificateVerification`` field to + /// ``CertificateVerification/noHostnameVerification``, which enables verification but disables + /// any hostname checking, which cannot succeed in a server context. + /// + /// For customising fields, modify the returned TLSConfiguration object. + public static func makeServerConfigurationWithMTLS( + certificateChain: [NIOSSLCertificateSource], + privateKey: NIOSSLPrivateKeySource, + trustRoots: NIOSSLTrustRoots + ) -> TLSConfiguration { + TLSConfiguration( + cipherSuites: defaultCipherSuites, + verifySignatureAlgorithms: nil, + signingSignatureAlgorithms: nil, + minimumTLSVersion: .tlsv1, + maximumTLSVersion: nil, + certificateVerification: .noHostnameVerification, + trustRoots: trustRoots, + certificateChain: certificateChain, + privateKey: privateKey, + applicationProtocols: [], + shutdownTimeout: .seconds(5), + keyLogCallback: nil, + renegotiationSupport: .none, + additionalTrustRoots: [], + sendCANameList: false, + pskClientProvider: nil, + pskServerProvider: nil, + pskHint: nil + ) + } } // MARK: Deprecated constructors. diff --git a/Tests/NIOSSLTests/TLSConfigurationTest.swift b/Tests/NIOSSLTests/TLSConfigurationTest.swift index 1d4187aa..b2790a92 100644 --- a/Tests/NIOSSLTests/TLSConfigurationTest.swift +++ b/Tests/NIOSSLTests/TLSConfigurationTest.swift @@ -2187,6 +2187,67 @@ class TLSConfigurationTest: XCTestCase { XCTAssertEqual(callbackCount.withLockedValue { $0 }, 5) } + + func testCorrectSetUpOfMTLSContext() throws { + var basicConfig = TLSConfiguration.makeServerConfiguration( + certificateChain: [.certificate(TLSConfigurationTest.cert2)], + privateKey: .privateKey(TLSConfigurationTest.key2) + ) + let mtlsConfig = TLSConfiguration.makeServerConfigurationWithMTLS( + certificateChain: [.certificate(TLSConfigurationTest.cert2)], + privateKey: .privateKey(TLSConfigurationTest.key2), + trustRoots: .default + ) + XCTAssertFalse(basicConfig.bestEffortEquals(mtlsConfig)) + + basicConfig.trustRoots = .default + basicConfig.certificateVerification = .noHostnameVerification + + XCTAssertTrue(basicConfig.bestEffortEquals(mtlsConfig)) + } + + func testMTLSContext_happyPath() throws { + var clientConfig = TLSConfiguration.makeClientConfiguration() + clientConfig.trustRoots = .certificates([TLSConfigurationTest.cert1]) + clientConfig.certificateChain = [.certificate(TLSConfigurationTest.cert2)] + clientConfig.privateKey = .privateKey(TLSConfigurationTest.key2) + clientConfig.certificateVerification = .noHostnameVerification + + let serverConfig = TLSConfiguration.makeServerConfigurationWithMTLS( + certificateChain: [.certificate(TLSConfigurationTest.cert1)], + privateKey: .privateKey(TLSConfigurationTest.key1), + trustRoots: .certificates([TLSConfigurationTest.cert2]) + ) + + let clientContext = try assertNoThrowWithValue( + NIOSSLContext(configuration: clientConfig) + ) + let serverContext = try assertNoThrowWithValue( + NIOSSLContext(configuration: serverConfig) + ) + + try assertHandshakeSucceeded(withClientContext: clientContext, andServerContext: serverContext) + } + + func testMTLSContext_clientPresentsWrongCert() throws { + var clientConfig = TLSConfiguration.makeClientConfiguration() + clientConfig.trustRoots = .certificates([TLSConfigurationTest.cert1]) + clientConfig.certificateChain = [.certificate(TLSConfigurationTest.cert1)] + clientConfig.privateKey = .privateKey(TLSConfigurationTest.key1) + clientConfig.certificateVerification = .noHostnameVerification + + let serverConfig = TLSConfiguration.makeServerConfigurationWithMTLS( + certificateChain: [.certificate(TLSConfigurationTest.cert1)], + privateKey: .privateKey(TLSConfigurationTest.key1), + trustRoots: .certificates([TLSConfigurationTest.cert2]) + ) + + try assertPostHandshakeError( + withClientConfig: clientConfig, + andServerConfig: serverConfig, + errorTextContainsAnyOf: ["ALERT_UNKNOWN_CA", "ALERT_CERTIFICATE_UNKNOWN"] + ) + } } extension EmbeddedChannel {