Skip to content

Commit 0ca62dd

Browse files
agnosticdevweissi
andauthored
Rebase: Added API for setting cipher suites in TLSConfiguration (#293)
* Rebase: Added API for setting cipher suites in TLSConfiguration Motivation: Issue-207 for adding an API for setting a cipher suite while also preserving the string based declaration. Additions to TLSConfiguration and SSLContext to allow safe access to ciphers on a context. Modifications: Additions to TLSConfiguration and SSLContext to allow safe access to ciphers on a context. Added test cases in TLSConfigurationTest. Result: Additional API for Issue-207. Simplified and safer access to STACK_OF(SSL_CIPHER) on a context. * Addressed all feedback expect for memberwise init. Motivation: Addressed all feedback from previous feedback except for memberwise init. Modifications: Changes to SSLContext and TLS Configuration. Result: Resolved all feedback except for memberwise init. * Addressed duplicating the memberwise init. Motivation: Addressing feedback for memberwise init. Modifications: Changes to TLSConfiguration init. Result: Reduced code surface. * Added additional cipher suite tests Motivation: Added additional cipher suites tests per feedback. Modifications: Added tests to TLSConfigurationTests. Result: Additional cipher suite tests. Co-authored-by: Johannes Weiss <[email protected]>
1 parent 6363cdf commit 0ca62dd

File tree

4 files changed

+417
-4
lines changed

4 files changed

+417
-4
lines changed

Sources/NIOSSL/SSLContext.swift

+84
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,90 @@ extension NIOSSLContext {
502502
}
503503
}
504504

