diff --git a/Sources/Hummingbird/Server/RequestID.swift b/Sources/Hummingbird/Server/RequestID.swift index f5cddc5ea..9c17bd183 100644 --- a/Sources/Hummingbird/Server/RequestID.swift +++ b/Sources/Hummingbird/Server/RequestID.swift @@ -15,7 +15,7 @@ import Atomics /// Generate Unique ID for each request -public struct RequestID: CustomStringConvertible { +public struct RequestID: CustomStringConvertible, Sendable { let low: UInt64 public init() { diff --git a/Sources/HummingbirdCore/Server/TSTLSOptions.swift b/Sources/HummingbirdCore/Server/TSTLSOptions.swift index a6a25428f..ff72dd617 100644 --- a/Sources/HummingbirdCore/Server/TSTLSOptions.swift +++ b/Sources/HummingbirdCore/Server/TSTLSOptions.swift @@ -15,12 +15,15 @@ #if canImport(Network) import Foundation import Network +import Security /// Wrapper for NIO transport services TLS options public struct TSTLSOptions: Sendable { - struct Error: Swift.Error, Equatable { + public struct Error: Swift.Error, Equatable { enum _Internal: Equatable { case invalidFormat + case interactionNotAllowed + case verificationFailed } private let value: _Internal @@ -28,8 +31,12 @@ public struct TSTLSOptions: Sendable { self.value = value } - // static invalid conversion - static var invalidFormat: Self { .init(.invalidFormat) } + // invalid format + public static var invalidFormat: Self { .init(.invalidFormat) } + // unable to import p12 as no interaction is allowed + public static var interactionNotAllowed: Self { .init(.interactionNotAllowed) } + // MAC verification failed during PKCS12 import (wrong password?) + public static var verificationFailed: Self { .init(.verificationFailed) } } public struct Identity { @@ -40,16 +47,25 @@ public struct TSTLSOptions: Sendable { } public static func p12(filename: String, password: String) throws -> Self { - guard let secIdentity = Self.loadP12(filename: filename, password: password) else { throw Error.invalidFormat } + guard let secIdentity = try Self.loadP12(filename: filename, password: password) else { throw Error.invalidFormat } return .init(secIdentity: secIdentity) } - private static func loadP12(filename: String, password: String) -> SecIdentity? { - guard let data = try? Data(contentsOf: URL(fileURLWithPath: filename)) else { return nil } + private static func loadP12(filename: String, password: String) throws -> SecIdentity? { + let data = try Data(contentsOf: URL(fileURLWithPath: filename)) let options: [String: String] = [kSecImportExportPassphrase as String: password] var rawItems: CFArray? let result = SecPKCS12Import(data as CFData, options as CFDictionary, &rawItems) - guard result == errSecSuccess else { return nil } + switch result { + case errSecSuccess: + break + case errSecInteractionNotAllowed: + throw Error.interactionNotAllowed + case errSecPkcs12VerifyFailure: + throw Error.verificationFailed + default: + throw Error.invalidFormat + } let items = rawItems! as! [[String: Any]] let firstItem = items[0] return firstItem[kSecImportItemIdentity as String] as! SecIdentity? diff --git a/Tests/HummingbirdCoreTests/TSTests.swift b/Tests/HummingbirdCoreTests/TSTests.swift index a79c8c278..5f2ace3e8 100644 --- a/Tests/HummingbirdCoreTests/TSTests.swift +++ b/Tests/HummingbirdCoreTests/TSTests.swift @@ -48,9 +48,13 @@ class TransportServicesTests: XCTestCase { func testTLS() async throws { let eventLoopGroup = NIOTSEventLoopGroup() let p12Path = Bundle.module.path(forResource: "server", ofType: "p12")! - let tlsOptions = try XCTUnwrap(TSTLSOptions.options( - serverIdentity: .p12(filename: p12Path, password: "HBTests") - )) + let tlsOptions: TSTLSOptions + do { + let identity = try TSTLSOptions.Identity.p12(filename: p12Path, password: "HBTests") + tlsOptions = try XCTUnwrap(TSTLSOptions.options(serverIdentity: identity)) + } catch let error as TSTLSOptions.Error where error == .interactionNotAllowed { + throw XCTSkip("Unable to import PKCS12 bundle: no interaction allowed") + } try await testServer( responder: helloResponder, configuration: .init(address: .hostname(port: 0), serverName: testServerName, tlsOptions: tlsOptions),