Skip to content

Commit

Permalink
Add option to specify signining and verification algorithms (apple#232)
Browse files Browse the repository at this point in the history
Motivation:

BoringSSLs built-in list of valid signing and verification algortihms is not
always the one you actually would like to use. For example adding or removing
algorithms from this list might increase compatibility as well as security

Modifications:

Add an option to set CNIOBoringSSL_SSL_CTX_set_verify_algorithm_prefs
and CNIOBoringSSL_SSL_CTX_set_signing_algorithm_prefs

Result:

The user can set signing and verification algortihms at their will
  • Loading branch information
fourplusone authored Jul 9, 2020
1 parent d381bc5 commit b94f2b6
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 1 deletion.
28 changes: 27 additions & 1 deletion Sources/NIOSSL/SSLContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,33 @@ public final class NIOSSLContext {
try NIOSSLContext.configureCertificateValidation(context: context,
verification: configuration.certificateVerification,
trustRoots: configuration.trustRoots)


// Configure verification algorithms
if let verifySignatureAlgorithms = configuration.verifySignatureAlgorithms {
returnCode = verifySignatureAlgorithms
.map { $0.rawValue }
.withUnsafeBufferPointer { algo in
CNIOBoringSSL_SSL_CTX_set_verify_algorithm_prefs(context, algo.baseAddress, algo.count)
}
if returnCode != 1 {
let errorStack = BoringSSLError.buildErrorStack()
throw BoringSSLError.unknownError(errorStack)
}
}

// Configure signing algorithms
if let signingSignatureAlgorithms = configuration.signingSignatureAlgorithms {
returnCode = signingSignatureAlgorithms
.map { $0.rawValue }
.withUnsafeBufferPointer { algo in
CNIOBoringSSL_SSL_CTX_set_signing_algorithm_prefs(context, algo.baseAddress, algo.count)
}
if returnCode != 1 {
let errorStack = BoringSSLError.buildErrorStack()
throw BoringSSLError.unknownError(errorStack)
}
}

// If we were given a certificate chain to use, load it and its associated private key. Before
// we do, set up a passphrase callback if we need to.
if let callbackManager = callbackManager {
Expand Down
103 changes: 103 additions & 0 deletions Sources/NIOSSL/TLSConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,31 @@ public enum NIORenegotiationSupport {
case always
}

/// Signature algorithms. The values are defined as in TLS 1.3
public struct SignatureAlgorithm : RawRepresentable, Hashable {

public typealias RawValue = UInt16
public var rawValue: UInt16

public init(rawValue: UInt16) {
self.rawValue = rawValue
}

public static let rsaPkcs1Sha1 = SignatureAlgorithm(rawValue: 0x0201)
public static let rsaPkcs1Sha256 = SignatureAlgorithm(rawValue: 0x0401)
public static let rsaPkcs1Sha384 = SignatureAlgorithm(rawValue: 0x0501)
public static let rsaPkcs1Sha512 = SignatureAlgorithm(rawValue: 0x0601)
public static let ecdsaSha1 = SignatureAlgorithm(rawValue: 0x0203)
public static let ecdsaSecp256R1Sha256 = SignatureAlgorithm(rawValue: 0x0403)
public static let ecdsaSecp384R1Sha384 = SignatureAlgorithm(rawValue: 0x0503)
public static let ecdsaSecp521R1Sha512 = SignatureAlgorithm(rawValue: 0x0603)
public static let rsaPssRsaeSha256 = SignatureAlgorithm(rawValue: 0x0804)
public static let rsaPssRsaeSha384 = SignatureAlgorithm(rawValue: 0x0805)
public static let rsaPssRsaeSha512 = SignatureAlgorithm(rawValue: 0x0806)
public static let ed25519 = SignatureAlgorithm(rawValue: 0x0807)
}


/// A secure default configuration of cipher suites for TLS 1.2 and earlier.
///
/// The goal of this cipher suite string is:
Expand Down Expand Up @@ -143,6 +168,12 @@ public struct TLSConfiguration {
/// TLS 1.3 cipher suites cannot be configured.
public var cipherSuites: String

/// Allowed algorithms to verify signatures. Passing nil means, that a built-in set of algorithms will be used.
public var verifySignatureAlgorithms : [SignatureAlgorithm]?

/// Allowed algorithms to sign signatures. Passing nil means, that a built-in set of algorithms will be used.
public var signingSignatureAlgorithms : [SignatureAlgorithm]?

/// Whether to verify remote certificates.
public var certificateVerification: CertificateVerification

Expand Down Expand Up @@ -182,6 +213,8 @@ public struct TLSConfiguration {
public var renegotiationSupport: NIORenegotiationSupport

private init(cipherSuites: String,
verifySignatureAlgorithms: [SignatureAlgorithm]?,
signingSignatureAlgorithms: [SignatureAlgorithm]?,
minimumTLSVersion: TLSVersion,
maximumTLSVersion: TLSVersion?,
certificateVerification: CertificateVerification,
Expand All @@ -193,6 +226,8 @@ public struct TLSConfiguration {
keyLogCallback: NIOSSLKeyLogCallback?,
renegotiationSupport: NIORenegotiationSupport) {
self.cipherSuites = cipherSuites
self.verifySignatureAlgorithms = verifySignatureAlgorithms
self.signingSignatureAlgorithms = signingSignatureAlgorithms
self.minimumTLSVersion = minimumTLSVersion
self.maximumTLSVersion = maximumTLSVersion
self.certificateVerification = certificateVerification
Expand Down Expand Up @@ -221,6 +256,8 @@ public struct TLSConfiguration {
shutdownTimeout: TimeAmount = .seconds(5),
keyLogCallback: NIOSSLKeyLogCallback? = nil) -> TLSConfiguration {
return TLSConfiguration(cipherSuites: cipherSuites,
verifySignatureAlgorithms: nil,
signingSignatureAlgorithms: nil,
minimumTLSVersion: minimumTLSVersion,
maximumTLSVersion: maximumTLSVersion,
certificateVerification: certificateVerification,
Expand All @@ -233,6 +270,36 @@ public struct TLSConfiguration {
renegotiationSupport: .none) // Servers never support renegotiation: there's no point.
}

/// Create a TLS configuration for use with server-side contexts.
///
/// This provides sensible defaults while requiring that you provide any data that is necessary
/// for server-side function. For client use, try `forClient` instead.
public static func forServer(certificateChain: [NIOSSLCertificateSource],
privateKey: NIOSSLPrivateKeySource,
cipherSuites: String = defaultCipherSuites,
verifySignatureAlgorithms: [SignatureAlgorithm]? = nil,
signingSignatureAlgorithms: [SignatureAlgorithm]? = nil,
minimumTLSVersion: TLSVersion = .tlsv1,
maximumTLSVersion: TLSVersion? = nil,
certificateVerification: CertificateVerification = .none,
trustRoots: NIOSSLTrustRoots = .default,
applicationProtocols: [String] = [],
shutdownTimeout: TimeAmount = .seconds(5),
keyLogCallback: NIOSSLKeyLogCallback? = nil) -> TLSConfiguration {
return TLSConfiguration(cipherSuites: cipherSuites,
verifySignatureAlgorithms: verifySignatureAlgorithms,
signingSignatureAlgorithms: signingSignatureAlgorithms,
minimumTLSVersion: minimumTLSVersion,
maximumTLSVersion: maximumTLSVersion,
certificateVerification: certificateVerification,
trustRoots: trustRoots,
certificateChain: certificateChain,
privateKey: privateKey,
applicationProtocols: applicationProtocols,
shutdownTimeout: shutdownTimeout,
keyLogCallback: keyLogCallback,
renegotiationSupport: .none) // Servers never support renegotiation: there's no point.
}

/// Creates a TLS configuration for use with client-side contexts.
///
Expand All @@ -249,6 +316,8 @@ public struct TLSConfiguration {
shutdownTimeout: TimeAmount = .seconds(5),
keyLogCallback: NIOSSLKeyLogCallback? = nil) -> TLSConfiguration {
return TLSConfiguration(cipherSuites: cipherSuites,
verifySignatureAlgorithms: nil,
signingSignatureAlgorithms: nil,
minimumTLSVersion: minimumTLSVersion,
maximumTLSVersion: maximumTLSVersion,
certificateVerification: certificateVerification,
Expand Down Expand Up @@ -278,6 +347,40 @@ public struct TLSConfiguration {
keyLogCallback: NIOSSLKeyLogCallback? = nil,
renegotiationSupport: NIORenegotiationSupport) -> TLSConfiguration {
return TLSConfiguration(cipherSuites: cipherSuites,
verifySignatureAlgorithms: nil,
signingSignatureAlgorithms: nil,
minimumTLSVersion: minimumTLSVersion,
maximumTLSVersion: maximumTLSVersion,
certificateVerification: certificateVerification,
trustRoots: trustRoots,
certificateChain: certificateChain,
privateKey: privateKey,
applicationProtocols: applicationProtocols,
shutdownTimeout: shutdownTimeout,
keyLogCallback: keyLogCallback,
renegotiationSupport: renegotiationSupport)
}

/// Creates a TLS configuration for use with client-side contexts.
///
/// This provides sensible defaults, and can be used without customisation. For server-side
/// contexts, you should use `forServer` instead.
public static func forClient(cipherSuites: String = defaultCipherSuites,
verifySignatureAlgorithms: [SignatureAlgorithm]? = nil,
signingSignatureAlgorithms: [SignatureAlgorithm]? = nil,
minimumTLSVersion: TLSVersion = .tlsv1,
maximumTLSVersion: TLSVersion? = nil,
certificateVerification: CertificateVerification = .fullVerification,
trustRoots: NIOSSLTrustRoots = .default,
certificateChain: [NIOSSLCertificateSource] = [],
privateKey: NIOSSLPrivateKeySource? = nil,
applicationProtocols: [String] = [],
shutdownTimeout: TimeAmount = .seconds(5),
keyLogCallback: NIOSSLKeyLogCallback? = nil,
renegotiationSupport: NIORenegotiationSupport) -> TLSConfiguration {
return TLSConfiguration(cipherSuites: cipherSuites,
verifySignatureAlgorithms: verifySignatureAlgorithms,
signingSignatureAlgorithms: signingSignatureAlgorithms,
minimumTLSVersion: minimumTLSVersion,
maximumTLSVersion: maximumTLSVersion,
certificateVerification: certificateVerification,
Expand Down
3 changes: 3 additions & 0 deletions Tests/NIOSSLTests/TLSConfigurationTest+XCTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ extension TLSConfigurationTest {
("testMutualValidation", testMutualValidation),
("testMutualValidationRequiresClientCertificatePreTLS13", testMutualValidationRequiresClientCertificatePreTLS13),
("testMutualValidationRequiresClientCertificatePostTLS13", testMutualValidationRequiresClientCertificatePostTLS13),
("testIncompatibleSignatures", testIncompatibleSignatures),
("testCompatibleSignatures", testCompatibleSignatures),
("testMatchingCompatibleSignatures", testMatchingCompatibleSignatures),
("testNonexistentFileObject", testNonexistentFileObject),
("testComputedApplicationProtocols", testComputedApplicationProtocols),
]
Expand Down
86 changes: 86 additions & 0 deletions Tests/NIOSSLTests/TLSConfigurationTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,35 @@ class TLSConfigurationTest: XCTestCase {
}
try clientChannel.closeFuture.wait()
}

func assertHandshakeSucceeded(withClientConfig clientConfig: TLSConfiguration,
andServerConfig serverConfig: TLSConfiguration,
file: StaticString = #file,
line: UInt = #line) throws {
let clientContext = try assertNoThrowWithValue(NIOSSLContext(configuration: clientConfig))
let serverContext = try assertNoThrowWithValue(NIOSSLContext(configuration: serverConfig))

let serverChannel = EmbeddedChannel()
let clientChannel = EmbeddedChannel()

defer {
// We expect the server case to throw
_ = try? serverChannel.finish()
_ = try? clientChannel.finish()
}

XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: serverContext)).wait(), file: (file), line: line)
XCTAssertNoThrow(try clientChannel.pipeline.addHandler(NIOSSLClientHandler(context: clientContext, serverHostname: nil)).wait(), file: (file), line: line)
let handshakeHandler = HandshakeCompletedHandler()
XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait(), file: (file), line: line)

// Connect. This should lead to a completed handshake.
XCTAssertNoThrow(try connectInMemory(client: clientChannel, server: serverChannel), file: (file), line: line)
XCTAssertTrue(handshakeHandler.handshakeSucceeded, file: (file), line: line)

_ = serverChannel.close()
try interactInMemory(clientChannel: clientChannel, serverChannel: serverChannel)
}

func testNonOverlappingTLSVersions() throws {
var clientConfig = TLSConfiguration.clientDefault
Expand Down Expand Up @@ -298,6 +327,63 @@ class TLSConfigurationTest: XCTestCase {

try assertPostHandshakeError(withClientConfig: clientConfig, andServerConfig: serverConfig, errorTextContainsAnyOf: ["CERTIFICATE_REQUIRED"])
}

func testIncompatibleSignatures() throws {
let clientConfig = TLSConfiguration.forClient(
verifySignatureAlgorithms: [.ecdsaSecp384R1Sha384],
minimumTLSVersion: .tlsv13,
certificateVerification:.noHostnameVerification,
trustRoots: .certificates([TLSConfigurationTest.cert1]),
renegotiationSupport: .none
)

let serverConfig = TLSConfiguration.forServer(
certificateChain: [.certificate(TLSConfigurationTest.cert1)],
privateKey: .privateKey(TLSConfigurationTest.key1),
signingSignatureAlgorithms: [.rsaPssRsaeSha256],
minimumTLSVersion: .tlsv13,
certificateVerification: .none)

try assertHandshakeError(withClientConfig: clientConfig, andServerConfig: serverConfig, errorTextContains: "ALERT_HANDSHAKE_FAILURE")
}

func testCompatibleSignatures() throws {

let clientConfig = TLSConfiguration.forClient(
minimumTLSVersion: .tlsv13,
certificateVerification:.noHostnameVerification,
trustRoots: .certificates([TLSConfigurationTest.cert1])
)

let serverConfig = TLSConfiguration.forServer(
certificateChain: [.certificate(TLSConfigurationTest.cert1)],
privateKey: .privateKey(TLSConfigurationTest.key1),
signingSignatureAlgorithms: [.rsaPssRsaeSha256],
minimumTLSVersion: .tlsv13,
certificateVerification: .none)

try assertHandshakeSucceeded(withClientConfig: clientConfig, andServerConfig: serverConfig)
}

func testMatchingCompatibleSignatures() throws {

let clientConfig = TLSConfiguration.forClient(
verifySignatureAlgorithms: [.rsaPssRsaeSha256],
minimumTLSVersion: .tlsv13,
certificateVerification:.noHostnameVerification,
trustRoots: .certificates([TLSConfigurationTest.cert1]),
renegotiationSupport: .none
)

let serverConfig = TLSConfiguration.forServer(
certificateChain: [.certificate(TLSConfigurationTest.cert1)],
privateKey: .privateKey(TLSConfigurationTest.key1),
signingSignatureAlgorithms: [.rsaPssRsaeSha256],
minimumTLSVersion: .tlsv13,
certificateVerification: .none)

try assertHandshakeSucceeded(withClientConfig: clientConfig, andServerConfig: serverConfig)
}

func testNonexistentFileObject() throws {
let clientConfig = TLSConfiguration.forClient(trustRoots: .file("/thispathbetternotexist/bogus.foo"))
Expand Down

0 comments on commit b94f2b6

Please sign in to comment.