How to Compare Ssl Certificates Using Afnetworking

How to compare SSL certificates using AFNetworking

The term you're looking for is SSL Pinning, where the app verifies that a known certificate or public key matches one presented by a remote server.

AFNetworking supports both pinning with certificates or public keys. You'll need to add the certificate(s) or public key(s) to your app's Bundle, and enable the feature by setting either the defaultSSLPinningMode property on AFHttpClient or the SSLPinningMode property on AFURLConnectionOperation.

You can pin using AFSSLPinningModePublicKey or AFSSLPinningModeCertificate. AFSSLPinningModeCertificate means that the server's certificate must exactly match one of those in the bundle.

AFSSLPinningModePublicKey is more liberal and means that the server's certificate must match for any public key in the bundle, or any public key attached to certificates in the bundle.

There's an example of setting the pinning mode in the AppDotNet example.

self-signed SSL certificate using AFNetworking 2.3.1

AFSecurityPolicy's default behaviour is to validate the certificate chain. You should add all intermediate certificates, or disable validation of the chain:

self.securityPolicy.validatesCertificateChain = NO;

Adding intermediate certificates is the preferred approach.

SSL Pinning with AFNetworking

I got it working.

I was in the situation where I had created a self-signed cert for to hit my own server API from my iOS App.
I created my cert with OpenSSL. When I was done creating it, I had several files, one of which was "server.crt". Initially, I tried just renaming it to "server.cer", and using "AFSSLPinningModeCertificate" for my AFURLConnectionOperation objects. That did not work, and I noticed the example uses "AFSSLPinningModePublicKey", so I tried that, still no luck.

So I compared my file (that was the renamed ".crt" file) to his.

I noticed that "server.crt" was base64-encoded, and like this:

-----BEGIN CERTIFICATE-----
394230AFDFD...
-----END CERTIFICATE-----

I noticed from Mattt's example in AFNetworking that the "adn.cer" file he uses is not base64-encoded. It is just raw bytes.
So, I did this:

$ base64 -D -i ./server.crt -o ./server.cer

I set my AFURLConnectionOperation to AFSSLPinningModePublicKey.

I put that back in the project and did a clean and build of my iOS project, and everything worked fine.

Hope this helps!!

Btw, you may notice that Xcode will display info for for your ".crt" or ".cer" key whether it is the base64 or the raw one, so don't let that confuse you. You should be able to see the certificate data in either case, it's just that AF will only accept the raw (non-base64) one.

UPDATE:
Anyone having trouble with base64, this what works for me on OS X using OpenSSL:

$ openssl base64 -d -in ./server.crt -out ./server.cer

AFNetworking problems with TLS Verification of a self signed server root CA

With the help of a bunch of different SSL resources, I've found the solution to enabling the use of self signed certificates to validate a SSL enabled private server. I have also gotten a much much better understanding of SSL, existing iOS solutions, and the minor issues with each one that made it not work in my system. I'll attempt to outline all the resources that went into my solution and what small things made the difference.

We are still using AFNetworking and currently it is 2.6.0 which supposedly includes certificate pinning. This was the root of our problem; we were unable to verify the identity of our private server, which was sending down a leaf certificate signed by a self-signed CA root. In our iOS app, we bundle the self signed root certificate, which is then set as a trusted anchor by AFNetworking. However, because the server is a local server (hardware included with our product) the IP address is dynamic, so AFNetworking's certificate validation fails because we weren't able to disable the IP check.

