iOS 的三種自建證書方法https請求相關配置


 

如果你的app服務端安裝的是SLL頒發的CA,可以使用系統方法直接實現信任SSL證書,關於Apple對SSL證書的要求請參考:蘋果官方文檔CertKeyTrustProgGuide

 

這種方式不需要在Bundle中引入CA文件,可以交給系統去判斷服務器端的證書是不是SSL證書,驗證過程也不需要我們去具體實現。

 

第1種

#import "ViewController.h"

#import "HttpManager.h"

 

@interface ViewController ()<NSURLSessionDataDelegate>

 

@property(nonatomic,copy)NSString *etag;

 

@end

 

@implementation ViewController

 

- (void)viewDidLoad {

    [super viewDidLoad];

    

    UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(100, 100, 200, 100)];

    [btn setTitle:@"AFNetworking" forState:UIControlStateNormal];

    [btn setBackgroundColor:[UIColor redColor]];

    [btn addTarget:self action:@selector(btnClicked) forControlEvents:UIControlEventTouchUpInside];

    [self.view addSubview:btn];

    

    UIButton *sessionBtn = [[UIButton alloc]initWithFrame:CGRectMake(100, 300, 200, 100)];

    [sessionBtn setTitle:@"NSUrlSession" forState:UIControlStateNormal];

    [sessionBtn setBackgroundColor:[UIColor redColor]];

    [sessionBtn addTarget:self action:@selector(sessionBtnClicked) forControlEvents:UIControlEventTouchUpInside];

    [self.view addSubview:sessionBtn];

}

 

- (void)btnClicked {

    NSString *urlString = @"https://10.20.129.25:8443/dreamVideo/restful/show";

    HttpManager *httpManager = [HttpManager shareHttpManager];

    [httpManager post:urlString withParameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {

        NSLog(@"success");

    } failure:^(NSURLSessionDataTask *task, NSError *error) {

        NSLog(@"failure");

    }];

}

 

- (void)sessionBtnClicked {

    NSString *urlString = @"https://10.20.129.25:8443/dreamVideo/restful/show";

    NSURL *url = [NSURL URLWithString:urlString];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0f];

    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];

    NSURLSessionDataTask *task = [session dataTaskWithRequest:request];

    [task resume];

}

 

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask

didReceiveResponse:(NSURLResponse *)response

 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {

    NSLog(@"接收到服務器響應");

    //注意:這里需要使用completionHandler回調告訴系統應該如何處理服務器返回的數據

    //默認是取消

    /**

     NSURLSessionResponseCancel = 0,            默認的處理方式,取消

     NSURLSessionResponseAllow = 1,             接收服務器返回的數據

     NSURLSessionResponseBecomeDownload = 2,    變成一個下載請求

     NSURLSessionResponseBecomeStream           變成一個流

     */

    completionHandler(NSURLSessionResponseAllow);

}

 

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask

    didReceiveData:(NSData *)data {

    NSLog(@"獲取到服務段數據");

    NSLog(@"%@",[self jsonToDictionary:data]);

}

 

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task

