Object-C 中的证书校验
什么是证书校验?
证书校验是客户端、服务器建立信任的一种手段,一般发生在建立网络连接的时候,比如:
- HTTPS连接 - 服务器要求验证SSL/TLS证书
- HTTP代理认证 - 需要代理服务器认证
- HTTP基本认证 - 服务器要求用户名密码
- 客户端证书 - 服务器要求客户端提供证书
证书校验分类
客户端校验服务器证书:客户端对服务器发来的证书进行校验(常用)
服务器校验客户端证书:服务器对客户端发出的证书进行校验
证书校验流程
HTTPS握手流程客户端 服务器| ||---- 1. ClientHello -------->| "我想建立HTTPS连接"| ||<--- 2. ServerHello ---------| "好的,这是我的证书 📜"| || |👆 此时触发 NSURLAuthenticationMethodServerTrust客户端需要验证:✅ 这个证书是不是CA签发的?✅ 证书有没有过期?✅ 证书的域名是否匹配?✅ 证书链是否完整?| ||---- 3. Finished ----------->| "我信任你的证书,继续"| ||<--- 4. 加密通信 ------------|
服务器 客户端| |📜 拥有证书文件 |(server.crt + server.key) || ||-------- 发送证书 📜 ------------->|| (只发送公钥证书,不发送私钥) || || | 👀 接收到证书| | 📋 验证步骤:| | 1. 检查证书颁发者| | 2. 检查证书有效期| | 3. 检查域名匹配| | 4. 检查证书链| || | ✅ 创建信任凭据| | (不是新证书!)| ||<-------- 继续连接 -----------------|方法释义、相关参数解释
/// - Parameters:
/// - session:本次网络连接
/// - challenge:需要验证授权的内容,包括服务器发来的证书
/// - completionHandler:授权之后,告诉系统是信任该服务器,还是不信任该服务器,
/// -NSURLSessionAuthChallengeDisposition:操作的枚举值,cancel表示拒绝,trust表示信任
/// -NSURLCredential:操作的凭据,有了凭据,系统才能信任这么操作没有问题- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler
参数详解:
Challenge:需要验证的内容详情,其中里面的,里面的protectionSpace参数,表示详细内容NSURLProtectionSpace *protectionSpace:authenticationMethod:表示验证当前连接的方法,比较常用地就是,表示验证服务器发来的证书NSURLAuthenticationMethodServerTrustSecTrustRef serverTrust:表示服务器发来的证书,用来执行X.509证书的计算completionHandler:验证完之后的回调,它内部是调用的系统的方法,根据传递的参数,来决定是否要信任本次连接//本次校验的结果
NSURLSessionAuthChallengeDispositiontypedef NS_ENUM(NSInteger, NSURLSessionAuthChallengeDisposition) {// 1️⃣ 使用提供的凭据NSURLSessionAuthChallengeUseCredential = 0,// 含义:我已经验证了,使用我提供的credential继续// 场景:自定义证书验证通过,信任服务器// 2️⃣ 使用系统默认处理NSURLSessionAuthChallengePerformDefaultHandling = 1,// 含义:我不处理,交给系统默认逻辑// 场景:使用iOS系统的证书验证(信任系统根证书)// 3️⃣ 取消认证挑战NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2,// 含义:拒绝这个证书,取消连接// 场景:证书验证失败,不信任服务器// 4️⃣ 拒绝该保护空间NSURLSessionAuthChallengeRejectProtectionSpace = 3// 含义:拒绝这个认证保护空间,但可以尝试其他凭据// 场景:用户名密码错误,允许重试
};
校验示例
完全自定义校验方法
ODNetwork *network = [[ODNetwork alloc] init];// 设置自定义验证器
network.sessionDidReceiveAuthenticationChallenge = ^NSURLSessionAuthChallengeDisposition(NSURLSession *session,NSURLAuthenticationChallenge *challenge,NSURLCredential *__autoreleasing *credential) {// 完全自定义逻辑,比如:// - 测试环境信任所有证书// - 特殊的证书绑定策略// - 用户手动选择是否信任if (isTestEnvironment) {*credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];return NSURLSessionAuthChallengeUseCredential;}return NSURLSessionAuthChallengePerformDefaultHandling;
};
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;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]) {//在进行服务器信任的身份验证时,客户端需要验证服务器的证书有效性。当收到一个服务器信任的身份验证挑战时,可以使用 `NSURLCredential` 类的 `credentialForTrust:` 方法来创建一个凭据对象。credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];if (credential) {disposition = NSURLSessionAuthChallengeUseCredential;} else {disposition = NSURLSessionAuthChallengePerformDefaultHandling;}} else {disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;}} else {disposition = NSURLSessionAuthChallengePerformDefaultHandling;}}if (completionHandler) {completionHandler(disposition, credential);}
}自定义校验策略(内置证书校验)
校验服务端的证书,和客户端内置的证书,是否一样
// 场景:生产环境使用证书绑定(Certificate Pinning)// 1. 配置 SecurityPolicy
ODSecurityPolicy *policy = [ODSecurityPolicy policyWithPinningMode:ODSSLPinningModeCertificate];
policy.pinnedCertificates = [self loadPinnedCertificates];
network.securityPolicy = policy;// 2. 发起HTTPS请求
[network flushEvent:event immediately:YES];// 3. 触发证书验证
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler {// 没有自定义验证器,走默认流程if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {// 服务器证书SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;// 使用 SecurityPolicy 验证// 会检查证书是否在 pinnedCertificates 列表中if ([self.securityPolicy evaluateServerTrust:serverTrust forDomain:@"api.example.com"]) {// ✅ 证书匹配,信任NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];completionHandler(NSURLSessionAuthChallengeUseCredential, credential);} else {// ❌ 证书不匹配,拒绝(防止中间人攻击)completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);}}
}客户端自己证书校验
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler {// 服务器要求客户端证书if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {// 从钥匙串加载客户端证书SecIdentityRef identity = [self loadClientCertificate];NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identitycertificates:nilpersistence:NSURLCredentialPersistenceForSession];completionHandler(NSURLSessionAuthChallengeUseCredential, credential);}
}