一次完整的https请求大概是这样的。
1.
客户端
向
服务器
发出https,请求。
2.
服务器
发送自己信息到
客户端
,包括
服务器端
的
公钥
(.car证书文件) 等信息。
3.
客户端
根据
服务器
发回来的
公钥
去自己
钥匙串
里面匹配本地证书(
公钥
),来判断证书是否过期,可用等。如果本地证书没有的话。就会弹出警告改证书不被信任,询问用户是否要安装这个证书。一旦安装,下次访问的时候再去自己的钥匙串里面去找的时候就能找到了。
4.
客户单
用
服务器
的证书加密随机值发给
服务器
(这个随机值在TCP协议里面会自动帮我加。OSX内核部分,我们都无法接触到。这个值我们改不了)。
5.
服务器
用自己的
私钥
解密发回来,
客户端
校验这个随机值是否正确。双方信任对方。
以上就是一次https握手过程。完成握手以后,就用公钥加密传输内容了。
举个例子:
查看百度的https证书。苹果一般会自动更新自己 钥匙串
里面的权威机构的证书。
以上是苹果钥匙串
里已经安装的证书。
以上是百度的买的证书服务商。查了一下是比利时的。
这里说一下,我们开发中用到https。(ios9开始默认开始https)
一般只要你买的证书大部分都是直接过。客户端
什么都不用做,只要把http改https就行了。你买的权威机构证书,手机端默认在钥匙串
里面安装了默认信任机构的证书。(特别权威的https在访问的网址那里会有绿色符号)。所以网上说了一堆什么什么的,都不用管只要你用权威机构的,客户端
什么都不用做。服务器端
我就不知道了。阿里云貌似的就不用。客户端
直接改https就行了。
另外,费事的就是自制的证书了。苹果钥匙串
里没有你的证书,不信任你。这时候可能要做一些功夫了。
NSURLConnection的代理方法里面默认的https大概代码是这样的:
// Now start the connection
NSURL * httpsURL = [NSURL URLWithString:@"https://www.baidu.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];
}
}
在一般购买的证书。这个代理根本不用实现。啥都不用做。
自制的证书代码是这样的:
//先导入证书
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];
}
}
在老版本的ios系统里 可能 不实现这个方法没问题。在新版本的ios系统里,恐怕不行。
为了做一次实验。用百度写了一个小demo.
找到刚刚百度用的证书,把他导出来然后添加项目工程了。用的时候,别用代理里面返回的证书信息。别别去钥匙串
里面查找了,直接用我导的这个证书。
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSString * cerPath = [[NSBundle mainBundle] pathForResource:@"11111" ofType:@"cer"]; //证书的路径
NSData * cerData = [NSData dataWithContentsOfFile:cerPath];
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(cerData));
// self.trustedCertificates = @[CFBridgingRelease(certificate)];
SecTrustRef trust = challenge.protectionSpace.serverTrust;
SecTrustResultType result;
//注意:这里将之前导入的证书设置成下面验证的Trust Object的anchor certificate
SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)@[CFBridgingRelease(certificate)]);
//2)SecTrustEvaluate会查找前面SecTrustSetAnchorCertificates设置的证书或者系统默认提供的证书,对trust进行验证
NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];
[challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(@"%@",response);
}
最后我们发现我们也能收到response.
所以。本地证书和服务器
证书是可以互相校验的。放哪无所谓。只不过默认去钥匙串
里面找。
Charles这个工具用过的都知道。这里直接叫
青花瓷
吧。
他是怎么工作的呢。
他就像
客户端
和
服务器
之间的一个中间人。我们叫他
代理服务器
吧。
文章的开始就说了https请求的过程。
青花瓷在第二步开始的时候就把
青花瓷
自己的
公钥
返回给
客户端
了。
把
客户端
请求信息作为自己的信息 请求去访问
服务器
。
重新屡一下:
1.客户端
发送请求到青花瓷
2青花瓷
发送客户端
请求到服务器
。
3.服务器
返回 公钥
信息到青花瓷
。
4.青花瓷
把自己的 公钥
发送给客户端
。
5.客户端
认证青花瓷
的证书。
6.客户端
用青花瓷
的 公钥
加密随机值发给青花瓷
。
7.青花瓷
用服务器
的 公钥
加密随机值发给 服务器
。
8.服务器
用自己的私钥
解密随机值,然后发给青花瓷
。
9.青花瓷
用自己的私钥
加密随机值,然后发送给客户端
。
10.客户端
对比。完成握手。
11.青花瓷
对比。完整握手。
以后。客户端
就用青花瓷
的公钥
加密数据发给青花瓷
,青花瓷
用自己私钥
解密,展现给我们看。然后用服务器
的公钥
加密已经解密的数据发给服务器
。
就这样,帮我们拦截所有请求。
这也是为什么青花瓷
拦截https时客户端要安装一份青花瓷的公钥证书,这样在第5步时候,客户端
去钥匙串
找证书的时候正好能找到。
目前大部分app的https都能抓到数据。
但是某些apps 用自己的一套加密加密,然后用https加密传传输。所以有时候,我们在青花瓷
看到也是加密数据。
有图有证据:
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
//1)获取trust object
SecTrustRef trust = challenge.protectionSpace.serverTrust;
SecTrustResultType result;
SecKeyRef xxx =SecTrustCopyPublicKey(trust);
SecCertificateRef pppp = SecTrustGetCertificateAtIndex(trust,0);
//2)SecTrustEvaluate对trust进行验证
}
未用青花瓷
时:证书信息
用青花瓷
时:证书信息
两个证书明显不一样了。
木有木发现appstore有部分请求,用青花瓷
拦截不到。
因为在第5步的时候,苹果没有根据服务器
返回的证书来加密数据。而是直接用自己已知公钥
证书加密返回,这样即使你钥匙串
里面安装的别人的证书的时候也没有用了。
代码类似上面“用百度写了一个小demo”那里的代码:
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSString * cerPath = [[NSBundle mainBundle] pathForResource:@"11111" ofType:@"cer"]; //证书的路径
NSData * cerData = [NSData dataWithContentsOfFile:cerPath];
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(cerData));
// self.trustedCertificates = @[CFBridgingRelease(certificate)];
SecTrustRef trust = challenge.protectionSpace.serverTrust;
SecTrustResultType result;
//注意:这里将之前导入的证书设置成下面验证的Trust Object的anchor certificate
SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)@[CFBridgingRelease(certificate)]);
//2)SecTrustEvaluate会查找前面SecTrustSetAnchorCertificates设置的证书或者系统默认提供的证书,对trust进行验证
NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];
[challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
}
不去判断公钥
证书是否安装、是否过期,也不去查找。直接用我知道的公钥
证书去加密。这样就可以防止青花瓷
做中间人拦截数据。(不查找,直接用已知的公钥
证书和发过来的公钥
证书对比信息,用服务器
发过来的公钥
信息查找证书直接判断过期、可用。还是有风险的)
这是青花瓷
拦截不到了
猜测苹果可能在这里做了判断。
保险的做法用了https之外,吧数据用自己的一套加密机制在加密一遍。
几个测试例子:
https://pan.baidu.com/s/1i5cB6Yh
用到某个测试就有个注释去掉就行了
参考文章:
http://www.cocoachina.com/ios/20150811/12947.html