diff --git a/Package.swift b/Package.swift index 43637ba7..dc3b90f4 100644 --- a/Package.swift +++ b/Package.swift @@ -300,7 +300,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-atomics.git", from: "1.2.0"), .package(url: "https://github.com/apple/swift-algorithms.git", from: "1.2.0"), .package(url: "https://github.com/apple/swift-certificates.git", from: "1.10.0"), - .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.29.3"), + .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.34.0"), .package(url: "https://github.com/apple/swift-asn1.git", from: "1.3.1"), .package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.8.0"), .package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.0.0"), diff --git a/Sources/NIOCertificateReloading/CertificateReloader.swift b/Sources/NIOCertificateReloading/CertificateReloader.swift index 0cd61eab..a7879000 100644 --- a/Sources/NIOCertificateReloading/CertificateReloader.swift +++ b/Sources/NIOCertificateReloading/CertificateReloader.swift @@ -89,6 +89,43 @@ extension TLSConfiguration { return configuration } + /// Create a ``NIOSSL/TLSConfiguration`` for use with server-side contexts that expect to validate a client, with + /// certificate reloading enabled. For servers that don't need mTLS, try ``TLSConfiguration/makeServerConfiguration(certificateReloader:)``. + /// This configuration is very similar to ``TLSConfiguration/makeServerConfiguration(certificateReloader:)`` but + /// adds a `trustRoots` requirement. These roots will be used to validate the certificate presented by the peer. It + /// also sets the `certificateVerification` field to `noHostnameVerification`, which enables verification but + /// disables any hostname checking, which cannot succeed in a server context. + /// + /// - Parameters: + /// - certificateReloader: A ``CertificateReloader`` to watch for certificate and key pair updates. + /// - trustRoots: The roots used to validate the client certificate. + /// - Returns: A ``NIOSSL/TLSConfiguration`` for use with server-side contexts, that reloads the certificate and key + /// used in its SSL handshake. + /// - Throws: This method will throw if an override isn't present. This may happen if a certificate or private key + /// could not be loaded from the given paths. + public static func makeServerConfigurationWithMTLS( + certificateReloader: some CertificateReloader, + trustRoots: NIOSSLTrustRoots + ) throws -> Self { + let override = certificateReloader.sslContextConfigurationOverride + + guard let certificateChain = override.certificateChain else { + throw CertificateReloaderError.missingCertificateChain + } + + guard let privateKey = override.privateKey else { + throw CertificateReloaderError.missingPrivateKey + } + + var configuration = Self.makeServerConfigurationWithMTLS( + certificateChain: certificateChain, + privateKey: privateKey, + trustRoots: trustRoots + ) + configuration.setCertificateReloader(certificateReloader) + return configuration + } + /// Create a ``NIOSSL/TLSConfiguration`` for use with client-side contexts, with certificate reloading enabled. /// - Parameter certificateReloader: A ``CertificateReloader`` to watch for certificate and key pair updates. /// - Returns: A ``NIOSSL/TLSConfiguration`` for use with client-side contexts, that reloads the certificate and key diff --git a/Tests/NIOCertificateReloadingTests/TimedCertificateReloaderTests.swift b/Tests/NIOCertificateReloadingTests/TimedCertificateReloaderTests.swift index 110f4c14..7271c04a 100644 --- a/Tests/NIOCertificateReloadingTests/TimedCertificateReloaderTests.swift +++ b/Tests/NIOCertificateReloadingTests/TimedCertificateReloaderTests.swift @@ -730,6 +730,56 @@ final class TimedCertificateReloaderTests: XCTestCase { XCTAssertThrowsError(try TimedCertificateReloader.makeReloaderValidatingSources(configuration: config)) } + /// This tests that `makeServerConfigurationWithMTLS(certificateReloader:trustRoots:)` correctly extracts the + /// certificate chain and private key from `certificateReloader` and sets those in the returned `TLSConfiguration` + /// (along with `trustRoots` and setting `.certificateVerification` to `.noHostnameVerification`) + func testCreateServerConfigWithMTLS() async throws { + let certificateReloader = try TimedCertificateReloader.makeReloaderValidatingSources( + refreshInterval: .seconds(10), + certificateSource: .init( + location: .memory(provider: { + .init( + try Self.sampleCertChain.map { try $0.serializeAsPEM().pemString }.joined(separator: "\n").utf8 + ) + }), + format: .pem + ), + privateKeySource: .init( + location: .memory(provider: { .init(Self.samplePrivateKey1.derRepresentation) }), + format: .der + ) + ) + + let trustRoots = NIOSSLTrustRoots.certificates( + try Self.sampleCertChain.map { + try NIOSSLCertificate(bytes: .init($0.serializeAsPEM().pemString.utf8), format: .pem) + } + ) + + let tlsConfiguration = try TLSConfiguration.makeServerConfigurationWithMTLS( + certificateReloader: certificateReloader, + trustRoots: trustRoots + ) + + // Check whether the configuration is set up with the same certificate chain, private key, and trust roots + // that were used to initialize the reloader + XCTAssertEqual( + tlsConfiguration.certificateChain, + try Self.sampleCertChain.map { + .certificate(try NIOSSLCertificate(bytes: $0.serializeAsPEM().derBytes, format: .der)) + } + ) + XCTAssertEqual( + tlsConfiguration.privateKey, + NIOSSLPrivateKeySource.privateKey( + try NIOSSLPrivateKey(bytes: .init(Self.samplePrivateKey1.derRepresentation), format: .der) + ) + ) + XCTAssertEqual(tlsConfiguration.trustRoots, trustRoots) + + XCTAssertEqual(tlsConfiguration.certificateVerification, .noHostnameVerification) + } + static let startDate = Date() static let samplePrivateKey1 = P384.Signing.PrivateKey() static let samplePrivateKey2 = P384.Signing.PrivateKey()