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
61 changes: 58 additions & 3 deletions Sources/NIOSSL/TLSConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,20 @@ public enum TLSVersion {
}

/// Places NIOSSL can obtain certificates from.
public enum NIOSSLCertificateSource {
public enum NIOSSLCertificateSource: Hashable {
@available(*, deprecated, message: "Use 'NIOSSLCertificate.fromPEMFile(_:)' to load the certificate(s) and use the '.certificate(NIOSSLCertificate)' case to provide them as a source")
case file(String)
case certificate(NIOSSLCertificate)
}

/// Places NIOSSL can obtain private keys from.
public enum NIOSSLPrivateKeySource {
public enum NIOSSLPrivateKeySource: Hashable {
case file(String)
case privateKey(NIOSSLPrivateKey)
}

/// Places NIOSSL can obtain a trust store from.
public enum NIOSSLTrustRoots {
public enum NIOSSLTrustRoots: Hashable {
/// Path to either a file of CA certificates in PEM format, or a directory containing CA certificates in PEM format.
///
/// If a path to a file is provided, the file can contain several CA certificates identified by
Expand Down Expand Up @@ -507,3 +507,58 @@ public struct TLSConfiguration {
additionalTrustRoots: additionalTrustRoots)
}
}

// MARK: BestEffortHashable
extension TLSConfiguration {
/// Returns a best effort result of whether two `TLSConfiguration` objects are equal.
///
/// The "best effort" stems from the fact that we are checking the pointer to the `keyLogCallback` closure.
///
/// - warning: You should probably not use this function. This function can return false-negatives, but not false-positives.
public func bestEffortEquals(_ comparing: TLSConfiguration) -> Bool {
let isKeyLoggerCallbacksEqual = withUnsafeBytes(of: self.keyLogCallback) { callbackPointer1 in
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given SR-14520 we should probably make this

let isBitEqual = withUnsafeBytes(of: self) { selfBits in
    withUnsafeBytes(of: comparing) { otherBits in
        return selfBits.elementsEqual(otherBits)
    }
}

with a comment liking the bug.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Lukasa wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think we should stick with the closure-based version, but yes agree we should link the bug.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Lukasa the problem is that if we do hit the bug, then they will never be equal. The allocations are for reabstraction thunks which aren't equal because they capture (the heap allocs) :P

return withUnsafeBytes(of: comparing.keyLogCallback) { callbackPointer2 in
return callbackPointer1.elementsEqual(callbackPointer2)
}
}

return self.minimumTLSVersion == comparing.minimumTLSVersion &&
self.maximumTLSVersion == comparing.maximumTLSVersion &&
self.cipherSuites == comparing.cipherSuites &&
self.verifySignatureAlgorithms == comparing.verifySignatureAlgorithms &&
self.signingSignatureAlgorithms == comparing.signingSignatureAlgorithms &&
self.certificateVerification == comparing.certificateVerification &&
self.trustRoots == comparing.trustRoots &&
self.certificateChain == comparing.certificateChain &&
self.privateKey == comparing.privateKey &&
self.applicationProtocols == comparing.applicationProtocols &&
self.encodedApplicationProtocols == comparing.encodedApplicationProtocols &&
self.shutdownTimeout == comparing.shutdownTimeout &&
isKeyLoggerCallbacksEqual &&
self.renegotiationSupport == comparing.renegotiationSupport
}

/// Returns a best effort hash of this TLS configuration.
///
/// The "best effort" stems from the fact that we are hashing the pointer bytes of the `keyLogCallback` closure.
///
/// - warning: You should probably not use this function. This function can return false-negatives, but not false-positives.
public func bestEffortHash(into hasher: inout Hasher) {
hasher.combine(minimumTLSVersion)
hasher.combine(maximumTLSVersion)
hasher.combine(cipherSuites)
hasher.combine(verifySignatureAlgorithms)
hasher.combine(signingSignatureAlgorithms)
hasher.combine(certificateVerification)
hasher.combine(trustRoots)
hasher.combine(certificateChain)
hasher.combine(privateKey)
hasher.combine(applicationProtocols)
hasher.combine(encodedApplicationProtocols)
hasher.combine(shutdownTimeout)
withUnsafeBytes(of: keyLogCallback) { closureBits in
hasher.combine(bytes: closureBits)
}
hasher.combine(renegotiationSupport)
}
}
3 changes: 3 additions & 0 deletions Tests/NIOSSLTests/TLSConfigurationTest+XCTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ extension TLSConfigurationTest {
("testNonexistentFileObject", testNonexistentFileObject),
("testComputedApplicationProtocols", testComputedApplicationProtocols),
("testKeyLogManagerOverlappingAccess", testKeyLogManagerOverlappingAccess),
("testTheSameHashValue", testTheSameHashValue),
("testDifferentHashValues", testDifferentHashValues),
("testDifferentCallbacksNotEqual", testDifferentCallbacksNotEqual),
]
}
}
Expand Down
23 changes: 23 additions & 0 deletions Tests/NIOSSLTests/TLSConfigurationTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -496,4 +496,27 @@ class TLSConfigurationTest: XCTestCase {
group.wait()
XCTAssertEqual([true, true], completionsQueue.sync { completions })
}

func testTheSameHashValue() {
let config = TLSConfiguration.forServer(certificateChain: [], privateKey: .file("fake.file"), applicationProtocols: ["http/1.1"])
let theSameConfig = TLSConfiguration.forServer(certificateChain: [], privateKey: .file("fake.file"), applicationProtocols: ["http/1.1"])
var hasher = Hasher()
var hasher2 = Hasher()
config.bestEffortHash(into: &hasher)
theSameConfig.bestEffortHash(into: &hasher2)
XCTAssertEqual(hasher.finalize(), hasher2.finalize())
XCTAssertTrue(config.bestEffortEquals(theSameConfig))
}

func testDifferentHashValues() {
let config = TLSConfiguration.forServer(certificateChain: [], privateKey: .file("fake.file"), applicationProtocols: ["http/1.1"])
let differentConfig = TLSConfiguration.forServer(certificateChain: [], privateKey: .file("fake2.file"), applicationProtocols: ["http/1.1"])
XCTAssertFalse(config.bestEffortEquals(differentConfig))
}

func testDifferentCallbacksNotEqual() {
let config = TLSConfiguration.forServer(certificateChain: [], privateKey: .file("fake.file"), applicationProtocols: ["http/1.1"], keyLogCallback: { _ in })
let differentConfig = TLSConfiguration.forServer(certificateChain: [], privateKey: .file("fake.file"), applicationProtocols: ["http/1.1"], keyLogCallback: { _ in })
XCTAssertFalse(config.bestEffortEquals(differentConfig))
}
}