Skip to content

Commit 5ecc622

Browse files
committed
Add support for configuring verification behaviour.
1 parent a6be2ce commit 5ecc622

File tree

2 files changed

+71
-21
lines changed

2 files changed

+71
-21
lines changed

Sources/HTTPServer/NIOHTTPServer.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ public struct NIOHTTPServer: HTTPServerProtocol {
101101
}
102102

103103
/// The peer's validated certificate chain. This returns `nil` if a
104-
/// ``NIOHTTPServerConfiguration/TransportSecurity/CustomCertificateVerificationCallback`` was not set in the
105-
/// ``NIOHTTPServerConfiguration/TransportSecurity`` property of the server configuration, or if the peer did
106-
/// not authenticate with certificates.
104+
/// ``NIOHTTPServerConfiguration/TransportSecurity/CustomCertificateVerificationCallback`` was not set when
105+
/// configuring mTLS in the server configuration, or if the custom verification callback did not return the
106+
/// derived validated chain.
107107
public var peerCertificateChain: X509.ValidatedCertificateChain? {
108108
get async throws {
109109
if let certs = try await self.peerCertificateChainFuture?.get() {
@@ -235,7 +235,7 @@ public struct NIOHTTPServer: HTTPServerProtocol {
235235
http2Configuration: http2Config
236236
)
237237

238-
case .mTLS(let certificateChain, let privateKey, let trustRoots, let verificationCallback):
238+
case .mTLS(let certificateChain, let privateKey, let trustRoots, let verificationMode, let verificationCallback):
239239
let http2Config = NIOHTTP2Handler.Configuration(
240240
httpServerHTTP2Configuration: configuration.http2
241241
)
@@ -249,6 +249,7 @@ public struct NIOHTTPServer: HTTPServerProtocol {
249249
privateKey: privateKey,
250250
trustRoots: nioTrustRoots
251251
)
252+
tlsConfiguration.certificateVerification = .init(verificationMode)
252253
tlsConfiguration.applicationProtocols = ["h2", "http/1.1"]
253254

254255
try await self.serveSecureUpgrade(
@@ -260,7 +261,7 @@ public struct NIOHTTPServer: HTTPServerProtocol {
260261
verificationCallback: verificationCallback
261262
)
262263

263-
case .reloadingMTLS(let certificateReloader, let trustRoots, let verificationCallback):
264+
case .reloadingMTLS(let certificateReloader, let trustRoots, let verificationMode, let verificationCallback):
264265
let http2Config = NIOHTTP2Handler.Configuration(
265266
httpServerHTTP2Configuration: configuration.http2
266267
)
@@ -271,6 +272,7 @@ public struct NIOHTTPServer: HTTPServerProtocol {
271272
certificateReloader: certificateReloader,
272273
trustRoots: nioTrustRoots
273274
)
275+
tlsConfiguration.certificateVerification = .init(verificationMode)
274276
tlsConfiguration.applicationProtocols = ["h2", "http/1.1"]
275277

276278
try await self.serveSecureUpgrade(

Sources/HTTPServer/NIOHTTPServerConfiguration.swift

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,13 @@ public struct NIOHTTPServerConfiguration: Sendable {
6565
certificateChain: [Certificate],
6666
privateKey: Certificate.PrivateKey,
6767
trustRoots: [Certificate]?,
68+
certificateVerification: CertificateVerification = .noHostnameVerification,
6869
customCertificateVerificationCallback: CustomCertificateVerificationCallback? = nil
6970
)
7071
case reloadingMTLS(
7172
certificateReloader: any CertificateReloader,
7273
trustRoots: [Certificate]?,
74+
certificateVerification: CertificateVerification = .noHostnameVerification,
7375
customCertificateVerificationCallback: CustomCertificateVerificationCallback? = nil
7476
)
7577
}
@@ -109,12 +111,15 @@ public struct NIOHTTPServerConfiguration: Sendable {
109111
/// - certificateChain: The certificate chain to present during negotiation.
110112
/// - privateKey: The private key corresponding to the leaf certificate in `certificateChain`.
111113
/// - trustRoots: The root certificates to trust when verifying client certificates.
114+
/// - certificateVerification: Configures the client certificate validation behaviour. Defaults to
115+
/// ``CertificateVerification/noHostnameVerification``.
112116
/// - customCertificateVerificationCallback: A custom certificate verification callback. This will override
113117
/// NIOSSL's default certificate verification logic.
114118
public static func mTLS(
115119
certificateChain: [Certificate],
116120
privateKey: Certificate.PrivateKey,
117121
trustRoots: [Certificate]?,
122+
certificateVerification: CertificateVerification = .noHostnameVerification,
118123
customCertificateVerificationCallback: CustomCertificateVerificationCallback? = nil
119124
) -> Self {
120125
Self(
@@ -134,11 +139,14 @@ public struct NIOHTTPServerConfiguration: Sendable {
134139
/// - Parameters:
135140
/// - certificateReloader: The certificate reloader instance.
136141
/// - trustRoots: The root certificates to trust when verifying client certificates.
142+
/// - certificateVerification: Configures the client certificate validation behaviour. Defaults to
143+
// ``CertificateVerification/noHostnameVerification``.
137144
/// - customCertificateVerificationCallback: A custom certificate verification callback. This will override
138145
/// NIOSSL's default certificate verification logic.
139146
public static func mTLS(
140147
certificateReloader: any CertificateReloader,
141148
trustRoots: [Certificate]?,
149+
certificateVerification: CertificateVerification = .noHostnameVerification,
142150
customCertificateVerificationCallback: CustomCertificateVerificationCallback? = nil
143151
) throws -> Self {
144152
Self(
@@ -245,18 +253,7 @@ extension NIOHTTPServerConfiguration.TransportSecurity {
245253
///
246254
/// Indicates whether certificate verification succeeded or failed, and provides associated metadata when
247255
/// verification is successful.
248-
public enum CertificateVerificationResult: Sendable, Hashable {
249-
/// An error representing certificate verification failure.
250-
public struct VerificationError: Swift.Error, Hashable {
251-
let description: String
252-
253-
/// Creates a verification error with a description of the failure.
254-
/// - Parameter description: A description of why certificate verification failed.
255-
public init(description: String) {
256-
self.description = description
257-
}
258-
}
259-
256+
public enum VerificationResult: Sendable, Hashable {
260257
/// Metadata resulting from successful certificate verification.
261258
public struct VerificationMetadata: Sendable, Hashable {
262259
/// A container for the validated certificate chain: an array of certificates forming a verified and ordered
@@ -273,6 +270,17 @@ extension NIOHTTPServerConfiguration.TransportSecurity {
273270
}
274271
}
275272

273+
/// An error representing certificate verification failure.
274+
public struct VerificationError: Swift.Error, Hashable {
275+
public let reason: String
276+
277+
/// Creates a verification error with the reason why verification failed.
278+
/// - Parameter reason: The reason of why certificate verification failed.
279+
public init(reason: String) {
280+
self.reason = reason
281+
}
282+
}
283+
276284
/// Certificate verification succeeded.
277285
///
278286
/// The associated metadata contains information captured during verification.
@@ -288,11 +296,51 @@ extension NIOHTTPServerConfiguration.TransportSecurity {
288296
/// by the peer as `[X509.Certificate]`. Within the callback, you must validate these certificates against your
289297
/// trust roots and derive a validated chain of trust per [RFC 4158](https://datatracker.ietf.org/doc/html/rfc4158).
290298
///
291-
/// Return ``CertificateVerificationResult/certificateVerified(_:)`` if verification succeeds, optionally including
299+
/// Return ``VerificationResult/certificateVerified(_:)`` if verification succeeds, optionally including
292300
/// the validated certificate chain you derived. Returning the validated certificate chain allows ``NIOHTTPServer``
293301
/// to provide access to it in the request handler through ``NIOHTTPServer/ConnectionContext/peerCertificateChain``,
294302
/// accessed via the task-local ``NIOHTTPServer/connectionContext`` property. Otherwise, return
295-
/// ``CertificateVerificationResult/failed(_:)`` if verification fails.
296-
public typealias CustomCertificateVerificationCallback =
297-
@Sendable ([Certificate]) async throws -> CertificateVerificationResult
303+
/// ``VerificationResult/failed(_:)`` if verification fails.
304+
public typealias CustomCertificateVerificationCallback = @Sendable ([Certificate]) async throws -> VerificationResult
305+
}
306+
307+
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
308+
extension NIOHTTPServerConfiguration.TransportSecurity {
309+
/// Represents the certificate verification behaviour.
310+
public struct CertificateVerification: Sendable {
311+
enum VerificationMode {
312+
case optionalVerification
313+
case noHostnameVerification
314+
}
315+
316+
let mode: VerificationMode
317+
318+
/// Allows peers to connect without presenting any certificates. However, if the peer *does* present
319+
/// certificates, they are validated like normal (exactly like with ``noHostnameVerification``), and the TLS
320+
/// handshake will fail if verification fails.
321+
///
322+
/// - Warning: With this mode, a peer can successfully connect even without presenting any certificates. As such,
323+
/// this mode must be used with great caution.
324+
public static var optionalVerification: Self {
325+
Self(mode: .optionalVerification)
326+
}
327+
328+
/// Validates the certificates presented by the peer but skips hostname verification as it cannot succeed in
329+
/// a server context.
330+
public static var noHostnameVerification: Self {
331+
Self(mode: .noHostnameVerification)
332+
}
333+
}
334+
}
335+
336+
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
337+
extension NIOSSL.CertificateVerification {
338+
init(_ verificationMode: NIOHTTPServerConfiguration.TransportSecurity.CertificateVerification) {
339+
switch verificationMode.mode {
340+
case .noHostnameVerification:
341+
self = .noHostnameVerification
342+
case .optionalVerification:
343+
self = .optionalVerification
344+
}
345+
}
298346
}

0 commit comments

Comments
 (0)