Compare commits

...

12 Commits

Author SHA1 Message Date
Nick Ratelle
71ebc08f24 Fix deprecation warning in WSCompression
Apple recommends using the withUnsafeMutableBytes method on the buffer
to bind an unsafe pointer.
2022-02-16 12:34:32 -08:00
Jordan Rose
52d8c91ee2 Backport upstream PR#777 to the 3.x branch
In FoundationStream deinit, remove self as a delegate to InputStream
and OutputStream to prevent EXC_BAD_ACCESS.

See https://github.com/daltoniam/Starscream/pull/777.
2022-02-16 12:11:29 -08:00
Jordan Rose
b5c9019396 Merge tag '3.1.1' into signal-release 2022-02-16 12:09:46 -08:00
Michael Kirk
b09ea163c3 Merge branch 'mkirk/needs-compression-race' into signal-release 2019-02-06 17:37:43 -07:00
Michael Kirk
401ce53eca Avoid TSan data race with CompressionState
Most of CompressionState is only mutated once per connection, as
part of setting up the websocket.

But needsCompression is mutated per-read-message.

Handling reads is serialized on one queue, and handling writes is serialized on
another queueu, which means that a read can be handled concurrently with
handling a write.

In theory this concurrent read/write should be harmless because the mutated
field is not accessed by the other queue, but for best practice, we should
write our code in a way that keeps TSan happy.
2019-02-06 17:37:28 -07:00
Michael Kirk
edfb5964c5 Merge branch 'mkirk/configurable-socket-security-level' into signal-release 2019-01-16 10:34:12 -07:00
Michael Kirk
7ee961e23a Configurable socket security level
Still "negotiated" by default, but allows clients to specify other versions if
they prefer.
2019-01-16 10:34:01 -07:00
Michael Kirk
34369f1386 fixup linux syntax 2019-01-16 10:34:01 -07:00
Michael Kirk
8414c7fefb Merge branch 'mkirk/use-secrandom-for-sec-key' into signal-release 2019-01-16 10:33:22 -07:00
Michael Kirk
b09ba2877c use SecRandom to generate 16 full bytes of entropy for Sec-Key 2019-01-16 10:31:25 -07:00
Michael Kirk
fd76ffc24f Merge branch 'mkirk/check-results' into signal-release 2019-01-15 16:19:26 -07:00
Michael Kirk
fe4c768506 verify OSStatus returned from Sec* functions 2019-01-15 16:18:32 -07:00
3 changed files with 66 additions and 31 deletions

View File

