SSL Pinning:加強 App 和 Server 通訊安全的方法

Zhi-Hong Lin
8 min readSep 2, 2023

--

什麼是 SSL Pinning?

一種加強 App 和 Server 間通訊安全性的方法。主要目標是確保 App 僅與預先驗證的 Server 建立安全連接,防止中間人攻擊(Man-in-the-Middle,MitM)等安全風險。一般會有兩種方式進行驗證,Certificate PinningPublic Key Pinning

Certificate Pinning

Certificate Pinning 通常需要驗證整張 SSL 證書,因此安全性相對較高。然而,此方法較為固定,如果 Server 更新證書,App 需要定期更新並重新上架。

首先,我們可以建立一個 Certificates 結構,用於從 App 內取得憑證檔案。

struct Certificates {
static let testSSLPinning = Certificates.certificate(filename: "testSSLPinning")

private static func certificate(filename: String, type: String = "cer") -> SecCertificate {
let filePath = Bundle.main.path(forResource: filename, ofType: type)!
let data = try! Data(contentsOf: URL(fileURLWithPath: filePath))
let certificate = SecCertificateCreateWithData(nil, data as CFData)!
return certificate
}
}

建立自定義的 Session,以下使用 Alamofire 的 ServerTrustManager 進行 SSL Pinning

  • allHostsMustBeEvaluated 設置為 false,表示不需要對所有 domain 進行 SSLPinning
  • evaluators 指定對特定 domain 進行 SSLPinning
let CertificateSessionManager: Session = {
let configuration = URLSessionConfiguration.af.default
configuration.urlCache = nil

let interceptor = SPNetworkInterceptor()

// domain 移除 https://
let testSSLPinningDomain = "www.github.com"

// key 是要進行 SSLPinning 的 domain
// value 則是一個 PinnedCertificatesTrustEvaluator,將前面所定義的憑證檔進行驗證
let evaluators: [String: ServerTrustEvaluating] = [
testSSLPinningDomain: PinnedCertificatesTrustEvaluator(certificates: [Certificates.testSSLPinning]))
]

let serverTrustManager = ServerTrustManager(allHostsMustBeEvaluated: false, evaluators: evaluators)
let session = Session(configuration: configuration,
interceptor: interceptor,
serverTrustManager: serverTrustManager,
cachedResponseHandler: ResponseCacher(behavior: .doNotCache))

return session
}()

Public key Pinning

Public Key Pinning 則僅驗證公開金鑰,不需驗證整張憑證。這使得當憑證需要更新時,只需確保公鑰保持不變,無需經常更新 App。

可以利用 檢測網站 SSL 憑證安全等級 這個網站取得公鑰 SHA256

導入 TrustKit

pod 'TrustKit'

建立自定義的 SessionDelegate,用於處理 SSL Pinning 驗證:

  • 如果驗證失敗則返回 false,可能是因為不需要進行伺服器信任,或者該 domain 未進行 Pinning。在這種情況下,將執行默認的處理方式。
class CustomSessionDelegate: SessionDelegate {
override func urlSession(_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let pinning = TrustKit.sharedInstance().pinningValidator
if pinning.handle(challenge, completionHandler: completionHandler) == false {
completionHandler(.performDefaultHandling, nil)
}
}
}

接著,建立自定義的 Session,以下使用 TrustKit 進行 SSL Pinning

設定 Config:

  • kTSKSwizzleNetworkDelegates 這個參數設置是否採用替換網絡代理的方式來進行 SSL Pinning。當設置為 false 時,不會替換網絡代理,而是在 CustomSessionDelegate 中使用 TrustKit 來處理驗證。
  • kTSKEnforcePinning是否強制執行 SSL Pinning
  • kTSKIncludeSubdomains是否包含子域名。當設置為 true 時,也會對子域名進行 SSL Pinning 驗證
  • kTSKDisableDefaultReportUri是否禁用默認的報告 URI。當設置為 true 時,如果 SSL Pinning 驗證失敗,不會自動向默認的報告 URI 發送報告
  • kTSKPublicKeyHashes設定公鑰 SHA256
let PublicKeySessionManager: Session = {
let interceptor = SPNetworkInterceptor()

let configuration = URLSessionConfiguration.af.default
configuration.urlCache = nil

let sessionDelegate = CustomSessionDelegate()
// domain 移除 https://
let testSSLPinningDomain = "www.github.com"
// 設定 TrustKit 的配置
let trustKitConfig = [
kTSKSwizzleNetworkDelegates: false,
kTSKPinnedDomains: [
testSSLPinningDomain: [
kTSKEnforcePinning: true,
kTSKIncludeSubdomains: true,
kTSKDisableDefaultReportUri: true,
kTSKPublicKeyHashes: [
"jSd+RbSAB3215SSioJKeyfdEFELVT/xz+Fwod2ypqtE=",
],
]
]
] as [String : Any]

TrustKit.initSharedInstance(withConfiguration: trustKitConfig)
let afSession = Session(configuration: configuration,
delegate: sessionDelegate,
interceptor: interceptor,
cachedResponseHandler: ResponseCacher(behavior: .doNotCache))


return afSession
}()

以上是 SSL Pinning 的基本原理和實現方式。選擇憑證驗證或公鑰驗證取決於需求和安全性考量。無論哪種方法,都能有效提升 App 和 Server 間的通訊安全性。

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

No responses yet

Write a response