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()
    }
}