iOS SSL Pinning防止中间人攻击

系统: 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的通讯图:
在这里插入图片描述

流行的技术解决方案

NamePinning implementationLanguageTypeLink
NSURLSessionCertificate file, public keyObjective-CApple networking libraryLink
AlamoFireCertificate file, public keySwiftNetworking libraryLink
AFNetworkingCertificate file, public keyObjective-CNetworking libraryLink
TrustKitPublic keyObjective-CSSL pinningLink

SSL/TLS的技术实现

TLS是SSL的继任者,TLS指出了SSL 3.0中的一些缺陷,并得到Internet Engineering Task Force (IETF) 证实。
实现TLS通讯有三个步骤:

  1. 建立连接
  2. 通过非对称加密,客户端通过公钥加密,服务端通过私钥加密,交互以后通讯要用的对称加密秘钥
  3. 通过第二步沟通好的秘钥,用对称加密来通讯。
    在这里插入图片描述

下载代码

为了后续好说明,请下载下面代码
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,下面的代码解析:

  1. 创建一个dictionary,key表示host,value表示证书信息。目的很明显是校验证书的host是否是正确的。
  2. 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,如果没有的话。

  1. 打开Charles, 切换到Sequence视图,Filter输入api.stackexchange.com,清除掉记录。(注意:如果开了梯子或者VPN,请先关掉,否则影响抓不到数据。)
    在这里插入图片描述
  2. 查看Charles的菜单,确认macOS Proxy已经勾选上。
  3. 接着Charles菜单 Proxy ▸ SSL Proxying Settings ▸ 增加 api.stackexchange.com. 端口Port field可以留白. 选择开启Enable SSL Proxying,点击OK确认.
  4. 接着,你需要安装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.
  5. 运行项目则看到下面的结果
    在这里插入图片描述
  6. 在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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值