Просмотр исходного кода

Provide restricted TLS configurations (#513)

* Provide restricted TLS configurations

Motivation:

Providing TLS configuration is difficult without prior knowledge since
one must provide the application protocols identifiers for ALPN. In
addition the default minimum TLS version provided by NIOs
TLSConfiguration is lower than that specified by the gRPC protococl.

Modifications:

Add TLS configuration for both server and client which defaults which
better align to gRPC.

Add shims marking the old APIs as deprecated.

Result:

TLS is easier to configure.

* Update Sources/GRPC/TLSConfiguration.swift
George Barnett 6 лет назад
Родитель
Сommit
04227ec625

+ 15 - 37
Sources/Examples/Echo/main.swift

@@ -32,61 +32,39 @@ let messageOption = Option("message",
                            default: "Testing 1 2 3",
                            description: "message to send")
 
-func makeClientSSLContext() throws -> NIOSSLContext {
-  return try NIOSSLContext(configuration: makeClientTLSConfiguration())
-}
-
-func makeServerSSLContext() throws -> NIOSSLContext {
-  return try NIOSSLContext(configuration: makeServerTLSConfiguration())
-}
-
-func makeClientTLSConfiguration() -> TLSConfiguration {
+func makeClientTLSConfiguration() -> ClientConnection.Configuration.TLS {
   let caCert = SampleCertificate.ca
   let clientCert = SampleCertificate.client
   precondition(!caCert.isExpired && !clientCert.isExpired,
                "SSL certificates are expired. Please submit an issue at https://github.com/grpc/grpc-swift.")
 
-  return .forClient(certificateVerification: .noHostnameVerification,
-                    trustRoots: .certificates([caCert.certificate]),
-                    certificateChain: [.certificate(clientCert.certificate)],
-                    privateKey: .privateKey(SamplePrivateKey.client),
-                    applicationProtocols: GRPCApplicationProtocolIdentifier.allCases.map { $0.rawValue })
+  return .init(certificateChain: [.certificate(clientCert.certificate)],
+               privateKey: .privateKey(SamplePrivateKey.client),
+               trustRoots: .certificates([caCert.certificate]),
+               certificateVerification: .noHostnameVerification)
 }
 
-func makeServerTLSConfiguration() -> TLSConfiguration {
+func makeServerTLSConfiguration() -> Server.Configuration.TLS {
   let caCert = SampleCertificate.ca
   let serverCert = SampleCertificate.server
   precondition(!caCert.isExpired && !serverCert.isExpired,
                "SSL certificates are expired. Please submit an issue at https://github.com/grpc/grpc-swift.")
 
-  return .forServer(certificateChain: [.certificate(serverCert.certificate)],
-                    privateKey: .privateKey(SamplePrivateKey.server),
-                    trustRoots: .certificates([caCert.certificate]),
-                    applicationProtocols: GRPCApplicationProtocolIdentifier.allCases.map { $0.rawValue })
+  return .init(certificateChain: [.certificate(serverCert.certificate)],
+               privateKey: .privateKey(SamplePrivateKey.server),
+               trustRoots: .certificates([caCert.certificate]))
 }
 
 /// Create en `EchoClient` and wait for it to initialize. Returns nil if initialisation fails.
 func makeEchoClient(address: String, port: Int, ssl: Bool) -> Echo_EchoServiceClient? {
   let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
 
-  do {
-    let tlsConfiguration: ClientConnection.TLSConfiguration?
-    if ssl {
-      tlsConfiguration = .init(sslContext: try makeClientSSLContext())
-    } else {
-      tlsConfiguration = nil
-    }
+  let configuration = ClientConnection.Configuration(
+    target: .hostAndPort(address, port),
+    eventLoopGroup: eventLoopGroup,
+    tls: ssl ? makeClientTLSConfiguration() : nil)
 
-    let configuration = ClientConnection.Configuration(
-      target: .hostAndPort(address, port),
-      eventLoopGroup: eventLoopGroup,
-      tlsConfiguration: tlsConfiguration)
-
-    return Echo_EchoServiceClient(connection: ClientConnection(configuration: configuration))
-  } catch {
-    print("Unable to create an EchoClient: \(error)")
-    return nil
-  }
+  return Echo_EchoServiceClient(connection: ClientConnection(configuration: configuration))
 }
 
 Group {
@@ -104,7 +82,7 @@ Group {
 
     if ssl {
       print("starting secure server")
-      configuration.tlsConfiguration = .init(sslContext: try makeServerSSLContext())
+      configuration.tls = makeServerTLSConfiguration()
     } else {
       print("starting insecure server")
     }

+ 8 - 21
Sources/GRPC/ClientConnection.swift

@@ -184,7 +184,7 @@ extension ClientConnection {
     )
 
     let channel = bootstrap.connect(to: configuration.target).flatMap { channel -> EventLoopFuture<Channel> in
-      if configuration.tlsConfiguration != nil {
+      if configuration.tls != nil {
         return channel.verifyTLS().map { channel }
       } else {
         return channel.eventLoop.makeSucceededFuture(channel)
@@ -251,7 +251,7 @@ extension ClientConnection {
       .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
       .channelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1)
       .channelInitializer { channel in
-        let tlsConfigured = configuration.tlsConfiguration.map { tlsConfiguration in
+        let tlsConfigured = configuration.tls.map { tlsConfiguration in
           channel.configureTLS(tlsConfiguration, errorDelegate: configuration.errorDelegate)
         }
 
@@ -308,7 +308,7 @@ extension ClientConnection {
     public var connectivityStateDelegate: ConnectivityStateDelegate?
 
     /// TLS configuration for this connection. `nil` if TLS is not desired.
-    public var tlsConfiguration: TLSConfiguration?
+    public var tls: TLS?
 
     /// The connection backoff configuration. If no connection retrying is required then this should
     /// be `nil`.
@@ -316,7 +316,7 @@ extension ClientConnection {
 
     /// The HTTP protocol used for this connection.
     public var httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol {
-      return self.tlsConfiguration == nil ? .http : .https
+      return self.tls == nil ? .http : .https
     }
 
     /// Create a `Configuration` with some pre-defined defaults.
@@ -334,30 +334,17 @@ extension ClientConnection {
       eventLoopGroup: EventLoopGroup,
       errorDelegate: ClientErrorDelegate? = DebugOnlyLoggingClientErrorDelegate.shared,
       connectivityStateDelegate: ConnectivityStateDelegate? = nil,
-      tlsConfiguration: TLSConfiguration? = nil,
+      tls: Configuration.TLS? = nil,
       connectionBackoff: ConnectionBackoff? = nil
     ) {
       self.target = target
       self.eventLoopGroup = eventLoopGroup
       self.errorDelegate = errorDelegate
       self.connectivityStateDelegate = connectivityStateDelegate
-      self.tlsConfiguration = tlsConfiguration
+      self.tls = tls
       self.connectionBackoff = connectionBackoff
     }
   }
-
-  /// The TLS configuration for a connection.
-  public struct TLSConfiguration {
-    /// The SSL context to use.
-    public var sslContext: NIOSSLContext
-    /// Value to use for TLS SNI extension; this must not be an IP address.
-    public var hostnameOverride: String?
-
-    public init(sslContext: NIOSSLContext, hostnameOverride: String? = nil) {
-      self.sslContext = sslContext
-      self.hostnameOverride = hostnameOverride
-    }
-  }
 }
 
 // MARK: - Configuration helpers/extensions
@@ -389,12 +376,12 @@ fileprivate extension Channel {
   /// - Parameter configuration: The configuration to configure the channel with.
   /// - Parameter errorDelegate: The error delegate to use for the TLS verification handler.
   func configureTLS(
-    _ configuration: ClientConnection.TLSConfiguration,
+    _ configuration: ClientConnection.Configuration.TLS,
     errorDelegate: ClientErrorDelegate?
   ) -> EventLoopFuture<Void> {
     do {
       let sslClientHandler = try NIOSSLClientHandler(
-        context: configuration.sslContext,
+        context: try NIOSSLContext(configuration: configuration.configuration),
         serverHostname: configuration.hostnameOverride)
 
       return self.pipeline.addHandlers(sslClientHandler, TLSVerificationHandler())

+ 8 - 7
Sources/GRPC/Server.swift

@@ -117,8 +117,8 @@ public final class Server {
           return channel.pipeline.addHandlers(handlers)
         }
 
-        if let tlsConfiguration = configuration.tlsConfiguration {
-          return channel.configureTLS(configuration: tlsConfiguration).flatMap {
+        if let tls = configuration.tls {
+          return channel.configureTLS(configuration: tls).flatMap {
             channel.pipeline.addHandler(protocolSwitcher)
           }
         } else {
@@ -186,7 +186,7 @@ extension Server {
     public var errorDelegate: ServerErrorDelegate?
 
     /// TLS configuration for this connection. `nil` if TLS is not desired.
-    public var tlsConfiguration: TLSConfiguration?
+    public var tls: TLS?
 
     /// Create a `Configuration` with some pre-defined defaults.
     ///
@@ -201,13 +201,13 @@ extension Server {
       eventLoopGroup: EventLoopGroup,
       serviceProviders: [CallHandlerProvider],
       errorDelegate: ServerErrorDelegate? = LoggingServerErrorDelegate.shared,
-      tlsConfiguration: TLSConfiguration? = nil
+      tls: TLS? = nil
     ) {
       self.target = target
       self.eventLoopGroup = eventLoopGroup
       self.serviceProviders = serviceProviders
       self.errorDelegate = errorDelegate
-      self.tlsConfiguration = tlsConfiguration
+      self.tls = tls
     }
   }
 
@@ -234,9 +234,10 @@ fileprivate extension Channel {
   /// - Parameters:
   ///   - configuration: The configuration to use when creating the handler.
   /// - Returns: A future which will be succeeded when the pipeline has been configured.
-  func configureTLS(configuration: Server.TLSConfiguration) -> EventLoopFuture<Void> {
+  func configureTLS(configuration: Server.Configuration.TLS) -> EventLoopFuture<Void> {
     do {
-      return self.pipeline.addHandler(try NIOSSLServerHandler(context: configuration.sslContext))
+      let context = try NIOSSLContext(configuration: configuration.configuration)
+      return self.pipeline.addHandler(try NIOSSLServerHandler(context: context))
     } catch {
       return self.pipeline.eventLoop.makeFailedFuture(error)
     }

+ 34 - 0
Sources/GRPC/Shims.swift

@@ -0,0 +1,34 @@
+/*
+ * Copyright 2019, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import NIOSSL
+
+// This file contains shims to notify users of API changes between v1.0.0-alpha.1 and v1.0.0.
+
+// TODO: Remove these shims before v1.0.0 is tagged.
+
+extension ClientConnection.Configuration {
+  @available(*, deprecated, message: "use 'tls' and 'ClientConnection.Configuration.TLS'")
+  public var tlsConfiguration: TLSConfiguration? {
+    return nil
+  }
+}
+
+extension Server.Configuration {
+  @available(*, deprecated, message: "use 'tls' and 'Server.Configuration.TLS'")
+  public var tlsConfiguration: TLSConfiguration? {
+    return nil
+  }
+}

+ 195 - 0
Sources/GRPC/TLSConfiguration.swift

@@ -0,0 +1,195 @@
+/*
+ * Copyright 2019, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import NIOSSL
+
+extension ClientConnection.Configuration {
+  /// TLS configuration for a `ClientConnection`.
+  ///
+  /// Note that this configuration is a subset of `NIOSSL.TLSConfiguration` where certain options
+  /// are removed from the user's control to ensure the configuration complies with the gRPC
+  /// specification.
+  public struct TLS {
+    public private(set) var configuration: TLSConfiguration
+
+    /// Value to use for TLS SNI extension; this must not be an address.
+    public var hostnameOverride: String?
+
+    /// The certificates to offer during negotiation. If not present, no certificates will be offered.
+    public var certificateChain: [NIOSSLCertificateSource] {
+      get {
+        return self.configuration.certificateChain
+      }
+      set {
+        self.configuration.certificateChain = newValue
+      }
+    }
+
+    /// The private key associated with the leaf certificate.
+    public var privateKey: NIOSSLPrivateKeySource? {
+      get {
+        return self.configuration.privateKey
+      }
+      set {
+        self.configuration.privateKey = newValue
+      }
+    }
+
+    /// The trust roots to use to validate certificates. This only needs to be provided if you
+    /// intend to validate certificates.
+    public var trustRoots: NIOSSLTrustRoots? {
+      get {
+        return self.configuration.trustRoots
+      }
+      set {
+        self.configuration.trustRoots = newValue
+      }
+    }
+
+    /// Whether to verify remote certificates.
+    public var certificateVerification: CertificateVerification {
+      get {
+        return self.configuration.certificateVerification
+      }
+      set {
+        self.configuration.certificateVerification = newValue
+      }
+    }
+
+    /// TLS Configuration with suitable defaults for clients.
+    ///
+    /// This is a wrapper around `NIOSSL.TLSConfiguration` to restrict input to values which comply
+    /// with the gRPC protocol.
+    ///
+    /// - Parameter certificateChain: The certificate to offer during negotiation, defaults to an
+    ///     empty array.
+    /// - Parameter privateKey: The private key associated with the leaf certificate. This defaults
+    ///     to `nil`.
+    /// - Parameter trustRoots: The trust roots to validate certificates, this defaults to using a
+    ///     root provided by the platform.
+    /// - Parameter certificateVerification: Whether to verify the remote certificate. Defaults to
+    ///     `.fullVerification`.
+    /// - Parameter hostnameOverride: Value to use for TLS SNI extension; this must not be an IP
+    ///     address, defaults to `nil`.
+    public init(
+      certificateChain: [NIOSSLCertificateSource] = [],
+      privateKey: NIOSSLPrivateKeySource? = nil,
+      trustRoots: NIOSSLTrustRoots = .default,
+      certificateVerification: CertificateVerification = .fullVerification,
+      hostnameOverride: String? = nil
+    ) {
+      self.configuration = .forClient(
+        minimumTLSVersion: .tlsv12,
+        certificateVerification: certificateVerification,
+        trustRoots: trustRoots,
+        certificateChain: certificateChain,
+        privateKey: privateKey,
+        applicationProtocols: GRPCApplicationProtocolIdentifier.allCases.map { $0.rawValue }
+      )
+      self.hostnameOverride = hostnameOverride
+    }
+
+    /// Creates a TLS Configuration using the given `NIOSSL.TLSConfiguration`.
+    public init(configuration: TLSConfiguration, hostnameOverride: String? = nil) {
+      self.configuration = configuration
+      self.hostnameOverride = hostnameOverride
+    }
+  }
+}
+
+extension Server.Configuration {
+  /// TLS configuration for a `Server`.
+  ///
+  /// Note that this configuration is a subset of `NIOSSL.TLSConfiguration` where certain options
+  /// are removed from the users control to ensure the configuration complies with the gRPC
+  /// specification.
+  public struct TLS {
+    public private(set) var configuration: TLSConfiguration
+
+    /// The certificates to offer during negotiation. If not present, no certificates will be
+    /// offered.
+    public var certificateChain: [NIOSSLCertificateSource] {
+      get {
+        return self.configuration.certificateChain
+      }
+      set {
+        self.configuration.certificateChain = newValue
+      }
+    }
+
+    /// The private key associated with the leaf certificate.
+    public var privateKey: NIOSSLPrivateKeySource? {
+      get {
+        return self.configuration.privateKey
+      }
+      set {
+        self.configuration.privateKey = newValue
+      }
+    }
+
+    /// The trust roots to use to validate certificates. This only needs to be provided if you
+    /// intend to validate certificates.
+    public var trustRoots: NIOSSLTrustRoots? {
+      get {
+        return self.configuration.trustRoots
+      }
+      set {
+        self.configuration.trustRoots = newValue
+      }
+    }
+
+    /// Whether to verify remote certificates.
+    public var certificateVerification: CertificateVerification {
+      get {
+        return self.configuration.certificateVerification
+      }
+      set {
+        self.configuration.certificateVerification = newValue
+      }
+    }
+
+    /// TLS Configuration with suitable defaults for clients.
+    ///
+    /// This is a wrapper around `NIOSSL.TLSConfiguration` to restrict input to values which comply
+    /// with the gRPC protocol.
+    ///
+    /// - Parameter certificateChain: The certificate to offer during negotiation.
+    /// - Parameter privateKey: The private key associated with the leaf certificate.
+    /// - Parameter trustRoots: The trust roots to validate certificates, this defaults to using a
+    ///     root provided by the platform.
+    /// - Parameter certificateVerification: Whether to verify the remote certificate. Defaults to
+    ///     `.none`.
+    public init(
+      certificateChain: [NIOSSLCertificateSource],
+      privateKey: NIOSSLPrivateKeySource,
+      trustRoots: NIOSSLTrustRoots = .default,
+      certificateVerification: CertificateVerification = .none
+    ) {
+      self.configuration = .forServer(
+        certificateChain: certificateChain,
+        privateKey: privateKey,
+        minimumTLSVersion: .tlsv12,
+        certificateVerification: certificateVerification,
+        trustRoots: trustRoots,
+        applicationProtocols: GRPCApplicationProtocolIdentifier.allCases.map { $0.rawValue }
+      )
+    }
+
+    /// Creates a TLS Configuration using the given `NIOSSL.TLSConfiguration`.
+    public init(configuration: TLSConfiguration) {
+      self.configuration = configuration
+    }
+  }
+}

+ 3 - 6
Sources/GRPCInteroperabilityTests/InteroperabilityTestClientConnection.swift

@@ -39,13 +39,10 @@ public func makeInteroperabilityTestClientConnection(
   if useTLS {
     // The CA certificate has a common name of "*.test.google.fr", use the following host override
     // so we can do full certificate verification.
-    let hostOverride = "foo.test.google.fr"
-    let tlsConfiguration = TLSConfiguration.forClient(
+    configuration.tls = .init(
       trustRoots: .certificates([InteroperabilityTestCredentials.caCertificate]),
-      applicationProtocols: ["h2"])
-
-    let context = try NIOSSLContext(configuration: tlsConfiguration)
-    configuration.tlsConfiguration = .init(sslContext: context, hostnameOverride: hostOverride)
+      hostnameOverride: "foo.test.google.fr"
+    )
   }
 
   return ClientConnection(configuration: configuration)

+ 2 - 6
Sources/GRPCInteroperabilityTests/InteroperabilityTestServer.swift

@@ -49,15 +49,11 @@ public func makeInteroperabilityTestServer(
     let serverCert = InteroperabilityTestCredentials.server1Certificate
     let serverKey = InteroperabilityTestCredentials.server1Key
 
-    let tlsConfiguration = TLSConfiguration.forServer(
+    configuration.tls = .init(
       certificateChain: [.certificate(serverCert)],
       privateKey: .privateKey(serverKey),
-      trustRoots: .certificates([caCert]),
-      applicationProtocols: ["h2"]
+      trustRoots: .certificates([caCert])
     )
-
-    let sslContext = try NIOSSLContext(configuration: tlsConfiguration)
-    configuration.tlsConfiguration = .init(sslContext: sslContext)
   }
 
   return Server.start(configuration: configuration)

+ 37 - 35
Sources/GRPCPerformanceTests/main.swift

@@ -217,7 +217,7 @@ func measure(description: String, benchmark: Benchmark, repeats: Int) -> Benchma
 /// - Parameter certificatePath: The path to the certificate.
 /// - Parameter privateKeyPath: The path to the private key.
 /// - Parameter server: Whether this is for the server or not.
-private func makeSSLContext(caCertificatePath: String, certificatePath: String, privateKeyPath: String, server: Bool) -> NIOSSLContext? {
+private func makeServerTLSConfiguration(caCertificatePath: String, certificatePath: String, privateKeyPath: String) -> Server.Configuration.TLS? {
   // Commander doesn't have Optional options; we use empty strings to indicate no value.
   guard certificatePath.isEmpty == privateKeyPath.isEmpty &&
     privateKeyPath.isEmpty == caCertificatePath.isEmpty else {
@@ -230,29 +230,35 @@ private func makeSSLContext(caCertificatePath: String, certificatePath: String,
     return nil
   }
 
-  let configuration: TLSConfiguration
-  if server {
-    configuration = .forServer(
-      certificateChain: [.file(certificatePath)],
-      privateKey: .file(privateKeyPath),
-      trustRoots: .file(caCertificatePath),
-      applicationProtocols: ["h2"]
-    )
-  } else {
-    configuration = .forClient(
-      trustRoots: .file(caCertificatePath),
-      certificateChain: [.file(certificatePath)],
-      privateKey: .file(privateKeyPath),
-      applicationProtocols: ["h2"]
-    )
+  return .init(
+    certificateChain: [.file(certificatePath)],
+    privateKey: .file(privateKeyPath),
+    trustRoots: .file(caCertificatePath)
+  )
+}
+
+private func makeClientTLSConfiguration(
+  caCertificatePath: String,
+  certificatePath: String,
+  privateKeyPath: String
+) -> ClientConnection.Configuration.TLS? {
+  // Commander doesn't have Optional options; we use empty strings to indicate no value.
+  guard certificatePath.isEmpty == privateKeyPath.isEmpty &&
+    privateKeyPath.isEmpty == caCertificatePath.isEmpty else {
+      print("Paths for CA certificate, certificate and private key must be provided")
+      exit(1)
   }
 
-  do {
-    return try NIOSSLContext(configuration: configuration)
-  } catch {
-    print("Unable to create SSL context: \(error)")
-    exit(1)
+  // No need to check them all because of the guard statement above.
+  if caCertificatePath.isEmpty {
+    return nil
   }
+
+  return .init(
+    certificateChain: [.file(certificatePath)],
+    privateKey: .file(privateKeyPath),
+    trustRoots: .file(caCertificatePath)
+  )
 }
 
 enum Benchmarks: String, CaseIterable {
@@ -357,19 +363,15 @@ Group { group in
     privateKeyOption,
     hostOverrideOption
   ) { benchmarkNames, host, port, caCertificatePath, certificatePath, privateKeyPath, hostOverride in
-    var configuration = ClientConnection.Configuration(
-      target: .hostAndPort(host, port),
-      eventLoopGroup: MultiThreadedEventLoopGroup(numberOfThreads: 1))
-
-    let sslContext = makeSSLContext(
+    let tlsConfiguration = makeClientTLSConfiguration(
       caCertificatePath: caCertificatePath,
       certificatePath: certificatePath,
-      privateKeyPath: privateKeyPath,
-      server: false)
+      privateKeyPath: privateKeyPath)
 
-    if let sslContext = sslContext {
-      configuration.tlsConfiguration = .init(sslContext: sslContext, hostnameOverride: hostOverride)
-    }
+    let configuration = ClientConnection.Configuration(
+      target: .hostAndPort(host, port),
+      eventLoopGroup: MultiThreadedEventLoopGroup(numberOfThreads: 1),
+      tls: tlsConfiguration)
 
     let factory = ConnectionFactory(configuration: configuration)
 
@@ -399,17 +401,17 @@ Group { group in
     privateKeyOption
   ) { host, port, caCertificatePath, certificatePath, privateKeyPath in
     let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
-    let sslContext = makeSSLContext(
+
+    let tlsConfiguration = makeServerTLSConfiguration(
       caCertificatePath: caCertificatePath,
       certificatePath: certificatePath,
-      privateKeyPath: privateKeyPath,
-      server: true)
+      privateKeyPath: privateKeyPath)
 
     let configuration = Server.Configuration(
       target: .hostAndPort(host, port),
       eventLoopGroup: group,
       serviceProviders: [EchoProvider()],
-      tlsConfiguration: sslContext.map { .init(sslContext: $0) })
+      tls: tlsConfiguration)
 
     let server: Server
 

+ 15 - 34
Tests/GRPCTests/BasicEchoTestCase.swift

@@ -60,53 +60,34 @@ extension TransportSecurity {
 }
 
 extension TransportSecurity {
-  func makeServerConfiguration() throws -> Server.TLSConfiguration? {
-    guard let config = try self.makeServerTLSConfiguration() else {
-      return nil
-    }
-
-    let context = try NIOSSLContext(configuration: config)
-    return .init(sslContext: context)
-  }
-
-  func makeServerTLSConfiguration() throws -> TLSConfiguration? {
+  func makeServerTLSConfiguration() -> Server.Configuration.TLS? {
     switch self {
     case .none:
       return nil
 
     case .anonymousClient, .mutualAuthentication:
-      return .forServer(certificateChain: [.certificate(self.serverCert)],
-                        privateKey: .privateKey(SamplePrivateKey.server),
-                        trustRoots: .certificates ([self.caCert]),
-                        applicationProtocols: GRPCApplicationProtocolIdentifier.allCases.map { $0.rawValue })
+      return .init(certificateChain: [.certificate(self.serverCert)],
+                   privateKey: .privateKey(SamplePrivateKey.server),
+                   trustRoots: .certificates ([self.caCert]))
     }
   }
 
-  func makeClientConfiguration() throws -> ClientConnection.TLSConfiguration? {
-    guard let config = try self.makeClientTLSConfiguration() else {
-      return nil
-    }
-
-    let context = try NIOSSLContext(configuration: config)
-    return ClientConnection.TLSConfiguration(sslContext: context)
-  }
-
-  func makeClientTLSConfiguration() throws -> TLSConfiguration? {
+  func makeClientTLSConfiguration() -> ClientConnection.Configuration.TLS? {
     switch self {
     case .none:
       return nil
 
     case .anonymousClient:
-      return .forClient(certificateVerification: .noHostnameVerification,
-                        trustRoots: .certificates([self.caCert]),
-                        applicationProtocols: GRPCApplicationProtocolIdentifier.allCases.map { $0.rawValue })
+      return .init(
+        trustRoots: .certificates([self.caCert]),
+        certificateVerification: .noHostnameVerification)
 
     case .mutualAuthentication:
-      return .forClient(certificateVerification: .noHostnameVerification,
-                        trustRoots: .certificates([self.caCert]),
-                        certificateChain: [.certificate(self.clientCert)],
-                        privateKey: .privateKey(SamplePrivateKey.client),
-                        applicationProtocols: GRPCApplicationProtocolIdentifier.allCases.map { $0.rawValue })
+      return .init(
+        certificateChain: [.certificate(self.clientCert)],
+        privateKey: .privateKey(SamplePrivateKey.client),
+        trustRoots: .certificates([self.caCert]),
+        certificateVerification: .noHostnameVerification)
     }
   }
 }
@@ -133,7 +114,7 @@ class EchoTestCaseBase: XCTestCase {
     return .init(
       target: .hostAndPort("localhost", port),
       eventLoopGroup: self.clientEventLoopGroup,
-      tlsConfiguration: try self.transportSecurity.makeClientConfiguration())
+      tls: self.transportSecurity.makeClientTLSConfiguration())
   }
 
   func makeServerConfiguration() throws -> Server.Configuration {
@@ -142,7 +123,7 @@ class EchoTestCaseBase: XCTestCase {
       eventLoopGroup: self.serverEventLoopGroup,
       serviceProviders: [makeEchoProvider()],
       errorDelegate: self.makeErrorDelegate(),
-      tlsConfiguration: try self.transportSecurity.makeServerConfiguration())
+      tls: self.transportSecurity.makeServerTLSConfiguration())
   }
 
   func makeServer() throws -> Server {

+ 25 - 33
Tests/GRPCTests/ClientTLSFailureTests.swift

@@ -35,16 +35,15 @@ class ErrorRecordingDelegate: ClientErrorDelegate {
 }
 
 class ClientTLSFailureTests: XCTestCase {
-  let defaultServerTLSConfiguration = TLSConfiguration.forServer(
+  let defaultServerTLSConfiguration = Server.Configuration.TLS(
     certificateChain: [.certificate(SampleCertificate.server.certificate)],
-    privateKey: .privateKey(SamplePrivateKey.server),
-    applicationProtocols: GRPCApplicationProtocolIdentifier.allCases.map { $0.rawValue })
+    privateKey: .privateKey(SamplePrivateKey.server))
 
-  let defaultClientTLSConfiguration = TLSConfiguration.forClient(
-    trustRoots: .certificates([SampleCertificate.ca.certificate]),
+  let defaultClientTLSConfiguration = ClientConnection.Configuration.TLS(
     certificateChain: [.certificate(SampleCertificate.client.certificate)],
     privateKey: .privateKey(SamplePrivateKey.client),
-    applicationProtocols: GRPCApplicationProtocolIdentifier.allCases.map { $0.rawValue })
+    trustRoots: .certificates([SampleCertificate.ca.certificate]),
+    hostnameOverride: SampleCertificate.server.commonName)
 
   var defaultTestTimeout: TimeInterval = 1.0
 
@@ -54,41 +53,28 @@ class ClientTLSFailureTests: XCTestCase {
   var port: Int!
 
   func makeClientConfiguration(
-    tls: TLSConfiguration,
-    hostOverride: String? = SampleCertificate.server.commonName
-  ) throws -> ClientConnection.Configuration {
-    return ClientConnection.Configuration(
+    tls: ClientConnection.Configuration.TLS
+  ) -> ClientConnection.Configuration {
+    return .init(
       target: .hostAndPort("localhost", self.port),
       eventLoopGroup: self.clientEventLoopGroup,
-      tlsConfiguration: try .init(
-        sslContext: NIOSSLContext(configuration: tls),
-        hostnameOverride: hostOverride
-      )
+      tls: tls
     )
   }
 
-  func makeClientTLSConfiguration(
-    tls: TLSConfiguration,
-    hostOverride: String? = SampleCertificate.server.commonName
-  ) throws -> ClientConnection.TLSConfiguration {
-    let context = try NIOSSLContext(configuration: tls)
-    return .init(sslContext: context, hostnameOverride: hostOverride)
-  }
-
   func makeClientConnectionExpectation() -> XCTestExpectation {
     return self.expectation(description: "EventLoopFuture<ClientConnection> resolved")
   }
 
   override func setUp() {
     self.serverEventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
-    let sslContext = try! NIOSSLContext(configuration: self.defaultServerTLSConfiguration)
 
     let configuration = Server.Configuration(
       target: .hostAndPort("localhost", 0),
       eventLoopGroup: self.serverEventLoopGroup,
       serviceProviders: [EchoProvider()],
       errorDelegate: nil,
-      tlsConfiguration: .init(sslContext: sslContext))
+      tls: self.defaultServerTLSConfiguration)
 
     self.server = try! Server.start(configuration: configuration).wait()
 
@@ -114,10 +100,17 @@ class ClientTLSFailureTests: XCTestCase {
     let shutdownExpectation = self.expectation(description: "client shutdown")
     let errorExpectation = self.expectation(description: "error")
 
-    var tls = defaultClientTLSConfiguration
-    tls.applicationProtocols = ["not-h2", "not-grpc-ext"]
-    var configuration = try self.makeClientConfiguration(tls: tls)
+    // We use the underlying configuration because `applicationProtocols` is not user-configurable
+    // via `Configuration.TLS`.
+    var tlsConfiguration = self.defaultClientTLSConfiguration.configuration
+    tlsConfiguration.applicationProtocols = ["not-h2", "not-grpc-ext"]
 
+    let tls = ClientConnection.Configuration.TLS(
+      configuration: tlsConfiguration,
+      hostnameOverride: self.defaultClientTLSConfiguration.hostnameOverride
+    )
+
+    var configuration = self.makeClientConfiguration(tls: tls)
     let errorRecorder = ErrorRecordingDelegate(expectation: errorExpectation)
     configuration.errorDelegate = errorRecorder
 
@@ -136,9 +129,9 @@ class ClientTLSFailureTests: XCTestCase {
     let shutdownExpectation = self.expectation(description: "client shutdown")
     let errorExpectation = self.expectation(description: "error")
 
-    var tls = defaultClientTLSConfiguration
+    var tls = self.defaultClientTLSConfiguration
     tls.trustRoots = .certificates([])
-    var configuration = try self.makeClientConfiguration(tls: tls)
+    var configuration = self.makeClientConfiguration(tls: tls)
 
     let errorRecorder = ErrorRecordingDelegate(expectation: errorExpectation)
     configuration.errorDelegate = errorRecorder
@@ -162,11 +155,10 @@ class ClientTLSFailureTests: XCTestCase {
     let shutdownExpectation = self.expectation(description: "client shutdown")
     let errorExpectation = self.expectation(description: "error")
 
-    var configuration = try self.makeClientConfiguration(
-      tls: self.defaultClientTLSConfiguration,
-      hostOverride: "not-the-server-hostname"
-    )
+    var tls = self.defaultClientTLSConfiguration
+    tls.hostnameOverride = "not-the-server-hostname"
 
+    var configuration = self.makeClientConfiguration(tls: tls)
     let errorRecorder = ErrorRecordingDelegate(expectation: errorExpectation)
     configuration.errorDelegate = errorRecorder