didCompleteWithError:(nullable NSError *)error {

    NSLog(@"請求完成");

}

 

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge

 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {

    NSLog(@"證書認證");

    if ([[[challenge protectionSpace] authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust]) {

        do

        {

            SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];

            NSCAssert(serverTrust != nil, @"serverTrust is nil");

            if(nil == serverTrust)

                break; /* failed */

            /**

             *  導入多張CA證書(Certification Authority,支持SSL證書以及自簽名的CA),請替換掉你的證書名稱

             */

            NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"ca" ofType:@"cer"];//自簽名證書

            NSData* caCert = [NSData dataWithContentsOfFile:cerPath];

 

            NSCAssert(caCert != nil, @"caCert is nil");

            if(nil == caCert)

                break; /* failed */

            

            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 */

            

            //將讀取的證書設置為服務端幀數的根證書

            OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);

            NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed");

            if(!(errSecSuccess == status))

                break; /* failed */

            SecTrustResultType result = -1;

            //通過本地導入的證書來驗證服務器的證書是否可信

            status = SecTrustEvaluate(serverTrust, &result);

            if(!(errSecSuccess == status))

                break; /* failed */

            NSLog(@"stutas:%d",(int)status);

            NSLog(@"Result: %d", result);

            

            BOOL allowConnect = (result == kSecTrustResultUnspecified) || (result == kSecTrustResultProceed);

            if (allowConnect) {

                NSLog(@"success");

            }else {

                NSLog(@"error");

            }

 

            /* kSecTrustResultUnspecified and kSecTrustResultProceed are success */

            if(! allowConnect)

            {

                break; /* failed */

            }

            

#if 0

            /* Treat kSecTrustResultConfirm and kSecTrustResultRecoverableTrustFailure as success */

            /*   since the user will likely tap-through to see the dancing bunnies */

            if(result == kSecTrustResultDeny || result == kSecTrustResultFatalTrustFailure || result == kSecTrustResultOtherError)

                break; /* failed to trust cert (good in this case) */

#endif

            

            // The only good exit point

            NSLog(@"信任該證書");

            

            NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];

            completionHandler(NSURLSessionAuthChallengeUseCredential,credential);

            return [[challenge sender] useCredential: credential

                          forAuthenticationChallenge: challenge];

            

        }

        while(0);

    }

    

    // Bad dog

    NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];

    completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge,credential);

    return [[challenge sender] cancelAuthenticationChallenge: challenge];

}

 

注:調用SecTrustSetAnchorCertificates設置可信任證書列表后就只會在設置的列表中進行驗證,會屏蔽掉系統原本的信任列表,要使系統的繼續起作用只要調用SecTrustSetAnchorCertificates方法,第二個參數設置成NO即可。

AFNetworking中應該是這句(個人見解望指正)

 SecTrustSetAnchorCertificatesOnly(serverTrust,NO);

第2種

#import "HttpManager.h"

#import "AFHTTPSessionManager.h"

 

@interface HttpManager()

 

@property(nonatomic,retain)AFHTTPSessionManager *manager;

 

@end

 

@implementation HttpManager

 

+(instancetype)shareHttpManager{

    static dispatch_once_t onece = 0;

    static HttpManager *httpManager = nil;

    dispatch_once(&onece, ^(void){

        httpManager = [[self alloc]init];

    });

    return httpManager;

}

 

//https訪問

-(void)post:(NSString *)url withParameters:(id)parameters success:(void (^)(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject))success failure:(void (^)(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error))failure {

FSecurityPolicy分三種驗證模式:
1、AFSSLPinningModeNone:只驗證證書是否在新人列表中
2、AFSSLPinningModeCertificate:驗證證書是否在信任列表中,然后再對比服務端證書和客戶端證書是否一致
3、 AFSSLPinningModePublicKey:只驗證服務端與客戶端證書的公鑰是否一致
這里我們選第二種模式,並且對AFSecurityPolicy的allowInvalidCertificates和 validatesDomainName進行設置。

    AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];

//    securityPolicy = [AFSecurityPolicy defaultPolicy];

//allowInvalidCertificates 是否允許無效證書(也就是自建的證書),默認為NO
//如果是需要驗證自建證書,需要設置為YES

    securityPolicy.allowInvalidCertificates = YES;

//validatesDomainName 是否需要驗證域名,默認為YES;
//假如證書的域名與你請求的域名不一致,需把該項設置為NO
//主要用於這種情況:客戶端請求的是子域名,而證書上的是另外一個域名。因為SSL證書上的域名是獨立的,假如證書上注冊的域名是www.google.com,那么mail.google.com是無法驗證通過的;當然,有錢可以注冊通配符的域名*.google.com,但這個還是比較貴的。

  securityPolicy.validatesDomainName = NO;

