IOS應(yīng)用安全-HTTP/HTTPS網(wǎng)絡(luò)安全(二)

導(dǎo)讀

本文主要講解IOS中ATS相關(guān)的配置說(shuō)明和使用AFNetworking框架來(lái)實(shí)現(xiàn)證書(shū)驗(yàn)證的方法。講解了AFNetworking各個(gè)配置試用的場(chǎng)景和注意點(diǎn)。

ATS

IOS9之后,蘋(píng)果開(kāi)啟了App Transport Security(簡(jiǎn)稱ATS)特性,即禁止HTTP請(qǐng)求,必須使用支持TLS1.2的HTTPS請(qǐng)求。但是也支持在Info.plist中做一些配置,來(lái)做緩沖。需要在info.plist中加入App Transport Security Settings字段。

plist里面的結(jié)構(gòu)如下

NSAppTransportSecurity : Dictionary {
 NSAllowsArbitraryLoads : Boolean
 NSAllowsArbitraryLoadsForMedia : Boolean
 NSAllowsArbitraryLoadsInWebContent : Boolean
 NSAllowsLocalNetworking : Boolean
 NSExceptionDomains : Dictionary {
   <domain-name-string> : Dictionary  {
       NSIncludesSubdomains : Boolean
       NSExceptionAllowsInsecureHTTPLoads : Boolean
       NSExceptionMinimumTLSVersion : String
       NSExceptionRequiresForwardSecrecy : Boolean   // Default value is YES
       NSRequiresCertificateTransparency : Boolean
   }
  }
}

ATS整體配置(NSAllowsArbitraryLoads)

  1. 配置ATS生效或不生效

    App Transport Security Settings字段下加入Allow Arbitrary Loads,或NSAllowsArbitraryLoads,配置為NO。PS:如果要禁用則為YES。但是如果配置為YES會(huì)導(dǎo)致審核失敗,需要單獨(dú)向APPStrore申訴說(shuō)明。

  2. 配置web(H5)訪問(wèn)限制生效或不生效

    App Transport Security Settings字段下加入Allow Arbitrary Loads in Web ContentNSAllowsArbitraryLoadsInWebContent,默認(rèn)配置生效為NO。如果要容許訪問(wèn)任意web網(wǎng)頁(yè)內(nèi)容,配置為YES。但是如果配置為YES會(huì)導(dǎo)致審核失敗,需要單獨(dú)向APPStrore申訴說(shuō)明。

  3. 配置多媒體訪問(wèn)限制生效或不生效

    App Transport Security Settings字段下加入Allow Arbitrary Loads in Web Content,默認(rèn)配置生效為NO。設(shè)置YES,容許訪問(wèn)通過(guò)AVFoundation框架訪問(wèn)媒體內(nèi)容。

ATS根據(jù)域名配置(Exception Domains)

App Transport Security Settings字段下加入Exception DomainsNSExceptionDomains,系統(tǒng)優(yōu)先響應(yīng)NSExceptionDomains中的配置。比如之前設(shè)置NSAllowsArbitraryLoadsInMedia為 YES,然而NSExceptionDomain所代表的域名,如果沒(méi)有特殊配置,依然默認(rèn)不能訪問(wèn)不安全的媒體內(nèi)容。

  1. 加入域名配置

    Exception Domains下,添加字典。其中key為域名的名稱,比如baidu.com。

  2. 容許訪問(wèn)HTTP

    在步驟1對(duì)應(yīng)的域名字典下,加入字段NSExceptionAllowsInsecureHTTPLoads.默認(rèn)為NO,如果設(shè)置YES,則容許訪問(wèn)HTTP

  3. 容許TLS支持非正向保密算法(Perfect Forward Secrecy)

    在步驟1對(duì)應(yīng)的域名字典下,加入字段NSExceptionRequiresForwardSecrecy.默認(rèn)為YES。如果設(shè)置為NO,則支持非正向保密的加密算法。

    正向保密算法(Forward Secrecy),指如果通信密鑰泄露,使用FS算法,可以保證這個(gè)密鑰泄露只會(huì)影響之后的加密數(shù)據(jù),之前的加密數(shù)據(jù)無(wú)法解密。主要防止攻擊者保存之前的數(shù)據(jù),等到私鑰泄露之后再解密數(shù)據(jù)。這個(gè)算法的基礎(chǔ)是基于橢圓曲線向前保密的秘鑰交換算法ECDHE(Elliptic Curve Diffie-Hellman Ephemeral)。這些算法有:

    TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
    TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
    TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
    TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
    

    如果設(shè)置為NO,則非正向保密算法,有下面幾種:

    TLS_RSA_WITH_AES_256_GCM_SHA384
    TLS_RSA_WITH_AES_128_GCM_SHA256
    TLS_RSA_WITH_AES_256_CBC_SHA256
    TLS_RSA_WITH_AES_256_CBC_SHA
    TLS_RSA_WITH_AES_128_CBC_SHA256
    TLS_RSA_WITH_AES_128_CBC_SHA
    

    具體原理參考TLS/SSL 高級(jí)進(jìn)階

  4. 容許支持低版本的TLS算法。

    在步驟1對(duì)應(yīng)的域名字典下,加入字段NSExceptionMinimumTLSVersion。值為對(duì)應(yīng)的支持的最低版本。包含下面值

     TLSv1.0
     TLSv1.1
     TLSv1.2
  1. 包含域名下的所有子域名。

    在步驟1對(duì)應(yīng)的域名字典下,加入字段NSIncludesSubdomains。默認(rèn)為NO。如果配置為YES則包含域名下的所有子域名。

  2. 開(kāi)啟Certificate Transparency

    在步驟1對(duì)應(yīng)的域名字典下,加入字段NSRequiresCertificateTransparency,這個(gè)默認(rèn)為NO.如果設(shè)為YES,則開(kāi)啟Certificate Transparency。這個(gè)是IETF啟動(dòng)的一個(gè)開(kāi)源項(xiàng)目,目的是進(jìn)一步驗(yàn)證證書(shū)是否安全。個(gè)人覺(jué)得沒(méi)什么用,沒(méi)必要開(kāi)啟。