To get to the root of the answer, we are using an AFHTTPSessionManager in order to implement a custom sessionDidReceiveAuthenticationChallengeCallback. (See: https://gist.github.com/r00m/e450b8b391a4bf312966) In that callback, we validate the server certificate using a SecPolicy that doesn't check for host name; see http://blog.roderickmann.org/2013/05/validating-a-self-signed-ssl-certificate-in-ios-and-os-x-against-a-changing-host-name/, which is an older implementation for NSURLConnection rather than NSURLSession.

The code:

Creating an AFHTTPSessionManager

    var manager: AFHTTPSessionManager = AFHTTPSessionManager()
manager.requestSerializer = AFJSONRequestSerializer() // force serializer to use JSON encoding
manager.setSessionDidReceiveAuthenticationChallengeBlock { (session, challenge, credential) -> NSURLSessionAuthChallengeDisposition in

if self.shouldTrustProtectionSpace(challenge, credential: credential) {
// shouldTrustProtectionSpace will evaluate the challenge using bundled certificates, and set a value into credential if it succeeds
return NSURLSessionAuthChallengeDisposition.UseCredential
}
return NSURLSessionAuthChallengeDisposition.PerformDefaultHandling
}

Implementation of custom validation

class func shouldTrustProtectionSpace(challenge: NSURLAuthenticationChallenge, var credential: AutoreleasingUnsafeMutablePointer<NSURLCredential?>) -> Bool {
// note: credential is a reference; any created credential should be sent back using credential.memory

let protectionSpace: NSURLProtectionSpace = challenge.protectionSpace
var trust: SecTrustRef = protectionSpace.serverTrust!

// load the root CA bundled with the app
let certPath: String? = NSBundle.mainBundle().pathForResource("rootCA", ofType: "cer")
if certPath == nil {
println("Certificate does not exist!")
return false
}

let certData: NSData = NSData(contentsOfFile: certPath!)!
let cert: SecCertificateRef? = SecCertificateCreateWithData(kCFAllocatorDefault, certData).takeUnretainedValue()

if cert == nil {
println("Certificate data could not be loaded. DER format?")
return false
}

// create a policy that ignores hostname
let domain: CFString? = nil
let policy:SecPolicy = SecPolicyCreateSSL(1, domain).takeRetainedValue()

// takes all certificates from existing trust
let numCerts = SecTrustGetCertificateCount(trust)
var certs: [SecCertificateRef] = [SecCertificateRef]()
for var i = 0; i < numCerts; i++ {
let c: SecCertificateRef? = SecTrustGetCertificateAtIndex(trust, i).takeUnretainedValue()
certs.append(c!)
}

// and adds them to the new policy
var newTrust: Unmanaged<SecTrust>? = nil
var err: OSStatus = SecTrustCreateWithCertificates(certs, policy, &newTrust)
if err != noErr {
println("Could not create trust")
}
trust = newTrust!.takeUnretainedValue() // replace old trust

// set root cert
let rootCerts: [AnyObject] = [cert!]
err = SecTrustSetAnchorCertificates(trust, rootCerts)

// evaluate the certificate and product a trustResult
var trustResult: SecTrustResultType = SecTrustResultType()
SecTrustEvaluate(trust, &trustResult)

if Int(trustResult) == Int(kSecTrustResultProceed) || Int(trustResult) == Int(kSecTrustResultUnspecified) {
// create the credential to be used
credential.memory = NSURLCredential(trust: trust)
return true
}
return false
}

A few things I learned about swift while going through this code.

  1. AFNetworking's implementation of setSessionDidReceiveAuthenticationChallengeBlock has this signature:

    • (void)setSessionDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __nullable __autoreleasing * __nullable credential))block;

The credential parameter is a reference/inout variable that needs to be assigned. In swift it looks like this: AutoreleasingUnsafeMutablePointer. In order to assign something to it in C, you'd do something like this:

*credential = [[NSURLCredential alloc] initWithTrust...];

In swift, it looks like this: (from converting NSArray to RLMArray with RKValueTransFormer fails converting outputValue to AutoreleasingUnsafeMutablePointer<AnyObject?>)

credential.memory = NSURLCredential(trust: trust)

  1. SecPolicyCreateSSL, SecCertificateCreateWithData and SecTrustGetCertificateAtIndex return Unmanaged! objects, you have to essentially convert them/bridge them using takeRetainedValue() or takeUnretainedValue(). (See http://nshipster.com/unmanaged/). We had memory issues/crashes when we used takeRetainedValue() and called the method more than once (there was crash on SecDestroy). Right now the build seems stable after we switched to using takeUnretainedValue(), since you don't need the certificates or ssl policies after the validation.

  2. TLS sessions cache. https://developer.apple.com/library/ios/qa/qa1727/_index.html That means when you get a successful verification on a challenge, you never get the challenge again. This can really mess with your head when you're testing a valid certificate, then test an invalid certificate, which then skips all validation, and you get a successful response from the server. The solution is to Product->Clean in your iOS simulator after each time you use a valid certificate and pass the validation challenge. Otherwise you might spend some time thinking incorrectly that you finally got the root CA to validate.

So here's simply a working solution for the issues I was having with my servers. I wanted to post everything on here to hopefully help someone else who's running a local or dev server with a self signed CA and an iOS product that needs to be SSL enabled. Of course, with ATS in iOS 9 I expect to be digging into SSL very soon again.

This code currently has some memory management issues and will be updated in the near future. Also, if anyone sees this implementation and says "Ah hah, this is just as bad as returning TRUE for invalid certificates", please let me know! As far as I can tell through our own testing, the app rejects invalid server certificates not signed by our root CA, and accepts the leaf certificate generated and signed by the root CA. The app bundle only has the root CA included, so the server certificate can be cycled after they expire and existing apps won't fail.

If I dig into AFNetworking a little bit more and figure out a one-to-three line solution to all of this (by toggling all those little flags they provide) I'll also post an update.

If AlamoFire starts supporting SSL also feel free to post a solution here.

I want to allow invalid SSL certificates with AFNetworking

To allow Invalid SSL Certificate with AFNetworking. Add the following line in AFURLConnectionOperation.h below #import Availability.h

#define _AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_ 1


Related Topics



Leave a reply



Submit