Init
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// PortalTlsPinning.swift
|
||||
// CamExtension
|
||||
//
|
||||
// TLS Certificate Pinning for Camera Extension.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Security
|
||||
import CryptoKit
|
||||
import os.log
|
||||
|
||||
public enum PortalTlsPinning {
|
||||
public static func extractLeafCert(from serverTrust: SecTrust) -> SecCertificate? {
|
||||
if #available(macOS 12.0, *) {
|
||||
if let chain = SecTrustCopyCertificateChain(serverTrust) as? [SecCertificate], !chain.isEmpty {
|
||||
return chain[0]
|
||||
}
|
||||
}
|
||||
let count = SecTrustGetCertificateCount(serverTrust)
|
||||
guard count > 0 else { return nil }
|
||||
return SecTrustGetCertificateAtIndex(serverTrust, 0)
|
||||
}
|
||||
|
||||
public static func computeCertSha256(cert: SecCertificate) -> (data: Data, hex: String) {
|
||||
let certDer = SecCertificateCopyData(cert) as Data
|
||||
let digest = SHA256.hash(data: certDer)
|
||||
let data = Data(digest)
|
||||
let hex = data.map { String(format: "%02x", $0) }.joined()
|
||||
return (data, hex)
|
||||
}
|
||||
|
||||
public static func evaluate(
|
||||
challenge: URLAuthenticationChallenge,
|
||||
pinnedFingerprint: String? = nil,
|
||||
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
|
||||
) {
|
||||
guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
|
||||
let serverTrust = challenge.protectionSpace.serverTrust else {
|
||||
completionHandler(.cancelAuthenticationChallenge, nil)
|
||||
return
|
||||
}
|
||||
|
||||
guard let cert = extractLeafCert(from: serverTrust) else {
|
||||
os_log(.error, "PortalCam Extension TLS: No certificate found in server trust chain")
|
||||
completionHandler(.cancelAuthenticationChallenge, nil)
|
||||
return
|
||||
}
|
||||
|
||||
let (_, certHashHex) = computeCertSha256(cert: cert)
|
||||
let targetFingerprint = pinnedFingerprint ?? PortalAuth.pinnedCertSha256
|
||||
|
||||
guard let pinned = targetFingerprint, !pinned.isEmpty else {
|
||||
os_log(.error, "PortalCam Extension TLS: No pinned certificate configured; rejecting connection")
|
||||
completionHandler(.cancelAuthenticationChallenge, nil)
|
||||
return
|
||||
}
|
||||
|
||||
guard certHashHex.caseInsensitiveCompare(pinned) == .orderedSame else {
|
||||
os_log(.error, "PortalCam Extension TLS Pinning Mismatch! Expected: %{public}@, Got: %{public}@", pinned, certHashHex)
|
||||
completionHandler(.cancelAuthenticationChallenge, nil)
|
||||
return
|
||||
}
|
||||
|
||||
completionHandler(.useCredential, URLCredential(trust: serverTrust))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user