diff --git a/Sources/NIOSSL/SSLContext.swift b/Sources/NIOSSL/SSLContext.swift index 55995dc6..16a2f608 100644 --- a/Sources/NIOSSL/SSLContext.swift +++ b/Sources/NIOSSL/SSLContext.swift @@ -310,7 +310,8 @@ public final class NIOSSLContext { verification: configuration.certificateVerification, trustRoots: configuration.trustRoots, additionalTrustRoots: configuration.additionalTrustRoots, - sendCANames: configuration.sendCANameList) + sendCANames: configuration.sendCANameList, + certificateRequired: configuration.certificateRequired) // Configure verification algorithms if let verifySignatureAlgorithms = configuration.verifySignatureAlgorithms { @@ -566,11 +567,12 @@ extension NIOSSLContext { // Configuring certificate verification extension NIOSSLContext { - private static func configureCertificateValidation(context: OpaquePointer, verification: CertificateVerification, trustRoots: NIOSSLTrustRoots?, additionalTrustRoots: [NIOSSLAdditionalTrustRoots], sendCANames: Bool) throws { + private static func configureCertificateValidation(context: OpaquePointer, verification: CertificateVerification, trustRoots: NIOSSLTrustRoots?, additionalTrustRoots: [NIOSSLAdditionalTrustRoots], sendCANames: Bool, certificateRequired: Bool) throws { // If validation is turned on, set the trust roots and turn on cert validation. switch verification { case .fullVerification, .noHostnameVerification: - CNIOBoringSSL_SSL_CTX_set_verify(context, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nil) + let flags = certificateRequired ? SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT : SSL_VERIFY_PEER + CNIOBoringSSL_SSL_CTX_set_verify(context, flags, nil) // Also, set TRUSTED_FIRST to work around dumb clients that don't know what they're doing and send // untrusted root certs. X509_VERIFY_PARAM will or-in the flags, so we don't need to load them first. diff --git a/Sources/NIOSSL/TLSConfiguration.swift b/Sources/NIOSSL/TLSConfiguration.swift index f3429b41..972c9ebe 100644 --- a/Sources/NIOSSL/TLSConfiguration.swift +++ b/Sources/NIOSSL/TLSConfiguration.swift @@ -268,6 +268,7 @@ public struct TLSConfiguration { /// Whether to verify remote certificates. public var certificateVerification: CertificateVerification + public var certificateRequired: Bool = true /// The trust roots to use to validate certificates. This only needs to be provided if you intend to validate /// certificates. diff --git a/Tests/NIOSSLTests/TLSConfigurationTest.swift b/Tests/NIOSSLTests/TLSConfigurationTest.swift index c3acd576..4307ec06 100644 --- a/Tests/NIOSSLTests/TLSConfigurationTest.swift +++ b/Tests/NIOSSLTests/TLSConfigurationTest.swift @@ -390,6 +390,40 @@ class TLSConfigurationTest: XCTestCase { try assertPostHandshakeError(withClientConfig: clientConfig, andServerConfig: serverConfig, errorTextContainsAnyOf: ["CERTIFICATE_REQUIRED"]) } + + func testMutualValidationOptionalClientCertificatePreTLS13() throws { + var clientConfig = TLSConfiguration.makeClientConfiguration() + clientConfig.maximumTLSVersion = .tlsv12 + clientConfig.certificateVerification = .none + + var serverConfig = TLSConfiguration.makeServerConfiguration( + certificateChain: [.certificate(TLSConfigurationTest.cert1)], + privateKey: .privateKey(TLSConfigurationTest.key1) + ) + serverConfig.maximumTLSVersion = .tlsv12 + serverConfig.certificateVerification = .noHostnameVerification + serverConfig.certificateRequired = false + serverConfig.trustRoots = .certificates([TLSConfigurationTest.cert2]) + + try assertHandshakeSucceeded(withClientConfig: clientConfig, andServerConfig: serverConfig) + } + + func testMutualValidationOptionalClientCertificatePostTLS13() throws { + var clientConfig = TLSConfiguration.makeClientConfiguration() + clientConfig.minimumTLSVersion = .tlsv13 + clientConfig.certificateVerification = .none + + var serverConfig = TLSConfiguration.makeServerConfiguration( + certificateChain: [.certificate(TLSConfigurationTest.cert1)], + privateKey: .privateKey(TLSConfigurationTest.key1) + ) + serverConfig.minimumTLSVersion = .tlsv13 + serverConfig.certificateVerification = .noHostnameVerification + serverConfig.certificateRequired = false + serverConfig.trustRoots = .certificates([TLSConfigurationTest.cert2]) + + try assertHandshakeSucceeded(withClientConfig: clientConfig, andServerConfig: serverConfig) + } func testIncompatibleSignatures() throws { var clientConfig = TLSConfiguration.makeClientConfiguration()