505+
// For accessing STACK_OF(SSL_CIPHER) from a SSLContext
506+
extension NIOSSLContext {
507+
/// A collection of buffers representing a STACK_OF(SSL_CIPHER)
508+
struct NIOTLSCipherBuffers {
509+
private let basePointer: OpaquePointer
510+
511+
fileprivate init(basePointer: OpaquePointer) {
512+
self.basePointer = basePointer
513+
}
514+
}
515+
516+
/// Invokes a block with a collection of pointers to STACK_OF(SSL_CIPHER).
517+
///
518+
/// The pointers are only guaranteed to be valid for the duration of this call. This method aligns with the RandomAccessCollection protocol
519+
/// to access UInt16 pointers at a specific index. This pointer is used to safely access id values of the cipher to create a new NIOTLSCipher.
520+
fileprivate func withStackOfCipherSuiteBuffers<Result>(_ body: (NIOTLSCipherBuffers?) throws -> Result) rethrows -> Result {
521+
guard let stackPointer = CNIOBoringSSL_SSL_CTX_get_ciphers(self.sslContext) else {
522+
return try body(nil)
523+
}
524+
return try body(NIOTLSCipherBuffers(basePointer: stackPointer))
525+
}
526+
527+
/// Access cipher suites applied to the context
528+
internal var cipherSuites: [NIOTLSCipher] {
529+
return self.withStackOfCipherSuiteBuffers { buffers in
530+
guard let buffers = buffers else {
531+
return []
532+
}
533+
return Array(buffers)
534+
}
535+
}
536+
}
537+
538+
extension NIOSSLContext.NIOTLSCipherBuffers: RandomAccessCollection {
539+
540+
struct Index: Hashable, Comparable, Strideable {
541+
typealias Stride = Int
542+
543+
fileprivate var index: Int
544+
545+
fileprivate init(_ index: Int) {
546+
self.index = index
547+
}
548+
549+
static func < (lhs: Index, rhs: Index) -> Bool {
550+
return lhs.index < rhs.index
551+
}
552+
553+
func advanced(by n: NIOSSLContext.NIOTLSCipherBuffers.Index.Stride) -> NIOSSLContext.NIOTLSCipherBuffers.Index {
554+
var result = self
555+
result.index += n
556+
return result
557+
}
558+
559+
func distance(to other: NIOSSLContext.NIOTLSCipherBuffers.Index) -> NIOSSLContext.NIOTLSCipherBuffers.Index.Stride {
560+
return other.index - self.index
561+
}
562+
}
563+
564+
typealias Element = NIOTLSCipher
565+
566+
var startIndex: Index {
567+
return Index(0)
568+
}
569+
570+
var endIndex: Index {
571+
return Index(self.count)
572+
}
573+
574+
var count: Int {
575+
return CNIOBoringSSL_sk_SSL_CIPHER_num(self.basePointer)
576+
}
577+
578+
subscript(position: Index) -> NIOTLSCipher {
579+
precondition(position < self.endIndex)
580+
precondition(position >= self.startIndex)
581+
guard let ptr = CNIOBoringSSL_sk_SSL_CIPHER_value(self.basePointer, position.index) else {
582+
preconditionFailure("Unable to locate backing pointer.")
583+
}
584+
let cipherID = CNIOBoringSSL_SSL_CIPHER_get_protocol_id(ptr)
585+
return NIOTLSCipher(cipherID)
586+
}
587+
}
588+
505589
extension Optional where Wrapped == String {
506590
internal func withCString<Result>(_ body: (UnsafePointer<CChar>?) throws -> Result) rethrows -> Result {
507591
switch self {

Sources/NIOSSL/TLSConfiguration.swift

+124-2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,43 @@ public enum NIOSSLAdditionalTrustRoots {
8181
case certificates([NIOSSLCertificate])
8282
}
8383

84+
/// Available ciphers to use for TLS instead of a string based representation.
85+
public struct NIOTLSCipher: RawRepresentable, Hashable {
86+
public init(rawValue: UInt16) {
87+
self.rawValue = rawValue
88+
}
89+
90+
public init(_ rawValue: RawValue) {
91+
self.rawValue = rawValue
92+
}
93+
94+
public var rawValue: UInt16
95+
public typealias RawValue = UInt16
96+
97+
public static let TLS_RSA_WITH_AES_128_CBC_SHA = NIOTLSCipher(rawValue: 0x2F)
98+
public static let TLS_RSA_WITH_AES_256_CBC_SHA = NIOTLSCipher(rawValue: 0x35)
99+
public static let TLS_RSA_WITH_AES_128_GCM_SHA256 = NIOTLSCipher(rawValue: 0x9C)
100+
public static let TLS_RSA_WITH_AES_256_GCM_SHA384 = NIOTLSCipher(rawValue: 0x9D)
101+
public static let TLS_AES_128_GCM_SHA256 = NIOTLSCipher(rawValue: 0x1301)
102+
public static let TLS_AES_256_GCM_SHA384 = NIOTLSCipher(rawValue: 0x1302)
103+
public static let TLS_CHACHA20_POLY1305_SHA256 = NIOTLSCipher(rawValue: 0x1303)
104+
public static let TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = NIOTLSCipher(rawValue: 0xC009)
105+
public static let TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = NIOTLSCipher(rawValue: 0xC00A)
106+
public static let TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = NIOTLSCipher(rawValue: 0xC013)
107+
public static let TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = NIOTLSCipher(rawValue: 0xC014)
108+
public static let TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = NIOTLSCipher(rawValue: 0xC02B)
109+
public static let TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = NIOTLSCipher(rawValue: 0xC02C)
110+
public static let TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = NIOTLSCipher(rawValue: 0xC02F)
111+
public static let TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = NIOTLSCipher(rawValue: 0xC030)
112+
public static let TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = NIOTLSCipher(rawValue: 0xCCA8)
113+
public static let TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = NIOTLSCipher(rawValue: 0xCCA9)
114+
115+
var standardName: String {
116+
let boringSSLCipher = CNIOBoringSSL_SSL_get_cipher_by_value(self.rawValue)
117+
return String(cString: CNIOBoringSSL_SSL_CIPHER_standard_name(boringSSLCipher))
118+
}
119+
}
120+
84121
/// Formats NIOSSL supports for serializing keys and certificates.
85122
public enum NIOSSLSerializationFormats {
86123
case pem
@@ -200,7 +237,21 @@ public struct TLSConfiguration {
200237

201238
/// The pre-TLS1.3 cipher suites supported by this handler. This uses the OpenSSL cipher string format.
202239
/// TLS 1.3 cipher suites cannot be configured.
203-
public var cipherSuites: String
240+
public var cipherSuites: String = defaultCipherSuites
241+
242+
/// Public property used to set the internal cipherSuites from NIOTLSCipher.
243+
public var cipherSuiteValues: [NIOTLSCipher] {
244+
get {
245+
guard let sslContext = try? NIOSSLContext(configuration: self) else {
246+
return []
247+
}
248+
return sslContext.cipherSuites
249+
}
250+
set {
251+
let assignedCiphers = newValue.map { $0.standardName }
252+
self.cipherSuites = assignedCiphers.joined(separator: ":")
253+
}
254+
}
204255

205256
/// Allowed algorithms to verify signatures. Passing nil means, that a built-in set of algorithms will be used.
206257
public var verifySignatureAlgorithms : [SignatureAlgorithm]?
@@ -252,7 +303,8 @@ public struct TLSConfiguration {
252303
/// Whether renegotiation is supported.
253304
public var renegotiationSupport: NIORenegotiationSupport
254305

255-
private init(cipherSuites: String,
306+
private init(cipherSuiteValues: [NIOTLSCipher] = [],
307+
cipherSuites: String = defaultCipherSuites,
256308
verifySignatureAlgorithms: [SignatureAlgorithm]?,
257309
signingSignatureAlgorithms: [SignatureAlgorithm]?,
258310
minimumTLSVersion: TLSVersion,
@@ -281,6 +333,42 @@ public struct TLSConfiguration {
281333
self.renegotiationSupport = renegotiationSupport
282334
self.applicationProtocols = applicationProtocols
283335
self.keyLogCallback = keyLogCallback
336+
if !cipherSuiteValues.isEmpty {
337+
self.cipherSuiteValues = cipherSuiteValues
338+
}
339+
}
340+
341+
/// Create a TLS configuration for use with server-side contexts. This allows setting the `NIOTLSCipher` property specifically.
342+
///
343+
/// This provides sensible defaults while requiring that you provide any data that is necessary
344+
/// for server-side function. For client use, try `forClient` instead.
345+
public static func forServer(certificateChain: [NIOSSLCertificateSource],
346+
privateKey: NIOSSLPrivateKeySource,
347+
cipherSuites: [NIOTLSCipher],
348+
verifySignatureAlgorithms: [SignatureAlgorithm]? = nil,
349+
signingSignatureAlgorithms: [SignatureAlgorithm]? = nil,
350+
minimumTLSVersion: TLSVersion = .tlsv1,
351+
maximumTLSVersion: TLSVersion? = nil,
352+
certificateVerification: CertificateVerification = .none,
353+
trustRoots: NIOSSLTrustRoots = .default,
354+
applicationProtocols: [String] = [],
355+
shutdownTimeout: TimeAmount = .seconds(5),
356+
keyLogCallback: NIOSSLKeyLogCallback? = nil,
357+
additionalTrustRoots: [NIOSSLAdditionalTrustRoots] = []) -> TLSConfiguration {
358+
return TLSConfiguration(cipherSuiteValues: cipherSuites,
359+
verifySignatureAlgorithms: verifySignatureAlgorithms,
360+
signingSignatureAlgorithms: signingSignatureAlgorithms,
361+
minimumTLSVersion: minimumTLSVersion,
362+
maximumTLSVersion: maximumTLSVersion,
363+
certificateVerification: certificateVerification,
364+
trustRoots: trustRoots,
365+
certificateChain: certificateChain,
366+
privateKey: privateKey,
367+
applicationProtocols: applicationProtocols,
368+
shutdownTimeout: shutdownTimeout,
369+
keyLogCallback: keyLogCallback,
370+
renegotiationSupport: .none, // Servers never support renegotiation: there's no point.
371+
additionalTrustRoots: additionalTrustRoots)
284372
}
285373

286374
/// Create a TLS configuration for use with server-side contexts.
@@ -377,6 +465,40 @@ public struct TLSConfiguration {
377465
renegotiationSupport: .none, // Servers never support renegotiation: there's no point.
378466
additionalTrustRoots: additionalTrustRoots)
379467
}
468+
469+
/// Creates a TLS configuration for use with client-side contexts. This allows setting the `NIOTLSCipher` property specifically.
470+
///
471+
/// This provides sensible defaults, and can be used without customisation. For server-side
472+
/// contexts, you should use `forServer` instead.
473+
public static func forClient(cipherSuites: [NIOTLSCipher],
474+
verifySignatureAlgorithms: [SignatureAlgorithm]? = nil,
475+
signingSignatureAlgorithms: [SignatureAlgorithm]? = nil,
476+
minimumTLSVersion: TLSVersion = .tlsv1,
477+
maximumTLSVersion: TLSVersion? = nil,
478+
certificateVerification: CertificateVerification = .fullVerification,
479+
trustRoots: NIOSSLTrustRoots = .default,
480+
certificateChain: [NIOSSLCertificateSource] = [],
481+
privateKey: NIOSSLPrivateKeySource? = nil,
482+
applicationProtocols: [String] = [],
483+
shutdownTimeout: TimeAmount = .seconds(5),
484+
keyLogCallback: NIOSSLKeyLogCallback? = nil,
485+
renegotiationSupport: NIORenegotiationSupport = .none,
486+
additionalTrustRoots: [NIOSSLAdditionalTrustRoots] = []) -> TLSConfiguration {
487+
return TLSConfiguration(cipherSuiteValues: cipherSuites,
488+
verifySignatureAlgorithms: verifySignatureAlgorithms,
489+
signingSignatureAlgorithms: signingSignatureAlgorithms,
490+
minimumTLSVersion: minimumTLSVersion,
491+
maximumTLSVersion: maximumTLSVersion,
492+
certificateVerification: certificateVerification,
493+
trustRoots: trustRoots,
494+
certificateChain: certificateChain,
495+
privateKey: privateKey,
496+
applicationProtocols: applicationProtocols,
497+
shutdownTimeout: shutdownTimeout,
498+
keyLogCallback: keyLogCallback,
499+
renegotiationSupport: renegotiationSupport,
500+
additionalTrustRoots: additionalTrustRoots)
501+
}
380502

381503
/// Creates a TLS configuration for use with client-side contexts.
382504
///

Tests/NIOSSLTests/TLSConfigurationTest+XCTest.swift

+12
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ extension TLSConfigurationTest {
4646
("testTheSameHashValue", testTheSameHashValue),
4747
("testDifferentHashValues", testDifferentHashValues),
4848
("testDifferentCallbacksNotEqual", testDifferentCallbacksNotEqual),
49+
("testCompatibleCipherSuite", testCompatibleCipherSuite),
50+
("testNonCompatibleCipherSuite", testNonCompatibleCipherSuite),
51+
("testDefaultWithRSACipherSuite", testDefaultWithRSACipherSuite),
52+
("testDefaultWithECDHERSACipherSuite", testDefaultWithECDHERSACipherSuite),
53+
("testStringBasedCipherSuite", testStringBasedCipherSuite),
54+
("testMultipleCompatibleCipherSuites", testMultipleCompatibleCipherSuites),
55+
("testMultipleCompatibleCipherSuitesWithStringBasedCipher", testMultipleCompatibleCipherSuitesWithStringBasedCipher),
56+
("testMultipleClientCipherSuitesWithDefaultCipher", testMultipleClientCipherSuitesWithDefaultCipher),
57+
("testNonCompatibleClientCiphersWithServerStringBasedCiphers", testNonCompatibleClientCiphersWithServerStringBasedCiphers),
58+
("testSettingCiphersWithCipherSuiteValues", testSettingCiphersWithCipherSuiteValues),
59+
("testSettingCiphersWithCipherSuitesString", testSettingCiphersWithCipherSuitesString),
60+
("testDefaultCipherSuiteValues", testDefaultCipherSuiteValues),
4961
]
5062
}
5163
}

0 commit comments

Comments
 (0)