Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions Sources/GRPCNIOTransportHTTP2Posix/NIOSSL+GRPC.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024, gRPC Authors All rights reserved.
* Copyright 2025, 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.
Expand Down Expand Up @@ -141,19 +141,24 @@ extension NIOSSLTrustRoots {
fileprivate init(_ trustRoots: TLSConfig.TrustRootsSource) throws {
switch trustRoots.wrapped {
case .certificates(let certificateSources):
let certificates = try certificateSources.map { source in
var certificates: [NIOSSLCertificate] = []
for source in certificateSources {
switch source.wrapped {
case .bytes(let bytes, let serializationFormat):
return try NIOSSLCertificate(
bytes: bytes,
format: NIOSSLSerializationFormats(serializationFormat)
certificates.append(
try NIOSSLCertificate(
bytes: bytes,
format: NIOSSLSerializationFormats(serializationFormat)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably use fromPEMBytes here as well

)
)

case .file(let path, let serializationFormat):
return try NIOSSLCertificate(
file: path,
format: NIOSSLSerializationFormats(serializationFormat)
)
switch NIOSSLSerializationFormats(serializationFormat) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the indirection here? Can we just switch over serializationFormat.wrapped?

case .pem:
certificates.append(contentsOf: try NIOSSLCertificate.fromPEMFile(path))
case .der:
certificates.append(try NIOSSLCertificate(file: path, format: .der))
}

case .transportSpecific(let specific):
guard let source = specific.wrapped as? NIOSSLCertificateSource else {
Expand All @@ -162,14 +167,14 @@ extension NIOSSLTrustRoots {

switch source {
case .certificate(let certificate):
return certificate
certificates.append(certificate)

case .file(let path):
switch path.split(separator: ".").last {
case "pem":
return try NIOSSLCertificate(file: path, format: .pem)
certificates.append(contentsOf: try NIOSSLCertificate.fromPEMFile(path))
case "der":
return try NIOSSLCertificate(file: path, format: .der)
certificates.append(try NIOSSLCertificate(file: path, format: .der))
default:
throw RPCError(
code: .invalidArgument,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024, gRPC Authors All rights reserved.
* Copyright 2025, 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.
Expand Down Expand Up @@ -69,7 +69,8 @@ struct HTTP2TransportTLSEnabledTests {
func intercept<Input, Output>(
request: GRPCCore.StreamingServerRequest<Input>,
context: GRPCCore.ServerContext,
next: @Sendable (GRPCCore.StreamingServerRequest<Input>, GRPCCore.ServerContext) async throws
next:
@Sendable (GRPCCore.StreamingServerRequest<Input>, GRPCCore.ServerContext) async throws
-> GRPCCore.StreamingServerResponse<Output>
) async throws -> GRPCCore.StreamingServerResponse<Output>
where Input: Sendable, Output: Sendable {
Expand Down Expand Up @@ -146,6 +147,49 @@ struct HTTP2TransportTLSEnabledTests {
}
}

@Test(
"When using mTLS with PEM files, both client and server verify each others' certificates"
)
@available(gRPCSwiftNIOTransport 2.0, *)
func testRPC_mTLS_posixFileBasedCertificates_OK() async throws {
// Create a new certificate chain that has 4 certificate/key pairs: root, intermediate, client, server
let certificateChain = try CertificateChain()
// Tag our certificate files with the function name
let fileNames = try certificateChain.writeToTemp(fileTag: #function)
// Check that the files
let clientCertPath = fileNames[.clientCert]!
#expect(FileManager.default.fileExists(atPath: clientCertPath))
let clientKeyPath = fileNames[.clientKey]!
#expect(FileManager.default.fileExists(atPath: clientKeyPath))
let serverCertPath = fileNames[.serverCert]!
#expect(FileManager.default.fileExists(atPath: serverCertPath))
let serverKeyPath = fileNames[.serverKey]!
#expect(FileManager.default.fileExists(atPath: serverKeyPath))
let trustRootsPath = fileNames[.trustRoots]!
#expect(FileManager.default.fileExists(atPath: trustRootsPath))
// Create configurations
let clientConfig = self.makeMTLSClientConfig(
certificate: clientCertPath,
key: clientKeyPath,
trustRoots: trustRootsPath,
serverHostname: certificateChain.serverName
)
let serverConfig = self.makeMTLSServerConfig(
certificate: serverCertPath,
key: serverKeyPath,
trustRoots: trustRootsPath
)
// Run the test
try await self.withClientAndServer(
clientConfig: clientConfig,
serverConfig: serverConfig
) { control in
await #expect(throws: Never.self) {
try await self.executeUnaryRPC(control: control)
}
}
}

@Test(
"Error is surfaced when client fails server verification",
arguments: TransportKind.clientsWithTLS,
Expand Down Expand Up @@ -471,6 +515,26 @@ struct HTTP2TransportTLSEnabledTests {
}
}

@available(gRPCSwiftNIOTransport 2.0, *)
private func makeMTLSClientConfig(
certificate: String,
key: String,
trustRoots: String,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add Path to the end of each of these?

serverHostname: String?
) -> ClientConfig {
var config = self.makeDefaultPlaintextPosixClientConfig()
config.security = .mTLS(
certificateChain: [.file(path: certificate, format: .pem)],
privateKey: .file(path: key, format: .pem)
) {
$0.trustRoots = .certificates([
.file(path: trustRoots, format: .pem)
])
}
config.transport.http2.authority = serverHostname
return .posix(config)
}

@available(gRPCSwiftNIOTransport 2.0, *)
private func makeDefaultPlaintextPosixServerConfig() -> ServerConfig.Posix {
ServerConfig.Posix(security: .plaintext, transport: .defaults)
Expand Down Expand Up @@ -558,6 +622,24 @@ struct HTTP2TransportTLSEnabledTests {
}
}

@available(gRPCSwiftNIOTransport 2.0, *)
private func makeMTLSServerConfig(
certificate: String,
key: String,
trustRoots: String
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, can you add Path to the end of each of these?

) -> ServerConfig {
var config = self.makeDefaultPlaintextPosixServerConfig()
config.security = .mTLS(
certificateChain: [.file(path: certificate, format: .pem)],
privateKey: .file(path: key, format: .pem)
) {
$0.trustRoots = .certificates([
.file(path: trustRoots, format: .pem)
])
}
return .posix(config)
}

@available(gRPCSwiftNIOTransport 2.0, *)
func withClientAndServer(
clientConfig: ClientConfig,
Expand Down
Loading