ATS各種字段含義說(shuō)明

主要的幾個(gè)key:

  • NSAllowsArbitraryLoads

    默認(rèn)NO。如果設(shè)置為YES,則不生效ATS規(guī)則。但是配置在NSExceptionDomains里面的規(guī)則,按照里面的規(guī)則生效。配置為YES,提交APP Strore需要說(shuō)明

  • NSAllowsArbitraryLoadsForMedia

    默認(rèn)NO.如果設(shè)置為YES,那使用AVFoundation加載資源不生效ATS。

  • NSAllowsArbitraryLoadsInWebContent

    默認(rèn)NO.如果設(shè)置為YES.使用webview加載的頁(yè)面資源不生效ATS。

  • NSExceptionDomains

    用于單獨(dú)配置其他域名ATS策略的鍵。值應(yīng)該是字典類型。

下面是NSExceptionDomains相關(guān)的key

  • NSIncludesSubdomains

    默認(rèn)NO。如果設(shè)置為YES,則生效此域名下的子域名

  • NSExceptionAllowsInsecureHTTPLoads

    默認(rèn)NO。如果設(shè)置為YES,則容許HTTP請(qǐng)求。設(shè)置YES,在審核時(shí)需要提供說(shuō)明。

  • NSExceptionMinimumTLSVersion

    默認(rèn)TLSv1.2??梢栽O(shè)置為:TLSv1.0、TLSv1.1。在審核時(shí)需要提供說(shuō)明

  • NSExceptionRequiresForwardSecrecy

    默認(rèn)YES。設(shè)置為NO標(biāo)示不支持正向保密。

  • NSRequiresCertificateTransparency

    默認(rèn)NO。如果設(shè)置為YES,開(kāi)啟Certificate Transparency。

上面的是方便本人查找,詳細(xì)設(shè)置case也可以參考[ATS 官方文檔] (https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW35) 。目前過(guò)渡階段最多出現(xiàn)的是第三方不兼容的問(wèn)題+不支持NSExceptionRequiresForwardSecrecy+TLS版本不到1.2+h5訪問(wèn)的鏈接不支持ATS。按照要求配置就可以了,最重要的是推動(dòng)第三方和自己后臺(tái)使用HTTPS。自己的后臺(tái)如果要求不高,可以用自制證書(shū)。

推薦的一個(gè)配置:

自己的域名使用最安全的方案,防止被蘋(píng)果拒絕。第三方可以按照需求配置,但是審核時(shí)也建議進(jìn)一步說(shuō)明。

 NSAppTransportSecurity
 NSExceptionDomains
 "domain-i-control.example.com" // 后臺(tái)的域名
 NSExceptionAllowsInsecureHTTPLoads = NO //不容許HTTP
 NSExceptionRequiresForwardSecrecy = YES //支持正向加密
 NSExceptionMinimumTLSVersion = "TLSv1.2" //使用1.2版本
 NSIncludesSubdomains = YES //包含子域名
 "other-domain-i-control.example.com" //部分不支持的第三方域名
 NSExceptionAllowsInsecureHTTPLoads = NO  //支持http
 NSExceptionRequiresForwardSecrecy = YES  //不支持正向加密
 NSExceptionMinimumTLSVersion = "TLSv1.0" //第三方支持的TLS版本
 NSAllowsArbitraryLoads = NO

