4

Is it possible to check the SSL certificate thumbprint on iOS?

Bonus round: Does the thumbprint change when i extend my certificate? Are there any special considerations when extending the certificate if verify the thumbprint?

Thanks

2 Answers 2

14
+200

To verify the thumbprint/fingerprint, I use a category on NSURLAuthenticationChallenge. You don't have to use a category or can use a different input but the code to get the fingerprint of a certificate would actually be the same.

NSURLAuthenticationChallenge+Fingerprint.h

@import Foundation; @interface NSURLAuthenticationChallenge (Fingerprint) - (NSString *)SHA1Fingerprint; - (NSString *)MD5Fingerprint; @end 

NSURLAuthenticationChallenge+Fingerprint.m

#import "NSURLAuthenticationChallenge+Fingerprint.h" #import <CommonCrypto/CommonCrypto.h> typedef NS_ENUM(NSInteger, kFingerprintType) { kFingerprintTypeSHA1, kFingerprintTypeMD5 }; @implementation NSURLAuthenticationChallenge (Fingerprint) - (NSString *)SHA1Fingerprint { return [self fingerprintWithType:kFingerprintTypeSHA1]; } - (NSString *)MD5Fingerprint { return [self fingerprintWithType:kFingerprintTypeMD5]; } - (NSString *)fingerprintWithType:(kFingerprintType)type { SecTrustRef serverTrust = [[self protectionSpace] serverTrust]; SecTrustResultType trustResultType; SecTrustEvaluate(serverTrust, &trustResultType); SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, (SecTrustGetCertificateCount(serverTrust) - 1)); NSData *data = CFBridgingRelease(SecCertificateCopyData(certificate)); const NSUInteger length = [self lengthWithType:type]; unsigned char buffer[length]; switch (type) { case kFingerprintTypeSHA1: { CC_SHA1(data.bytes, (CC_LONG)data.length, buffer); break; } case kFingerprintTypeMD5: { CC_MD5(data.bytes, (CC_LONG)data.length, buffer); break; } } NSMutableString *fingerprint = [NSMutableString stringWithCapacity:length * 3]; for (int i = 0; i < length; i++) { [fingerprint appendFormat:@"%02x ",buffer[i]]; } return [fingerprint stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; } - (NSUInteger)lengthWithType:(kFingerprintType)type { switch (type) { case kFingerprintTypeSHA1: { return CC_SHA1_DIGEST_LENGTH; } case kFingerprintTypeMD5: { return CC_MD5_DIGEST_LENGTH; } } } 

With the example code:

#pragma mark - UIViewController - (void)viewDidLoad { [super viewDidLoad]; NSURL *url = [NSURL URLWithString:@"YOUR_HTTPS_URL"]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { // Do something meaningful }]; [task resume]; } #pragma mark - NSURLSessionDelegate - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { NSLog(@"%@", challenge.SHA1Fingerprint); NSLog(@"%@", challenge.MD5Fingerprint); } // Do something meaningful } 

I would get the output:

2014-11-17 00:09:10.880 test[48237:2922518] f9 d5 24 c2 08 6b bf 12 6f 48 cd 8a f0 4d ca 3e 7c f0 3f bc 2014-11-17 00:09:10.880 test[48237:2922518] bf 30 1a 8d f9 cb 15 bd 51 73 c8 22 a5 54 62 8a 

Safari can be used to verify the data:

Safari certificate fingerprint

Regarding the Extended Validation certificates, they're not a different type of certificates, they have the same mechanisms, but the certificate policies field will use a specific certificate policy identifier.

The fingerprint being the hash of the entire certificate, with any modifications (like using EV certificates), the fingerprint would be different but the process to get the fingerprint would be the same.

Sign up to request clarification or add additional context in comments.

6 Comments

Hi, thanks for the response. The question regarding extension is about what happens when the certificate expires and is extended with the same key and CSR, not about EV certificates at all.
Ho, my bad, didn't realize you were talking about renewal, so yes, if you renew with the same key and CSR, your renewed certificate will have a different fingerprint.
Is there any good common practice regarding that? If i hardcode the thumbprint into the app, i need to submit a new app for review before the certificate expires.
Regarding this particular case, I've never found any advices or best practices to follow. What I like to do is include the new value during a regular update of the app. During the transition period, I provide both the old and new value with expiration dates so the app can fallback to the old value (if not expired) during the transition to give time to the end-user. If both values fail to verify, I warn the user that an update is available and should be installed.
That works for me, but you are checking the root CA. Can you tell me how to check the certificates at intermediate and leaf level, please?
|
4

For those interested; this is the extension translated in Swift:

enum FingerprintType { case SHA1 case MD5 } extension NSURLAuthenticationChallenge { func getSHA1Fingerprint() -> String { return getFingerprintWithType(.SHA1) } func getMD5Fingerprint() -> String { return getFingerprintWithType(.MD5) } private func getFingerprintWithType(type : FingerprintType) -> String { var serverTrust = self.protectionSpace.serverTrust SecTrustEvaluate(serverTrust, nil) var certificate = SecTrustGetCertificateAtIndex(serverTrust, 0).takeUnretainedValue() var data = SecCertificateCopyData(certificate).takeUnretainedValue() as NSData let length = self.lengthWityType(type) var buffer = [UInt8](count:Int(length), repeatedValue: 0) switch(type) { case .SHA1: CC_SHA1(data.bytes, CC_LONG(data.length), &buffer) break case .MD5: CC_MD5(data.bytes, CC_LONG(data.length), &buffer) } var fingerPrint = NSMutableString() for byte in buffer { fingerPrint.appendFormat("%02x ", byte) } return fingerPrint.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()) } private func lengthWityType(type : FingerprintType) -> Int32 { switch type { case .SHA1: return CC_SHA1_DIGEST_LENGTH case .MD5: return CC_MD5_DIGEST_LENGTH default: return 0 } } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.