@ -80,10 +80,12 @@ class Decompressor {
strm.avail_in = CUnsignedInt(count)
repeat {
strm.next_out = UnsafeMutablePointer<UInt8>(&buffer)
strm.avail_out = CUnsignedInt(buffer.count)
buffer.withUnsafeMutableBytes { (bufferPtr) in
strm.next_out = bufferPtr.bindMemory(to: UInt8.self).baseAddress
strm.avail_out = CUnsignedInt(bufferPtr.count)
res = inflate(&strm, 0)
res = inflate(&strm, 0)
}
let byteCount = buffer.count - Int(strm.avail_out)
out.append(buffer, count: byteCount)
@ -142,10 +144,12 @@ class Compressor {
strm.avail_in = CUnsignedInt(data.count)
repeat {
strm.next_out = UnsafeMutablePointer<UInt8>(&buffer)
strm.avail_out = CUnsignedInt(buffer.count)
buffer.withUnsafeMutableBytes { (bufferPtr) in
strm.next_out = bufferPtr.bindMemory(to: UInt8.self).baseAddress
strm.avail_out = CUnsignedInt(bufferPtr.count)
res = deflate(&strm, Z_SYNC_FLUSH)
res = deflate(&strm, Z_SYNC_FLUSH)
}
let byteCount = buffer.count - Int(strm.avail_out)
compressed.append(buffer, count: byteCount)

View File

@ -149,7 +149,10 @@ open class SSLSecurity : SSLTrustValidator {
} else {
policy = SecPolicyCreateBasicX509()
}
SecTrustSetPolicies(trust,policy)
guard SecTrustSetPolicies(trust, policy) == errSecSuccess else {
assertionFailure("unable to set trust policies")
return false
}
if self.usePublicKeys {
if let keys = self.pubKeys {
let serverPubKeys = publicKeyChain(trust)
@ -167,9 +170,15 @@ open class SSLSecurity : SSLTrustValidator {
for cert in certs {
collect.append(SecCertificateCreateWithData(nil,cert as CFData)!)
}
SecTrustSetAnchorCertificates(trust,collect as NSArray)
guard SecTrustSetAnchorCertificates(trust, collect as NSArray) == errSecSuccess else {
assertionFailure("unable to set trust anchor certificates")
return false
}
var result: SecTrustResultType = .unspecified
SecTrustEvaluate(trust,&result)
guard SecTrustEvaluate(trust, &result) == errSecSuccess else {
assertionFailure("unable to evaluate trust")
return false
}
if result == .unspecified || result == .proceed {
if !validateEntireChain {
return true
@ -213,11 +222,17 @@ open class SSLSecurity : SSLTrustValidator {
*/
public func extractPublicKey(_ cert: SecCertificate, policy: SecPolicy) -> SecKey? {
var possibleTrust: SecTrust?
SecTrustCreateWithCertificates(cert, policy, &possibleTrust)
guard SecTrustCreateWithCertificates(cert, policy, &possibleTrust) == errSecSuccess else {
assertionFailure("failed to create trust with certificate")
return nil
}
guard let trust = possibleTrust else { return nil }
var result: SecTrustResultType = .unspecified
SecTrustEvaluate(trust, &result)
guard SecTrustEvaluate(trust, &result) == errSecSuccess else {
assertionFailure("failed to evaluate trust")
return nil
}
return SecTrustCopyPublicKey(trust)
}

View File

@ -49,6 +49,7 @@ public enum ErrorType: Error {
case protocolError //There was an error parsing the WebSocket frames
case upgradeError //There was an error during the HTTP upgrade
case closeError //There was an error during the close (socket probably has been dereferenced)
case osError // There was an error with the underlying OS
}
public struct WSError: Error {
@ -69,6 +70,7 @@ public protocol WebSocketClient: class {
#else
var security: SSLTrustValidator? {get set}
var enabledSSLCipherSuites: [SSLCipherSuite]? {get set}
var socketSecurityLevel: StreamSocketSecurityLevel { get set }
#endif
var isConnected: Bool {get}
@ -113,6 +115,7 @@ public struct SSLSettings {
#if os(Linux)
#else
public let cipherSuites: [SSLCipherSuite]?
public var socketSecurityLevel: StreamSocketSecurityLevel
#endif
}
@ -143,6 +146,11 @@ open class FoundationStream : NSObject, WSStream, StreamDelegate {
public var enableSOCKSProxy = false
deinit {
inputStream?.delegate = nil
outputStream?.delegate = nil
}
public func connect(url: URL, port: Int, timeout: TimeInterval, ssl: SSLSettings, completion: @escaping ((Error?) -> Void)) {
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
@ -166,8 +174,8 @@ open class FoundationStream : NSObject, WSStream, StreamDelegate {
inStream.delegate = self
outStream.delegate = self
if ssl.useSSL {
inStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey)
outStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey)
inStream.setProperty(ssl.socketSecurityLevel as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey)
outStream.setProperty(ssl.socketSecurityLevel as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey)
#if os(watchOS) //watchOS us unfortunately is missing the kCFStream properties to make this work
#else
var settings = [NSObject: NSObject]()
@ -416,6 +424,7 @@ open class WebSocket : NSObject, StreamDelegate, WebSocketClient, WSStreamDelega
#else
public var security: SSLTrustValidator?
public var enabledSSLCipherSuites: [SSLCipherSuite]?
public var socketSecurityLevel: StreamSocketSecurityLevel = .negotiatedSSL
#endif
public var isConnected: Bool {
@ -433,7 +442,6 @@ open class WebSocket : NSObject, StreamDelegate, WebSocketClient, WSStreamDelega
private struct CompressionState {
var supportsCompression = false
var messageNeedsDecompression = false
var serverMaxWindowBits = 15
var clientMaxWindowBits = 15
var clientNoContextTakeover = false
@ -447,6 +455,10 @@ open class WebSocket : NSObject, StreamDelegate, WebSocketClient, WSStreamDelega
private var isConnecting = false
private let mutex = NSLock()
private var compressionState = CompressionState()
// `currentMessageNeedsDecompression` is not part of the `compressionState` struct
// because currentMessageNeedsDecompression can be mutated in the read queue concurrently
// with other `compressionState` fields being accesseed on the write queue.
private var currentMessageNeedsDecompression = false
private var writeQueue = OperationQueue()
private var readStack = [WSResponse]()
private var inputQueue = [Data]()
@ -589,8 +601,8 @@ open class WebSocket : NSObject, StreamDelegate, WebSocketClient, WSStreamDelega
}
request.setValue(headerWSUpgradeValue, forHTTPHeaderField: headerWSUpgradeName)
request.setValue(headerWSConnectionValue, forHTTPHeaderField: headerWSConnectionName)
headerSecKey = generateWebSocketKey()
request.setValue(headerWSVersionValue, forHTTPHeaderField: headerWSVersionName)
headerSecKey = try! generateWebSocketKey()
request.setValue(headerSecKey, forHTTPHeaderField: headerWSKeyName)
if enableCompression {
@ -627,16 +639,16 @@ open class WebSocket : NSObject, StreamDelegate, WebSocketClient, WSStreamDelega
/**
Generate a WebSocket key as needed in RFC.
*/
private func generateWebSocketKey() -> String {
var key = ""
let seed = 16
for _ in 0..<seed {
let uni = UnicodeScalar(UInt32(97 + arc4random_uniform(25)))
key += "\(Character(uni!))"
private func generateWebSocketKey() throws -> String {
let kSocketKeyByteLength = 16
var randomData = Data(count: kSocketKeyByteLength)
try randomData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) throws -> Void in
guard SecRandomCopyBytes(kSecRandomDefault, kSocketKeyByteLength, bytes) == errSecSuccess else {
throw WSError(type: .osError, message: "unable to generate random bytes", code: 0)
}
}
let data = key.data(using: String.Encoding.utf8)
let baseKey = data?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
return baseKey!
return randomData.base64EncodedString()
}
/**
@ -657,15 +669,16 @@ open class WebSocket : NSObject, StreamDelegate, WebSocketClient, WSStreamDelega
let settings = SSLSettings(useSSL: useSSL,
disableCertValidation: disableSSLCertValidation,
overrideTrustHostname: overrideTrustHostname,
desiredTrustHostname: desiredTrustHostname),
sslClientCertificate: sslClientCertificate
desiredTrustHostname: desiredTrustHostname,
sslClientCertificate: sslClientCertificate)
#else
let settings = SSLSettings(useSSL: useSSL,
disableCertValidation: disableSSLCertValidation,
overrideTrustHostname: overrideTrustHostname,
desiredTrustHostname: desiredTrustHostname,
sslClientCertificate: sslClientCertificate,
cipherSuites: self.enabledSSLCipherSuites)
cipherSuites: enabledSSLCipherSuites,
socketSecurityLevel: socketSecurityLevel)
#endif
certValidated = !useSSL
let timeout = request.timeoutInterval * 1_000_000
@ -996,9 +1009,9 @@ open class WebSocket : NSObject, StreamDelegate, WebSocketClient, WSStreamDelega
let payloadLen = (PayloadLenMask & baseAddress[1])
var offset = 2
if compressionState.supportsCompression && receivedOpcode != .continueFrame {
compressionState.messageNeedsDecompression = (RSV1Mask & baseAddress[0]) > 0
currentMessageNeedsDecompression = (RSV1Mask & baseAddress[0]) > 0
}
if (isMasked > 0 || (RSVMask & baseAddress[0]) > 0) && receivedOpcode != .pong && !compressionState.messageNeedsDecompression {
if (isMasked > 0 || (RSVMask & baseAddress[0]) > 0) && receivedOpcode != .pong && !currentMessageNeedsDecompression {
let errCode = CloseCode.protocolError.rawValue
doDisconnect(WSError(type: .protocolError, message: "masked and rsv data is not currently supported", code: Int(errCode)))
writeError(errCode)
@ -1059,7 +1072,7 @@ open class WebSocket : NSObject, StreamDelegate, WebSocketClient, WSStreamDelega
len -= UInt64(size)
}
let data: Data
if compressionState.messageNeedsDecompression, let decompressor = compressionState.decompressor {
if currentMessageNeedsDecompression, let decompressor = compressionState.decompressor {
do {
data = try decompressor.decompress(bytes: baseAddress+offset, count: Int(len), finish: isFin > 0)
if isFin > 0 && compressionState.serverNoContextTakeover {
@ -1250,7 +1263,10 @@ open class WebSocket : NSObject, StreamDelegate, WebSocketClient, WSStreamDelega
}
buffer[1] |= self.MaskMask
let maskKey = UnsafeMutablePointer<UInt8>(buffer + offset)
_ = SecRandomCopyBytes(kSecRandomDefault, Int(MemoryLayout<UInt32>.size), maskKey)
guard SecRandomCopyBytes(kSecRandomDefault, Int(MemoryLayout<UInt32>.size), maskKey) == errSecSuccess else {
self.doDisconnect(WSError(type: .osError, message: "unable to generate random bytes", code: 0))
return
}
offset += MemoryLayout<UInt32>.size
for i in 0..<dataLength {