使用AFNetworking配置HTTPS安全

AFNetworking是最常用的網(wǎng)絡(luò)框架。所以以這個(gè)為基礎(chǔ)說(shuō)明一些配置信息。本人是使用2.6版本的。3.x版本和2.6相比安全驗(yàn)證的邏輯沒(méi)有變化,可以參考。2.6之前的版本,建議所有配置項(xiàng)顯示配置,不要用默認(rèn)配置(因?yàn)橛袀€(gè)版本有漏洞,默認(rèn)不校驗(yàn)域名)。下面先講解下配置參數(shù),清楚之后再講解代碼實(shí)現(xiàn)。

AFSecurityPolicy說(shuō)明

AFNetworking使用AFSecurityPolicy類來(lái)管理安全策略。

主要的屬性和方法:

@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode; //證書(shū)驗(yàn)證的策略
@property (nonatomic, assign) BOOL allowInvalidCertificates; //是否容許無(wú)效的證書(shū)
@property (nonatomic, assign) BOOL validatesDomainName; //是否驗(yàn)證證書(shū)的域名
@property (nonatomic, strong, nullable) NSArray *pinnedCertificates; //app自己導(dǎo)入的證書(shū)文件,默認(rèn)情況下主bundle里面的.cer文件都會(huì)導(dǎo)入到這個(gè)數(shù)組里。

validatesDomainName 說(shuō)明

是否容許證書(shū)包含的域名和實(shí)際訪問(wèn)的域名不匹配,默認(rèn)為YES。采用的策略為:

  1. 如果validatesDomainName == YES,則開(kāi)啟域名驗(yàn)證。如果allowInvalidCertificates == NO,則不容許所用的證書(shū)里面的域名和實(shí)際域名不一致。如果allowInvalidCertificates == YES,則忽略域名驗(yàn)證,直接按照AFSSLPinningMode方式驗(yàn)證。

  2. 如果validatesDomainName == NO,則不對(duì)證書(shū)做域名驗(yàn)證。

allowInvalidCertificates 說(shuō)明

是否容許無(wú)效證書(shū),默認(rèn)為NO。采用的策略為:

  1. 如果allowInvalidCertificates == YES,則容許使用自制證書(shū),或容許CA頒發(fā)的證書(shū)或系統(tǒng)信任的第三方證書(shū)(比如手動(dòng)信任Charles證書(shū))無(wú)效(包括域名無(wú)效和超過(guò)有效期)

  2. 如果allowInvalidCertificates == NO,那無(wú)法使用自制證書(shū),且不容許CA頒發(fā)的證書(shū)或系統(tǒng)信任的第三方證書(shū)(比如手動(dòng)信任Charles證書(shū))超過(guò)有效期。如果配置了validatesDomainName == YES,則容許證書(shū)的域名不匹配,否則也不容許域名不匹配。

SSLPinningMode 說(shuō)明

證書(shū)文件實(shí)體驗(yàn)證策略,默認(rèn)為AFSSLPinningModeNone。AFSSLPinningMode包括的值為:

typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
 AFSSLPinningModeNone,
 AFSSLPinningModePublicKey,
 AFSSLPinningModeCertificate
};
  • AFSSLPinningModeNone。AFNetworking默認(rèn)配置模式。采用的策略為:

    1. 如果容許無(wú)效證書(shū)(allowInvalidCertificates == YES),則直接返回驗(yàn)證成功(YES)

    2. 如果不容許無(wú)效證書(shū)(allowInvalidCertificates == YES),則驗(yàn)證證書(shū)是否有效:是否是CA頒發(fā)機(jī)構(gòu)頒發(fā)的或者是否是系統(tǒng)信任的第三方證書(shū)(比如手動(dòng)信任Charles證書(shū))。如果另外配置了validatesDomainName == YES,則需要證書(shū)對(duì)應(yīng)的域名是否匹配。如果配置了validatesDomainName == NO,則不驗(yàn)證證書(shū)對(duì)應(yīng)的域名是否匹配。

  • AFSSLPinningModePublicKey。驗(yàn)證證書(shū)公鑰模式。采用的策略為:

    1. 如果容許無(wú)效證書(shū)(allowInvalidCertificates == YES),則比對(duì)服務(wù)端發(fā)來(lái)的證書(shū)鏈中的公鑰和自己加入的所有證書(shū)的的公鑰是否匹配,只要有一個(gè)證書(shū)匹配就返回成功。

    2. 如果不容許無(wú)效證書(shū)(allowInvalidCertificates == YES),則先驗(yàn)證證書(shū)是否有效:是否是CA頒發(fā)機(jī)構(gòu)頒發(fā)的或者是否是系統(tǒng)信任的第三方證書(shū)(比如手動(dòng)信任Charles證書(shū))。如果另外配置了validatesDomainName == YES,則需要證書(shū)對(duì)應(yīng)的域名是否匹配。如果配置了validatesDomainName == NO,則不驗(yàn)證證書(shū)對(duì)應(yīng)的域名是否匹配。驗(yàn)證通過(guò)后,則比對(duì)服務(wù)端發(fā)來(lái)的證書(shū)鏈中的公鑰和自己加入的所有證書(shū)的的公鑰是否匹配,只要有一個(gè)證書(shū)匹配就返回成功。

  • AFSSLPinningModeCertificate。證書(shū)完全匹配模式。采用的策略為:

    1. 如果容許無(wú)效證書(shū)(allowInvalidCertificates == YES),則將自己導(dǎo)入的所有證書(shū)作為錨點(diǎn),判斷服務(wù)端是否有效。如果有效,判斷服務(wù)端證書(shū)鏈中的證書(shū)中,是否有證書(shū)包含在導(dǎo)入的證書(shū)里(使用二進(jìn)制比較,也就是必須完全一樣)。

    2. 如果不容許無(wú)效證書(shū)(allowInvalidCertificates == YES),則先驗(yàn)證證書(shū)是否有效:是否是CA頒發(fā)機(jī)構(gòu)頒發(fā)的或者是否是系統(tǒng)信任的第三方證書(shū)(比如手動(dòng)信任Charles證書(shū))。如果另外配置了validatesDomainName == YES,則需要證書(shū)對(duì)應(yīng)的域名是否匹配。如果配置了validatesDomainName == NO,則不驗(yàn)證證書(shū)對(duì)應(yīng)的域名是否匹配。驗(yàn)證通過(guò)后,則將自己導(dǎo)入的所有證書(shū)作為錨點(diǎn),判斷服務(wù)端證書(shū)是否有效。如果有效,判斷服務(wù)端證書(shū)鏈中的證書(shū)中,是否有證書(shū)包含在導(dǎo)入的證書(shū)里(使用二進(jìn)制比較,也就是必須完全一樣)。

上面的比較繞,其實(shí)就是三個(gè)配置的組合。下面把這幾種組合起來(lái),看看驗(yàn)證了什么,使用于什么策略。其中AC表示allowInvalidCertificates,VD表示validatesDomainName。需要的可以去查這個(gè)表來(lái)決定方案。

