CVE-2017-7192: Improper Certificate Validation (CWE-295)
value: WebSocket.swift in Starscream before 2.0.4 allows an SSL Pinning bypass because of incorrect management of the certValidated variable (it can be set to true but cannot be set to false).
CWE description: The product does not validate, or incorrectly validates, a certificate.
CVSS v3 base score: 7.5
Published: 2017-04-06T14:59Z
Repository: https://github.com/daltoniam/Starscream
Language: Swift
Files changed: Source/WebSocket.swift
Commit date: 2017-03-14 13:12:39 -0500
Commit message: some cleanup and SSL pinning fix

Diff: diff --git a/Source/WebSocket.swift b/Source/WebSocket.swift
index 03e5752..3630628 100644
--- a/Source/WebSocket.swift
+++ b/Source/WebSocket.swift
@@ -256,11 +256,10 @@ open class WebSocket : NSObject, StreamDelegate {
 
     /**
      Private method that starts the connection.
      */
     private func createHTTPRequest() {
-
         let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET" as CFString,
                                                     url as CFURL, kCFHTTPVersion1_1).takeRetainedValue()
 
         var port = url.port
         if port == nil {
@@ -381,38 +380,42 @@ open class WebSocket : NSObject, StreamDelegate {
             while !outStream.hasSpaceAvailable && !sOperation.isCancelled {
                 usleep(100) // wait until the socket is ready
                 guard !sOperation.isCancelled else { return }
                 out -= 100
                 if out < 0 {
-                    self?.cleanupStream()
+                    WebSocket.sharedWorkQueue.async {
+                        self?.cleanupStream()
+                    }
                     self?.doDisconnect(self?.errorWithDetail("write wait timed out", code: 2))
                     return
                 } else if outStream.streamError != nil {
                     return // disconnectStream will be called.
                 }
             }
-            guard !sOperation.isCancelled else { return }
+            guard !sOperation.isCancelled, let s = self else { return }
+            // Do the pinning now if needed
+            if let sec = s.security, !s.certValidated {
+                let trust = outStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust
+                let domain = outStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String
+                s.certValidated = sec.isValid(trust, domain: domain)
+                if !s.certValidated {
+                    WebSocket.sharedWorkQueue.async {
+                        let error = s.errorWithDetail("Invalid SSL certificate", code: 1)
+                        s.disconnectStream(error)
+                    }
+                    return
+                }
+            }
             outStream.write(bytes, maxLength: data.count)
         }
         writeQueue.addOperation(operation)
     }
     
     /**
      Delegate for the stream methods. Processes incoming bytes
      */
     open func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
-        if let sec = security, !certValidated && [.hasBytesAvailable, .hasSpaceAvailable].contains(eventCode) {
-            let trust = aStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust
-            let domain = aStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String
-            if sec.isValid(trust, domain: domain) {
-                certValidated = true
-            } else {
-                let error = errorWithDetail("Invalid SSL certificate", code: 1)
-                disconnectStream(error)
-                return
-            }
-        }
         if eventCode == .hasBytesAvailable {
             if aStream == inputStream {
                 processInputStream()
             }
         } else if eventCode == .errorOccurred {
@@ -943,10 +946,11 @@ open class WebSocket : NSObject, StreamDelegate {
     deinit {
         mutex.lock()
         readyToWrite = false
         mutex.unlock()
         cleanupStream()
+        writeQueue.cancelAllOperations()
     }
 
 }
 
 private extension Data {
diff --git a/examples/SimpleTest/SimpleTest.xcodeproj/project.xcworkspace/xcuserdata/dalton.xcuserdatad/UserInterfaceState.xcuserstate b/examples/SimpleTest/SimpleTest.xcodeproj/project.xcworkspace/xcuserdata/dalton.xcuserdatad/UserInterfaceState.xcuserstate
index 7123ff3..9495132 100644
Binary files a/examples/SimpleTest/SimpleTest.xcodeproj/project.xcworkspace/xcuserdata/dalton.xcuserdatad/UserInterfaceState.xcuserstate and b/examples/SimpleTest/SimpleTest.xcodeproj/project.xcworkspace/xcuserdata/dalton.xcuserdatad/UserInterfaceState.xcuserstate differ

Vulnerable code: 
                    self?.cleanupStream()
            guard !sOperation.isCancelled else { return }
        if let sec = security, !certValidated && [.hasBytesAvailable, .hasSpaceAvailable].contains(eventCode) {
            let trust = aStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust
            let domain = aStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String
            if sec.isValid(trust, domain: domain) {
                certValidated = true
            } else {
                let error = errorWithDetail("Invalid SSL certificate", code: 1)
                disconnectStream(error)
                return
            }
        }
Fixed code:                     WebSocket.sharedWorkQueue.async {
                        self?.cleanupStream()
                    }
            guard !sOperation.isCancelled, let s = self else { return }
            // Do the pinning now if needed
            if let sec = s.security, !s.certValidated {
                let trust = outStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust
                let domain = outStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String
                s.certValidated = sec.isValid(trust, domain: domain)
                if !s.certValidated {
                    WebSocket.sharedWorkQueue.async {
                        let error = s.errorWithDetail("Invalid SSL certificate", code: 1)
                        s.disconnectStream(error)
                    }
                    return
                }
            }
        writeQueue.cancelAllOperations()