iOS - HTTPS自制證書驗證

證書類型

  • 從權(quán)威認證機構(gòu)購買的證書
    • 優(yōu)點:服務(wù)端如果使用的是這類證書的話,那么客戶端一般不需要做什么,直接使用HTTPS請求就行了,蘋果內(nèi)置了那些受信任的根證書
    • 缺點:需要花錢
  • 自制證書
    • 優(yōu)點:無需花錢
    • 缺點:這類證書是不受信任的,因此需要我們在代碼中將自制證書設(shè)置為可信任證書

自己實現(xiàn)自制證書驗證

  • 這里以NSURLSession請求為例演示下如何驗證自制證書
  • 實現(xiàn)NSURLSessionDelegate的代理方法-URLSession:didReceiveChallenge:completionHandler:
  • 只要訪問的是HTTPS請求就會調(diào)用此代理方法
  • 此代理方法的作用:對自制證書的驗證
  • 代理方法的各個參數(shù)介紹
    • challenge:挑戰(zhàn)、質(zhì)問 (包含了受保護的空間)
    • completionHandler:處理自制證書,該block的兩個參數(shù)的含義:disposition表示證書處理方式,credential表示需要處理的證書
    • disposition類型介紹
    NSURLSessionAuthChallengeUseCredential = 0,                  使用該證書,安裝該證書
    NSURLSessionAuthChallengePerformDefaultHandling = 1,         默認方式,證書被忽略
    NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2,  取消請求,證書被忽略
    NSURLSessionAuthChallengeRejectProtectionSpace = 3,          拒絕本次請求
    
    • 證書認證結(jié)果SecTrustResultType類型介紹
    -------以下認證結(jié)果表示可信證書---------
    // 證書通過驗證,是由于用戶有操作設(shè)置了證書被信任,如:在彈出的是否信任的alert框中選擇always trust
    // 一般是自制證書,非權(quán)威機構(gòu)頒發(fā)的,如:Charles的代理證書
    kSecTrustResultProceed = 1,
    // 證書通過驗證,用戶沒有設(shè)置這些證書是否被信任
    // 一般驗證權(quán)威機構(gòu)頒發(fā)的CA證書會返回這個結(jié)果
    kSecTrustResultUnspecified = 4,
    ------------------------------------
    
    -------以下認證結(jié)果表示不可信證書-------
    // 無效證書
    kSecTrustResultInvalid = 0,
    // 待確認證書              
    kSecTrustResultConfirm = 2,   
    // 被拒證書                   
    kSecTrustResultDeny = 3,      
    // 不可信證書      
    kSecTrustResultRecoverableTrustFailure = 5,       
    // 嚴重不可信證書
    kSecTrustResultFatalTrustFailure = 6, 
    // 其他錯誤       
    kSecTrustResultOtherError = 7
    
    • 受保護空間參數(shù)介紹
    // 認證方式
    challenge.protectionSpace.authenticationMethod
    
    // 返回的服務(wù)端證書
    // 如果authenticationMethod不是NSURLAuthenticationMethodServerTrust,那么serverTrust為nil
    challenge.protectionSpace.serverTrust
    
  • 自制證書認證步驟
    • 首先判斷認證方式,是否為NSURLAuthenticationMethodServerTrust
    • 獲取服務(wù)端返回的自制證書challenge.protectionSpace.serverTrust
    • 從app的資源文件中獲取內(nèi)置的本地證書(可以是多個),并設(shè)置為服務(wù)端證書的根證書,設(shè)置后會屏蔽掉系統(tǒng)的證書列表,當然可以通過SecTrustSetAnchorCertificatesOnly方法(第二個參數(shù)設(shè)置為NO)來使系統(tǒng)的證書列表繼續(xù)起作用
    • 在設(shè)置的證書列表中驗證服務(wù)端自制證書是否可信
    • 完成驗證后通過回調(diào)告知系統(tǒng)如何處理自制證書
  • 代碼實現(xiàn):這里使用的do-while編程思想不錯,避免了太多的if-else
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    // 證書的處理方式
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
    // 被處理的證書
    NSURLCredential *credential = nil;

    do
    {
        // 1、校驗認證方式是否為NSURLAuthenticationMethodServerTrust
        if (![challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
            break; /* failed */

        // 2、獲取服務(wù)端返回的證書
        SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;

        // 3、從app的資源文件中獲取內(nèi)置的本地證書(可以是多個,這里只演示只有一個內(nèi)置證書)
        // custom是你證書的名稱,記得替換
        NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"custom" ofType:@"cer"];
        NSData *caCert = [NSData dataWithContentsOfFile:cerPath];
        if(nil != caCert) {
            // 創(chuàng)建證書
            SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
//            NSCAssert(caRef != nil, @"caRef is nil");
            if(nil == caRef)
                break; /* failed */

            // 添加自制證書,這里表明了可以添加多張自制證書
            NSArray *caArray = @[(__bridge id)(caRef)];
//            NSCAssert(caArray != nil, @"caArray is nil");
            if(nil == caArray)
                break; /* failed */

            // 將內(nèi)置的本地證書列表設(shè)置為服務(wù)端證書的根證書
            // 設(shè)置可信任證書列表,設(shè)置后就只會在設(shè)置的證書列表中進行驗證,屏蔽掉系統(tǒng)的證書列表
            OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
            // 要使系統(tǒng)的證書列表繼續(xù)起作用可以調(diào)用此方法,第二個參數(shù)設(shè)置成NO即可
            SecTrustSetAnchorCertificatesOnly(serverTrust, NO);
//            NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed");
            if(errSecSuccess != status)// errSecSuccess表示沒有錯誤
                break; /* failed */
        }

        // 4、使用本地導入的證書列表驗證服務(wù)器的證書是否可信
        SecTrustResultType result = -1;
        OSStatus status = SecTrustEvaluate(serverTrust, &result);
        if(errSecSuccess != status)
            break; /* failed */
        // kSecTrustResultUnspecified and kSecTrustResultProceed are success
        BOOL allowConnect = (result == kSecTrustResultUnspecified)// 證書通過驗證,是由于用戶沒有設(shè)置這些證書是否被信任
                            || (result == kSecTrustResultProceed);// 證書通過驗證,用戶有操作設(shè)置了證書被信任
        if (!allowConnect)
            break; /* failed */

        // 5、設(shè)置證書的處理方式
        credential = [NSURLCredential credentialForTrust:serverTrust];
        disposition = NSURLSessionAuthChallengeUseCredential;
    } while(0);

    // 6、告知系統(tǒng)如何處理自制證書
    if(completionHandler) completionHandler(disposition, credential);
}

