Quantcast
Channel: Nelson 寫些 iOS 開發的東東
Viewing all articles
Browse latest Browse all 20

如何正確設定 AFNetworking 的安全連線

$
0
0

TL;DR

前一陣子 AFNetworking被爆出存在安全性漏洞,它們也針對這件事情發出聲明稿

簡單的說,就是建議開發者使用最新版的 AFNetworking,並且啟用安全連線。不過它們也承認這一部份的說明文件沒有寫得很齊全,所以困擾了不少開發者。

今天花了一點時間研究,順手把它記錄下來。安全相關的東西不是我的專長,所以如果有任何錯誤的地方,請留言告訴我。

取得安全憑證

1. 確認有使用安全連線

如果你跟遠端伺服器是透過 HTTP 連線,那就不是安全連線,如果是 HTTPS 那就是安全連線。

2. 準備好網站的安全憑證

接下來我們需要憑證檔(Certification file),它的副檔名是 .cer,你可以跟你們的網站管理員詢問,通常他們都知道怎麼拿到這個檔案。

如果你的網站管理員沒有 .cer檔,只有 .crt檔,那你可以透過以下這行指令轉檔,要注意的是它是採用 DER編碼格式(請自行將 myWebsite替換成你想要的名字):

openssl x509 -in myWebsite.crt -out myWebsite.cer -outform der

如果很不幸的,你的網站管理員連 .crt檔都沒有,那你也可以使用下列這一整行指令從你們的網站取得憑證(請自行將 www.mywebsite.com替換成你們的網址):

openssl s_client -connect www.mywebsite.com:443 </dev/null 2>/dev/null | openssl x509 -outform DER > myWebsite.cer

現在你有一個憑證檔了。

3. 將憑證加入你的專案

將你的憑證拖拉放到 Xcode 專案底下,記得要把 Copy items if neededAdd to targets打勾。

好了,事前準備都做完,接著我們來設定 AFNetworking。

設定 AFNetworking

1. Pinning Mode

AFNetworking 的安全相關設定放在 AFSecurityPolicy,它定義了三種 SSL Pinning Mode:

/*
 `AFSSLPinningModeNone`
 Do not used pinned certificates to validate servers.

 `AFSSLPinningModePublicKey`
 Validate host certificates against public keys of pinned certificates.

 `AFSSLPinningModeCertificate`
 Validate host certificates against pinned certificates.
*/
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
    AFSSLPinningModeNone,
    AFSSLPinningModePublicKey,
    AFSSLPinningModeCertificate,
};

關於 pinning mode 詳細的說明可以參考這篇文章,簡單的說就是你可以將憑證跟你的 APP 一起打包,藉由此機制來避免中間人偽造憑證的風險。

  • AFSSLPinningModeNone : 你不必將憑證跟你的 APP 一起打包,完全信任伺服器的憑證
  • AFSSLPinningModeCertificate : 比對伺服器憑證跟你的憑證是否完全匹配
  • AFSSLPinningModePublicKey : 只比對伺服器憑證的 public key 跟你的憑證的 public key 是否匹配

那要選用何種模式比較好呢?

AFSSLPinningModeCertificate比較安全但也比較麻煩,它會比對你打包的憑證跟伺服器的憑證是否一致。因為你的憑證是跟 APP 一起打包的,這也就代表說如果你的憑證過期了或是變動了,你就得出一版新的 APP 而且舊版 APP 的憑證就失效了。你也可以在每次 APP 啟動時,就自動連到某個伺服器下載最新的憑證,不過此時這個下載連線就會是有風險的。

AFSSLPinningModePublicKey則是只有比對憑證裡的 public key,所以即使伺服器憑證有所變動,只要 public key 不變,就能通過驗證。

所以如果你能確保每個使用者總是使用最新版本的 APP(例如是公司企業內部專用的),那就可以考慮 AFSSLPinningModeCertificate,否則的話選擇 AFSSLPinningModePublicKey是比較實際的作法。

2. Certification Chain

/**
 Whether to evaluate an entire SSL certificate chain, or just the leaf certificate. Defaults to `YES`.
 */
@property (nonatomic, assign) BOOL validatesCertificateChain;

你的憑證是某家機構發出的,該機構的憑證是由更高一級的機構發出的,一路往上追,最後會到一個根機構,這樣一串由各機構發出的憑證稱為 certification chain。

如果你把 validatesCertificateChain設為 YES,那就得把這一整串憑證都打包進你的 APP,必須每個驗證都通過才算通過。如果設為 NO,只需要打包你自己的憑證就夠了。

Update: validatesCertificateChain這個選項已經在 AFNetworking v2.6.0 拿掉了。

3. 如何使用 AFSecurityPolicy

這裡以最新版的 AFNetworking 為例,假設你有一個 APIManager處理所有的 API call,它繼承自 AFHTTPSessionManager,我們可以設定它的 security policy 如下:

@interface APIManager : AFHTTPSessionManager
+ (APIManager *)sharedInstance;
@end

@implementation APIManager
+ (APIManager *)sharedInstance {
  static APIManager *_sharedClient = nil;
  static dispatch_once_t onceToken;

  dispatch_once(&onceToken, ^{
    NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
    _sharedClient = [[APIManager alloc] initWithBaseURL:nil sessionConfiguration:sessionConfiguration];
    AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
     policy.validatesCertificateChain = NO; // v2.6.0 之後沒有這個選項了
    _sharedClient.securityPolicy = policy;
  });

  return _sharedClient;
}
@end

好了,到此大功告成,你已經正確設定好安全連線了!

錯誤排解

Q: 為什麼我可以連上其他的網址?我不是應該只能連上憑證綁定的網址嗎?
  1. 檢查 validatesDomainName是否設為 NO 了,是的話就將它改成 YES。
  2. 檢查是否連到 http開頭的網址,非安全連線是不受限制的。
Q: 為什麼有打包憑證了,還是會連線失敗?
  1. 確認你的憑證有加到你的 target 裡頭,拖拉到 Xcode 時要把 Copy items if neededAdd to targets打勾。
  2. 如果 validatesCertificateChain是 YES,記得把它改成 NO,或是把上級憑證也一同打包進 APP。(v2.6.0 之後沒有這個選項了)

參考文件

https://github.com/AFNetworking/AFNetworking/issues/2673
https://github.com/rnapier/RNPinnedCertValidator
http://stackoverflow.com/a/24625969
http://oncenote.com/2014/10/21/Security-1-HTTPS/


Viewing all articles
Browse latest Browse all 20