发送HTTPS请求首先要进行SSL/TLS握手,握手过程大致如下:
上述过程中,和HTTPDNS有关的是第3步,客户端需要验证服务端下发的证书,验证过程有以下两个要点:
如果上述两点都校验通过,就证明当前的服务端是可信任的,否则就是不可信任,应当中断当前连接。
当客户端使用HTTPDNS解析域名时,请求URL中的host会被替换成HTTPDNS解析出来的IP,所以在证书验证的第2步,会出现domain不匹配的情况,导致SSL/TLS握手不成功。
针对“domain不匹配”问题,可以采用如下方案解决:hook证书校验过程中第2步,将IP直接替换成原来的域名,再执行证书验证。
下面分别列出Android和iOS平台的示例代码。
此示例针对HttpURLConnection接口。
try { String url = "https://140.205.160.59/?sprefer=sypc00"; HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); connection.setRequestProperty("Host", "m.taobao.com"); connection.setHostnameVerifier(new HostnameVerifier() { /* * 关于这个接口的说明,官方有文档描述: * This is an extended verification option that implementers can provide. * It is to be used during a handshake if the URL's hostname does not match the * peer's identification hostname. * * 使用HTTPDNS后URL里设置的hostname不是远程的主机名(如:m.taobao.com),与证书颁发的域不匹配, * Android HttpsURLConnection提供了回调接口让用户来处理这种定制化场景。 * 在确认HTTPDNS返回的源站IP与Session携带的IP信息一致后,您可以在回调方法中将待验证域名替换为原来的真实域名进行验证。 * */ @Override public boolean verify(String hostname, SSLSession session) { return HttpsURLConnection.getDefaultHostnameVerifier(). verify("m.taobao.com“, session); return false; } }); connection.connect(); } catch (Exception e) { e.printStackTrace(); } finally { }
try {
String url = "https://140.205.160.59/?sprefer=sypc00";
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setRequestProperty("Host", "m.taobao.com");
connection.setHostnameVerifier(new HostnameVerifier() {
/*
* 关于这个接口的说明,官方有文档描述:
* This is an extended verification option that implementers can provide.
* It is to be used during a handshake if the URL's hostname does not match the
* peer's identification hostname.
*
* 使用HTTPDNS后URL里设置的hostname不是远程的主机名(如:m.taobao.com),与证书颁发的域不匹配,
* Android HttpsURLConnection提供了回调接口让用户来处理这种定制化场景。
* 在确认HTTPDNS返回的源站IP与Session携带的IP信息一致后,您可以在回调方法中将待验证域名替换为原来的真实域名进行验证。
*/
@Override
public boolean verify(String hostname, SSLSession session) {
return HttpsURLConnection.getDefaultHostnameVerifier().
verify("m.taobao.com“, session);
return false;
}
});
connection.connect();
} catch (Exception e) {
e.printStackTrace();
} finally {
此示例针对NSURLSession/NSURLConnection接口。
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain { /* * 创建证书校验策略 */ NSMutableArray *policies = [NSMutableArray array]; if (domain) { [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)]; } else { [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()]; } /* * 绑定校验策略到服务端的证书上 */ SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies); /* * 评估当前serverTrust是否可信任, * 官方建议在result = kSecTrustResultUnspecified 或 kSecTrustResultProceed * 的情况下serverTrust可以被验证通过,https://developer.apple.com/library/ios/technotes/tn2232/_index.html * 关于SecTrustResultType的详细信息请参考SecTrust.h */ SecTrustResultType result; SecTrustEvaluate(serverTrust, &result); return (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed); } /* * NSURLConnection */ - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if (!challenge) { return; } /* * URL里面的host在使用HTTPDNS的情况下被设置成了IP,此处从HTTP Header中获取真实域名 */ NSString* host = [[self.request allHTTPHeaderFields] objectForKey:@"host"]; if (!host) { host = self.request.URL.host; } /* * 判断challenge的身份验证方法是否是NSURLAuthenticationMethodServerTrust(HTTPS模式下会进行该身份验证流程), * 在没有配置身份验证方法的情况下进行默认的网络请求流程。 */ if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:host]) { /* * 验证完以后,需要构造一个NSURLCredential发送给发起方 */ NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; } else { /* * 验证失败,取消这次验证流程 */ [[challenge sender] cancelAuthenticationChallenge:challenge]; } } else { /* * 对于其他验证方法直接进行处理流程 */ [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; } } //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// /* * NSURLSession */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler { if (!challenge) { return; } NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; NSURLCredential *credential = nil; /* * 获取原始域名信息。 */ NSString* host = [[self.request allHTTPHeaderFields] objectForKey:@"host"]; if (!host) { host = self.request.URL.host; } if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:host]) { disposition = NSURLSessionAuthChallengeUseCredential; credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; } else { disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } // 对于其他的challenges直接使用默认的验证方案 completionHandler(disposition,credential); }
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
* 创建证书校验策略
NSMutableArray *policies = [NSMutableArray array];
if (domain) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
* 绑定校验策略到服务端的证书上
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
* 评估当前serverTrust是否可信任,
* 官方建议在result = kSecTrustResultUnspecified 或 kSecTrustResultProceed
* 的情况下serverTrust可以被验证通过,https://developer.apple.com/library/ios/technotes/tn2232/_index.html
* 关于SecTrustResultType的详细信息请参考SecTrust.h
SecTrustResultType result;
SecTrustEvaluate(serverTrust, &result);
return (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
* NSURLConnection
- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
if (!challenge) {
return;
* URL里面的host在使用HTTPDNS的情况下被设置成了IP,此处从HTTP Header中获取真实域名
NSString* host = [[self.request allHTTPHeaderFields] objectForKey:@"host"];
if (!host) {
host = self.request.URL.host;
* 判断challenge的身份验证方法是否是NSURLAuthenticationMethodServerTrust(HTTPS模式下会进行该身份验证流程),
* 在没有配置身份验证方法的情况下进行默认的网络请求流程。
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:host]) {
* 验证完以后,需要构造一个NSURLCredential发送给发起方
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
* 验证失败,取消这次验证流程
[[challenge sender] cancelAuthenticationChallenge:challenge];
* 对于其他验证方法直接进行处理流程
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
////////////////////////////////////////////////////////////
* NSURLSession
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
NSURLCredential *credential = nil;
* 获取原始域名信息。
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
// 对于其他的challenges直接使用默认的验证方案
completionHandler(disposition,credential);
感谢您注册参加技术达人“秀”—“数字化变革新旗舰5K智能协作终端发布”在线研讨会。
美橙视频制作中心