通過AFN實現(xiàn)自制證書驗證

  • AFSecurityPolicy的三種驗證模式
    • AFSSLPinningModeNone只驗證證書是否在信任列表中
    • AFSSLPinningModePublicKey先驗證證書是否在信任列表中,然后僅驗證服務(wù)端證書與客戶端證書的公鑰是否一致
    • AFSSLPinningModeCertificate先驗證證書是否在信任列表中,然后對比服務(wù)端證書和客戶端證書是否一致(不僅限于公鑰是否一致)
  • 通過給AFHTTPSessionManager設(shè)置回調(diào)setSessionDidReceiveAuthenticationChallengeBlock來驗證自制證書,然后將內(nèi)置的本地證書加入到可信任的證書列表中,即可通過證書的校驗
  • 代碼實現(xiàn)
// 記得設(shè)置個AFHTTPSessionManager屬性
@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;

- (void)validateCerByAFN {
    _sessionManager = [AFHTTPSessionManager manager];

    // 創(chuàng)建安全策略
    AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
    // 是否允許使用自制證書
    securityPolicy.allowInvalidCertificates = YES;
    // 是否需要驗證域名,默認YES
    securityPolicy.validatesDomainName = YES;
    _sessionManager.securityPolicy = securityPolicy;

    // 響應(yīng)數(shù)據(jù)序列化格式
    _sessionManager.responseSerializer = [AFJSONResponseSerializer serializer];
    // 設(shè)置超時
    _sessionManager.requestSerializer.timeoutInterval = 30.f;
    // 緩存策略
    _sessionManager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringCacheData;
    // 接受的返回數(shù)據(jù)類型
    _sessionManager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/html", @"text/json", @"text/plain", @"text/javascript", @"text/xml", @"image/*", nil];
    // 打開狀態(tài)欄的等待菊花
//    [AFNetworkActivityIndicatorManager sharedManager].enabled = YES;

    __weak typeof(self) weakSelf = self;
    [_sessionManager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *_credential)
    {
        // 1、校驗認證方式是否為NSURLAuthenticationMethodServerTrust
        if (![challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            return NSURLSessionAuthChallengePerformDefaultHandling;
        }

        // 2、設(shè)置自制證書,可以導入多張自制證書(也就是個循環(huán),這里就不演示了)
        // custom是你證書的名稱,記得替換
        NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"custom" ofType:@"cer"];
        if(cerPath != nil) {
            NSData *caCert = [NSData dataWithContentsOfFile:cerPath];

            // 將內(nèi)置的本地證書列表賦值給AFN,便于后期基于此證書列表進行驗證
            NSSet *cerArray = [[NSSet alloc] initWithObjects:caCert, nil];
            weakSelf.sessionManager.securityPolicy.pinnedCertificates = cerArray;

            // 創(chuàng)建證書
            SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
            // 從這個數(shù)組可以看出是可以有多張本地自制證書
            NSArray *caArray = @[(__bridge id)(caRef)];
            // 獲取服務(wù)端返回的證書
            SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
            // 將內(nèi)置的本地證書列表設(shè)置為服務(wù)端證書的根證書
            // 設(shè)置可信任證書列表,設(shè)置后就只會在設(shè)置的證書列表中進行驗證,屏蔽掉系統(tǒng)的證書列表
            OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
            if(status == errSecSuccess) {// 表名設(shè)置成功
                // 要使系統(tǒng)的證書列表繼續(xù)起作用可以調(diào)用此方法,第二個參數(shù)設(shè)置成NO即可
                SecTrustSetAnchorCertificatesOnly(serverTrust, NO);
            }
        }

        // 證書驗證,獲取證書的處理方式
        NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        if ([weakSelf.sessionManager.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
            disposition = NSURLSessionAuthChallengeUseCredential;
        } else {
            disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
        }

        return disposition;
    }];
}

參考文章

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

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

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