//validatesCertificateChain 是否驗證整個證書鏈,默認為YES
//設置為YES,會將服務器返回的Trust Object上的證書鏈與本地導入的證書進行對比,這就意味着,假如你的證書鏈是這樣的:
//GeoTrust Global CA 
//    Google Internet Authority G2
//        *.google.com
//那么,除了導入*.google.com之外,還需要導入證書鏈上所有的CA證書(GeoTrust Global CA, Google Internet Authority G2);
//如是自建證書的時候,可以設置為YES,增強安全性;假如是信任的CA所簽發的證書,則建議關閉該驗證;
  securityPolicy.validatesCertificateChain = NO;

 

    _manager = [AFHTTPSessionManager manager];

    _manager.responseSerializer = [AFHTTPResponseSerializer serializer];

    _manager.securityPolicy = securityPolicy;

    //設置超時時間

    [_manager.requestSerializer willChangeValueForKey:@"timeoutinterval"];

    _manager.requestSerializer.timeoutInterval = 20.f;

    [_manager.requestSerializer didChangeValueForKey:@"timeoutinterval"];

    _manager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringCacheData;

//    if (_etag) {

//        [_manager.requestSerializer setValue:_etag forHTTPHeaderField:@"If-None-Match"];

//    } else {

//        [_manager.requestSerializer setValue:@"bb" forHTTPHeaderField:@"If-None-Match"];

//    }

    _manager.responseSerializer.acceptableContentTypes  = [NSSet setWithObjects:@"application/xml",@"text/xml",@"text/plain",@"application/json",nil];

    

    __weak typeof(self) weakSelf = self;

    [_manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *_credential) {

        

        SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];

        /**

         *  導入多張CA證書(Certification Authority,支持SSL證書以及自簽名的CA),請替換掉你的證書名稱

         */

        NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"ca" ofType:@"cer"];//自簽名證書

        NSData* caCert = [NSData dataWithContentsOfFile:cerPath];

        NSArray *cerArray = @[caCert];

        weakSelf.manager.securityPolicy.pinnedCertificates = cerArray;

        

        SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);

        NSCAssert(caRef != nil, @"caRef is nil");

        

        NSArray *caArray = @[(__bridge id)(caRef)];

        NSCAssert(caArray != nil, @"caArray is nil");

        

        OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);

        SecTrustSetAnchorCertificatesOnly(serverTrust,NO);

        NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed");

 //選擇質詢認證的處理方式

        NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;

        __autoreleasing NSURLCredential *credential = nil;

 //NSURLAuthenticationMethodServerTrust質詢認證方式

        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {

//基於客戶端的安全策略來決定是否信任該服務器,不信任則不響應質詢 。

            if ([weakSelf.manager.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;

        }

        

        return disposition;

    }];

    

    

    [_manager POST:url parameters:parameters success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {

        NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;

        NSDictionary *headDic = response.allHeaderFields;

        NSInteger code = response.statusCode;

        NSLog(@"response statusCode is %zd",code);

//        NSString *etag = headDic[@"Etag"];

//        if (etag) {

//            _etag = etag;

//        }

        NSLog(@"%@",[[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding]);

        NSDictionary *responseDic = [self jsonToDictionary:[[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding]];

        success(task,responseObject);

    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

        NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;

        NSDictionary *headDic = response.allHeaderFields;

        NSInteger code = response.statusCode;

        NSLog(@"response statusCode is %zd",code);

        failure(task,error);

    }];

}

 

- (NSDictionary *)jsonToDictionary:(NSString *)jsonString {

    NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];

    NSError *jsonError;

    NSDictionary *resultDic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableLeaves error:&jsonError];

    return resultDic;

}

 第3種

1). 第一步,先獲取需要驗證的信任對象(Trust Object)。這個Trust Object在不同的應用場景下獲取的方式都不一樣,對於NSURLConnection來說,是從delegate方法-connection:willSendRequestForAuthenticationChallenge:回調回來的參數challenge中獲取([challenge.protectionSpace serverTrust])。

