How to pin the Public key of a certificate on iOS

2019-01-10 01:25发布

While improving the security of an iOS application that we are developing, we found the need to PIN (the entire or parts of) the SSL certificate of server to prevent man-in-the-middle attacks.

Even though there are various approaches to do this, when you searching for thisI only found examples for pinning the entire certificate. Such practice poses a problem: As soon as the certificate is updated, your application will not be able to connect anymore. If you choose to pin the public key instead of the entire certificate you will find yourself (I believe) in an equally secure situation, while being more resilient to certificate updates in the server.

But how do you do this?

8条回答
走好不送
2楼-- · 2019-01-10 01:49

Here the Swifty answer. Save the certificate (as .cer file) of your website in the main bundle. Then use this URLSessionDelegate method:

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

    guard
        challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
        let serverTrust = challenge.protectionSpace.serverTrust,
        SecTrustEvaluate(serverTrust, nil) == errSecSuccess,
        let serverCert = SecTrustGetCertificateAtIndex(serverTrust, 0) else {

            reject(with: completionHandler)
            return
    }

    let serverCertData = SecCertificateCopyData(serverCert) as Data

    guard
        let localCertPath = Bundle.main.path(forResource: "shop.rewe.de", ofType: "cer"),
        let localCertData = NSData(contentsOfFile: localCertPath) as Data?,

        localCertData == serverCertData else {

            reject(with: completionHandler)
            return
    }

    accept(with: serverTrust, completionHandler)

}

...

func reject(with completionHandler: ((URLSession.AuthChallengeDisposition, URLCredential?) -> Void)) {
    completionHandler(.cancelAuthenticationChallenge, nil)
}

func accept(with serverTrust: SecTrust, _ completionHandler: ((URLSession.AuthChallengeDisposition, URLCredential?) -> Void)) {
    completionHandler(.useCredential, URLCredential(trust: serverTrust))
}

You can get the .cer file with Chrome like this.

查看更多
Emotional °昔
3楼-- · 2019-01-10 01:50

You can do public key SSL pinning using the SecTrustCopyPublicKey function of the Security.framework. See an example at connection:willSendRequestForAuthenticationChallenge: of the AFNetworking project.

If you need openSSL for iOS, use https://gist.github.com/foozmeat/5154962 It's based on st3fan/ios-openssl, which currently doesn't work.

查看更多
【Aperson】
4楼-- · 2019-01-10 01:53

...for pinning the entire certificate. Such practice poses a problem...

Also, Google changes the certificate monthly (or so) but retains or re-certifies the public. So certificate pinning will result in a lot of spurious warnings, while public key pinning will pass key continuity tests.

I believe Google does it to keep CRLs, OCSP and Revocation Lists manageable, and I expect others will do it also. For my sites, I usually re-certify the keys so folks to ensure key continuity.

But how do you do this?

Certificate and Public Key Pinning. The article discusses the practice and offers sample code for OpenSSL, Android, iOS, and .Net. There is at least one problem with iOS incumbent to the framework discussed at iOS: Provide Meaningful Error from NSUrlConnection didReceiveAuthenticationChallenge (Certificate Failure).

Also, Peter Gutmann has a great treatment of key continuity and pinning in his book Engineering Security.

查看更多
Summer. ? 凉城
5楼-- · 2019-01-10 01:58

If you use AFNetworking (more specifically, AFSecurityPolicy), and you choose the mode AFSSLPinningModePublicKey, it doesn't matter if your certificates change or not, as long as the public keys stay the same. Yes, it is true that AFSecurityPolicy doesn't provide a method for you to directly set your public keys; you can only set your certificates by calling setPinnedCertificates. However, if you look at the implementation of setPinnedCertificates, you'll see that the framework is extracting the public keys from the certificates and then comparing the keys.

In short, pass in the certificates, and don't worry about them changing in the future. The framework only cares about the public keys in those certificates.

The following code works for me.

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
[manager.securityPolicy setPinnedCertificates:myCertificate];
查看更多
闹够了就滚
6楼-- · 2019-01-10 01:59

You could use the PhoneGap (Build) plugin mentioned here: http://www.x-services.nl/certificate-pinning-plugin-for-phonegap-to-prevent-man-in-the-middle-attacks/734

The plugin supports multiple certificates, so the server and client don't need to be updated at the same time. If your fingerprint changes every (say) 2 year, then implement a mechanism for forcing the clients to update (add a version to your app and create a 'minimalRequiredVersion' API method on the server. Tell the client to update if the app version is too low (f.i. when the new certificate is activate).

查看更多
我欲成王,谁敢阻挡
7楼-- · 2019-01-10 02:08

If you use AFNetworking, use AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];

查看更多
登录 后发表回答