Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
34 changes: 18 additions & 16 deletions Sources/ConnectionHandlerExample/ConnectionHandlerExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,24 @@ struct ConnectionHandlerExample {
bindTarget: .hostAndPort(host: "127.0.0.1", port: 12346),
supportedHTTPVersions: [.http1_1, .http2(config: .init())],
transportSecurity: .tls(
credentials: .inMemory(
certificateChain: [
try Certificate(
version: .v3,
serialNumber: .init(bytes: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]),
publicKey: .init(privateKey.publicKey),
notValidBefore: Date.now.addingTimeInterval(-60),
notValidAfter: Date.now.addingTimeInterval(60 * 60),
issuer: DistinguishedName(),
subject: DistinguishedName(),
signatureAlgorithm: .ecdsaWithSHA256,
extensions: .init(),
issuerPrivateKey: Certificate.PrivateKey(privateKey)
)
],
privateKey: Certificate.PrivateKey(privateKey)
credentials: .x509(
.certificates(
chain: [
try Certificate(
version: .v3,
serialNumber: .init(bytes: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]),
publicKey: .init(privateKey.publicKey),
notValidBefore: Date.now.addingTimeInterval(-60),
notValidAfter: Date.now.addingTimeInterval(60 * 60),
issuer: DistinguishedName(),
subject: DistinguishedName(),
signatureAlgorithm: .ecdsaWithSHA256,
extensions: .init(),
issuerPrivateKey: Certificate.PrivateKey(privateKey)
)
],
privateKey: Certificate.PrivateKey(privateKey)
)
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,16 +236,25 @@ extension NIOQUIC.AuthenticationConfiguration {
case .mTLS:
throw NIOHTTPServerConfigurationError.mTLSNotCurrentlySupportedOverHTTP3

case .tls(let credentials):
switch credentials.backing {
case .inMemory, .reloading:
throw NIOHTTPServerConfigurationError.onlyPEMFileCredentialsCurrentlySupportedOverHTTP3

case .pemFile(let certificateChainPath, let privateKeyPath):
self = .x509Certificates(
certificateChainFilePath: certificateChainPath,
privateKeyFilePath: privateKeyPath
)
case .tls(let tlsCredentials):
switch tlsCredentials.backing {
case .x509(let x509Credentials):
switch x509Credentials.backing {
case .serialized(.file(let certificateChain, let privateKey, format: .pem)):
self = .x509Certificates(certificateChainFilePath: certificateChain, privateKeyFilePath: privateKey)

case .certificates, .reloading, .serialized(.file(_, _, .der)), .serialized(.bytes):
throw NIOHTTPServerConfigurationError.onlyPEMFileCredentialsCurrentlySupportedOverHTTP3
}

case .rawPublicKey(let rawPublicKeyCredentials):
switch rawPublicKeyCredentials.backing {
case .file(let publicKey, let privateKey, .der):
self = .rawPublicKeys(publicKeyFilePath: publicKey, privateKeyFilePath: privateKey)

case .file(_, _, .pem):
throw NIOHTTPServerConfigurationError.pemRawPublicKeysNotCurrentlySupported
}
}
}
}
Expand Down Expand Up @@ -291,26 +300,30 @@ extension NIOQUIC.Authenticator {
///
/// - Throws:
/// - ``NIOHTTPServerConfigurationError/incompatibleTransportSecurity`` if `transportSecurity` is `.plaintext`.
/// - ``NIOHTTPServerConfigurationError/inMemoryOrReloadingTLSCredentialsNotSupportedOverHTTP3`` if the X.509
/// credentials are provided as in-memory `X509.Certificate`/`X509.Certificate.PrivateKey` objects or as a
/// `CertificateReloader` instance.
/// - ``NIOHTTPServerConfigurationError/http3RequiresPEMFileCertificates`` if the X.509 credentials are not
/// provided as a PEM-encoded certificate chain and private key on disk.
/// - An underlying error from `Authenticator`'s initializer if the certificate chain or private key cannot be
/// loaded.
convenience init(_ transportSecurity: NIOHTTPServerConfiguration.TransportSecurity) throws {
convenience init?(_ transportSecurity: NIOHTTPServerConfiguration.TransportSecurity) throws {
switch transportSecurity.backing {
case .plaintext:
throw NIOHTTPServerConfigurationError.incompatibleTransportSecurity

case .tls(let tlsCredentials), .mTLS(let tlsCredentials, _):
switch tlsCredentials.backing {
case .reloading:
throw NIOHTTPServerConfigurationError.onlyPEMFileCredentialsCurrentlySupportedOverHTTP3

case .pemFile(let certificateChainPath, let privateKeyPath):
try self.init(certificateFilePath: certificateChainPath, privateKeyFilePath: privateKeyPath)

case .inMemory(let certificateChain, let privateKey):
try self.init(certificates: certificateChain, privateKey: privateKey)
case .rawPublicKey:
// Public/private key paths are read directly from `QUICConfiguration.authenticationConfiguration`, so
// we return `nil` here.
return nil

case .x509(let x509Credentials):
switch x509Credentials.backing {
case .reloading, .serialized(.bytes), .serialized(.file(_, _, .der)), .certificates:
throw NIOHTTPServerConfigurationError.onlyPEMFileCredentialsCurrentlySupportedOverHTTP3

case .serialized(.file(let certificateChain, let privateKey, .pem)):
try self.init(certificateFilePath: certificateChain, privateKeyFilePath: privateKey)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public struct NIOHTTPServerConfiguration: Sendable {
/// The custom mTLS certificate verification callback, if one was configured.
///
/// Returns the callback when the transport security is configured for mTLS with a
/// ``MTLSTrustConfiguration/customCertificateVerificationCallback(_:certificateVerification:)``,
/// ``MTLSTrustConfiguration/TrustSource/customCertificateVerificationCallback(_:)``,
/// or `nil` otherwise.
var customVerificationCallback: (@Sendable ([X509.Certificate]) async throws -> CertificateVerificationResult)?
{
Expand All @@ -109,11 +109,11 @@ public struct NIOHTTPServerConfiguration: Sendable {
return nil

case .mTLS(_, let trustRoots):
switch trustRoots.backing {
switch trustRoots.source.backing {
case .customCertificateVerificationCallback(let callback):
return callback

case .systemDefaults, .inMemory, .pemFile:
case .systemDefaults, .certificates, .serialized:
return nil
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ enum NIOHTTPServerConfigurationError: Error, CustomStringConvertible {
case incompatibleTransportSecurity
case noBindTargetsSpecified
case onlyPEMFileCredentialsCurrentlySupportedOverHTTP3
case rawPublicKeyTLSCredentialsNotCurrentlySupportedOverHTTP1OrHTTP2
case pemRawPublicKeysNotCurrentlySupported
// swift-nio-quic doesn't currently support mTLS. See https://github.com/apple/swift-nio-quic/issues/5.
case mTLSNotCurrentlySupportedOverHTTP3

Expand All @@ -33,7 +35,13 @@ enum NIOHTTPServerConfigurationError: Error, CustomStringConvertible {
"Invalid configuration: at least one bind target must be specified."

case .onlyPEMFileCredentialsCurrentlySupportedOverHTTP3:
"Invalid configuration: only PEM-file X.509 credentials are supported over HTTP/3. In-memory or reloading credential sources are not currently supported."
"Invalid configuration: only PEM-file X.509 credentials are supported over HTTP/3. DER-encoded, in-memory, reloading, and PEM/DER bytes credential sources are not currently supported."

case .rawPublicKeyTLSCredentialsNotCurrentlySupportedOverHTTP1OrHTTP2:
"Invalid configuration: raw public key TLS credentials are not currently supported over HTTP/1.1 or HTTP/2."

case .pemRawPublicKeysNotCurrentlySupported:
"Invalid configuration: PEM-encoded raw public key credentials are not currently supported."

case .mTLSNotCurrentlySupportedOverHTTP3:
"Invalid configuration: mTLS is not currently supported over HTTP/3."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ extension NIOHTTPServerConfiguration.TransportSecurity {
/// - `mode` (string, required): The transport security mode for the server (permitted values: `"plaintext"`,
/// `"tls"`, `"mTLS"`).
/// - `credentialSource` (string, required for `"tls"` and `"mTLS"`): How TLS credentials are provided (permitted
/// values: `"inline"`, `"file"`).
/// values: `"inline"`, `"file"`, `"rawPublicKey"`).
///
/// ### Configuration keys for `credentialSource: "inline"`:
/// - `certificateChainPEMString` (string, required): PEM-formatted certificate chain content.
Expand All @@ -199,7 +199,11 @@ extension NIOHTTPServerConfiguration.TransportSecurity {
/// - `refreshInterval` (int, optional): The interval (in seconds) at which the certificate chain and private key
/// will be reloaded. If omitted, credentials are loaded from the file only once at startup.
///
/// ### Configuration keys for `mode: "mTLS"`:
/// ### Configuration keys for `credentialSource: "rawPublicKey"` (only supported over HTTP/3):
/// - `publicKeyDERPath` (string, required): Path to the DER-encoded public key file.
/// - `privateKeyDERPath` (string, required): Path to the DER-encoded private key file.
///
/// ### Configuration keys for `mode: "mTLS"` (not supported over HTTP/3):
/// - `trustRootsSource` (string, required): How trust roots are provided (permitted values: `"inline"`, `"file"`,
/// `"systemDefaults"`, `"customCertificateVerificationCallback"`).
/// - `trustRootsPEMString` (string, required for `trustRootsSource: "inline"`): The root certificates as a
Expand Down Expand Up @@ -257,6 +261,8 @@ extension NIOHTTPServerConfiguration.TransportSecurity.TLSCredentials {
/// - When `credentialSource` is `"inline"`, the certificate chain and private key are read as PEM strings.
/// - When `credentialSource` is `"file"`, the certificate chain and private key are loaded from disk, and
/// optionally reloaded at a configured interval.
/// - When `credentialSource` is `"rawPublicKey"` (only supported over HTTP/3), DER-encoded public and private key
/// file paths are read.
fileprivate init(config: ConfigSnapshotReader) throws {
let credentialSource = try config.requiredString(
forKey: "credentialSource",
Expand All @@ -268,10 +274,12 @@ extension NIOHTTPServerConfiguration.TransportSecurity.TLSCredentials {
let certificateChainPEMString = try config.requiredString(forKey: "certificateChainPEMString")
let privateKeyPEMString = try config.requiredString(forKey: "privateKeyPEMString", isSecret: true)

self = .inMemory(
certificateChain: try PEMDocument.parseMultiple(pemString: certificateChainPEMString)
.map { try Certificate(pemEncoded: $0.pemString) },
privateKey: try .init(pemEncoded: privateKeyPEMString)
self = .x509(
.certificates(
chain: try PEMDocument.parseMultiple(pemString: certificateChainPEMString)
.map { try Certificate(pemEncoded: $0.pemString) },
privateKey: try .init(pemEncoded: privateKeyPEMString)
)
)

case .file:
Expand All @@ -280,19 +288,28 @@ extension NIOHTTPServerConfiguration.TransportSecurity.TLSCredentials {
let refreshInterval = config.int(forKey: "refreshInterval")

if let refreshInterval {
self = .reloading(
certificateReloader: TimedCertificateReloader(
refreshInterval: .seconds(refreshInterval),
certificateSource: .init(location: .file(path: certificateChainPEMPath), format: .pem),
privateKeySource: .init(location: .file(path: privateKeyPEMPath), format: .pem)
self = .x509(
.reloading(
TimedCertificateReloader(
refreshInterval: .seconds(refreshInterval),
certificateSource: .init(location: .file(path: certificateChainPEMPath), format: .pem),
privateKeySource: .init(location: .file(path: privateKeyPEMPath), format: .pem)
)
)
)
} else {
self = .pemFile(
certificateChainPath: certificateChainPEMPath,
privateKeyPath: privateKeyPEMPath
)
self = .x509(.pemFile(certificateChainPath: certificateChainPEMPath, privateKeyPath: privateKeyPEMPath))
}

#if HTTP3
case .rawPublicKey:
self = .rawPublicKey(
.derFile(
publicKeyPath: try config.requiredString(forKey: "publicKeyDERPath"),
privateKeyPath: try config.requiredString(forKey: "privateKeyDERPath")
)
)
#endif
}
}
}
Expand Down Expand Up @@ -338,21 +355,23 @@ extension NIOHTTPServerConfiguration.TransportSecurity.MTLSTrustConfiguration {
switch trustRootsSource {
case .inline:
let trustRootsPEMString = try config.requiredString(forKey: "trustRootsPEMString")
self = .inMemory(
trustRoots: try PEMDocument.parseMultiple(pemString: trustRootsPEMString)
.map { try Certificate(pemEncoded: $0.pemString) },
self.init(
.certificates(
trustRoots: try PEMDocument.parseMultiple(pemString: trustRootsPEMString)
.map { try Certificate(pemEncoded: $0.pemString) }
),
certificateVerification: .init(certificateVerificationMode)
)

case .file:
let trustRootsPEMPath = try config.requiredString(forKey: "trustRootsPEMPath")
self = .pemFile(
path: trustRootsPEMPath,
self.init(
.pemFile(trustRootsPath: trustRootsPEMPath),
certificateVerification: .init(certificateVerificationMode)
)

case .systemDefaults:
self = .systemDefaults(certificateVerification: .init(certificateVerificationMode))
self.init(.systemDefaults, certificateVerification: .init(certificateVerificationMode))

case .customCertificateVerificationCallback:
guard let customCertificateVerificationCallback else {
Expand All @@ -361,8 +380,8 @@ extension NIOHTTPServerConfiguration.TransportSecurity.MTLSTrustConfiguration {
throw NIOHTTPServerSwiftConfigurationError.trustRootsSourceAndVerificationCallbackMismatch
}

self = .customCertificateVerificationCallback(
customCertificateVerificationCallback,
self.init(
.customCertificateVerificationCallback(customCertificateVerificationCallback),
certificateVerification: .init(certificateVerificationMode)
)
}
Expand Down Expand Up @@ -418,6 +437,9 @@ extension NIOHTTPServerConfiguration.TransportSecurity {
fileprivate enum CredentialSource: String {
case inline
case file
#if HTTP3
case rawPublicKey
#endif
}
}

Expand Down
Loading
Loading