How to connect a certificate public key to iOS

While increasing the security of the iOS application we are developing, we discovered the need for a PIN (all or part) of the serverโ€™s SSL certificate to prevent man-in-the-middle attacks.

Despite the fact that there are various approaches for this, when you are looking for this, I have found examples for binding the entire certificate. This practice creates a problem: once the certificate is updated, your application will no longer be able to connect. If you decide to bind the public key, and not the entire certificate, you will find yourself (as I believe) in an equally safe situation, being more resistant to certificate updates on the server.

But how do you do that?

+45
security ios iphone ssl ipad
Mar 31 '13 at 10:51
source share
8 answers

If you need to know how to extract this information from a certificate in your iOS code, here you have one way to do this.

First add the security infrastructure.

#import <Security/Security.h> 

Add openssl libraries. You can download them from https://github.com/st3fan/ios-openssl

 #import <openssl/x509.h> 

The NSURLConnectionDelegate protocol allows you to decide whether the connection should respond to the security space. In short, this is when you can see the certificate coming from the server and decide to allow the connection to continue or to cancel. Here you want to compare the public key of the certificates with the one you bound. Now the question is, how do you get such a public key? Take a look at the following code:

First get a certificate in X509 format (for this you need ssl libraries)

 const unsigned char *certificateDataBytes = (const unsigned char *)[serverCertificateData bytes]; X509 *certificateX509 = d2i_X509(NULL, &certificateDataBytes, [serverCertificateData length]); 

Now we will prepare for reading the public key data

 ASN1_BIT_STRING *pubKey2 = X509_get0_pubkey_bitstr(certificateX509); NSString *publicKeyString = [[NSString alloc] init]; 

At this point, you can iterate over the string pubKey2 and extract the bytes in HEX format into a string with the following loop

  for (int i = 0; i < pubKey2->length; i++) { NSString *aString = [NSString stringWithFormat:@"%02x", pubKey2->data[i]]; publicKeyString = [publicKeyString stringByAppendingString:aString]; } 

Print the public key to see it

  NSLog(@"%@", publicKeyString); 

Full code

 - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace { const unsigned char *certificateDataBytes = (const unsigned char *)[serverCertificateData bytes]; X509 *certificateX509 = d2i_X509(NULL, &certificateDataBytes, [serverCertificateData length]); ASN1_BIT_STRING *pubKey2 = X509_get0_pubkey_bitstr(certificateX509); NSString *publicKeyString = [[NSString alloc] init]; for (int i = 0; i < pubKey2->length; i++) { NSString *aString = [NSString stringWithFormat:@"%02x", pubKey2->data[i]]; publicKeyString = [publicKeyString stringByAppendingString:aString]; } if ([publicKeyString isEqual:myPinnedPublicKeyString]){ NSLog(@"YES THEY ARE EQUAL, PROCEED"); return YES; }else{ NSLog(@"Security Breach"); [connection cancel]; return NO; } } 
+32
Mar 31 '13 at 10:51
source share

As far as I can tell, you cannot easily create the expected public key directly in iOS, you need to do this through a certificate. Thus, the necessary steps are similar to attaching a certificate, but in addition you need to extract the public key from the actual certificate and from the reference certificate (expected public key).

What you need to do:

  • Use NSURLConnectionDelegate to retrieve data and implement willSendRequestForAuthenticationChallenge .
  • Include a reference certificate in DER format. In this example, I used a simple resource file.
  • Retrieve the public key provided by the server
  • Extract public key from reference certificate
  • Compare two
  • If they match, continue regular checks (hostname, certificate signature, etc.)
  • If they do not match, fail.

Code example:

  (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { // get the public key offered by the server SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; SecKeyRef actualKey = SecTrustCopyPublicKey(serverTrust); // load the reference certificate NSString *certFile = [[NSBundle mainBundle] pathForResource:@"ref-cert" ofType:@"der"]; NSData* certData = [NSData dataWithContentsOfFile:certFile]; SecCertificateRef expectedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData); // extract the expected public key SecKeyRef expectedKey = NULL; SecCertificateRef certRefs[1] = { expectedCertificate }; CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, (void *) certRefs, 1, NULL); SecPolicyRef policy = SecPolicyCreateBasicX509(); SecTrustRef expTrust = NULL; OSStatus status = SecTrustCreateWithCertificates(certArray, policy, &expTrust); if (status == errSecSuccess) { expectedKey = SecTrustCopyPublicKey(expTrust); } CFRelease(expTrust); CFRelease(policy); CFRelease(certArray); // check a match if (actualKey != NULL && expectedKey != NULL && [(__bridge id) actualKey isEqual:(__bridge id)expectedKey]) { // public keys match, continue with other checks [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge]; } else { // public keys do not match [challenge.sender cancelAuthenticationChallenge:challenge]; } if(actualKey) { CFRelease(actualKey); } if(expectedKey) { CFRelease(expectedKey); } } 

Disclaimer: This is only sample code and not fully tested. For a complete implementation, start with the OWASP certificate binding example .

And remember that with SSL Kill Switch and similar tools you can always avoid certificate binding.

+17
Mar 24 '15 at 13:49
source share

You can use public key SSL encryption using the SecTrustCopyPublicKey function in Security.framework. See an example in connection: willSendRequestForAuthenticationChallenge: AFNetworking project.

If you need openSSL for iOS, use https://gist.github.com/foozmeat/5154962 It is based on st3fan / ios-openssl, which currently does not work.

+9
Apr 28 '13 at 0:57
source share

You can 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 several certificates, so the server and client should not be updated at the same time. If your fingerprint changes every time (say) for 2 years, and then implement a mechanism for forced updating of clients (add the version to the application and create the minimumRequiredVersion API method on the server). Tell the client to update if the version of the application is too low (fi when activating a new certificate).

+5
Sep 22 '13 at 13:53 on
source share

If you use AFNetworking (more precisely, AFSecurityPolicy), and you select AFSSLPinningModePublicKey mode, it does not matter if your certificates have changed or not, if the public keys remain unchanged. Yes, it is true that AFSecurityPolicy does not provide a method that allows you to directly set your public keys; you can set only your certificates by calling setPinnedCertificates . However, if you look at the implementation of setPinnedCertificates, you will see that the structure decompresses the public keys from the certificates and then compares the keys.

In short, go through the certificates and donโ€™t worry that they will change in the future. Frames only care about public keys in these certificates.

The following code works for me.

 AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey]; [manager.securityPolicy setPinnedCertificates:myCertificate]; 
+3
Apr 22 '15 at 10:16
source share

... to secure the entire certificate. This practice creates a problem ...

In addition, Google monthly changes the certificate (or so), but retains or re-confirms the publication. Thus, issuing a certificate will lead to many false warnings, while public key pinning will pass key continuity tests.

I believe that Google is doing this to keep CRLs, OCSPs, and revocation lists manageable, and I expect others to do it too. For my sites, I usually re-certify keys so that people ensure key continuity.

But how do you do that?

Certificate and public key . The article discusses practice and offers sample code for OpenSSL, Android, iOS, and .Net. There is at least one problem with iOS existing on the platform discussed in iOS: Provide a meaningful error from NSUrlConnection didReceiveAuthenticationChallenge (certificate failure) .

In addition, Peter Gutmann does an excellent job of key continuity and reinforcement in his Engineering Safety book.

+1
Apr 27 '13 at 4:44
source share

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

0
Feb 28 '17 at 3:54 on
source share

Here is Swifty's answer. Save the certificate (as a .cer file) of your website in the main set. 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 a .cer file with Chrome, like this .

0
Oct. 13 '17 at 10:13 on
source share



All Articles