一次完整的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
用到某個測試就有個注釋去掉就行了