前言
苹果在iOS9中加入了ATS,不清楚的同学点击任意门ATS适配, 当然这个其实也是蛮好的,安全嘛,这篇文章的主题是讲如何用CocoaHttpServer 实现https.
生成自签名证书
到OpenSSL官网下载最新版本,解压之后拷贝demoCA到你想要放的文件夹,当然你也可以自己生成一个demoCA,在app目录中找到CA.sh,拷贝到你的目录,执行
sh ./CA.sh -newca
然后创建一个目录
mkdir server
创建一个私钥
openssl genrsa -out server/server-key.pem 2048
创建证书请求
openssl req -new -out server/server-req.csr -key server/server-key.pem
然后会需要填写一系列信息
Country Name (2 letter code) [AU]:cn
State or Province Name (full name) [Some-State]:fujian
Locality Name (eg, city) []:xiamen
Organization Name (eg, company) [Internet Widgits Pty Ltd]:test
Organizational Unit Name (eg, section) []:test
Common Name (eg, YOUR name) []:127.0.0.1 注释:一定要写服务器所在的ip地址
Email Address []:sky
Common Name 这一项必须填 localhost 或者 127.0.0.1 ,如果填写127.0.0.1,那么页面中请求只能使用https://127.0.0.1:60000 而不能使用 https://localhost:60000,反之相同。
自签署证书
openssl x509 -req -in server/server-req.csr -out server/server-cert.pem -signkey server/server-key.pem -CA demoCA/cacert.pem -CAkey demoCA/private/cakey.pem -CAcreateserial -days 3650
将证书导出成浏览器支持的.p12格式,记得导出密码
openssl pkcs12 -export -clcerts -in server/server-cert.pem -inkey server/server-key.pem -out server/server.p12
自签名证书生成完成。
工程配置
将生成的p12放到工程目录下面,同时新建一个 MyHttpConnection 继承于 HttpConnection ,重载 - (BOOL)isSecureServer 方法及 sslIdentityAndCertificates 方法
- (BOOL)isSecureServer
{
// Create an HTTPS server (all connections will be secured via SSL/TLS)
return YES;
}
/**
* This method is expected to returns an array appropriate for use in kCFStreamSSLCertificates SSL Settings.
* It should be an array of SecCertificateRefs except for the first element in the array, which is a SecIdentityRef.
**/
- (NSArray *)sslIdentityAndCertificates
{
SecIdentityRef identityRef = NULL;
SecCertificateRef certificateRef = NULL;
SecTrustRef trustRef = NULL;
NSString *thePath = [[NSBundle mainBundle] pathForResource:@"TestCertificate" ofType:@"p12"];
NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:thePath];
CFDataRef inPKCS12Data = (CFDataRef)CFBridgingRetain(PKCS12Data);
CFStringRef password = CFSTR("123");
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = errSecSuccess;
securityError = SecPKCS12Import(inPKCS12Data, optionsDictionary, &items);
if (securityError == 0) {
CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex (items, 0);
const void *tempIdentity = NULL;
tempIdentity = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemIdentity);
identityRef = (SecIdentityRef)tempIdentity;
const void *tempTrust = NULL;
tempTrust = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust);
trustRef = (SecTrustRef)tempTrust;
} else {
NSLog(@"Failed with error code %d",(int)securityError);
return nil;
}
SecIdentityCopyCertificate(identityRef, &certificateRef);
NSArray *result = [[NSArray alloc] initWithObjects:(id)CFBridgingRelease(identityRef), (id)CFBridgingRelease(certificateRef), nil];
return result;
}
- (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
{
// do something
return [super httpResponseForMethod:method URI:path];
}
同时在启动 HTTPServer(调用 startServer )之前,使用 setConnectionClass 方法将 HTTPConnecdtion 替换为 MyHTTPConnection
[_httpServer setConnectionClass:[MyHTTPConnection class]];
WKWebView代理配置
WKWebView需要在WKNavagationDelegate方法中允许使用,这个方法和AFNetworking处理方法很相似,当然你如果是用safari打开对应的链接的话,就得safari允许自签名的证书。
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
NSURLCredential *card = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, card);
}
}
ATS设置
为App Transport Security Settings
添加Allow Arbitrary Loads in Web Content
设置为YES
结束
附上我自己写的小demo, 打完收工!!!