場(chǎng)景 mode AC VD 驗(yàn)證策略 適用場(chǎng)景 不適用場(chǎng)景
1 None NO YES 1.驗(yàn)證證書(shū)是否為信任的頒發(fā)機(jī)構(gòu)頒發(fā)或是否為用戶手動(dòng)信任的證書(shū)2.驗(yàn)證證書(shū)是否過(guò)期3.驗(yàn)證證書(shū)域名是否匹配 1.AF默認(rèn)的安全策略2.對(duì)于安全有基礎(chǔ)的要求3.使用CA機(jī)構(gòu)頒發(fā)的證書(shū) 1.使用自制證書(shū)的2.不容許使用第三方抓包工具抓包的應(yīng)用
2 None NO NO 1.驗(yàn)證證書(shū)是否為信任的頒發(fā)機(jī)構(gòu)頒發(fā)或是否為用戶手動(dòng)信任的證書(shū)2.驗(yàn)證證書(shū)是否過(guò)期 1.證書(shū)是正規(guī)CA頒發(fā)的。但是使用的域名不是證書(shū)中的域名 1.存在風(fēng)險(xiǎn),會(huì)導(dǎo)致攻擊方使用自己的合法的CA證書(shū)進(jìn)行攻擊2.使用自制證書(shū)的3.不容許使用第三方抓包工具抓包的應(yīng)用
3 None YES YES /NO 不對(duì)證書(shū)做任何驗(yàn)證 請(qǐng)勿使用這兒配置。 1.對(duì)安全沒(méi)有要求的 1.對(duì)安全有要求的
4 PublicKey NO YES 1.驗(yàn)證證書(shū)是否為信任的頒發(fā)機(jī)構(gòu)頒發(fā)或是否為用戶手動(dòng)信任的證書(shū)2.驗(yàn)證證書(shū)是否過(guò)期3.驗(yàn)證證書(shū)域名是否匹配4.驗(yàn)證證書(shū)和埋入的證書(shū)的公鑰是否一致 1.證書(shū)是正規(guī)CA頒發(fā)的。2.對(duì)安全有比較高的需求3.需要本地APP中導(dǎo)入證書(shū)4.禁止第三方工具抓包5.證書(shū)過(guò)期后只要保證公鑰一致,就可以保證請(qǐng)求有效 1.使用自制證書(shū)的2.害怕攻擊者拿到私鑰或公鑰文件,偽造證書(shū)(概率極低,因?yàn)樾枰狢A機(jī)構(gòu)再簽發(fā))3.證書(shū)過(guò)期需要更換,但是新舊證書(shū)公鑰不同
5 PublicKey NO NO 1.驗(yàn)證證書(shū)是否為信任的頒發(fā)機(jī)構(gòu)頒發(fā)或是否為用戶手動(dòng)信任的證書(shū)2.驗(yàn)證證書(shū)是否過(guò)期3.驗(yàn)證證書(shū)和埋入的證書(shū)的公鑰是否一致 1.證書(shū)是正規(guī)CA頒發(fā)的。2.需要本地APP中導(dǎo)入證書(shū)3.禁止第三方工具抓包4.使用的域名和證書(shū)域名不一致5.證書(shū)過(guò)期后只要保證公鑰一致,就可以保證請(qǐng)求有效 1.使用自制證書(shū)的2.害怕攻擊者拿到私鑰或公鑰文件,偽造證書(shū)(概率極低,因?yàn)樾枰狢A機(jī)構(gòu)再簽發(fā))3.證書(shū)過(guò)期需要更換,但是新舊證書(shū)公鑰不同
6 PublicKey YES YES/NO 1.驗(yàn)證證書(shū)和埋入的證書(shū)公鑰是否一致 1.使用自制證書(shū)2.需要本地APP中導(dǎo)入證書(shū)3.禁止第三方工具抓包4.不需要關(guān)心證書(shū)的有效期 1.攻擊者可以拿到私鑰或公鑰文件,偽造證書(shū)。相對(duì)于場(chǎng)景4和5,更容易攻擊一些。2.攻擊者可以用不在有效期的證書(shū)對(duì)進(jìn)行攻擊
7 Certificate NO YES 1.驗(yàn)證證書(shū)域名是否匹配2.驗(yàn)證證書(shū)是否為信任的頒發(fā)機(jī)構(gòu)頒發(fā)或是否為用戶手動(dòng)信任的證書(shū)3.驗(yàn)證證書(shū)是否過(guò)期4.驗(yàn)證證書(shū)和埋入的證書(shū)是否完全一致 1.證書(shū)是正規(guī)CA頒發(fā)的。2.對(duì)安全有最高的需求3.需要本地APP中導(dǎo)入證書(shū)4.禁止第三方工具抓包 1.需要考慮證書(shū)更新的場(chǎng)景2.證書(shū)如果失效,客戶端網(wǎng)絡(luò)請(qǐng)求將會(huì)失效3.自制證書(shū)
8 Certificate NO NO 1.驗(yàn)證證書(shū)是否為信任的頒發(fā)機(jī)構(gòu)頒發(fā)或是否為用戶手動(dòng)信任的證書(shū)2.驗(yàn)證證書(shū)是否過(guò)期3.驗(yàn)證證書(shū)和埋入的證書(shū)是否完全一致 1.證書(shū)是正規(guī)CA頒發(fā)的。2.對(duì)安全有最高的需求3.需要本地APP中導(dǎo)入證書(shū)4.禁止第三方工具抓包5.證書(shū)域名和實(shí)際域名不一致 1.需要考慮證書(shū)更新的場(chǎng)景2.證書(shū)如果失效,客戶端網(wǎng)絡(luò)請(qǐng)求將會(huì)失效3.自制證書(shū)4.攻擊者拿到公私鑰的前提下,可以利用不校驗(yàn)域名,攻擊或重定向其他域名。
9 Certificate YES YES 1.驗(yàn)證證書(shū)的域名是否匹配?2.驗(yàn)證證書(shū)是否過(guò)期?3.驗(yàn)證證書(shū)和埋入的證書(shū)是否完全一致 1.使用自制證書(shū)2.需要本地APP中導(dǎo)入證書(shū)3.禁止中間人攻擊 1.需要考慮證書(shū)更新的場(chǎng)景2.證書(shū)如果失效,客戶端網(wǎng)絡(luò)請(qǐng)求將會(huì)失效3.無(wú)法作廢不安全的證書(shū)。在攻擊者拿到公私鑰的前提下,可以監(jiān)聽(tīng)數(shù)據(jù)。
10 Certificate YES NO 1.驗(yàn)證證書(shū)是否過(guò)期?2.驗(yàn)證證書(shū)和埋入的證書(shū)是否完全一致 1.使用自制證書(shū)2.需要本地APP中導(dǎo)入證書(shū)3.禁止禁止中間人攻擊 1.需要考慮證書(shū)更新的場(chǎng)景2.證書(shū)如果失效,客戶端網(wǎng)絡(luò)請(qǐng)求將會(huì)失效3.攻擊者拿到公私鑰的前提下,可以利用不校驗(yàn)域名,定位到其他域名。

