@@ -2600,6 +2600,170 @@ class NIOSSLIntegrationTest: XCTestCase {
26002600 XCTAssertNoThrow ( try handshakeCompletePromise. futureResult. wait ( ) )
26012601 }
26022602
2603+ func testMacOSConnectionFailsIfServerVerificationOptionalAndPeerPresentsUntrustedCert( ) throws {
2604+ // This test checks that when setting verification to `.optionalVerification`, a peer cannot successfully
2605+ // connect when they present an untrusted certificate. On macOS, this exercises the SecTrust validation backend,
2606+ // as `serverConfig.trustRoots` is set to `.default` (see the behavioral matrix in
2607+ // `NIOSSL/Docs/trust-roots-behavior.md`).
2608+ var serverConfig = TLSConfiguration . makeServerConfiguration (
2609+ certificateChain: [ . certificate( NIOSSLIntegrationTest . cert) ] ,
2610+ privateKey: . privateKey( NIOSSLIntegrationTest . key)
2611+ )
2612+ serverConfig. certificateVerification = . optionalVerification
2613+ serverConfig. trustRoots = . default
2614+
2615+ var clientConfig = TLSConfiguration . makeClientConfiguration ( )
2616+ clientConfig. certificateVerification = . noHostnameVerification
2617+ clientConfig. trustRoots = . default
2618+ clientConfig. additionalTrustRoots = [ . certificates( [ NIOSSLIntegrationTest . cert] ) ]
2619+ // The client presents a random cert but the server won't trust it
2620+ let clientCertAndPrivateKey = generateSelfSignedCert ( )
2621+ clientConfig. certificateChain = [ . certificate( clientCertAndPrivateKey. 0 ) ]
2622+ clientConfig. privateKey = . privateKey( clientCertAndPrivateKey. 1 )
2623+
2624+ let serverContext = try assertNoThrowWithValue ( NIOSSLContext ( configuration: serverConfig) )
2625+ let clientContext = try assertNoThrowWithValue ( NIOSSLContext ( configuration: clientConfig) )
2626+
2627+ let group = MultiThreadedEventLoopGroup ( numberOfThreads: 1 )
2628+ defer {
2629+ XCTAssertNoThrow ( try group. syncShutdownGracefully ( ) )
2630+ }
2631+
2632+ let handshakeCompletePromise = group. next ( ) . makePromise ( of: Void . self)
2633+ let serverChannel : Channel = try serverTLSChannel (
2634+ context: serverContext,
2635+ handlers: [ WaitForHandshakeHandler ( handshakeResultPromise: handshakeCompletePromise) ] ,
2636+ group: group
2637+ )
2638+ defer {
2639+ XCTAssertNoThrow ( try serverChannel. close ( ) . wait ( ) )
2640+ }
2641+
2642+ let clientChannel = try clientTLSChannel (
2643+ context: clientContext,
2644+ preHandlers: [ ] ,
2645+ postHandlers: [ ] ,
2646+ group: group,
2647+ connectingTo: serverChannel. localAddress!,
2648+ serverHostname: " localhost "
2649+ )
2650+ defer {
2651+ XCTAssertNoThrow ( try clientChannel. close ( ) . wait ( ) )
2652+ }
2653+
2654+ // The handshake should fail: certificate verification is optional and the client hasn't presented any certs.
2655+ XCTAssertThrowsError ( try handshakeCompletePromise. futureResult. wait ( ) )
2656+ }
2657+
2658+ func testMacOSConnectionSuccessfulIfServerVerificationOptionalAndPeerPresentsTrustedCert( ) throws {
2659+ // This test checks that when setting verification to `.optionalVerification`, a peer can successfully
2660+ // connect when they present a trusted certificate. On macOS, this exercises the SecTrust validation backend,
2661+ // as `serverConfig.trustRoots` is set to `.default` and the client cert is registered under
2662+ // `additionalTrustRoots` (see the behavioral matrix in `NIOSSL/Docs.docc/trust-roots-behavior.md`).
2663+ var clientConfig = TLSConfiguration . makeClientConfiguration ( )
2664+ clientConfig. certificateVerification = . noHostnameVerification
2665+ clientConfig. trustRoots = . default
2666+ clientConfig. additionalTrustRoots = [ . certificates( [ NIOSSLIntegrationTest . cert] ) ]
2667+ // The client presents a generated cert
2668+ let clientCertAndPrivateKey = generateSelfSignedCert ( )
2669+ clientConfig. certificateChain = [ . certificate( clientCertAndPrivateKey. 0 ) ]
2670+ clientConfig. privateKey = . privateKey( clientCertAndPrivateKey. 1 )
2671+
2672+ var serverConfig = TLSConfiguration . makeServerConfiguration (
2673+ certificateChain: [ . certificate( NIOSSLIntegrationTest . cert) ] ,
2674+ privateKey: . privateKey( NIOSSLIntegrationTest . key)
2675+ )
2676+ serverConfig. certificateVerification = . optionalVerification
2677+ serverConfig. trustRoots = . default
2678+ // The server trusts the client's generated cert
2679+ serverConfig. additionalTrustRoots = [ . certificates( [ clientCertAndPrivateKey. 0 ] ) ]
2680+
2681+ let serverContext = try assertNoThrowWithValue ( NIOSSLContext ( configuration: serverConfig) )
2682+ let clientContext = try assertNoThrowWithValue ( NIOSSLContext ( configuration: clientConfig) )
2683+
2684+ let group = MultiThreadedEventLoopGroup ( numberOfThreads: 1 )
2685+ defer {
2686+ XCTAssertNoThrow ( try group. syncShutdownGracefully ( ) )
2687+ }
2688+
2689+ let handshakeCompletePromise = group. next ( ) . makePromise ( of: Void . self)
2690+ let serverChannel : Channel = try serverTLSChannel (
2691+ context: serverContext,
2692+ handlers: [ WaitForHandshakeHandler ( handshakeResultPromise: handshakeCompletePromise) ] ,
2693+ group: group
2694+ )
2695+ defer {
2696+ XCTAssertNoThrow ( try serverChannel. close ( ) . wait ( ) )
2697+ }
2698+
2699+ let clientChannel = try clientTLSChannel (
2700+ context: clientContext,
2701+ preHandlers: [ ] ,
2702+ postHandlers: [ ] ,
2703+ group: group,
2704+ connectingTo: serverChannel. localAddress!,
2705+ serverHostname: " localhost "
2706+ )
2707+ defer {
2708+ XCTAssertNoThrow ( try clientChannel. close ( ) . wait ( ) )
2709+ }
2710+
2711+ // The handshake should succeed: verification is optional, and the client presents a cert the server trusts.
2712+ XCTAssertNoThrow ( try handshakeCompletePromise. futureResult. wait ( ) )
2713+ }
2714+
2715+ func testMacOSConnectionSuccessfulIfServerVerificationOptionalAndNoPeerCert( ) throws {
2716+ // This test checks that when setting verification to `.optionalVerification`, a peer can successfully connect
2717+ // when they don't present any certificate. On macOS, this exercises the SecTrust validation backend, as
2718+ // `serverConfig.trustRoots` is set to `.default` (see the behavioral matrix in
2719+ // `NIOSSL/Docs.docc/trust-roots-behavior.md`).
2720+ var serverConfig = TLSConfiguration . makeServerConfiguration (
2721+ certificateChain: [ . certificate( NIOSSLIntegrationTest . cert) ] ,
2722+ privateKey: . privateKey( NIOSSLIntegrationTest . key)
2723+ )
2724+ serverConfig. certificateVerification = . optionalVerification
2725+ serverConfig. trustRoots = . default
2726+
2727+ // The client doesn't present any certs
2728+ var clientConfig = TLSConfiguration . makeClientConfiguration ( )
2729+ clientConfig. certificateVerification = . noHostnameVerification
2730+ clientConfig. trustRoots = . default
2731+ clientConfig. additionalTrustRoots = [ . certificates( [ NIOSSLIntegrationTest . cert] ) ]
2732+
2733+ let serverContext = try assertNoThrowWithValue ( NIOSSLContext ( configuration: serverConfig) )
2734+ let clientContext = try assertNoThrowWithValue ( NIOSSLContext ( configuration: clientConfig) )
2735+
2736+ let group = MultiThreadedEventLoopGroup ( numberOfThreads: 1 )
2737+ defer {
2738+ XCTAssertNoThrow ( try group. syncShutdownGracefully ( ) )
2739+ }
2740+
2741+ let handshakeCompletePromise = group. next ( ) . makePromise ( of: Void . self)
2742+ let serverChannel : Channel = try serverTLSChannel (
2743+ context: serverContext,
2744+ handlers: [ WaitForHandshakeHandler ( handshakeResultPromise: handshakeCompletePromise) ] ,
2745+ group: group
2746+ )
2747+ defer {
2748+ XCTAssertNoThrow ( try serverChannel. close ( ) . wait ( ) )
2749+ }
2750+
2751+ let clientChannel = try clientTLSChannel (
2752+ context: clientContext,
2753+ preHandlers: [ ] ,
2754+ postHandlers: [ ] ,
2755+ group: group,
2756+ connectingTo: serverChannel. localAddress!,
2757+ serverHostname: " localhost "
2758+ )
2759+ defer {
2760+ XCTAssertNoThrow ( try clientChannel. close ( ) . wait ( ) )
2761+ }
2762+
2763+ // The handshake should succeed: certificate verification is optional and the client hasn't presented any certs.
2764+ XCTAssertNoThrow ( try handshakeCompletePromise. futureResult. wait ( ) )
2765+ }
2766+
26032767 func testServerHasNewCallbackCalledToo( ) throws {
26042768 var config = TLSConfiguration . makeServerConfiguration (
26052769 certificateChain: [ . certificate( NIOSSLIntegrationTest . cert) ] ,
0 commit comments