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?
SSL Pinning is a technique that we use on the client-side to avoid a man-in-the-middle attack by validating the server certificates. The developers embed (or pin) a list of trustful certificates to the client application during development, and use them to compare against the server certificates during runtime.
Follow these steps to enable Certificate Pinning in Android: Navigate to the application resources folder. Copy the server certificate to the certs folder as shown. Create the folder hierarchy, if required.
Public key is embedded in the SSL certificate and Private key is stored on the server and kept secret. When a site visitor fills out a form with personal information and submits it to the server, the information gets encrypted with the public key to protect if from eavesdropping.
SSL Pinning is the process of associating a host with their expected X509 certificate or a public key. Once a host's certificate or public key is known or identified, the certificate or public key is associated or 'pinned' to the host. This offers protection against certificate forgery.
In case you are in need of knowing how to extract this information from the certificate in your iOS code, here you have one way to do it.
First of all add the security framework.
#import <Security/Security.h>
The add the 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 be able to respond to a protection space. In a nutshell, this is when you can have a look at the certificate that is coming from the server, and decide to allow the connection to proceed or to cancel. What you want to do here is compare the certificates public key with the one you've pinned. Now the question is, how do you get such public key? Have a look at the following code:
First get the certificate in X509 format (you will need the ssl libraries for this)
const unsigned char *certificateDataBytes = (const unsigned char *)[serverCertificateData bytes]; X509 *certificateX509 = d2i_X509(NULL, &certificateDataBytes, [serverCertificateData length]);
Now we will prepare to read the public key data
ASN1_BIT_STRING *pubKey2 = X509_get0_pubkey_bitstr(certificateX509); NSString *publicKeyString = [[NSString alloc] init];
At this point you can iterate through the pubKey2 string 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);
The complete 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; } }
As far as I can tell you cannot easily create the expected public key directly in iOS, you need to do it via a certificate. So the steps needed are similar to pinning the certificate, but additionally you need to extract the public key from the actual certificate, and from a reference certificate (the expected public key).
What you need to do is:
willSendRequestForAuthenticationChallenge
.Some example code:
(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 example code only, and not thoroughly tested. For a full implementation start with the certificate pinning example by OWASP.
And remember that certificate pinning can always be avoided using SSL Kill Switch and similar tools.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With