導(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)
-
配置ATS生效或不生效
在
App Transport Security Settings字段下加入Allow Arbitrary Loads,或NSAllowsArbitraryLoads,配置為NO。PS:如果要禁用則為YES。但是如果配置為YES會(huì)導(dǎo)致審核失敗,需要單獨(dú)向APPStrore申訴說(shuō)明。 -
配置web(H5)訪問(wèn)限制生效或不生效
在
App Transport Security Settings字段下加入Allow Arbitrary Loads in Web Content或NSAllowsArbitraryLoadsInWebContent,默認(rèn)配置生效為NO。如果要容許訪問(wèn)任意web網(wǎng)頁(yè)內(nèi)容,配置為YES。但是如果配置為YES會(huì)導(dǎo)致審核失敗,需要單獨(dú)向APPStrore申訴說(shuō)明。 -
配置多媒體訪問(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 Domains或NSExceptionDomains,系統(tǒng)優(yōu)先響應(yīng)NSExceptionDomains中的配置。比如之前設(shè)置NSAllowsArbitraryLoadsInMedia為 YES,然而NSExceptionDomain所代表的域名,如果沒(méi)有特殊配置,依然默認(rèn)不能訪問(wèn)不安全的媒體內(nèi)容。
-
加入域名配置
在
Exception Domains下,添加字典。其中key為域名的名稱,比如baidu.com。 -
容許訪問(wèn)HTTP
在步驟1對(duì)應(yīng)的域名字典下,加入字段
NSExceptionAllowsInsecureHTTPLoads.默認(rèn)為NO,如果設(shè)置YES,則容許訪問(wèn)HTTP -
容許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)階。
-
容許支持低版本的TLS算法。
在步驟1對(duì)應(yīng)的域名字典下,加入字段
NSExceptionMinimumTLSVersion。值為對(duì)應(yīng)的支持的最低版本。包含下面值
TLSv1.0
TLSv1.1
TLSv1.2
-
包含域名下的所有子域名。
在步驟1對(duì)應(yīng)的域名字典下,加入字段
NSIncludesSubdomains。默認(rèn)為NO。如果配置為YES則包含域名下的所有子域名。 -
開(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。采用的策略為:
如果validatesDomainName == YES,則開(kāi)啟域名驗(yàn)證。如果allowInvalidCertificates == NO,則不容許所用的證書(shū)里面的域名和實(shí)際域名不一致。如果allowInvalidCertificates == YES,則忽略域名驗(yàn)證,直接按照AFSSLPinningMode方式驗(yàn)證。
如果validatesDomainName == NO,則不對(duì)證書(shū)做域名驗(yàn)證。
allowInvalidCertificates 說(shuō)明
是否容許無(wú)效證書(shū),默認(rèn)為NO。采用的策略為:
如果allowInvalidCertificates == YES,則容許使用自制證書(shū),或容許CA頒發(fā)的證書(shū)或系統(tǒng)信任的第三方證書(shū)(比如手動(dòng)信任Charles證書(shū))無(wú)效(包括域名無(wú)效和超過(guò)有效期)
如果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)配置模式。采用的策略為:
如果容許無(wú)效證書(shū)(allowInvalidCertificates == YES),則直接返回驗(yàn)證成功(YES)
如果不容許無(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ū)公鑰模式。采用的策略為:
如果容許無(wú)效證書(shū)(allowInvalidCertificates == YES),則比對(duì)服務(wù)端發(fā)來(lái)的證書(shū)鏈中的公鑰和自己加入的所有證書(shū)的的公鑰是否匹配,只要有一個(gè)證書(shū)匹配就返回成功。
如果不容許無(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ū)完全匹配模式。采用的策略為:
如果容許無(wú)效證書(shū)(allowInvalidCertificates == YES),則將自己導(dǎo)入的所有證書(shū)作為錨點(diǎn),判斷服務(wù)端是否有效。如果有效,判斷服務(wù)端證書(shū)鏈中的證書(shū)中,是否有證書(shū)包含在導(dǎo)入的證書(shū)里(使用二進(jìn)制比較,也就是必須完全一樣)。
如果不容許無(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):
-
如何選擇合適的方案?
- 建議對(duì)安全沒(méi)有特別要求的或在測(cè)試環(huán)境下方便抓包,采用默認(rèn)規(guī)則就可以了,重要的數(shù)據(jù)單獨(dú)做加密。即選場(chǎng)景1
- 要校驗(yàn)域名,即:validatesDomainName不要設(shè)置為NO。如果設(shè)為NO,不校驗(yàn)域名,也最好自己加一層驗(yàn)證方法。
- 如果是自制證書(shū),allowInvalidCertificates設(shè)置為YES。如果是ca頒發(fā)的證書(shū)則建議設(shè)置為YES。
- 無(wú)論是使用AFSSLPinningModePublicKey還是AFSSLPinningModeCertificate都應(yīng)該考慮證書(shū)失效需要更換的問(wèn)題。
- 如果用AFSSLPinningModePublicKey方式,使用場(chǎng)景6只要保證后續(xù)更換的證書(shū)公鑰不變化就可以了。個(gè)人覺(jué)得是安全和方便性最平衡的一種模式,只要私鑰不泄露就可以了。這個(gè)要求公司的證書(shū)管理機(jī)構(gòu)知道這點(diǎn),不過(guò)如果出了意外,也可以延緩部署。
- 最安全的方案是7。也就是強(qiáng)校驗(yàn),漏洞最少,安全防護(hù)最高。但是必須考慮證書(shū)失效更換的問(wèn)題。
-
如何防止證書(shū)過(guò)期導(dǎo)致不過(guò)的問(wèn)題? 有以下方案:
- 可以用場(chǎng)景6,保證后續(xù)更換的證書(shū)公鑰不變化就可以了
- APP強(qiáng)制升級(jí),全局通知,熱更新等保護(hù)通道,建議不要使用強(qiáng)校驗(yàn)策略,使用強(qiáng)的加密手段保證安全,作為最后手段。
- 加入證書(shū)更新的通道,每次應(yīng)用啟動(dòng)的時(shí)候訪問(wèn),查看是否有證書(shū)更新,如果有就去下載證書(shū)。
-
證書(shū)更新有什么方案?
- 建議啟動(dòng)檢查是否有證書(shū)更新,可以合并在檢查APP更新或熱更新里面。
- 發(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)簽。
- 客戶端將下載的文件按照簽名等規(guī)則保存。下次加載前,繼續(xù)對(duì)文件驗(yàn)證簽名,保證沒(méi)有篡改。
-
對(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需要考慮更新的情況更少。 -
如果使用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)化方式都可以:
命令行
openssl x509 -in 你的證書(shū).crt -out 你的證書(shū).cer -outform der-
通過(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)博客供參考。
