Browse Source

Allow using a customVerificationCallback for clients verifying servers (#134)

We have a use case where a gRPC server runs locally with a custom CA. I
want to verify some attributes in that certificate prior to connecting.

---------

Co-authored-by: George Barnett <gbarnett@apple.com>
Joannis Orlandos 1 month ago
parent
commit
caa731fefc

+ 1 - 1
Package.swift

@@ -71,7 +71,7 @@ let dependencies: [Package.Dependency] = [
 
 
 // This adds some build settings which allow us to map "@available(gRPCSwiftNIOTransport 2.x, *)" to
 // This adds some build settings which allow us to map "@available(gRPCSwiftNIOTransport 2.x, *)" to
 // the appropriate OS platforms.
 // the appropriate OS platforms.
-let nextMinorVersion = 2
+let nextMinorVersion = 3
 let availabilitySettings: [SwiftSetting] = (0 ... nextMinorVersion).map { minor in
 let availabilitySettings: [SwiftSetting] = (0 ... nextMinorVersion).map { minor in
   let name = "gRPCSwiftNIOTransport"
   let name = "gRPCSwiftNIOTransport"
   let version = "2.\(minor)"
   let version = "2.\(minor)"

+ 11 - 0
Sources/GRPCNIOTransportHTTP2Posix/Config+TLS.swift

@@ -354,6 +354,17 @@ extension HTTP2ClientTransport.Posix.TransportSecurity {
     /// How to verify the server certificate, if one is presented.
     /// How to verify the server certificate, if one is presented.
     public var serverCertificateVerification: TLSConfig.CertificateVerification
     public var serverCertificateVerification: TLSConfig.CertificateVerification
 
 
+    /// Override the certificate verification with a custom callback that must return the verified certificate chain on success.
+    /// Note: The callback is only used when `serverCertificateVerification` is *not* set to `noVerification`!
+    @available(gRPCSwiftNIOTransport 2.3, *)
+    public var customVerificationCallback:
+      (
+        @Sendable (
+          _ certificates: [NIOSSLCertificate],
+          _ promise: EventLoopPromise<NIOSSLVerificationResultWithMetadata>
+        ) -> Void
+      )?
+
     /// The trust roots to be used when verifying server certificates.
     /// The trust roots to be used when verifying server certificates.
     public var trustRoots: TLSConfig.TrustRootsSource
     public var trustRoots: TLSConfig.TrustRootsSource
 
 

+ 23 - 5
Sources/GRPCNIOTransportHTTP2Posix/HTTP2ClientTransport+Posix.swift

@@ -138,6 +138,12 @@ extension HTTP2ClientTransport.Posix {
 
 
     private let sslContext: NIOSSLContext?
     private let sslContext: NIOSSLContext?
     private let isPlainText: Bool
     private let isPlainText: Bool
+    private let customVerificationCallback:
+      (
+        @Sendable (
+          [NIOSSLCertificate], EventLoopPromise<NIOSSLVerificationResultWithMetadata>
+        ) -> Void
+      )?
 
 
     init(
     init(
       eventLoopGroup: any EventLoopGroup,
       eventLoopGroup: any EventLoopGroup,
@@ -151,10 +157,12 @@ extension HTTP2ClientTransport.Posix {
       case .plaintext:
       case .plaintext:
         self.sslContext = nil
         self.sslContext = nil
         self.isPlainText = true
         self.isPlainText = true
+        self.customVerificationCallback = nil
       case .tls(let tlsConfig):
       case .tls(let tlsConfig):
         do {
         do {
           self.sslContext = try NIOSSLContext(configuration: TLSConfiguration(tlsConfig))
           self.sslContext = try NIOSSLContext(configuration: TLSConfiguration(tlsConfig))
           self.isPlainText = false
           self.isPlainText = false
+          self.customVerificationCallback = tlsConfig.customVerificationCallback
         } catch {
         } catch {
           throw RuntimeError(
           throw RuntimeError(
             code: .transportError,
             code: .transportError,
@@ -174,12 +182,22 @@ extension HTTP2ClientTransport.Posix {
       ).connect(to: address) { channel in
       ).connect(to: address) { channel in
         channel.eventLoop.makeCompletedFuture {
         channel.eventLoop.makeCompletedFuture {
           if let sslContext = self.sslContext {
           if let sslContext = self.sslContext {
-            try channel.pipeline.syncOperations.addHandler(
-              NIOSSLClientHandler(
-                context: sslContext,
-                serverHostname: sniServerHostname
+            if let customVerificationCallback = self.customVerificationCallback {
+              try channel.pipeline.syncOperations.addHandler(
+                NIOSSLClientHandler(
+                  context: sslContext,
+                  serverHostname: sniServerHostname,
+                  customVerificationCallbackWithMetadata: customVerificationCallback
+                )
               )
               )
-            )
+            } else {
+              try channel.pipeline.syncOperations.addHandler(
+                NIOSSLClientHandler(
+                  context: sslContext,
+                  serverHostname: sniServerHostname
+                )
+              )
+            }
           }
           }
 
 
           return try channel.pipeline.syncOperations.configureGRPCClientPipeline(
           return try channel.pipeline.syncOperations.configureGRPCClientPipeline(