相關(guān)問(wèn)題

下面是一些疑問(wèn):

  1. 如何選擇合適的方案?

    1. 建議對(duì)安全沒(méi)有特別要求的或在測(cè)試環(huán)境下方便抓包,采用默認(rèn)規(guī)則就可以了,重要的數(shù)據(jù)單獨(dú)做加密。即選場(chǎng)景1
    1. 要校驗(yàn)域名,即:validatesDomainName不要設(shè)置為NO。如果設(shè)為NO,不校驗(yàn)域名,也最好自己加一層驗(yàn)證方法。
    1. 如果是自制證書(shū),allowInvalidCertificates設(shè)置為YES。如果是ca頒發(fā)的證書(shū)則建議設(shè)置為YES。
    1. 無(wú)論是使用AFSSLPinningModePublicKey還是AFSSLPinningModeCertificate都應(yīng)該考慮證書(shū)失效需要更換的問(wèn)題。
    1. 如果用AFSSLPinningModePublicKey方式,使用場(chǎng)景6只要保證后續(xù)更換的證書(shū)公鑰不變化就可以了。個(gè)人覺(jué)得是安全和方便性最平衡的一種模式,只要私鑰不泄露就可以了。這個(gè)要求公司的證書(shū)管理機(jī)構(gòu)知道這點(diǎn),不過(guò)如果出了意外,也可以延緩部署。
    1. 最安全的方案是7。也就是強(qiáng)校驗(yàn),漏洞最少,安全防護(hù)最高。但是必須考慮證書(shū)失效更換的問(wèn)題。
  2. 如何防止證書(shū)過(guò)期導(dǎo)致不過(guò)的問(wèn)題? 有以下方案:

    1. 可以用場(chǎng)景6,保證后續(xù)更換的證書(shū)公鑰不變化就可以了
    1. APP強(qiáng)制升級(jí),全局通知,熱更新等保護(hù)通道,建議不要使用強(qiáng)校驗(yàn)策略,使用強(qiáng)的加密手段保證安全,作為最后手段。
    1. 加入證書(shū)更新的通道,每次應(yīng)用啟動(dòng)的時(shí)候訪問(wèn),查看是否有證書(shū)更新,如果有就去下載證書(shū)。
  3. 證書(shū)更新有什么方案?

    1. 建議啟動(dòng)檢查是否有證書(shū)更新,可以合并在檢查APP更新或熱更新里面。
    1. 發(fā)現(xiàn)有更新的時(shí)候,服務(wù)端把證書(shū)二進(jìn)制數(shù)據(jù)轉(zhuǎn)為16進(jìn)制字符串下發(fā)給客戶端。服務(wù)端對(duì)數(shù)據(jù)使用私鑰簽名,客戶端使用公鑰對(duì)數(shù)據(jù)進(jìn)行驗(yàn)簽。
    1. 客戶端將下載的文件按照簽名等規(guī)則保存。下次加載前,繼續(xù)對(duì)文件驗(yàn)證簽名,保證沒(méi)有篡改。
  4. 對(duì)于場(chǎng)景9,容許無(wú)效的證書(shū),使用AFSSLPinningModeCertificate模式,為什么說(shuō)明里面還說(shuō)會(huì)驗(yàn)證證書(shū)過(guò)期?

    我個(gè)人也不確定,但是這個(gè)模式在加入證書(shū)錨點(diǎn)后,代碼里還是會(huì)調(diào)用AFServerTrustIsValid()方法,然后再匹配證書(shū)數(shù)據(jù)是否一致。這個(gè)AFServerTrustIsValid()最終調(diào)用的是系統(tǒng)驗(yàn)證的方法,不確定系統(tǒng)是否還是會(huì)驗(yàn)證有效期,還是只驗(yàn)證包含證書(shū)就可以了,目前沒(méi)有手段驗(yàn)證,大概率認(rèn)為系統(tǒng)還是會(huì)驗(yàn)證是否過(guò)期。所以相對(duì)來(lái)說(shuō)驗(yàn)證AFSSLPinningModePublicKey需要考慮更新的情況更少。

  5. 如果使用AFSSLPinningModePublicKey模式,更換證書(shū)怎么保證公鑰不變?

    參考上一篇文章的附錄,有一步是使用私鑰.key文件生成.csr。只要.key.csr,下次簽發(fā)的時(shí)候直接用這兩文件,簽發(fā)就可以了。這樣能保證下次的證書(shū)公鑰也不變化。建議生產(chǎn)私鑰的時(shí)候使用位數(shù)在2048位以上,可以保證安全性。

