---
title: Implement certificate pinning cho API quan trọng
impact: CRITICAL
impactDescription: Không pin certificate cho phép attacker dùng proxy (Charles, Proxyman) hoặc cài root CA để đọc toàn bộ API traffic và đánh cắp token, dữ liệu người dùng.
tags: swift, ios, certificate-pinning, tls, ssl-pinning, urlsession, alamofire, security
---

## Implement certificate pinning cho API quan trọng

Certificate pinning đảm bảo app chỉ chấp nhận certificate của server đã biết trước, ngăn chặn MITM attack ngay cả khi attacker cài root CA. Implement bằng `URLSessionDelegate` hoặc Alamofire `ServerTrustManager`.

**Incorrect (không pin certificate - chấp nhận mọi certificate):**

```swift
import Foundation

// !! Không pin - chấp nhận certificate bất kỳ trust chain nào phát hành
class InsecureNetworkManager: NSObject {
    lazy var session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)

    func fetchUserData(token: String) {
        var request = URLRequest(url: URL(string: "https://api.example.com/user")!)
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        session.dataTask(with: request).resume()
    }
}

extension InsecureNetworkManager: URLSessionDelegate {
    // !! Không implement - mặc định không pin
    // Proxy như Charles/Proxyman đọc được hết traffic
}

// !! Hoặc tệ hơn: disable validation hoàn toàn (chỉ thấy trong dev code nhưng sót production)
extension InsecureNetworkManager: URLSessionDelegate {
    func urlSession(_ session: URLSession,
                    didReceive challenge: URLAuthenticationChallenge,
                    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        // !! CHỚ BAO GIỜ: accept mọi certificate
        completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
    }
}
```

**Correct (certificate pinning với public key hash):**

```swift
import Foundation
import CryptoKit

class PinnedNetworkManager: NSObject {
    // SAFE: Hardcode SHA256 hash của public key server certificate
    // Nên pin ít nhất 2 hashes (primary + backup) để rollover không gây outage
    private let pinnedKeyHashes: Set<String> = [
        "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=",  // Primary cert
        "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC="   // Backup cert
    ]

    lazy var session: URLSession = {
        URLSession(configuration: .default, delegate: self, delegateQueue: nil)
    }()
}

extension PinnedNetworkManager: URLSessionDelegate {
    func urlSession(_ session: URLSession,
                    didReceive challenge: URLAuthenticationChallenge,
                    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
              let serverTrust = challenge.protectionSpace.serverTrust else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }

        // Evaluate server trust
        var error: CFError?
        guard SecTrustEvaluateWithError(serverTrust, &error) else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }

        // Lấy certificate chain và kiểm tra public key hash
        let certificateCount = SecTrustGetCertificateCount(serverTrust)
        for index in 0..<certificateCount {
            guard let certificate = SecTrustGetCertificateAtIndex(serverTrust, index) else { continue }
            let publicKey = SecCertificateCopyKey(certificate)
            guard let keyData = publicKey.flatMap({ SecKeyCopyExternalRepresentation($0, nil) as Data? }) else { continue }

            let hash = SHA256.hash(data: keyData)
            let hashBase64 = Data(hash).base64EncodedString()

            if pinnedKeyHashes.contains(hashBase64) {
                completionHandler(.useCredential, URLCredential(trust: serverTrust))
                return
            }
        }

        // Không match pin nào → reject
        completionHandler(.cancelAuthenticationChallenge, nil)
    }
}

// SAFE: Với Alamofire, dùng ServerTrustManager
// import Alamofire
// let evaluators: [String: ServerTrustEvaluating] = [
//     "api.example.com": PublicKeysTrustEvaluator()
// ]
// let session = Session(serverTrustManager: ServerTrustManager(evaluators: evaluators))
```

**Tools:** TrustKit (library), Alamofire ServerTrustManager, OWASP MASVS-NETWORK-2, SSL Labs, Proxyman (test pinning)
