Skip to content
This repository was archived by the owner on May 13, 2024. It is now read-only.

Commit 40732b3

Browse files
author
Joseph Ross
committed
Add support for SSL public key pinning. Fixes #89.
1 parent d1761d9 commit 40732b3

File tree

1 file changed

+50
-0
lines changed

1 file changed

+50
-0
lines changed

Source/WebSocket.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,7 @@ private class InnerWebSocket: Hashable {
538538
var _event = WebSocketEvents()
539539
var _eventDelegate: WebSocketDelegate?
540540
var _binaryType = WebSocketBinaryType.uInt8Array
541+
var _pinnedPublicKeys: [SecKey]? = nil
541542
var _readyState = WebSocketReadyState.connecting
542543
var _networkTimeout = TimeInterval(-1)
543544

@@ -587,6 +588,10 @@ private class InnerWebSocket: Hashable {
587588
get { lock(); defer { unlock() }; return _readyState }
588589
set { lock(); defer { unlock() }; _readyState = newValue }
589590
}
591+
var pinnedPublicKeys: [SecKey]? {
592+
get { lock(); defer { unlock() }; return _pinnedPublicKeys }
593+
set { lock(); defer { unlock() }; _pinnedPublicKeys = newValue }
594+
}
590595

591596
func copyOpen(_ request: URLRequest, subProtocols : [String] = []) -> InnerWebSocket{
592597
let ws = InnerWebSocket(request: request, subProtocols: subProtocols, stub: false)
@@ -597,6 +602,7 @@ private class InnerWebSocket: Hashable {
597602
ws.event = event
598603
ws.eventQueue = eventQueue
599604
ws.binaryType = binaryType
605+
ws.pinnedPublicKeys = pinnedPublicKeys
600606
return ws
601607
}
602608

@@ -699,6 +705,7 @@ private class InnerWebSocket: Hashable {
699705
stage = .readResponse
700706
case .readResponse:
701707
try readResponse()
708+
try verifySSLPinning()
702709
privateReadyState = .open
703710
fire {
704711
self.event.open()
@@ -1192,6 +1199,42 @@ private class InnerWebSocket: Hashable {
11921199
inputBytesStart += bufferCount+4
11931200
}
11941201
}
1202+
1203+
private func verifySSLPinning() throws {
1204+
let keys = self._pinnedPublicKeys ?? []
1205+
if keys.isEmpty { return }
1206+
1207+
let peerTrust = wr.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust
1208+
1209+
let serverPubKeys = publicKeys(from: peerTrust)
1210+
for key in keys as [AnyObject] {
1211+
for serverKey in serverPubKeys as [AnyObject] {
1212+
if key.isEqual(serverKey) {
1213+
return
1214+
}
1215+
}
1216+
}
1217+
1218+
throw WebSocketError.invalidResponse("Failed SSL public key pinning verification")
1219+
}
1220+
1221+
private func publicKeys(from trust: SecTrust) -> [SecKey] {
1222+
let policy = SecPolicyCreateBasicX509()
1223+
let keys = (0..<SecTrustGetCertificateCount(trust)).flatMap { (index:Int) -> SecKey? in
1224+
let cert = SecTrustGetCertificateAtIndex(trust, index)
1225+
return extractPublicKey(cert!, policy: policy)
1226+
}
1227+
1228+
return keys
1229+
}
1230+
1231+
private func extractPublicKey(_ cert: SecCertificate, policy: SecPolicy) -> SecKey? {
1232+
var possibleTrust: SecTrust? = nil
1233+
SecTrustCreateWithCertificates(cert, policy, &possibleTrust)
1234+
1235+
guard let trust = possibleTrust else { return nil }
1236+
return SecTrustCopyPublicKey(trust)
1237+
}
11951238

11961239
class ByteReader {
11971240
var start : UnsafePointer<UInt8>
@@ -1722,6 +1765,13 @@ open class WebSocket: NSObject {
17221765
get { return ws.binaryType }
17231766
set { ws.binaryType = newValue }
17241767
}
1768+
1769+
/// A collection of public keys to pin the SSL connection against. If no keys are provided, pinning is not enforced.
1770+
@nonobjc open var pinnedPublicKeys: [SecKey]? {
1771+
get { return ws.pinnedPublicKeys }
1772+
set { ws.pinnedPublicKeys = newValue }
1773+
}
1774+
17251775
/// The current state of the connection; this is one of the WebSocketReadyState constants. Read only.
17261776
open var readyState : WebSocketReadyState{
17271777
return ws.readyState

0 commit comments

Comments
 (0)