feat: consolidate iOS with API and web preserving history
Rebuild & Deploy / rebuild (push) Failing after 23s
Rebuild & Deploy / rebuild (push) Failing after 23s
This commit is contained in:
commit
d735ee72fa
102 files changed
+13077
-258
No files matched your search
@@ -0,0 +1,72 @@
|
||||
import Foundation
|
||||
import CommonCrypto
|
||||
import Security
|
||||
|
||||
/// Pins the self-signed cert of the LAN deployment (CN=192.168.86.2, regenerated
|
||||
/// at container start — re-export the hash after server cert rotation, see README).
|
||||
/// Extract with:
|
||||
/// openssl s_client -connect 192.168.86.2:9178 </dev/null 2>/dev/null \
|
||||
/// | openssl x509 -outform der | openssl dgst -sha256
|
||||
enum TLSPinning {
|
||||
static let pinnedCertSHA256Hex: Set<String> = [
|
||||
"07907abde3bb9c685265909927627d9ea4705d7e7b27056eefe06e10b192af46"
|
||||
]
|
||||
|
||||
static let host = "192.168.86.2"
|
||||
|
||||
static func session() -> URLSession {
|
||||
let config = URLSessionConfiguration.ephemeral
|
||||
config.urlCache = nil
|
||||
config.httpCookieStorage = nil
|
||||
config.urlCredentialStorage = nil
|
||||
config.timeoutIntervalForRequest = 15
|
||||
return URLSession(configuration: config, delegate: PinnedTrustDelegate(), delegateQueue: nil)
|
||||
}
|
||||
|
||||
static func sha256Hex(_ data: Data) -> String {
|
||||
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
|
||||
data.withUnsafeBytes { buffer in
|
||||
guard let base = buffer.baseAddress else { return }
|
||||
CC_SHA256(base, CC_LONG(data.count), &hash)
|
||||
}
|
||||
return hash.map { String(format: "%02x", $0) }.joined()
|
||||
}
|
||||
|
||||
static func matchesPinnedCert(_ trust: SecTrust) -> Bool {
|
||||
guard let chain = SecTrustCopyCertificateChain(trust) as? [SecCertificate],
|
||||
let leaf = chain.first,
|
||||
let der = SecCertificateCopyData(leaf) as Data? else { return false }
|
||||
return pinnedCertSHA256Hex.contains(sha256Hex(der))
|
||||
}
|
||||
}
|
||||
|
||||
final class PinnedTrustDelegate: NSObject, URLSessionDelegate, URLSessionTaskDelegate {
|
||||
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
|
||||
// Authentication/password-bearing requests must never be redirected to
|
||||
// another endpoint or origin. Correct the configured server instead.
|
||||
completionHandler(nil)
|
||||
}
|
||||
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
|
||||
guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
|
||||
let trust = challenge.protectionSpace.serverTrust else {
|
||||
completionHandler(.performDefaultHandling, nil)
|
||||
return
|
||||
}
|
||||
// Apply the fixed self-signed pin only to the pinned LAN deployment
|
||||
// host. Other explicitly configured HTTPS hosts use normal system
|
||||
// trust evaluation (Finding 12). This allows public deployments and
|
||||
// other LAN hosts with valid certificates while never weakening trust
|
||||
// for the default self-signed host.
|
||||
let host = challenge.protectionSpace.host
|
||||
if host == TLSPinning.host {
|
||||
if TLSPinning.matchesPinnedCert(trust) {
|
||||
completionHandler(.useCredential, URLCredential(trust: trust))
|
||||
} else {
|
||||
completionHandler(.cancelAuthenticationChallenge, nil)
|
||||
}
|
||||
} else {
|
||||
// Non-pinned host: let the system evaluate the certificate chain.
|
||||
completionHandler(.performDefaultHandling, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user