网上有太多教程教你如何使用charles调试,但是一到Https,大部分教程只是让你可以启用ssl代理,但是没有说明如何才能通过charles来真正查看https加密后的数据,这里我给出一个完整的教程来指导大家如何启用https后任然可以查看加密请求内容。在讲解让charles实现查看https请求内容之前,先简单讲解一下代理实现的原理。
代理的原理
HTTP 代理存在两种形式,分别简单介绍如下:
第一种是 RFC 7230 - HTTP/1.1: Message Syntax and Routing(即修订后的 RFC 2616,HTTP/1.1 协议的第一部分)描述的普通代理。这种代理扮演的是「中间人」角色,对于连接到它的客户端来说,它是服务端;对于要连接的服务端来说,它是客户端。它就负责在两端之间来回传送 HTTP 报文。
第二种是 Tunneling TCP based protocols through Web proxy servers(通过 Web 代理服务器用隧道方式传输基于 TCP 的协议)描述的隧道代理。它通过 HTTP 协议正文部分(Body)完成通讯,以 HTTP 的方式实现任意基于 TCP 的应用层协议代理。这种代理使用 HTTP 的 CONNECT 方法建立连接,但 CONNECT 最开始并不是 RFC 2616 - HTTP/1.1 的一部分,直到 2014 年发布的 HTTP/1.1 修订版中,才增加了对 CONNECT 及隧道代理的描述,详见 RFC 7231 - HTTP/1.1: Semantics and Content。实际上这种代理早就被广泛实现。
普通代理
第一种 Web 代理原理特别简单:
HTTP 客户端向代理发送请求报文,代理服务器需要正确地处理请求和连接(例如正确处理 Connection: keep-alive),同时向服务器发送请求,并将收到的响应转发给客户端。
下面这张图片来自于《HTTP 权威指南》,直观地展示了上述行为:
[图片上传失败...(image-615be4-1570502309613)]
客户端请求先发送到代理服务器,代理再和真实服务器进行请求,对于web服务器来说,代理就是客户端。服务器完全察觉不到真正客户端的存在,这实现了隐藏客户端 IP 的目的。当然代理也可以修改 HTTP 请求头部,通过 X-Forwarded-IP 这样的自定义头部告诉服务端真正的客户端 IP。但服务器无法验证这个自定义头部真的是由代理添加,还是客户端修改了请求头,所以从 HTTP 头部字段获取 IP 时,需要格外小心。
还有一种情况是访问网站时,实际上访问的是代理,代理收到请求报文后,再向真正提供服务的服务器发起请求,并将响应转发给浏览器。这种情况一般被称之为反向代理,它可以用来隐藏服务器 IP 及端口。一般使用反向代理后,需要通过修改 DNS 让域名解析到代理服务器 IP,这时浏览器无法察觉到真正服务器的存在,当然也就不需要修改配置了。反向代理是 Web 系统最为常见的一种部署方式,例如本博客就是使用 Nginx 的 proxy_pass 功能将浏览器请求转发到背后的 Node.js 服务。两者的区别在于代理服务器是由谁配置的,如果是客户端配置的代理,就是前置代理,如果是服务器配置的代理访问,一般都是反向代理。
隧道代理
第二种 Web 代理的原理也很简单:
HTTP 客户端通过 CONNECT 方法请求隧道代理创建一条到达任意目的服务器和端口的 TCP 连接,并对客户端和服务器之间的后继数据进行盲转发。
下面这张图片同样来自于《HTTP 权威指南》,直观地展示了上述行为:
[图片上传失败...(image-8ec42b-1570502309613)]
假如我通过代理访问A网站,浏览器首先通过 CONNECT 请求,让代理创建一条到 A 网站的 TCP 连接;一旦 TCP 连接建好,代理无脑转发后续流量即可。所以这种代理,理论上适用于任意基于 TCP 的应用层协议,HTTPS 网站使用的 TLS 协议当然也可以。这也是这种代理为什么被称为隧道的原因。对于 HTTPS 来说,客户端透过代理直接跟服务端进行 TLS 握手协商密钥,所以依然是安全的。
Charles实现Https代理
步骤一:将Charles的根证书(Charles Root Certificates)安装到Mac上。
Help -> SSL Proxying -> Install Charles Root Certificate
步骤二:Mac信任Charles的根证书。
打开系统钥匙串应用,在我的证书中找到刚安装的Charles的Root证书:
选择始终信任。如果提示需要输入密码,请输入你本机管理员密码
步骤三:将Charles证书安装到移动设备上。
Help -> SSL Proxying -> Install Charles Root Certificate On a Mobile Device or Remote Browser...
这时候会弹出一个框,在移动设备的浏览器输入弹框中提供的URL就可以安装Charles证书(这时候需要移动设备已经设置代理)。
在移动设备的浏览器中输入上面的URL后会弹出一个确认框,点击“允许”跳转到“设置”页面,安装描述文件即可。
步骤四:移动设备信任Charles证书。
步骤五:接下来就可以在charles中启用https代理了:
Proxy -> SSL Proxying Settings...
指定地址和443端口就可以了,还可以支持通配符。
到了这一步,网上大部分教程就结束了,实际上还是会遇到虽然看到了https请求通过了代理,但是还是看不到任何内容。出现下面的提示:
SSLHandshake: Remote host closed connection during handshake
You may need to configure your browser or application to trust the Charles Root Certificate. See SSL Proxying in the Help menu.
这个时候就比较懵逼了,我已经安装了Charles Root Certificate为什么还是提示这个错误,也让我查找了很久。这里我们先回到开头的代理原理,我们可以看到代理有两种模式,其中第二种模式隧道代理模式,是直接将数据转发,数据内容还是加密状态,自然不能查看。那第一种简单代理模式可以满足我们的需求么?
可以也不可以,为什么这么说?Https最重要的特性就是加密防止信息窥探和劫持,使用第一种模式其实是加入了中间人,而且中间人需要被客户端和服务器两端同时信任,并且可以解析加密内容进行转发。我上一篇文章也讲到,IOS下Https对连接进行校验有多种模式,校验证书,校验域名,校验证书链。为了支持charles分析Https请求内容,我们需要构造条件让charles成为受信任的中间人。还记得我们刚刚安装了charles的Root证书么?为了让charles成为受信任的代理中间人,我们在客户端也需要设置信任charles的Root证书。
方法如下:
1、Charles菜单中选择Help > SSLProxying > View Generated SSL Certificates Keystore Password,将显示的密码记下来。这个是在每个Charles安装的时候自动生成的,和你本机的Charles Root Certificates相对应。
2、Charles菜单中选择Help > SSLProxying > Export Charles Root Certificate and Private Key…,然后输入刚才的密码,将生成的p12文件保存好。
3、Charles菜单中选择Help > SSLProxying > Save Charles Root Certificate…,选择cert文件保存下来
4、将保存的cert文件导入到Xcode工程,并设置信任该证书
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"https" ofType:@"cer"];
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
NSString *chlsCertPath = [[NSBundle mainBundle] pathForResource:@"charles-ssl-proxying-cert" ofType:@"cer"];
NSData *chlsCertData = [NSData dataWithContentsOfFile:certPath];
AFSecurityPolicy *securityPolicy = [[AFSecurityPolicy alloc] init];
[securityPolicy setAllowInvalidCertificates:NO];
[securityPolicy setPinnedCertificates:@[certData,chlsCertData]];
[securityPolicy setSSLPinningMode:AFSSLPinningModeCertificate];
[securityPolicy setValidatesDomainName:YES];
[securityPolicy setValidatesCertificateChain:NO];
manager.securityPolicy = securityPolicy;
5、重新编译你的App,这个时候在charles中就可以看到你的加密内容了。
到了这里还不算结束,因为charles生成的root证书是和你本机绑定的,一旦换了测试来抓包,一样还是看不到,那怎么办呢?在Charles启用https代理的时候,我们可以看到最右侧还有Root Certificate选项:
这里可以选择第二步导出的p12文件,并输入p12文件的密码,这样其他小伙伴也可以通过charles来查看https加密请求的内容了。