代碼具體實(shí)現(xiàn)

導(dǎo)出證書(shū)

建議向自己公司的網(wǎng)絡(luò)管理員導(dǎo)出對(duì)應(yīng)的crt文件?;蛘呤褂妹睿?/p>

openssl s_client -connect www.google.com:443 </dev/null 2>/dev/null | openssl x509 -outform DER > https.cer //獲取www.google.com:443的ssl證書(shū),地址可以換成自己的

建議最好導(dǎo)出根證書(shū)的crt文件。因?yàn)楦C書(shū)crt文件有效期長(zhǎng),很少更換。

如果是crt格式,使用時(shí)需要轉(zhuǎn)化為cer格式。兩種轉(zhuǎn)化方式都可以:

  1. 命令行 openssl x509 -in 你的證書(shū).crt -out 你的證書(shū).cer -outform der

  2. 通過(guò)電腦導(dǎo)出。

    • 雙擊crt,安裝到鑰匙鏈中。

    • 鑰匙鏈中選中需要導(dǎo)出的證書(shū),鼠標(biāo)右鍵,菜單中選擇>>導(dǎo)出,點(diǎn)擊存儲(chǔ)即可。

然后將.cer文件導(dǎo)入到工程中。注意選Copy items if needed .

設(shè)置生效規(guī)則

代碼實(shí)現(xiàn)其實(shí)非常簡(jiǎn)單,重要的是規(guī)則的設(shè)置,建議認(rèn)真搞清楚上面講的配置說(shuō)明,然后再配置。

//設(shè)置模式
 AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
 //設(shè)置是否驗(yàn)證域名,不建議設(shè)置為NO
 policy.validatesDomainName = YES;
 //設(shè)置是否容許無(wú)效的證書(shū),自制證書(shū)選YES
 policy.allowInvalidCertificates = NO;


 //AF如果模式為AFSSLPinningModeCertificate或AFSSLPinningModePublicKey會(huì)默認(rèn)導(dǎo)入mainBundle里的所有cer文件,如果沒(méi)有特別需求,沒(méi)必要實(shí)現(xiàn)下面加載cer的代碼
 //先導(dǎo)入證書(shū)路徑
 NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"myCer" ofType:@"cer"];//證書(shū)的路徑
 // 有多個(gè)加多個(gè)
 NSData *certData = [NSData dataWithContentsOfFile:cerPath];
 policy.pinnedCertificates = @[certData];


 AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
 //生效policy
 manager.securityPolicy = policy;

 //調(diào)用
 NSString *host = @"exmple";
 NSDictionary *params = @{};
 [manager GET:host
 parameters:params
 success:^(AFHTTPRequestOperation *_Nonnull operation, id _Nonnull responseObject) {

 }
 failure:^(AFHTTPRequestOperation *_Nonnull operation, NSError *_Nonnull error){
   _hasUpdating = NO;
 }];

具體配置請(qǐng)參考上面AFSecurityPolicy的介紹。通常測(cè)試環(huán)境下使用默認(rèn)模式,其他環(huán)境使用校驗(yàn)?zāi)J健?/p>

驗(yàn)證策略源碼解讀

