diff --git a/Examples/v2/echo/Subcommands/Collect.swift b/Examples/v2/echo/Subcommands/Collect.swift index 63402e0c6..3a61915df 100644 --- a/Examples/v2/echo/Subcommands/Collect.swift +++ b/Examples/v2/echo/Subcommands/Collect.swift @@ -32,7 +32,7 @@ struct Collect: AsyncParsableCommand { let client = GRPCClient( transport: try .http2NIOPosix( target: self.arguments.target, - config: .defaults() + config: .defaults(transportSecurity: .plaintext) ) ) diff --git a/Examples/v2/echo/Subcommands/Expand.swift b/Examples/v2/echo/Subcommands/Expand.swift index 33f89895d..1d06bdd99 100644 --- a/Examples/v2/echo/Subcommands/Expand.swift +++ b/Examples/v2/echo/Subcommands/Expand.swift @@ -32,7 +32,7 @@ struct Expand: AsyncParsableCommand { let client = GRPCClient( transport: try .http2NIOPosix( target: self.arguments.target, - config: .defaults() + config: .defaults(transportSecurity: .plaintext) ) ) diff --git a/Examples/v2/echo/Subcommands/Get.swift b/Examples/v2/echo/Subcommands/Get.swift index 400ba24a8..0dd551002 100644 --- a/Examples/v2/echo/Subcommands/Get.swift +++ b/Examples/v2/echo/Subcommands/Get.swift @@ -30,7 +30,7 @@ struct Get: AsyncParsableCommand { let client = GRPCClient( transport: try .http2NIOPosix( target: self.arguments.target, - config: .defaults() + config: .defaults(transportSecurity: .plaintext) ) ) diff --git a/Examples/v2/echo/Subcommands/Update.swift b/Examples/v2/echo/Subcommands/Update.swift index 04fdc1335..1c189caa8 100644 --- a/Examples/v2/echo/Subcommands/Update.swift +++ b/Examples/v2/echo/Subcommands/Update.swift @@ -32,7 +32,7 @@ struct Update: AsyncParsableCommand { let client = GRPCClient( transport: try .http2NIOPosix( target: self.arguments.target, - config: .defaults() + config: .defaults(transportSecurity: .plaintext) ) ) diff --git a/Examples/v2/hello-world/Subcommands/Greet.swift b/Examples/v2/hello-world/Subcommands/Greet.swift index aca973814..069b8faee 100644 --- a/Examples/v2/hello-world/Subcommands/Greet.swift +++ b/Examples/v2/hello-world/Subcommands/Greet.swift @@ -33,7 +33,7 @@ struct Greet: AsyncParsableCommand { let client = GRPCClient( transport: try .http2NIOPosix( target: .ipv4(host: "127.0.0.1", port: self.port), - config: .defaults() + config: .defaults(transportSecurity: .plaintext) ) ) diff --git a/Examples/v2/route-guide/Subcommands/GetFeature.swift b/Examples/v2/route-guide/Subcommands/GetFeature.swift index 6e51f2427..1c42ec6da 100644 --- a/Examples/v2/route-guide/Subcommands/GetFeature.swift +++ b/Examples/v2/route-guide/Subcommands/GetFeature.swift @@ -39,7 +39,7 @@ struct GetFeature: AsyncParsableCommand { func run() async throws { let transport = try HTTP2ClientTransport.Posix( target: .ipv4(host: "127.0.0.1", port: self.port), - config: .defaults() + config: .defaults(transportSecurity: .plaintext) ) let client = GRPCClient(transport: transport) diff --git a/Examples/v2/route-guide/Subcommands/ListFeatures.swift b/Examples/v2/route-guide/Subcommands/ListFeatures.swift index ea95cb593..887a944e2 100644 --- a/Examples/v2/route-guide/Subcommands/ListFeatures.swift +++ b/Examples/v2/route-guide/Subcommands/ListFeatures.swift @@ -53,7 +53,7 @@ struct ListFeatures: AsyncParsableCommand { func run() async throws { let transport = try HTTP2ClientTransport.Posix( target: .ipv4(host: "127.0.0.1", port: self.port), - config: .defaults() + config: .defaults(transportSecurity: .plaintext) ) let client = GRPCClient(transport: transport) diff --git a/Examples/v2/route-guide/Subcommands/RecordRoute.swift b/Examples/v2/route-guide/Subcommands/RecordRoute.swift index 829f5ee06..cd443230b 100644 --- a/Examples/v2/route-guide/Subcommands/RecordRoute.swift +++ b/Examples/v2/route-guide/Subcommands/RecordRoute.swift @@ -32,7 +32,7 @@ struct RecordRoute: AsyncParsableCommand { func run() async throws { let transport = try HTTP2ClientTransport.Posix( target: .ipv4(host: "127.0.0.1", port: self.port), - config: .defaults() + config: .defaults(transportSecurity: .plaintext) ) let client = GRPCClient(transport: transport) diff --git a/Examples/v2/route-guide/Subcommands/RouteChat.swift b/Examples/v2/route-guide/Subcommands/RouteChat.swift index 7fbf673c2..81cb5c3e2 100644 --- a/Examples/v2/route-guide/Subcommands/RouteChat.swift +++ b/Examples/v2/route-guide/Subcommands/RouteChat.swift @@ -32,7 +32,7 @@ struct RouteChat: AsyncParsableCommand { func run() async throws { let transport = try HTTP2ClientTransport.Posix( target: .ipv4(host: "127.0.0.1", port: self.port), - config: .defaults() + config: .defaults(transportSecurity: .plaintext) ) let client = GRPCClient(transport: transport) diff --git a/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ClientTransport+Posix.swift b/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ClientTransport+Posix.swift index 995f8948f..57e969e3a 100644 --- a/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ClientTransport+Posix.swift +++ b/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ClientTransport+Posix.swift @@ -19,14 +19,18 @@ public import GRPCHTTP2Core // should be @usableFromInline public import NIOCore public import NIOPosix // has to be public because of default argument value in init +#if canImport(NIOSSL) +import NIOSSL +#endif + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) extension HTTP2ClientTransport { - /// A `ClientTransport` using HTTP/2 built on top of `NIOPosix`. + /// A ``GRPCCore/ClientTransport`` using HTTP/2 built on top of `NIOPosix`. /// /// This transport builds on top of SwiftNIO's Posix networking layer and is suitable for use /// on Linux and Darwin based platform (macOS, iOS, etc.) However, it's *strongly* recommended /// that if you are targeting Darwin platforms then you should use the `NIOTS` variant of - /// the `HTTP2ClientTransport`. + /// the ``GRPCHTTP2Core/HTTP2ClientTransport``. /// /// To use this transport you need to provide a 'target' to connect to which will be resolved /// by an appropriate resolver from the resolver registry. By default the resolver registry can @@ -34,16 +38,19 @@ extension HTTP2ClientTransport { /// targets. If you use a custom target you must also provide an appropriately configured /// registry. /// - /// You can control various aspects of connection creation, management and RPC behavior via the - /// ``Config``. Load balancing policies and other RPC specific behavior can be configured via + /// You can control various aspects of connection creation, management, security and RPC behavior via + /// the ``Config``. Load balancing policies and other RPC specific behavior can be configured via /// the ``ServiceConfig`` (if it isn't provided by a resolver). /// /// Beyond creating the transport you don't need to interact with it directly, instead, pass it /// to a `GRPCClient`: /// /// ```swift - /// try await withThrowingDiscardingTaskGroup { - /// let transport = try HTTP2ClientTransport.Posix(target: .dns(host: "example.com")) + /// try await withThrowingDiscardingTaskGroup { group in + /// let transport = try HTTP2ClientTransport.Posix( + /// target: .ipv4(host: "example.com"), + /// config: .defaults(transportSecurity: .plaintext) + /// ) /// let client = GRPCClient(transport: transport) /// group.addTask { /// try await client.run() @@ -87,7 +94,7 @@ extension HTTP2ClientTransport { // Configure a connector. self.channel = GRPCChannel( resolver: resolver, - connector: Connector(eventLoopGroup: eventLoopGroup, config: config), + connector: try Connector(eventLoopGroup: eventLoopGroup, config: config), config: GRPCChannel.Config(posix: config), defaultServiceConfig: serviceConfig ) @@ -125,9 +132,33 @@ extension HTTP2ClientTransport.Posix { private let config: HTTP2ClientTransport.Posix.Config private let eventLoopGroup: any EventLoopGroup - init(eventLoopGroup: any EventLoopGroup, config: HTTP2ClientTransport.Posix.Config) { + #if canImport(NIOSSL) + private let nioSSLContext: NIOSSLContext? + private let serverHostname: String? + #endif + + init(eventLoopGroup: any EventLoopGroup, config: HTTP2ClientTransport.Posix.Config) throws { self.eventLoopGroup = eventLoopGroup self.config = config + + #if canImport(NIOSSL) + switch self.config.transportSecurity.wrapped { + case .plaintext: + self.nioSSLContext = nil + self.serverHostname = nil + case .tls(let tlsConfig): + do { + self.nioSSLContext = try NIOSSLContext(configuration: TLSConfiguration(tlsConfig)) + self.serverHostname = tlsConfig.serverHostname + } catch { + throw RuntimeError( + code: .transportError, + message: "Couldn't create SSL context, check your TLS configuration.", + cause: error + ) + } + } + #endif } func establishConnection( @@ -137,7 +168,18 @@ extension HTTP2ClientTransport.Posix { group: self.eventLoopGroup ).connect(to: address) { channel in channel.eventLoop.makeCompletedFuture { - try channel.pipeline.syncOperations.configureGRPCClientPipeline( + #if canImport(NIOSSL) + if let nioSSLContext = self.nioSSLContext { + try channel.pipeline.syncOperations.addHandler( + NIOSSLClientHandler( + context: nioSSLContext, + serverHostname: self.serverHostname + ) + ) + } + #endif + + return try channel.pipeline.syncOperations.configureGRPCClientPipeline( channel: channel, config: GRPCChannel.Config(posix: self.config) ) @@ -164,31 +206,48 @@ extension HTTP2ClientTransport.Posix { /// Compression configuration. public var compression: HTTP2ClientTransport.Config.Compression + /// The transport's security. + public var transportSecurity: TransportSecurity + /// Creates a new connection configuration. /// - /// See also ``defaults``. + /// - Parameters: + /// - http2: HTTP2 configuration. + /// - backoff: Backoff configuration. + /// - connection: Connection configuration. + /// - compression: Compression configuration. + /// - transportSecurity: The transport's security configuration. + /// + /// - SeeAlso: ``defaults(_:)``. public init( http2: HTTP2ClientTransport.Config.HTTP2, backoff: HTTP2ClientTransport.Config.Backoff, connection: HTTP2ClientTransport.Config.Connection, - compression: HTTP2ClientTransport.Config.Compression + compression: HTTP2ClientTransport.Config.Compression, + transportSecurity: TransportSecurity ) { self.http2 = http2 self.connection = connection self.backoff = backoff self.compression = compression + self.transportSecurity = transportSecurity } /// Default values. /// /// - Parameters: + /// - transportSecurity: The security settings applied to the transport. /// - configure: A closure which allows you to modify the defaults before returning them. - public static func defaults(_ configure: (_ config: inout Self) -> Void = { _ in }) -> Self { + public static func defaults( + transportSecurity: TransportSecurity, + configure: (_ config: inout Self) -> Void = { _ in } + ) -> Self { var config = Self( http2: .defaults, backoff: .defaults, connection: .defaults, - compression: .defaults + compression: .defaults, + transportSecurity: transportSecurity ) configure(&config) return config diff --git a/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ServerTransport+Posix.swift b/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ServerTransport+Posix.swift index 49d15c2d5..0717f0f35 100644 --- a/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ServerTransport+Posix.swift +++ b/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ServerTransport+Posix.swift @@ -15,7 +15,7 @@ */ public import GRPCCore -public import GRPCHTTP2Core +public import GRPCHTTP2Core // should be @usableFromInline internal import NIOCore internal import NIOExtras internal import NIOHTTP2 @@ -27,7 +27,33 @@ import NIOSSL #endif extension HTTP2ServerTransport { - /// A NIOPosix-backed implementation of a server transport. + /// A ``GRPCCore/ServerTransport`` using HTTP/2 built on top of `NIOPosix`. + /// + /// This transport builds on top of SwiftNIO's Posix networking layer and is suitable for use + /// on Linux and Darwin based platform (macOS, iOS, etc.) However, it's *strongly* recommended + /// that if you are targeting Darwin platforms then you should use the `NIOTS` variant of + /// the ``GRPCHTTP2Core/HTTP2ServerTransport``. + /// + /// You can control various aspects of connection creation, management, security and RPC behavior via + /// the ``Config``. + /// + /// Beyond creating the transport you don't need to interact with it directly, instead, pass it + /// to a `GRPCServer`: + /// + /// ```swift + /// try await withThrowingDiscardingTaskGroup { group in + /// let transport = HTTP2ServerTransport.Posix( + /// address: .ipv4(host: "127.0.0.1", port: 0), + /// config: .defaults(transportSecurity: .plaintext) + /// ) + /// let server = GRPCServer(transport: transport, services: someServices) + /// group.addTask { + /// try await server.serve() + /// } + /// + /// // ... + /// } + /// ``` @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) public final class Posix: ServerTransport, ListeningServerTransport { private let address: GRPCHTTP2Core.SocketAddress @@ -340,27 +366,34 @@ extension HTTP2ServerTransport.Posix { public struct Config: Sendable { /// Compression configuration. public var compression: HTTP2ServerTransport.Config.Compression + /// Connection configuration. public var connection: HTTP2ServerTransport.Config.Connection + /// HTTP2 configuration. public var http2: HTTP2ServerTransport.Config.HTTP2 + /// RPC configuration. public var rpc: HTTP2ServerTransport.Config.RPC + /// The transport's security. public var transportSecurity: TransportSecurity /// Construct a new `Config`. + /// /// - Parameters: - /// - compression: Compression configuration. - /// - connection: Connection configuration. /// - http2: HTTP2 configuration. /// - rpc: RPC configuration. + /// - connection: Connection configuration. + /// - compression: Compression configuration. /// - transportSecurity: The transport's security configuration. + /// + /// - SeeAlso: ``defaults(transportSecurity:configure:)`` public init( - compression: HTTP2ServerTransport.Config.Compression, - connection: HTTP2ServerTransport.Config.Connection, http2: HTTP2ServerTransport.Config.HTTP2, rpc: HTTP2ServerTransport.Config.RPC, + connection: HTTP2ServerTransport.Config.Connection, + compression: HTTP2ServerTransport.Config.Compression, transportSecurity: TransportSecurity ) { self.compression = compression @@ -380,10 +413,10 @@ extension HTTP2ServerTransport.Posix { configure: (_ config: inout Self) -> Void = { _ in } ) -> Self { var config = Self( - compression: .defaults, - connection: .defaults, http2: .defaults, rpc: .defaults, + connection: .defaults, + compression: .defaults, transportSecurity: transportSecurity ) configure(&config) diff --git a/Sources/GRPCHTTP2TransportNIOPosix/NIOSSL+GRPC.swift b/Sources/GRPCHTTP2TransportNIOPosix/NIOSSL+GRPC.swift index 4bc3f143e..94436f56e 100644 --- a/Sources/GRPCHTTP2TransportNIOPosix/NIOSSL+GRPC.swift +++ b/Sources/GRPCHTTP2TransportNIOPosix/NIOSSL+GRPC.swift @@ -131,18 +131,34 @@ extension TLSConfiguration { let certificateChain = try tlsConfig.certificateChain.sslCertificateSources() let privateKey = try NIOSSLPrivateKey(privateKey: tlsConfig.privateKey) - var tlsConfiguration = TLSConfiguration.makeServerConfiguration( + self = TLSConfiguration.makeServerConfiguration( certificateChain: certificateChain, privateKey: .privateKey(privateKey) ) - tlsConfiguration.minimumTLSVersion = .tlsv12 - tlsConfiguration.certificateVerification = CertificateVerification( + self.minimumTLSVersion = .tlsv12 + self.certificateVerification = CertificateVerification( tlsConfig.clientCertificateVerification ) - tlsConfiguration.trustRoots = try NIOSSLTrustRoots(tlsConfig.trustRoots) - tlsConfiguration.applicationProtocols = ["grpc-exp", "h2"] + self.trustRoots = try NIOSSLTrustRoots(tlsConfig.trustRoots) + self.applicationProtocols = ["grpc-exp", "h2"] + } + + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + package init(_ tlsConfig: HTTP2ClientTransport.Posix.Config.TLS) throws { + self = TLSConfiguration.makeClientConfiguration() + self.certificateChain = try tlsConfig.certificateChain.sslCertificateSources() - self = tlsConfiguration + if let privateKey = tlsConfig.privateKey { + let privateKeySource = try NIOSSLPrivateKey(privateKey: privateKey) + self.privateKey = .privateKey(privateKeySource) + } + + self.minimumTLSVersion = .tlsv12 + self.certificateVerification = CertificateVerification( + tlsConfig.serverCertificateVerification + ) + self.trustRoots = try NIOSSLTrustRoots(tlsConfig.trustRoots) + self.applicationProtocols = ["grpc-exp", "h2"] } } #endif diff --git a/Sources/GRPCHTTP2TransportNIOPosix/TLSConfig.swift b/Sources/GRPCHTTP2TransportNIOPosix/TLSConfig.swift index 591bafbe7..2e42d58c1 100644 --- a/Sources/GRPCHTTP2TransportNIOPosix/TLSConfig.swift +++ b/Sources/GRPCHTTP2TransportNIOPosix/TLSConfig.swift @@ -147,10 +147,12 @@ extension HTTP2ServerTransport.Posix.Config { /// This connection is plaintext: no encryption will take place. public static let plaintext = Self(wrapped: .plaintext) + #if canImport(NIOSSL) /// This connection will use TLS. public static func tls(_ tls: TLS) -> Self { Self(wrapped: .tls(tls)) } + #endif } public struct TLS: Sendable { @@ -184,7 +186,7 @@ extension HTTP2ServerTransport.Posix.Config { certificateChain: [TLSConfig.CertificateSource], privateKey: TLSConfig.PrivateKeySource ) -> Self { - Self.init( + Self( certificateChain: certificateChain, privateKey: privateKey, clientCertificateVerification: .noVerification, @@ -207,7 +209,7 @@ extension HTTP2ServerTransport.Posix.Config { certificateChain: [TLSConfig.CertificateSource], privateKey: TLSConfig.PrivateKeySource ) -> Self { - Self.init( + Self( certificateChain: certificateChain, privateKey: privateKey, clientCertificateVerification: .noHostnameVerification, @@ -217,3 +219,81 @@ extension HTTP2ServerTransport.Posix.Config { } } } + +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) +extension HTTP2ClientTransport.Posix.Config { + /// The security configuration for this connection. + public struct TransportSecurity: Sendable { + package enum Wrapped: Sendable { + case plaintext + case tls(TLS) + } + + package let wrapped: Wrapped + + /// This connection is plaintext: no encryption will take place. + public static let plaintext = Self(wrapped: .plaintext) + + #if canImport(NIOSSL) + /// This connection will use TLS. + public static func tls(_ tls: TLS) -> Self { + Self(wrapped: .tls(tls)) + } + #endif + } + + public struct TLS: Sendable { + /// The certificates the client will offer during negotiation. + public var certificateChain: [TLSConfig.CertificateSource] + + /// The private key associated with the leaf certificate. + public var privateKey: TLSConfig.PrivateKeySource? + + /// How to verify the server certificate, if one is presented. + public var serverCertificateVerification: TLSConfig.CertificateVerification + + /// The trust roots to be used when verifying server certificates. + public var trustRoots: TLSConfig.TrustRootsSource + + /// An optional server hostname to use when verifying certificates. + public var serverHostname: String? + + /// Create a new HTTP2 NIO Posix transport TLS config, with some values defaulted: + /// - `certificateChain` equals `[]` + /// - `privateKey` equals `nil` + /// - `serverCertificateVerification` equals `fullVerification` + /// - `trustRoots` equals `systemDefault` + /// - `serverHostname` equals `nil` + /// + /// - Returns: A new HTTP2 NIO Posix transport TLS config. + public static var defaults: Self { + Self( + certificateChain: [], + privateKey: nil, + serverCertificateVerification: .fullVerification, + trustRoots: .systemDefault, + serverHostname: nil + ) + } + + /// Create a new HTTP2 NIO Posix transport TLS config, with some values defaulted to match + /// the requirements of mTLS: + /// - `trustRoots` equals `systemDefault` + /// + /// - Parameters: + /// - certificateChain: The certificates the client will offer during negotiation. + /// - privateKey: The private key associated with the leaf certificate. + /// - Returns: A new HTTP2 NIO Posix transport TLS config. + public static func mTLS( + certificateChain: [TLSConfig.CertificateSource], + privateKey: TLSConfig.PrivateKeySource + ) -> Self { + Self( + certificateChain: certificateChain, + privateKey: privateKey, + serverCertificateVerification: .fullVerification, + trustRoots: .systemDefault + ) + } + } +} diff --git a/Sources/interoperability-tests/InteroperabilityTestsExecutable.swift b/Sources/interoperability-tests/InteroperabilityTestsExecutable.swift index 9b60f6031..15e1ba0fa 100644 --- a/Sources/interoperability-tests/InteroperabilityTestsExecutable.swift +++ b/Sources/interoperability-tests/InteroperabilityTestsExecutable.swift @@ -106,7 +106,7 @@ struct InteroperabilityTestsExecutable: AsyncParsableCommand { return GRPCClient( transport: try .http2NIOPosix( target: .ipv4(host: host, port: port), - config: .defaults { + config: .defaults(transportSecurity: .plaintext) { $0.compression.enabledAlgorithms = .all }, serviceConfig: serviceConfig diff --git a/Sources/performance-worker/WorkerService.swift b/Sources/performance-worker/WorkerService.swift index f029e146b..2864fc405 100644 --- a/Sources/performance-worker/WorkerService.swift +++ b/Sources/performance-worker/WorkerService.swift @@ -457,7 +457,7 @@ extension WorkerService { client: GRPCClient( transport: try .http2NIOPosix( target: target, - config: .defaults() + config: .defaults(transportSecurity: .plaintext) ) ), concurrentRPCs: Int(config.outstandingRpcsPerChannel), diff --git a/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOPosixTests.swift b/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOPosixTests.swift index 4f6993dde..aa6af81c9 100644 --- a/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOPosixTests.swift +++ b/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOPosixTests.swift @@ -263,7 +263,7 @@ final class HTTP2TransportNIOPosixTests: XCTestCase { -----END RSA PRIVATE KEY----- """ - func testTLSConfig_Defaults() throws { + func testServerTLSConfig_Defaults() throws { let grpcTLSConfig = HTTP2ServerTransport.Posix.Config.TLS.defaults( certificateChain: [ .bytes(Array(Self.samplePemCert.utf8), format: .pem) @@ -293,7 +293,7 @@ final class HTTP2TransportNIOPosixTests: XCTestCase { XCTAssertEqual(nioSSLTLSConfig.applicationProtocols, ["grpc-exp", "h2"]) } - func testTLSConfig_mTLS() throws { + func testServerTLSConfig_mTLS() throws { let grpcTLSConfig = HTTP2ServerTransport.Posix.Config.TLS.mTLS( certificateChain: [ .bytes(Array(Self.samplePemCert.utf8), format: .pem) @@ -323,7 +323,7 @@ final class HTTP2TransportNIOPosixTests: XCTestCase { XCTAssertEqual(nioSSLTLSConfig.applicationProtocols, ["grpc-exp", "h2"]) } - func testTLSConfig_FullVerifyClient() throws { + func testServerTLSConfig_FullVerifyClient() throws { var grpcTLSConfig = HTTP2ServerTransport.Posix.Config.TLS.defaults( certificateChain: [ .bytes(Array(Self.samplePemCert.utf8), format: .pem) @@ -354,7 +354,7 @@ final class HTTP2TransportNIOPosixTests: XCTestCase { XCTAssertEqual(nioSSLTLSConfig.applicationProtocols, ["grpc-exp", "h2"]) } - func testTLSConfig_CustomTrustRoots() throws { + func testServerTLSConfig_CustomTrustRoots() throws { var grpcTLSConfig = HTTP2ServerTransport.Posix.Config.TLS.defaults( certificateChain: [ .bytes(Array(Self.samplePemCert.utf8), format: .pem) @@ -387,5 +387,75 @@ final class HTTP2TransportNIOPosixTests: XCTestCase { ) XCTAssertEqual(nioSSLTLSConfig.applicationProtocols, ["grpc-exp", "h2"]) } + + func testClientTLSConfig_Defaults() throws { + let grpcTLSConfig = HTTP2ClientTransport.Posix.Config.TLS.defaults + let nioSSLTLSConfig = try TLSConfiguration(grpcTLSConfig) + + XCTAssertEqual(nioSSLTLSConfig.certificateChain, []) + XCTAssertNil(nioSSLTLSConfig.privateKey) + XCTAssertEqual(nioSSLTLSConfig.minimumTLSVersion, .tlsv12) + XCTAssertEqual(nioSSLTLSConfig.certificateVerification, .fullVerification) + XCTAssertEqual(nioSSLTLSConfig.trustRoots, .default) + XCTAssertEqual(nioSSLTLSConfig.applicationProtocols, ["grpc-exp", "h2"]) + } + + func testClientTLSConfig_CustomCertificateChainAndPrivateKey() throws { + var grpcTLSConfig = HTTP2ClientTransport.Posix.Config.TLS.defaults + grpcTLSConfig.certificateChain = [ + .bytes(Array(Self.samplePemCert.utf8), format: .pem) + ] + grpcTLSConfig.privateKey = .bytes(Array(Self.samplePemKey.utf8), format: .pem) + let nioSSLTLSConfig = try TLSConfiguration(grpcTLSConfig) + + XCTAssertEqual( + nioSSLTLSConfig.certificateChain, + [ + .certificate( + try NIOSSLCertificate( + bytes: Array(Self.samplePemCert.utf8), + format: .pem + ) + ) + ] + ) + XCTAssertEqual( + nioSSLTLSConfig.privateKey, + .privateKey(try NIOSSLPrivateKey(bytes: Array(Self.samplePemKey.utf8), format: .pem)) + ) + XCTAssertEqual(nioSSLTLSConfig.minimumTLSVersion, .tlsv12) + XCTAssertEqual(nioSSLTLSConfig.certificateVerification, .fullVerification) + XCTAssertEqual(nioSSLTLSConfig.trustRoots, .default) + XCTAssertEqual(nioSSLTLSConfig.applicationProtocols, ["grpc-exp", "h2"]) + } + + func testClientTLSConfig_CustomTrustRoots() throws { + var grpcTLSConfig = HTTP2ClientTransport.Posix.Config.TLS.defaults + grpcTLSConfig.trustRoots = .certificates([.bytes(Array(Self.samplePemCert.utf8), format: .pem)]) + let nioSSLTLSConfig = try TLSConfiguration(grpcTLSConfig) + + XCTAssertEqual(nioSSLTLSConfig.certificateChain, []) + XCTAssertNil(nioSSLTLSConfig.privateKey) + XCTAssertEqual(nioSSLTLSConfig.minimumTLSVersion, .tlsv12) + XCTAssertEqual(nioSSLTLSConfig.certificateVerification, .fullVerification) + XCTAssertEqual( + nioSSLTLSConfig.trustRoots, + .certificates(try NIOSSLCertificate.fromPEMBytes(Array(Self.samplePemCert.utf8))) + ) + XCTAssertEqual(nioSSLTLSConfig.applicationProtocols, ["grpc-exp", "h2"]) + } + + func testClientTLSConfig_CustomCertificateVerification() throws { + var grpcTLSConfig = HTTP2ClientTransport.Posix.Config.TLS.defaults + grpcTLSConfig.serverCertificateVerification = .noHostnameVerification + let nioSSLTLSConfig = try TLSConfiguration(grpcTLSConfig) + + XCTAssertEqual(nioSSLTLSConfig.certificateChain, []) + XCTAssertNil(nioSSLTLSConfig.privateKey) + XCTAssertEqual(nioSSLTLSConfig.minimumTLSVersion, .tlsv12) + XCTAssertEqual(nioSSLTLSConfig.certificateVerification, .noHostnameVerification) + XCTAssertEqual(nioSSLTLSConfig.trustRoots, .default) + XCTAssertEqual(nioSSLTLSConfig.applicationProtocols, ["grpc-exp", "h2"]) + } #endif } diff --git a/Tests/GRPCHTTP2TransportTests/HTTP2TransportTests.swift b/Tests/GRPCHTTP2TransportTests/HTTP2TransportTests.swift index 2de224ffc..a78751874 100644 --- a/Tests/GRPCHTTP2TransportTests/HTTP2TransportTests.swift +++ b/Tests/GRPCHTTP2TransportTests/HTTP2TransportTests.swift @@ -199,7 +199,7 @@ final class HTTP2TransportTests: XCTestCase { serviceConfig.loadBalancingConfig = [.roundRobin] transport = try HTTP2ClientTransport.Posix( target: target, - config: .defaults { + config: .defaults(transportSecurity: .plaintext) { $0.compression.algorithm = compression $0.compression.enabledAlgorithms = enabledCompression },