系统: Mac OS 10.14.6, XCode 11,swift 5.0
写作时间:2019-10-18
说明
黑客攻击App,其中一种常用方式是绕过 SSL/TLS (Secure Sockets Layer/Transport Layer Security) 的加密方式。虽然SSL/TLS 的传输过程是加密的,但是还可能被中间人攻击。
注释:这个图是server 到 App单向的,实际上劫持是双向都被劫持的。
所以需要SSL Pinning这项技术去保驾护航。SSL Pinning的意思就是提前把服务端的证书(Certificate file, *.der文件),或者把证书信息的公钥字符串(public key)提前存到APP里面,在发起请求之前,需要把服务端的证书信息与客户端预存的信息做对比,成功后才会相互通信。简单一句话就是服务端证书锁定?。
没有SSL Pinning的通讯图:
有SSL Pinning的通讯图:
流行的技术解决方案
Name | Pinning implementation | Language | Type | Link |
---|---|---|---|---|
NSURLSession | Certificate file, public key | Objective-C | Apple networking library | Link |
AlamoFire | Certificate file, public key | Swift | Networking library | Link |
AFNetworking | Certificate file, public key | Objective-C | Networking library | Link |
TrustKit | Public key | Objective-C | SSL pinning | Link |
SSL/TLS的技术实现
TLS是SSL的继任者,TLS指出了SSL 3.0中的一些缺陷,并得到Internet Engineering Task Force (IETF) 证实。
实现TLS通讯有三个步骤:
- 建立连接
- 通过非对称加密,客户端通过公钥加密,服务端通过私钥加密,交互以后通讯要用的对称加密秘钥
- 通过第二步沟通好的秘钥,用对称加密来通讯。
下载代码
为了后续好说明,请下载下面代码
https://github.com/zgpeace/SSLPinningDemo
生成证书存到App内部
Terminal 切换到项目文件夹
cd SSLPinningDemo
执行下面的命令,获取证书
openssl s_client -connect api.stackexchange.com:443 </dev/null
当执行结束以后,看到有相同名字Common Name (CN)的证书
Certificate chain
0 s:/C=US/ST=NY/L=New York/O=Stack Exchange, Inc./CN=*.stackexchange.com
i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA
1 s:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA
i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA
下面的证书,是我们比较感兴趣的,它的CN是*.stackexchange.com
Server certificate
-----BEGIN CERTIFICATE-----
MIIHMjCCBhqgAwIBAgIQBmgM1QeOzDnM9C33n9PrfTANBgkqhkiG9w0BAQsFADBw
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMS8wLQYDVQQDEyZEaWdpQ2VydCBTSEEyIEhpZ2ggQXNz
dXJhbmNlIFNlcnZlciBDQTAeFw0xNjA1MjEwMDAwMDBaFw0xOTA4MTQxMjAwMDBa
MGoxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJOWTERMA8GA1UEBxMITmV3IFlvcmsx
HTAbBgNVBAoTFFN0YWNrIEV4Y2hhbmdlLCBJbmMuMRwwGgYDVQQDDBMqLnN0YWNr
ZXhjaGFuZ2UuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr0YD
zscT5i6T2FaRsTGNCiLB8OtPXu8N9iAyuaROh/nS0kRRsN8wUMk1TmgZhPuYM6oF
S377V8W2LqhLBMrPXi7lnhvKt2DFWCyw38RrDbEsM5dzVGErmhux3F0QqcTI92zj
VW61DmE7NSQLiR4yonVpTpdAaO4jSPJxn8d+4p1sIlU2JGSk8LZSWFqaROc7KtXt
lWP4HahNRZtdwvL5dIEGGNWx+7B+XVAfY1ygc/UisldkA+a3D2+3WAtXgFZRZZ/1
CWFjKWJNMAI6ZBAtlbgSNgRYxdcdleIhPLCzkzWysfltfiBmsmgz6VCoFR4KgJo8
Gd3MeTWojBthM10SLwIDAQABo4IDzDCCA8gwHwYDVR0jBBgwFoAUUWj/kK8CB3U8
zNllZGKiErhZcjswHQYDVR0OBBYEFFrBQmPCYhOznZSEqjIeF8tto4Z7MIIB/AYD
VR0RBIIB8zCCAe+CEyouc3RhY2tleGNoYW5nZS5jb22CEXN0YWNrb3ZlcmZsb3cu
Y29tghMqLnN0YWNrb3ZlcmZsb3cuY29tgg1zdGFja2F1dGguY29tggtzc3RhdGlj
Lm5ldIINKi5zc3RhdGljLm5ldIIPc2VydmVyZmF1bHQuY29tghEqLnNlcnZlcmZh
dWx0LmNvbYINc3VwZXJ1c2VyLmNvbYIPKi5zdXBlcnVzZXIuY29tgg1zdGFja2Fw
cHMuY29tghRvcGVuaWQuc3RhY2thdXRoLmNvbYIRc3RhY2tleGNoYW5nZS5jb22C
GCoubWV0YS5zdGFja2V4Y2hhbmdlLmNvbYIWbWV0YS5zdGFja2V4Y2hhbmdlLmNv
bYIQbWF0aG92ZXJmbG93Lm5ldIISKi5tYXRob3ZlcmZsb3cubmV0gg1hc2t1YnVu
dHUuY29tgg8qLmFza3VidW50dS5jb22CEXN0YWNrc25pcHBldHMubmV0ghIqLmJs
b2dvdmVyZmxvdy5jb22CEGJsb2dvdmVyZmxvdy5jb22CGCoubWV0YS5zdGFja292
ZXJmbG93LmNvbYIVKi5zdGFja292ZXJmbG93LmVtYWlsghNzdGFja292ZXJmbG93
LmVtYWlsghJzdGFja292ZXJmbG93LmJsb2cwDgYDVR0PAQH/BAQDAgWgMB0GA1Ud
JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB1BgNVHR8EbjBsMDSgMqAwhi5odHRw
Oi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1oYS1zZXJ2ZXItZzUuY3JsMDSgMqAw
hi5odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1oYS1zZXJ2ZXItZzUuY3Js
MEwGA1UdIARFMEMwNwYJYIZIAYb9bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v
d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQICMIGDBggrBgEFBQcBAQR3MHUw
JAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBNBggrBgEFBQcw
AoZBaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkhpZ2hB
c3N1cmFuY2VTZXJ2ZXJDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsF
AAOCAQEARIdUz7n08ZtqWscAmTXegtB6yPrU0l5IQCXQRqnEVXPKyS+w8IVOcblT
T/W2Qlp5we2BTDbRDfVokXIOSxOTAT0XN3f3c+nbvKJ3XMBH236846AY6bpfqL0/
05gcdt39d2iXTL+qnJW9P0yFKpkfGXBBTYQl4ACSeThSuSBXIVJ0v/TfR9+ggXuP
pmXiIKkPOReKu2Tu8SO7+5KRqRJvYhP9mhL4Bl+YLrTQXzM1NwVAahRT1QJJNemy
yEY1kkZOCKt0xRu4CVWhJlpNdoRZenT9BrD8Fo22kt5MxAvCVrjT/g1BHDQd4S8p
PKC8kRwmMA8mdo8TiHJQMy0DBCDCDg==
-----END CERTIFICATE-----
subject=/C=US/ST=NY/L=New York/O=Stack Exchange, Inc./CN=*.stackexchange.com
issuer=/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA
保存证书到文件夹。(注意先删除项目里已经存在的证书stackexchange.com.der,运行下面的命令后自动生成。)
openssl s_client -connect api.stackexchange.com:443 </dev/null \
| openssl x509 -outform DER -out stackexchange.com.der
代码实现SSL Pinning
把证书stackexchange.com.der引入项目,
打开文件NetworkClient.swift ,下面的代码表示读取证书里面的内容,创建证书对象。
struct Certificates {
static let stackExchange =
Certificates.certificate(filename: "stackexchange.com")
private static func certificate(filename: String) -> SecCertificate {
let filePath = Bundle.main.path(forResource: filename, ofType: "der")!
let data = try! Data(contentsOf: URL(fileURLWithPath: filePath))
let certificate = SecCertificateCreateWithData(nil, data as CFData)!
return certificate
}
}
依然在文件NetworkClient.swift,下面的代码解析:
- 创建一个dictionary,key表示host,value表示证书信息。目的很明显是校验证书的host是否是正确的。
- ServerTrustManager 是执行上面参数的类。
// 1
let evaluators = [
"api.stackexchange.com":
PinnedCertificatesTrustEvaluator(certificates: [
Certificates.stackExchange
])
]
let session: Session
// 2
private init() {
session = Session(
serverTrustManager: ServerTrustManager(evaluators: evaluators)
)
}
在类ViewController.swift,下面的方法表示执行网络请求的时候,判断是否是证书校验不通过的错误。
NetworkClient.request(Router.users)
.responseDecodable { (response: DataResponse<UserList>) in
switch response.result {
case .success(let value):
self.users = value.users
case .failure(let error):
let isServerTrustEvaluationError =
error.asAFError?.isServerTrustEvaluationError ?? false
let message: String
if isServerTrustEvaluationError {
message = "Certificate Pinning Error"
} else {
message = error.localizedDescription
}
self.presentError(withTitle: "Oops!", message: message)
}
}
用Charles测试证书锁定Certificate Pinning
下载最新版本的Charles,如果没有的话。
- 打开Charles, 切换到Sequence视图,Filter输入api.stackexchange.com,清除掉记录。(注意:如果开了梯子或者VPN,请先关掉,否则影响抓不到数据。)
- 查看Charles的菜单,确认macOS Proxy已经勾选上。
- 接着Charles菜单 Proxy ▸ SSL Proxying Settings ▸ 增加 api.stackexchange.com. 端口Port field可以留白. 选择开启Enable SSL Proxying,点击OK确认.
- 接着,你需要安装Charles Proxy SSL certificate证书,使得模拟器可以发送proxying SSL requests请求。
在Charles, 点击 Help ▸ SSL Proxying ▸ Install Charles Root Certificate in iOS Simulators安装根证书到模拟器. 接着, 在模拟器打开Settings app. 点击General ▸ About ▸ Certificate Trust Settings (一般在组下面). 打开 Charles Proxy CA,并在确认框中点击Continue. - 运行项目则看到下面的结果
- 在Charles中,看到如下拒绝访问的记录
恭喜你! 成功阻止了中间人攻击!
参考
https://www.raywenderlich.com/1484288-preventing-man-in-the-middle-attacks-in-ios-with-ssl-pinning
https://www.guardsquare.com/en/blog/iOS-SSL-certificate-pinning-bypassing