文章目录
  1. 1. iOS HTTPS 双向认证
    1. 1.1. 流程
    2. 1.2. 证书
    3. 1.3. 网络

iOS HTTPS 双向认证

@(iOS)[网络,HTTPS]

搞了半天,记录一下,坑很多。
双向认证,就是在访问网络的时候进行证书认证,首先本地需要一个服务器证书,一个客户端证书。客户端发送请求,服务器返回服务器证书和本地服务器证书比对,然后客户端发送客户端证书到服务器。如果全部匹配就返回加密算法,然后可以访问网络,否则就不能访问。

流程

  • 1.需要服务端提供认证证书.crt文件,然后自己导出成.cer文件
  • 2.将导出的cer证书加入到项目中,注意勾选相应的target不然可能获取证书路径为nil
  • 3.通过cer证书生成证书校验的安全策略
  • 4.在AFNetworking的网络请求中设置安全策略:[_manager setSecurityPolicy:[CertificatehttpsTools customSecurityPolicy]];
  • 5.通过抓包工具Charles检验请求和返回的内容是否加密

证书

  • 需要server.cer 和client.p12两个文件,用的自签名证书,所以要配置项目info.plist,增加下面几个属性
  • 注意域名和服务器配置有关
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>xxxx.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.0</string>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>

网络

  • AFN3.0
  • 然后就是设置AFN的安全参数 AFSecurityPolicy
  • 在初始化网络工具类是指定这个参数
  • 需要注意这个baseUrl问题,指定类似这种就行 https://192.168.0.22" ,不然就会报错 'Invalid Security Policy', reason: 'A security policy configured with AFSSLPinningModeCertificate can only be applied on a manager with a secure base URL (i.e. https)'
- (void)setupAFNetwork{
AFHTTPSessionManager *manager = [[AFHTTPSessionManager manager] initWithBaseURL:[NSURL URLWithString:BaseURL]];
manager.securityPolicy = [self getCustomHttpsPolicy:manager];
manager.securityPolicy.allowInvalidCertificates = YES;
manager.responseSerializer.acceptableContentTypes = [manager.responseSerializer.acceptableContentTypes setByAddingObject:@"text/html"];

self.httpSessionManager = manager;

}
// 配置安全参数
-(AFSecurityPolicy*) getCustomHttpsPolicy:(AFHTTPSessionManager*)manager{

//https 公钥证书配置

NSString *certFilePath = [[NSBundle mainBundle] pathForResource:@"serverapple" ofType:@"cer"];

NSData *certData = [NSData dataWithContentsOfFile:certFilePath];

NSSet *certSet = [NSSet setWithObject:certData];

AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:certSet];

policy.allowInvalidCertificates = YES;

policy.validatesDomainName = NO;//是否校验证书上域名与请求域名一致

//https回调 客户端验证

[manager setSessionDidBecomeInvalidBlock:^(NSURLSession * _Nonnull session, NSError * _Nonnull error) {

NSLog(@"setSessionDidBecomeInvalidBlock");

}];

__weak typeof(manager)weakManger = manager;

__weak typeof(self)weakSelf = self;

//客户端请求验证 重写 setSessionDidReceiveAuthenticationChallengeBlock 方法

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

NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;

__autoreleasing NSURLCredential *credential =nil;

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

if([weakManger.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 {

// client authentication

SecIdentityRef identity = NULL;

SecTrustRef trust = NULL;

NSString *p12 = [[NSBundle mainBundle] pathForResource:@"clientapple"ofType:@"p12"];

NSFileManager *fileManager =[NSFileManager defaultManager];

if(![fileManager fileExistsAtPath:p12])

{

NSLog(@"client.p12:not exist");

}

else

{

NSData *PKCS12Data = [NSData dataWithContentsOfFile:p12];

if ([[weakSelf class]extractIdentity:&identity andTrust:&trust fromPKCS12Data:PKCS12Data])

{

SecCertificateRef certificate = NULL;

SecIdentityCopyCertificate(identity, &certificate);

const void*certs[] = {certificate};

CFArrayRef certArray =CFArrayCreate(kCFAllocatorDefault, certs,1,NULL);

credential =[NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCredentialPersistencePermanent];

disposition =NSURLSessionAuthChallengeUseCredential;

}

}

}

*_credential = credential;

return disposition;

}];

return policy;

}


+ (BOOL)extractIdentity:(SecIdentityRef*)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data {

OSStatus securityError = errSecSuccess;

//client certificate password

NSDictionary*optionsDictionary = [NSDictionary dictionaryWithObject:@"123456"

forKey:(__bridge id)kSecImportExportPassphrase];

CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);

securityError = SecPKCS12Import((__bridge CFDataRef)inPKCS12Data,(__bridge CFDictionaryRef)optionsDictionary,&items);

if(securityError == 0) {

CFDictionaryRef myIdentityAndTrust =CFArrayGetValueAtIndex(items,0);

const void*tempIdentity =NULL;

tempIdentity= CFDictionaryGetValue (myIdentityAndTrust,kSecImportItemIdentity);

*outIdentity = (SecIdentityRef)tempIdentity;

const void*tempTrust =NULL;

tempTrust = CFDictionaryGetValue(myIdentityAndTrust,kSecImportItemTrust);

*outTrust = (SecTrustRef)tempTrust;

} else {

NSLog(@"Failedwith error code %d",(int)securityError);

return NO;

}

return YES;

}

参考文章

参考文章