Skip to content

Commit eea3f4e

Browse files
authored
Add support for custom client certificate verification (#34)
Motivation: `NIOSSLServerHandler` allows a custom verification callback to be used for client certificate verification. This callback returns the peer's validated chain of trust, which `NIOSSL` then surfaces in the channel handler. Having control over certificate verification and accessing the peer's validated certificate chain can be useful for mTLS implementations. `NIOHTTPServer` currently does not support propagating a custom verification callback to `NIOSSLServerHandler`. This change adds support for that and also surfaces the peer's validated certificate chain which becomes available as a result. Modifications: - Updated the server configuration to also include a `customCertificateVerificationCallback` argument for the `mTLS` and `reloadingMTLS` cases of `TransportSecurity`. - **Note**: There are certain caveats here which I'd like to hear opinions about. We now expose `NIOSSL` types in the server configuration type (the arguments and return type of the callback). I'm also not sure whether custom client certificate verification is a requirement we want to impose for other server implementations, so for now, I have made the configuration type `NIOHTTPServer` specific and renamed `HTTPServerConfiguration` to `NIOHTTPServerConfiguration`. - Updated the `serveSecureUpgrade` method in `NIOHTTPServer` to: - Propagate the custom verification callback into the underlying `NIOSSLServerHandler`; - Extract the peer certificate chain `EventLoopFuture` per connection, and; - Expose that future in a new type in `NIOHTTPServer` named `ConnectionContext`. - `ConnectionContext` is accessible from a task-local property. Users can await the result of the promise from their route handlers by calling `NIOHTTPServer. connectionContext.peerCertificateChain()`. - The name of this type, its properties, the task-local approach, etc. are all very much open for discussion. - Added end-to-end tests for this functionality for both HTTP/1.1 and HTTP2. - Other changes: - Added `NIOSSL+X509` containing some convenience conversions between `NIOSSL` and `X509` types. - Added documentation to the `TransportSecurity` methods. Result: Users can now specify a custom verification callback and access the peer's validated certificate chain from the request handler.
1 parent d56f742 commit eea3f4e

12 files changed

+939
-319
lines changed

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ let package = Package(
2424
.package(url: "https://github.com/apple/swift-collections.git", from: "1.0.4"),
2525
.package(url: "https://github.com/apple/swift-http-types.git", from: "1.0.0"),
2626
.package(url: "https://github.com/apple/swift-distributed-tracing.git", from: "1.0.0"),
27-
.package(url: "https://github.com/apple/swift-certificates.git", from: "1.0.4"),
27+
.package(url: "https://github.com/apple/swift-certificates.git", from: "1.16.0"),
2828
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
2929
.package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"),
30-
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.0.0"),
30+
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.36.0"),
3131
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.30.0"),
3232
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.0.0"),
3333
],

Sources/HTTPServer/HTTPServerConfiguration.swift

Lines changed: 0 additions & 205 deletions
This file was deleted.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import NIOCore
2+
import NIOSSL
3+
public import X509
4+
5+
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
6+
extension NIOHTTPServer {
7+
/// Connection-specific information available during request handling.
8+
///
9+
/// Provides access to data such as the peer's validated certificate chain.
10+
public struct ConnectionContext: Sendable {
11+
var peerCertificateChainFuture: EventLoopFuture<NIOSSL.ValidatedCertificateChain?>?
12+
13+
init(_ peerCertificateChainFuture: EventLoopFuture<NIOSSL.ValidatedCertificateChain?>? = nil) {
14+
self.peerCertificateChainFuture = peerCertificateChainFuture
15+
}
16+
17+
/// The peer's validated certificate chain. This returns `nil` if a custom verification callback was not set
18+
/// when configuring mTLS in the server configuration, or if the custom verification callback did not return the
19+
/// derived validated chain.
20+
public var peerCertificateChain: X509.ValidatedCertificateChain? {
21+
get async throws {
22+
if let certs = try await self.peerCertificateChainFuture?.get() {
23+
return .init(uncheckedCertificateChain: try certs.map { try Certificate($0) })
24+
}
25+
return nil
26+
}
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)