iOS https認證

iOS https認證

項目背景

最近在做iOS 熱更新忱详,出于公司信息安全限制沒使用JSPatch平臺來下發(fā)js昆淡,而是把js放自己的對象存儲服務器上匣吊。為了簡單處理,js路徑里加了對應的版本號桌肴。這樣對應版本的app會去下載該js皇筛,能下載到表示該版本有patch,沒有則表示無patch坠七。既然沒有接入JSPatch平臺來下發(fā)水醋,自然存在下發(fā)js安全的問題旗笔。比如對象存儲服務器被劫持后,黑客篡改js拄踪,就可能造成全部版本不可用的風險蝇恶。最簡單的就是讓對象存儲服務器走https,簡單暴力惶桐。

https簡單理解

https是簡單的理解為http secure艘包,就是安全的http。我們知道http是應用層協(xié)議耀盗,它緊接著的一層為運輸層(TCP/UDP)想虎。那如何做到安全的http,且不影響原本的http協(xié)議叛拷,也不影響TCP或者UDP呢舌厨?加一層,也就是SSL(Secure Socket Layer)忿薇。SSL3.0以后開始叫TSL了裙椭,最新的是TSL1.3,不過主流支持到TSL1.2署浩。https具體原理不說了揉燃,有點繁瑣,具體百度筋栋。只要知道客戶端和服務端在握手階段商量出了一個非對稱秘鑰(也就是兩個不一樣的秘鑰炊汤,可以互相解密對方加密的內容,但是比較耗時)弊攘。然后用這個非對稱秘鑰加密傳輸一個秘鑰抢腐,讓彼此知道這個秘鑰,接下來就可以有用這個秘鑰來加密需要傳輸?shù)臄?shù)據(jù)了(也就是對稱加解密)襟交。還是用圖(網(wǎng)圖)來說明吧:

https原理圖

https防中間人攻擊以及利用

通常說https可以防中間人攻擊迈倍,那么怎么做到防中間人攻擊呢?怎么做到A與B通信就是A與B捣域,而不是A與C啼染,不是C與B,甚至是A通過C與B通信焕梅?簡單說給他們各自一個憑證迹鹅,A證證明A是A,B證證明B就是B丘侠。他們在開始通信的時候先亮出彼此的證書徒欣,A驗證后發(fā)現(xiàn)確實與我通信的就是B逐样,B驗證后與我通信的就是A才開始接下里的通信蜗字。好了打肝,大家說我平時訪問https的網(wǎng)站或者網(wǎng)址,都沒用到啥https證書挪捕,這有啥用粗梭?其實我們一直在用,只是他們隱匿的深一點级零。當我們在chrome里鍵入:https://www.baidu.com 的時候断医,瀏覽器里地址欄會出現(xiàn)一個小鎖的圖標,如下圖:

圖片

這表示該網(wǎng)站的https證書是CA(可以理解為給你發(fā)身份證的公安局)認證過的奏纪,可以放心使用了鉴嗤。那怎么認證的呢?瀏覽器和操作系統(tǒng)都會內置一些權威CA的證書(MBP里打開鑰匙串序调,選擇系統(tǒng)根證書醉锅,可有看到目前所有的根證書,選擇證書可以看到我們添加的信任的證書)发绢,

圖片

在訪問的時候這些網(wǎng)站時候硬耍,把網(wǎng)站亮出的證書與內置的權威證書對比下,如果有一個命中边酒,就表示認證通過(其實驗證的是一個證書鏈)经柴。所以不要隨便把一些未知證書導入系統(tǒng)里,這樣也會是潛在安全隱患墩朦。因為一旦你導入系統(tǒng)并信任坯认,那么它就具有系統(tǒng)內置權威CA證書一樣的功能了。大家常用的抓包工具Charles就是這樣的原理(嚴格意義上也算中間人攻擊氓涣,只是這個中間人是我們自己)鹃操。開啟了Charles后,需要你安裝Charles的證書到系統(tǒng)春哨,不然是無法攔截到并看到明文https請求的荆隘。下面圖中可以看到打開Charles,百度的證書簽發(fā)機構變成Charles了:

