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
58 changes: 33 additions & 25 deletions Sources/NIOHTTPServer/Configuration/TransportSecurity+NIOSSL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,51 +17,59 @@ import NIOSSL
import X509

@available(anyAppleOS 26.0, *)
extension NIOSSL.TLSConfiguration {
/// Creates a `NIOSSL.TLSConfiguration` from the server's TLS credentials and mTLS trust configuration.
static func makeServerConfiguration(
tlsCredentials: NIOHTTPServerConfiguration.TransportSecurity.TLSCredentials,
mTLSConfiguration: NIOHTTPServerConfiguration.TransportSecurity.MTLSTrustConfiguration?
extension NIOSSLContext {
/// Creates a `NIOSSL.NIOSSLContext` from the server's transport security configuration.
static func makeServerContext(
transportSecurity: NIOHTTPServerConfiguration.TransportSecurity,
alpnIdentifiers: [String]
) throws -> Self {
var config: Self
var configuration: TLSConfiguration

switch tlsCredentials.backing {
case .inMemory(let certificateChain, let privateKey):
config = .makeServerConfiguration(
certificateChain: try certificateChain.map { try NIOSSLCertificateSource($0) },
privateKey: try NIOSSLPrivateKeySource(privateKey)
)
switch transportSecurity.backing {
case .plaintext:
throw NIOHTTPServerConfigurationError.incompatibleTransportSecurity

case .reloading(let certificateReloader):
config = try .makeServerConfiguration(certificateReloader: certificateReloader)
case .tls(let tlsCredentials), .mTLS(let tlsCredentials, _):
switch tlsCredentials.backing {
case .inMemory(let certificateChain, let privateKey):
configuration = .makeServerConfiguration(
certificateChain: try certificateChain.map { try NIOSSLCertificateSource($0) },
privateKey: try NIOSSLPrivateKeySource(privateKey)
)

case .pemFile(let certificateChainPath, let privateKeyPath):
config = try .makeServerConfiguration(
certificateChain: NIOSSLCertificate.fromPEMFile(certificateChainPath).map { .certificate($0) },
privateKey: .privateKey(.init(file: privateKeyPath, format: .pem))
)
case .reloading(let certificateReloader):
configuration = try .makeServerConfiguration(certificateReloader: certificateReloader)

case .pemFile(let certificateChainPath, let privateKeyPath):
configuration = try .makeServerConfiguration(
certificateChain: NIOSSLCertificate.fromPEMFile(certificateChainPath).map { .certificate($0) },
privateKey: .privateKey(.init(file: privateKeyPath, format: .pem))
)
}
}

if let mTLSConfiguration {
if case .mTLS(_, let mTLSConfiguration) = transportSecurity.backing {
switch mTLSConfiguration.backing {
case .systemDefaults:
config.trustRoots = .default
configuration.trustRoots = .default

case .inMemory(let trustRoots):
config.trustRoots = .certificates(try trustRoots.map { try NIOSSLCertificate($0) })
configuration.trustRoots = .certificates(try trustRoots.map { try NIOSSLCertificate($0) })

case .pemFile(let path):
config.trustRoots = .file(path)
configuration.trustRoots = .file(path)

case .customCertificateVerificationCallback:
// There are no trust roots when a custom certificate verification callback is specified: the callback
// itself is responsible for establishing trust.
()
}

config.certificateVerification = .init(mTLSConfiguration.certificateVerification)
configuration.certificateVerification = .init(mTLSConfiguration.certificateVerification)
}

return config
configuration.applicationProtocols = alpnIdentifiers

return try Self(configuration: configuration)
}
}
23 changes: 9 additions & 14 deletions Sources/NIOHTTPServer/NIOHTTPServer+SecureUpgrade.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ extension NIOHTTPServer {
func setupSecureUpgradeServerChannels(
bindTargets: [NIOHTTPServerConfiguration.BindTarget],
supportedHTTPVersions: Set<NIOHTTPServerConfiguration.HTTPVersion>,
tlsConfiguration: TLSConfiguration
sslContext: NIOSSLContext
) async throws -> [NIOAsyncChannel<EventLoopFuture<NegotiatedChannel>, Never>] {
let bootstrap = ServerBootstrap(group: .singletonMultiThreadedEventLoopGroup)
.serverChannelOption(.socketOption(.so_reuseaddr), value: 1)
Expand All @@ -178,7 +178,7 @@ extension NIOHTTPServer {
self.setupSecureUpgradeConnectionChildChannel(
channel: channel,
supportedHTTPVersions: supportedHTTPVersions,
tlsConfiguration: tlsConfiguration
sslContext: sslContext
)
}
serverChannels.append(serverChannel)
Expand Down Expand Up @@ -263,17 +263,12 @@ extension NIOHTTPServer {
func setupSecureUpgradeConnectionChildChannel(
channel: any Channel,
supportedHTTPVersions: Set<NIOHTTPServerConfiguration.HTTPVersion>,
tlsConfiguration: TLSConfiguration
sslContext: NIOSSLContext
) -> EventLoopFuture<EventLoopFuture<NegotiatedChannel>> {
channel.eventLoop.makeCompletedFuture {
var tlsConfiguration = tlsConfiguration
// Set the application protocols to the appropriate value depending upon whether we want to serve HTTP/1.1,
// HTTP/2, or both.
tlsConfiguration.applicationProtocols = supportedHTTPVersions.alpnIdentifiers

try channel.pipeline.syncOperations.addHandler(
self.makeSSLServerHandler(
tlsConfiguration,
sslContext,
self.configuration.transportSecurity.customVerificationCallback
)
)
Expand Down Expand Up @@ -357,12 +352,12 @@ extension NIOHTTPServer {
@available(anyAppleOS 26.0, *)
extension NIOHTTPServer {
func makeSSLServerHandler(
_ tlsConfiguration: TLSConfiguration,
_ sslContext: NIOSSLContext,
_ customVerificationCallback: (@Sendable ([X509.Certificate]) async throws -> CertificateVerificationResult)?
) throws -> NIOSSLServerHandler {
) -> NIOSSLServerHandler {
if let customVerificationCallback {
return try NIOSSLServerHandler(
context: .init(configuration: tlsConfiguration),
return NIOSSLServerHandler(
context: sslContext,
customVerificationCallbackWithMetadata: { certificates, promise in
promise.completeWithTask {
// Convert input [NIOSSLCertificate] to [X509.Certificate]
Expand Down Expand Up @@ -393,7 +388,7 @@ extension NIOHTTPServer {
}
)
} else {
return try NIOSSLServerHandler(context: .init(configuration: tlsConfiguration))
return NIOSSLServerHandler(context: sslContext)
}
}
}
17 changes: 5 additions & 12 deletions Sources/NIOHTTPServer/NIOHTTPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,21 +179,14 @@ public struct NIOHTTPServer: HTTPServer {
return try await self.setupHTTP1_1ServerChannels(bindTargets: self.configuration.bindTargets)
.map { .plaintextHTTP1_1($0) }

case .tls(let credentials):
case .tls, .mTLS:
return try await self.setupSecureUpgradeServerChannels(
bindTargets: self.configuration.bindTargets,
supportedHTTPVersions: self.configuration.supportedHTTPVersions,
tlsConfiguration: try .makeServerConfiguration(tlsCredentials: credentials, mTLSConfiguration: nil)
).map { .secureUpgrade($0) }

case .mTLS(let credentials, let mTLSConfiguration):
return try await self.setupSecureUpgradeServerChannels(
bindTargets: self.configuration.bindTargets,
supportedHTTPVersions: self.configuration.supportedHTTPVersions,
tlsConfiguration: try .makeServerConfiguration(
tlsCredentials: credentials,
mTLSConfiguration: mTLSConfiguration
)
sslContext: .makeServerContext(
transportSecurity: self.configuration.transportSecurity,
alpnIdentifiers: self.configuration.supportedHTTPVersions.alpnIdentifiers
),
).map { .secureUpgrade($0) }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,28 +74,17 @@ struct TestingChannelSecureUpgradeServer {
// Create a connection channel: we will write this to the server channel to simulate an incoming connection.
let serverTestConnectionChannel = try await NIOAsyncTestingChannel.createActiveChannel()

let tlsConfiguration: TLSConfiguration

switch self.server.configuration.transportSecurity.backing {
case .plaintext:
throw NIOHTTPServerConfigurationError.incompatibleTransportSecurity

case .tls(let credentials):
tlsConfiguration = try .makeServerConfiguration(tlsCredentials: credentials, mTLSConfiguration: nil)

case .mTLS(let credentials, let trustConfiguration):
tlsConfiguration = try .makeServerConfiguration(
tlsCredentials: credentials,
mTLSConfiguration: trustConfiguration
)
}
let sslContext = try NIOSSLContext.makeServerContext(
transportSecurity: self.server.configuration.transportSecurity,
alpnIdentifiers: self.server.configuration.supportedHTTPVersions.alpnIdentifiers
)

// Set up the required channel handlers on `serverTestConnectionChannel`
let negotiatedServerConnectionFuture = try await serverTestConnectionChannel.eventLoop.flatSubmit {
self.server.setupSecureUpgradeConnectionChildChannel(
channel: serverTestConnectionChannel,
supportedHTTPVersions: self.server.configuration.supportedHTTPVersions,
tlsConfiguration: tlsConfiguration
sslContext: sslContext
)
}.get()

Expand Down
Loading