Implementing Certificate Pinning
Implementing Certificate Pinning
Certificate pinning prevents MITM attacks by validating server certificates against known good certificates.
Advanced Certificate Pinning Implementation:
// iOS - Advanced certificate pinning with backup pins
class CertificatePinningManager {
private struct PinConfiguration {
let host: String
let pins: [String] // SHA256 hashes
let includesSubdomains: Bool
let maxAge: TimeInterval
let enforceMode: Bool // true = enforce, false = report only
}
private var configurations: [PinConfiguration] = []
init() {
loadPinConfigurations()
}
private func loadPinConfigurations() {
// Load from configuration file or remote config
configurations = [
PinConfiguration(
host: "api.example.com",
pins: [
"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", // Backup pin
"sha256/CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=" // Root CA pin
],
includesSubdomains: true,
maxAge: 60 * 60 * 24 * 30, // 30 days
enforceMode: true
)
]
}
func validateCertificate(
for challenge: URLAuthenticationChallenge
) -> (disposition: URLSession.AuthChallengeDisposition, credential: URLCredential?) {
guard let serverTrust = challenge.protectionSpace.serverTrust else {
return (.cancelAuthenticationChallenge, nil)
}
let host = challenge.protectionSpace.host
guard let config = findConfiguration(for: host) else {
// No pinning configured for this host
return (.performDefaultHandling, nil)
}
// Extract certificate chain
let certificateChain = extractCertificateChain(from: serverTrust)
let pins = certificateChain.map { generatePin(for: $0) }
// Check if any certificate in the chain matches our pins
let hasValidPin = pins.contains { pin in
config.pins.contains(pin)
}
if hasValidPin {
let credential = URLCredential(trust: serverTrust)
return (.useCredential, credential)
} else {
// Pin validation failed
if config.enforceMode {
logPinFailure(host: host, pins: pins, expectedPins: config.pins)
return (.cancelAuthenticationChallenge, nil)
} else {
// Report-only mode
reportPinFailure(host: host, pins: pins, expectedPins: config.pins)
return (.performDefaultHandling, nil)
}
}
}
private func generatePin(for certificate: SecCertificate) -> String {
guard let publicKey = SecCertificateCopyKey(certificate),
let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil) else {
return ""
}
let keyHash = SHA256.hash(data: publicKeyData as Data)
return "sha256/" + Data(keyHash).base64EncodedString()
}
}