AF 2.6版本,在系統(tǒng)框架需要進(jìn)行證書(shū)驗(yàn)證的時(shí)候會(huì)調(diào)用AFURLCOnnectionOpeation.m中的evaluateServerTrust:forDomain方法:

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
 forDomain:(NSString *)domain
{
 //自制證書(shū)且驗(yàn)證域名不能用AFSSLPinningModeNone模式
 if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode ==     
     AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
 //     https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
 //  According to the docs, you should only trust your provided certs for evaluation.
 //  Pinned certificates are added to the trust. Without pinned certificates,
 //  there is nothing to evaluate against.
 //
 //  From Apple Docs:
 //          "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
 //           Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
 NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
   return NO;
 }

 NSMutableArray *policies = [NSMutableArray array];
 if (self.validatesDomainName) {
 //如果要驗(yàn)證域名需要把域名加入到規(guī)則里
   [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
 } else {
   [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
 }

 //設(shè)置驗(yàn)證規(guī)則,如果有域名驗(yàn)證就加入域名驗(yàn)證
 SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

 if (self.SSLPinningMode == AFSSLPinningModeNone) {
   //AFSSLPinningModeNone下,如果容許無(wú)效證書(shū)或者證書(shū)通過(guò)驗(yàn)證就返回成功,否則返回失敗
   return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
 } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
   //其他模式,如果沒(méi)有容許無(wú)效證書(shū),就做證書(shū)驗(yàn)證,失效了就返回失敗
   return NO;
 }

 //抽取服務(wù)端的所有證書(shū)鏈數(shù)據(jù)
 NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
 switch (self.SSLPinningMode) {
 //不會(huì)進(jìn)入到這個(gè)case
 case AFSSLPinningModeNone:
   default:
     return NO;
 case AFSSLPinningModeCertificate: {

   //將導(dǎo)入的證書(shū)加入到錨點(diǎn)里
   NSMutableArray *pinnedCertificates = [NSMutableArray array];
   for (NSData *certificateData in self.pinnedCertificates) {
     [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge   CFDataRef)certificateData)];
   }

   // PS:查看相關(guān)文檔發(fā)現(xiàn),如果只調(diào)用了SecTrustSetAnchorCertificates而沒(méi)有調(diào)用  SecTrustSetAnchorCertificatesOnly(serverTrust,false)方法,會(huì)導(dǎo)致只信任  SecTrustSetAnchorCertificatesOnly設(shè)置的錨點(diǎn)的證書(shū),不信任系統(tǒng)默認(rèn)內(nèi)置的錨點(diǎn)證書(shū)
   SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

   //驗(yàn)證是否證書(shū)是否在加入的錨點(diǎn)證書(shū)列表里。猜測(cè)會(huì)驗(yàn)證證書(shū)的有效期,如果有域名驗(yàn)證,驗(yàn)證域名。
   if (!AFServerTrustIsValid(serverTrust)) {
     return NO;
   }

   //查看證書(shū)鏈中的證書(shū)是否和埋入的證書(shū)完全一致。
   NSUInteger trustedCertificateCount = 0;
   for (NSData *trustChainCertificate in serverCertificates) {
     if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
       trustedCertificateCount++;
     }
    }
    return trustedCertificateCount > 0;
 }
 case AFSSLPinningModePublicKey: {
   NSUInteger trustedPublicKeyCount = 0;
   //從證書(shū)鏈中抽取所有公鑰列表
   NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
 //驗(yàn)證證書(shū)鏈中的公鑰是否在導(dǎo)入的證書(shū)里面的公鑰鏈中
   for (id trustChainPublicKey in publicKeys) {
     for (id pinnedPublicKey in self.pinnedPublicKeys) {
       if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge   SecKeyRef)pinnedPublicKey)) {
         trustedPublicKeyCount += 1;
       }
     }
   }
     return trustedPublicKeyCount > 0;
   }
 }

 return NO;
}

里面實(shí)際驗(yàn)證是否有效的方法為:AFServerTrustIsValid(SecTrustRef serverTrust)。實(shí)現(xiàn):

 static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
 BOOL isValid = NO;
 SecTrustResultType result;
 // 具體實(shí)現(xiàn),因?yàn)榭床坏皆创a無(wú)法確認(rèn),應(yīng)該是標(biāo)準(zhǔn)的證書(shū)鏈驗(yàn)證方式,驗(yàn)證證書(shū)有效性,驗(yàn)證證書(shū)鏈的對(duì)應(yīng)的CA根證書(shū)是否在頒發(fā)機(jī)構(gòu)里或者是否是用戶手動(dòng)同意或拒絕的證書(shū)。如果設(shè)置了SecTrustSetAnchorCertificates,則驗(yàn)證是否在SecTrustSetAnchorCertificates方法設(shè)置的錨點(diǎn)證書(shū)里(不包含系統(tǒng)的證書(shū))
 __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);

 //kSecTrustResultUnspecified:證書(shū)通過(guò)驗(yàn)證,但用戶沒(méi)有設(shè)置這些證書(shū)是否被信任
 //kSecTrustResultProceed:證書(shū)通過(guò)驗(yàn)證,用戶有操作設(shè)置了證書(shū)被信任,例如在彈出的是否信任的alert框中選擇always trust
 isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);

_out:
   return isValid;
}

WebView進(jìn)行證書(shū)驗(yàn)證

如果不配置,webview執(zhí)行系統(tǒng)默認(rèn)的策略。因?yàn)轫?xiàng)目中沒(méi)用到,暫時(shí)不敢評(píng)判,下面是相關(guān)博客供參考。

參考文獻(xiàn)

  1. iOS 10 適配 ATS(app支持https通過(guò)App Store審核)

  2. [ATS 官方文檔] (https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW35)

  3. RSA的公鑰和私鑰到底哪個(gè)才是用來(lái)加密和哪個(gè)用來(lái)解密?

  4. iOS安全系列之二:HTTPS進(jìn)階

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容