Skip to content

Commit a6be2ce

Browse files
committed
Drop public import of NIOSSL and NIOCore; use X509 type for custom verification callback
1 parent 32f8a84 commit a6be2ce

File tree

4 files changed

+83
-27
lines changed

4 files changed

+83
-27
lines changed

Sources/HTTPServer/NIOHTTPServer.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,27 @@ extension NIOHTTPServer {
502502
return try NIOSSLServerHandler(
503503
context: .init(configuration: tlsConfiguration),
504504
customVerificationCallbackWithMetadata: { certificates, promise in
505-
customVerificationCallback(certificates, promise)
505+
promise.completeWithTask {
506+
// Convert input [NIOSSLCertificate] to [X509.Certificate]
507+
let x509Certs = try certificates.map { try Certificate($0) }
508+
509+
let callbackResult = try await customVerificationCallback(x509Certs)
510+
511+
switch callbackResult {
512+
case .certificateVerified(let verificationMetadata):
513+
guard let peerChain = verificationMetadata.validatedCertificateChain else {
514+
return .certificateVerified(.init(nil))
515+
}
516+
517+
// Convert the result into [NIOSSLCertificate]
518+
let nioSSLCerts = try peerChain.map { try NIOSSLCertificate($0) }
519+
return .certificateVerified(.init(.init(nioSSLCerts)))
520+
521+
case .failed(let error):
522+
self.logger.error("Custom certificate verification failed: \(error)")
523+
return .failed
524+
}
525+
}
506526
}
507527
)
508528
} else {

Sources/HTTPServer/NIOHTTPServerConfiguration.swift

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
//===----------------------------------------------------------------------===//
1313

1414
public import NIOCertificateReloading
15-
public import NIOCore
16-
public import NIOSSL
15+
import NIOCore
16+
import NIOSSL
1717
public import X509
1818

1919
/// Configuration settings for ``NIOHTTPServer``.
@@ -54,26 +54,6 @@ public struct NIOHTTPServerConfiguration: Sendable {
5454
/// Provides options for running the server with or without TLS encryption.
5555
/// When using TLS, you must either provide a certificate chain and private key, or a `CertificateReloader`.
5656
public struct TransportSecurity: Sendable {
57-
/// A callback that replaces `NIOSSL`'s default certificate verification with custom verification logic.
58-
///
59-
/// This is just a `Sendable` version of `NIOSSLCustomVerificationCallbackWithMetadata`.
60-
///
61-
/// ## Usage
62-
///
63-
/// The callback receives:
64-
/// - **certificates**: The certificates presented by the peer. You are responsible for building and validating
65-
/// a chain of trust from these certificates.
66-
/// - **promise**: A promise that must be completed. Call `promise.succeed(...)` with the subset of certificates
67-
/// that formed the validated chain of trust, or `promise.fail()` if verification fails.
68-
///
69-
/// - Warning: This callback completely replaces NIOSSL's certificate verification logic and must be used with
70-
/// caution.
71-
public typealias CustomCertificateVerificationCallback =
72-
@Sendable (
73-
[NIOSSLCertificate],
74-
EventLoopPromise<NIOSSLVerificationResultWithMetadata>
75-
) -> Void
76-
7757
enum Backing {
7858
case plaintext
7959
case tls(
@@ -258,3 +238,61 @@ public struct NIOHTTPServerConfiguration: Sendable {
258238
self.http2 = http2
259239
}
260240
}
241+
242+
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
243+
extension NIOHTTPServerConfiguration.TransportSecurity {
244+
/// Represents the outcome of certificate verification.
245+
///
246+
/// Indicates whether certificate verification succeeded or failed, and provides associated metadata when
247+
/// 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+
260+
/// Metadata resulting from successful certificate verification.
261+
public struct VerificationMetadata: Sendable, Hashable {
262+
/// A container for the validated certificate chain: an array of certificates forming a verified and ordered
263+
/// chain of trust, starting from the peer's leaf certificate to a trusted root certificate.
264+
public var validatedCertificateChain: X509.ValidatedCertificateChain?
265+
266+
/// Creates an instance with the peer's *validated* certificate chain.
267+
///
268+
/// - Parameter validatedCertificateChain: An optional *validated* certificate chain. If provided, it must
269+
/// **only** contain the **validated** chain of trust that was built and verified from the certificates
270+
/// presented by the peer.
271+
public init(_ validatedCertificateChain: X509.ValidatedCertificateChain?) {
272+
self.validatedCertificateChain = validatedCertificateChain
273+
}
274+
}
275+
276+
/// Certificate verification succeeded.
277+
///
278+
/// The associated metadata contains information captured during verification.
279+
case certificateVerified(VerificationMetadata)
280+
281+
/// Certificate verification failed.
282+
case failed(VerificationError)
283+
}
284+
285+
/// A callback for implementing custom certificate verification logic.
286+
///
287+
/// Use this callback to perform custom certificate verification. The callback receives the certificates presented
288+
/// by the peer as `[X509.Certificate]`. Within the callback, you must validate these certificates against your
289+
/// trust roots and derive a validated chain of trust per [RFC 4158](https://datatracker.ietf.org/doc/html/rfc4158).
290+
///
291+
/// Return ``CertificateVerificationResult/certificateVerified(_:)`` if verification succeeds, optionally including
292+
/// the validated certificate chain you derived. Returning the validated certificate chain allows ``NIOHTTPServer``
293+
/// to provide access to it in the request handler through ``NIOHTTPServer/ConnectionContext/peerCertificateChain``,
294+
/// 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
298+
}

Sources/HTTPServer/SocketAddress.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
//
1212
//===----------------------------------------------------------------------===//
1313

14-
import NIOCore
15-
1614
/// Represents an IPv4 address.
1715
public struct IPv4: Hashable, Sendable {
1816
/// The resolved host address.

Tests/HTTPServerTests/NIOHTTPServerTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,9 @@ struct NIOHTTPServerTests {
145145
certificateChain: [serverChain.leaf],
146146
privateKey: serverChain.privateKey,
147147
trustRoots: [clientChain.ca],
148-
customCertificateVerificationCallback: { certificates, promise in
148+
customCertificateVerificationCallback: { certificates in
149149
// Return the peer's certificate chain; this must then be accessible in the request handler
150-
promise.succeed(.certificateVerified(.init(.init(certificates))))
150+
.certificateVerified(.init(.init(uncheckedCertificateChain: certificates)))
151151
}
152152
)
153153
)

0 commit comments

Comments
 (0)