2). 使用系統默認驗證方式驗證Trust Object。SecTrustEvaluate會根據Trust Object的驗證策略,一級一級往上,驗證證書鏈上每一級數字簽名的有效性(上一部分有講解),從而評估證書的有效性。

3). 如第二步驗證通過了,一般的安全要求下,就可以直接驗證通過,進入到下一步:使用Trust Object生成一份憑證([NSURLCredential credentialForTrust:serverTrust]),傳入challenge的sender中([challenge.sender useCredential:cred forAuthenticationChallenge:challenge])處理,建立連接。

4). 假如有更強的安全要求,可以繼續對Trust Object進行更嚴格的驗證。常用的方式是在本地導入證書,驗證Trust Object與導入的證書是否匹配。更多的方法可以查看Enforcing Stricter Server Trust Evaluation,這一部分在講解AFNetworking源碼中會講解到。

5). 假如驗證失敗,取消此次Challenge-Response Authentication驗證流程,拒絕連接請求。

ps: 假如是自建證書的,則會跳過第二步,使用第三部進行驗證,因為自建證書的根CA的數字簽名未在操作系統的信任列表中

使用NSURLConnection支持HTTPS的實現

// Now start the connection
NSURL * httpsURL = [NSURL URLWithString:@ "https://www.google.com" ];
self.connection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:httpsURL] delegate:self];
     
//回調
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
     //1)獲取trust object
     SecTrustRef trust = challenge.protectionSpace.serverTrust;
     SecTrustResultType result;
     
     //2)SecTrustEvaluate對trust進行驗證
     OSStatus status = SecTrustEvaluate(trust, &result);
     if  (status == errSecSuccess &&
         (result == kSecTrustResultProceed ||
         result == kSecTrustResultUnspecified)) {
         
         //3)驗證成功,生成NSURLCredential憑證cred,告知challenge的sender使用這個憑證來繼續連接
         NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];
         [challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
         
     else  {
     
         //5)驗證失敗,取消這次驗證流程
         [challenge.sender cancelAuthenticationChallenge:challenge];
         
   }
}
上面是代碼是通過系統默認驗證流程來驗證證書的。假如我們是自建證書的呢?這樣Trust Object里面服務器的證書因為不是可信任的CA簽發的,所以直接使用SecTrustEvaluate進行驗證是不會成功。又或者,即使服務器返回的證書是信任CA簽發的,又如何確定這證書就是我們想要的特定證書?這就需要先在本地導入證書,設置成需要驗證的Anchor Certificate(就是根證書),再調用SecTrustEvaluate來驗證。代碼如下
//先導入證書
NSString * cerPath = ...;  //證書的路徑
NSData * cerData = [NSData dataWithContentsOfFile:cerPath];
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(cerData));
self.trustedCertificates = @[CFBridgingRelease(certificate)];
//回調
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
     //1)獲取trust object
     SecTrustRef trust = challenge.protectionSpace.serverTrust;
     SecTrustResultType result;
     //注意:這里將之前導入的證書設置成下面驗證的Trust Object的anchor certificate
     SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)self.trustedCertificates);
     //2)SecTrustEvaluate會查找前面SecTrustSetAnchorCertificates設置的證書或者系統默認提供的證書,對trust進行驗證
     OSStatus status = SecTrustEvaluate(trust, &result);
     if  (status == errSecSuccess &&
         (result == kSecTrustResultProceed ||
         result == kSecTrustResultUnspecified)) {
         
         //3)驗證成功,生成NSURLCredential憑證cred,告知challenge的sender使用這個憑證來繼續連接
         NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];
         [challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
         
     else  {
     
         //5)驗證失敗,取消這次驗證流程
         [challenge.sender cancelAuthenticationChallenge:challenge];
         
   }
}建議采用本地導入證書的方式驗證證書,來保證足夠的安全性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM