证书类型
- 从权威认证机构购买的证书
- 优点:服务端如果使用的是这类证书的话,那么客户端一般不需要做什么,直接使用HTTPS请求就行了,苹果内置了那些受信任的根证书
- 缺点:需要花钱
- 自制证书
- 优点:无需花钱
- 缺点:这类证书是不受信任的,因此需要我们在代码中将自制证书设置为可信任证书
自己实现自制证书验证
- 这里以
NSURLSession
请求为例演示下如何验证自制证书
- 实现
NSURLSessionDelegate
的代理方法-URLSession:didReceiveChallenge:completionHandler:
- 只要访问的是
HTTPS请求
就会调用此代理方法
- 此代理方法的作用:对自制证书的验证
- 代理方法的各个参数介绍
- challenge:挑战、质问 (包含了受保护的空间)
- completionHandler:处理自制证书,该block的两个参数的含义:
disposition
表示证书处理方式,credential
表示需要处理的证书
- disposition类型介绍
NSURLSessionAuthChallengeUseCredential = 0, 使用该证书,安装该证书
NSURLSessionAuthChallengePerformDefaultHandling = 1, 默认方式,证书被忽略
NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2, 取消请求,证书被忽略
NSURLSessionAuthChallengeRejectProtectionSpace = 3, 拒绝本次请求
- 证书认证结果
SecTrustResultType
类型介绍
-------以下认证结果表示可信证书---------
// 证书通过验证,是由于用户有操作设置了证书被信任,如:在弹出的是否信任的alert框中选择always trust
// 一般是自制证书,非权威机构颁发的,如:Charles的代理证书
kSecTrustResultProceed = 1,
// 证书通过验证,用户没有设置这些证书是否被信任
// 一般验证权威机构颁发的CA证书会返回这个结果
kSecTrustResultUnspecified = 4,
------------------------------------
-------以下认证结果表示不可信证书-------
// 无效证书
kSecTrustResultInvalid = 0,
// 待确认证书
kSecTrustResultConfirm = 2,
// 被拒证书
kSecTrustResultDeny = 3,
// 不可信证书
kSecTrustResultRecoverableTrustFailure = 5,
// 严重不可信证书
kSecTrustResultFatalTrustFailure = 6,
// 其他错误
kSecTrustResultOtherError = 7
// 认证方式
challenge.protectionSpace.authenticationMethod
// 返回的服务端证书
// 如果authenticationMethod不是NSURLAuthenticationMethodServerTrust,那么serverTrust为nil
challenge.protectionSpace.serverTrust
- 自制证书认证步骤
- 首先判断认证方式,是否为
NSURLAuthenticationMethodServerTrust
- 获取服务端返回的自制证书
challenge.protectionSpace.serverTrust
- 从app的资源文件中获取
内置的本地证书
(可以是多个),并设置为服务端证书的根证书,设置后会屏蔽掉系统的证书列表,当然可以通过SecTrustSetAnchorCertificatesOnly
方法(第二个参数设置为NO
)来使系统的证书列表继续起作用
- 在设置的证书列表中验证服务端自制证书是否可信
- 完成验证后通过回调告知系统如何处理自制证书
- 代码实现:这里使用的
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、获取服务端返回的证书
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
// 3、从app的资源文件中获取内置的本地证书(可以是多个,这里只演示只有一个内置证书)
// custom是你证书的名称,记得替换
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"custom" ofType:@"cer"];
NSData *caCert = [NSData dataWithContentsOfFile:cerPath];
if(nil != caCert) {
// 创建证书
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);
// 要使系统的证书列表继续起作用可以调用此方法,第二个参数设置成NO即可
SecTrustSetAnchorCertificatesOnly(serverTrust, NO);
// NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed");
if(errSecSuccess != status)// errSecSuccess表示没有错误
break; /* failed */
}
// 4、使用本地导入的证书列表验证服务器的证书是否可信
SecTrustResultType result = -1;
OSStatus status = SecTrustEvaluate(serverTrust, &result);
if(errSecSuccess != status)
break; /* failed */
// kSecTrustResultUnspecified and kSecTrustResultProceed are success
BOOL allowConnect = (result == kSecTrustResultUnspecified)// 证书通过验证,是由于用户没有设置这些证书是否被信任
|| (result == kSecTrustResultProceed);// 证书通过验证,用户有操作设置了证书被信任
if (!allowConnect)
break; /* failed */
// 5、设置证书的处理方式
credential = [NSURLCredential credentialForTrust:serverTrust];
disposition = NSURLSessionAuthChallengeUseCredential;
} while(0);
// 6、告知系统如何处理自制证书
if(completionHandler) completionHandler(disposition, credential);
}
通过AFN实现自制证书验证
-
AFSecurityPolicy
的三种验证模式
-
AFSSLPinningModeNone
只验证证书是否在信任列表中
-
AFSSLPinningModePublicKey
先验证证书是否在信任列表中,然后仅验证服务端证书与客户端证书的公钥是否一致
-
AFSSLPinningModeCertificate
先验证证书是否在信任列表中,然后对比服务端证书和客户端证书是否一致(不仅限于
公钥是否一致)
- 通过给
AFHTTPSessionManager
设置回调setSessionDidReceiveAuthenticationChallengeBlock
来验证自制证书,然后将内置的本地证书加入到可信任的证书列表中,即可通过证书的校验
- 代码实现
// 记得设置个AFHTTPSessionManager属性
@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;
- (void)validateCerByAFN {
_sessionManager = [AFHTTPSessionManager manager];
// 创建安全策略
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
// 是否允许使用自制证书
securityPolicy.allowInvalidCertificates = YES;
// 是否需要验证域名,默认YES
securityPolicy.validatesDomainName = YES;
_sessionManager.securityPolicy = securityPolicy;
// 响应数据序列化格式
_sessionManager.responseSerializer = [AFJSONResponseSerializer serializer];
// 设置超时
_sessionManager.requestSerializer.timeoutInterval = 30.f;
// 缓存策略
_sessionManager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringCacheData;
// 接受的返回数据类型
_sessionManager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/html", @"text/json", @"text/plain", @"text/javascript", @"text/xml", @"image/*", nil];
// 打开状态栏的等待菊花
// [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、设置自制证书,可以导入多张自制证书(也就是个循环,这里就不演示了)
// custom是你证书的名称,记得替换
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"custom" ofType:@"cer"];
if(cerPath != nil) {
NSData *caCert = [NSData dataWithContentsOfFile:cerPath];
// 将内置的本地证书列表赋值给AFN,便于后期基于此证书列表进行验证
NSSet *cerArray = [[NSSet alloc] initWithObjects:caCert, nil];
weakSelf.sessionManager.securityPolicy.pinnedCertificates = cerArray;
// 创建证书
SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
// 从这个数组可以看出是可以有多张本地自制证书
NSArray *caArray = @[(__bridge id)(caRef)];
// 获取服务端返回的证书
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
// 将内置的本地证书列表设置为服务端证书的根证书
// 设置可信任证书列表,设置后就只会在设置的证书列表中进行验证,屏蔽掉系统的证书列表
OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
if(status == errSecSuccess) {// 表名设置成功
// 要使系统的证书列表继续起作用可以调用此方法,第二个参数设置成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;
}];
}
参考文章