This commit is contained in:
2026-09-13 12:15:36 -07:00
commit e473d00f4f
104 changed files with 16080 additions and 0 deletions
@@ -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))
}
}