@@ -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