圖片

這就表明與我們?yōu)g覽器通信的其實是Charles這個中間人赴背。之所以能看到百度網(wǎng)址的內容椰拒,是因為Charles這個中間人訪問百度并將內容返回了給我們?yōu)g覽器。這也是為什么Charles可以看到https請求的response是明文而不是亂碼的原因凰荚。

iOS中的https認證

鑒于越來越多的安全事故燃观,3年前蘋果要求所有的App都要配置ATS開關。如果不配置便瑟,默認所有http請求都打不開而且所有驗證不通過的https請求也打不開缆毁。也是逼著公司,開發(fā)者重視安全到涂,重視用戶隱私并且盡早接入https脊框。但是可能阻力太大颁督,蘋果額外加了個允許任意請求的開關,這樣開發(fā)者就可以繞開蘋果的要求了浇雹。不過對于這個下發(fā)js的項目背景來說沉御,認證是必須的。鑒于AFNetworking(后面簡稱AF)基本上是iOS網(wǎng)絡庫的事實標準昭灵,下面以AF里認證為例說明吠裆。

AF里做證書驗證的主要類是AFSecurityPolicy,負責網(wǎng)絡請求的AFHTTPSessionManager有個該類的實例屬性烂完,用于作證書認證试疙。打開AFSecurityPolicy的m文件,發(fā)現(xiàn)里面的注釋都比較清楚抠蚣,這里只單獨說兩個屬性:

@interface AFSecurityPolicy : NSObject <NSSecureCoding, NSCopying>

/**
 The criteria by which server trust should be evaluated against the pinned SSL certificates. Defaults to `AFSSLPinningModeNone`.
 */
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;

/**
 The certificates used to evaluate server trust according to the SSL pinning mode. 

  By default, this property is set to any (`.cer`) certificates included in the target compiling AFNetworking. Note that if you are using AFNetworking as embedded framework, no certificates will be pinned by default. Use `certificatesInBundle` to load certificates from your target, and then create a new policy by calling `policyWithPinningMode:withPinnedCertificates`.
 
 Note that if pinning is enabled, `evaluateServerTrust:forDomain:` will return true if any pinned certificate matches.
 */
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;

AFSSLPinningMode枚舉定義如下:

typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
    AFSSLPinningModeNone,//對于https請求我們不用作驗證效斑,丟給系統(tǒng)做,也就是去比對系統(tǒng)內置證書
    AFSSLPinningModePublicKey,//該模式要求app提供證書柱徙,會比較證書對應的公鑰是否一致
    AFSSLPinningModeCertificate,//該模式有要求app提供證書缓屠,會比較整個完整證書,也就是驗證策略最嚴格
};

上面3種枚舉的驗證策略注釋已經(jīng)說的很明確了护侮,可以看出AFSSLPinningModeNone適合網(wǎng)站或者后臺部署了權威CA簽發(fā)的https證書敌完。AFSSLPinningModePublicKey和AFSSLPinningModeCertificate就對應我們自簽名的https證書了。既然自簽名證書羊初,自然需要我們提供證書了滨溉,也就是把證書放在工程里與app一起打包。在初始化AFHTTPSessionManager時設置securityPolicy的pinnedCertificates屬性即可长赞。下面我們來看看自簽名證書的核心驗證邏輯代碼晦攒。在AFUrlSessionManager的m文件里,可以看到有個NSURLSessionDelegate的代理方法:

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    if (self.sessionDidReceiveAuthenticationChallenge) {
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {//表示驗證服務端證書
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                if (credential) {
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

這個就是NSURLSession在發(fā)https請求時遇到要求證書驗證時的回調得哆。具體的邏輯還是在[self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]脯颜,核心代碼如下:

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(NSString *)domain
{
    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;
    }//既然選擇系統(tǒng)驗證,就不要還允許無效證書了

    NSMutableArray *policies = [NSMutableArray array];
    if (self.validatesDomainName) {
        [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
    } else {
        [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
    }

    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

    if (self.SSLPinningMode == AFSSLPinningModeNone) {//如果是系統(tǒng)驗證贩据,采用系統(tǒng)驗證的結果
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
        return NO;
    }//系統(tǒng)驗證不通過栋操,但是有設置不允許無效證書,整個驗證結果就是false了

    switch (self.SSLPinningMode) {
        case AFSSLPinningModeNone://if (self.SSLPinningMode == AFSSLPinningModeNone)已處理饱亮,實際到不了該case
        default:
            return NO;
        case AFSSLPinningModeCertificate: {//如果驗證證書
            NSMutableArray *pinnedCertificates = [NSMutableArray array];
            for (NSData *certificateData in self.pinnedCertificates) {
                [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
            }
            SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);//設置serverTrust的可信任錨點證書矾芙,也就是我們提供的證書

            if (!AFServerTrustIsValid(serverTrust)) {//如果證書無效直接返回
                return NO;
            }

            // obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
            NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
            
            for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
                if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
                    return YES;
                }
            }//查看serverTrust的證書鏈是否有一個在我們提供的證書里列表里(與app一起打包的證書)
            
            return NO;
        }
        case AFSSLPinningModePublicKey: {//驗證公鑰,不驗證整個證書
            NSUInteger trustedPublicKeyCount = 0;
            NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);

            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;
}

上面的代碼注釋解釋的很清楚了近上。需要注意的是有兩點:

  1. 對于自簽名的https證書剔宪,需要自己驗證。
  2. 打包到App里的自簽名證書會存在過期問題,需要在到期之前提前處理葱绒。

最后說一句感帅,我們的存儲服務器就是https證書就是權威CA簽發(fā)的,意味著啥都不用做Orz...

參考鏈接

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末哈街,一起剝皮案震驚了整個濱河市留瞳,隨后出現(xiàn)的幾起案子拒迅,更是在濱河造成了極大的恐慌骚秦,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件璧微,死亡現(xiàn)場離奇詭異作箍,居然都是意外死亡,警方通過查閱死者的電腦和手機前硫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門胞得,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人屹电,你說我怎么就攤上這事阶剑。” “怎么了危号?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵牧愁,是天一觀的道長。 經(jīng)常有香客問我外莲,道長猪半,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任偷线,我火速辦了婚禮磨确,結果婚禮上,老公的妹妹穿的比我還像新娘声邦。我一直安慰自己乏奥,他們只是感情好,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布亥曹。 她就那樣靜靜地躺著英融,像睡著了一般。 火紅的嫁衣襯著肌膚如雪歇式。 梳的紋絲不亂的頭發(fā)上驶悟,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音材失,去河邊找鬼痕鳍。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的笼呆。 我是一名探鬼主播熊响,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼诗赌!你這毒婦竟也來了汗茄?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤铭若,失蹤者是張志新(化名)和其女友劉穎洪碳,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叼屠,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡瞳腌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了镜雨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫂侍。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖荚坞,靈堂內的尸體忽然破棺而出挑宠,到底是詐尸還是另有隱情,我是刑警寧澤颓影,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布各淀,位于F島的核電站,受9級特大地震影響瞭空,放射性物質發(fā)生泄漏揪阿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一咆畏、第九天 我趴在偏房一處隱蔽的房頂上張望南捂。 院中可真熱鬧,春花似錦旧找、人聲如沸溺健。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鞭缭。三九已至,卻和暖如春魏颓,著一層夾襖步出監(jiān)牢的瞬間岭辣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工甸饱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留沦童,地道東北人仑濒。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像偷遗,于是被迫代替她去往敵國和親墩瞳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355