iPhoneDeveloper
iPhoneDeveloper

Reputation: 996

SSL certificate validation in NSURLSession

Which are the different ways in which we can implement SSL certificate validation in my ios app ? Currently what i am doing is comparing the remote SSL certificate data with local certificate data . But drawback of this method is everytime we change remote certificate we need to ask for update from clients in our app which is quite annoying . Instead of comparing data is there another way to perform SSL certificate validation ? Here is my code inside didReceiveChallenge .

-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {


SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
      SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, 0); //index 0 indicates leaf certificate .

 NSMutableArray *policies = [NSMutableArray array];
      [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)challenge.protectionSpace.host)];
      SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);


      SecTrustResultType result;
      SecTrustEvaluate(serverTrust, &result);
      BOOL certificateIsValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);  //Unspecified-4 ,Proceed-1


      NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate));


      NSData *localCertificateData = [NSData dataWithContentsOfFile: self.nsurl_pathToCertificate ];


      if ([remoteCertificateData isEqualToData:localCertificateData ]&& certificateIsValid) {
        NSLog(@"Certificate data are same ");
        NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
        completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
      }
      else{
        NSLog(@"Certificate data are different or invalid certificate");
        completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, NULL);
      }
    }

Where self.nsurl_pathToCertificate is the path to my local certificate .

Upvotes: 4

Views: 2542

Answers (1)

iSashok
iSashok

Reputation: 2446

The different way do that is to use Public keys(Pinning the key) for compare it, look more in wiki

At first you load all certificates what you have in Bundle

+ (NSSet *)certificatesInBundle:(NSBundle *)bundle {
    NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];

    NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]];
    for (NSString *path in paths) {
        NSData *certificateData = [NSData dataWithContentsOfFile:path];
        [certificates addObject:certificateData];
    }

    return [NSSet setWithSet:certificates];
}

+ (NSSet *)defaultPinnedCertificates {
    static NSSet *_defaultPinnedCertificates = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSBundle *bundle = [NSBundle bundleForClass:[self class]];
        _defaultPinnedCertificates = [self certificatesInBundle:bundle];
    });

    return _defaultPinnedCertificates;
}

_pinnedCertificates = [YOUR_CLASS defaultPinnedCertificates];

and create public key from it

- (void)setPinnedCertificates:(NSSet *)pinnedCertificates {
    _pinnedCertificates = pinnedCertificates;

    if (self.pinnedCertificates) {
        NSMutableSet *mutablePinnedPublicKeys = [NSMutableSet setWithCapacity:[self.pinnedCertificates count]];
        for (NSData *certificate in self.pinnedCertificates) {
            id publicKey = AFPublicKeyForCertificate(certificate);
            if (!publicKey) {
                continue;
            }
            [mutablePinnedPublicKeys addObject:publicKey];
        }
        self.pinnedPublicKeys = [NSSet setWithSet:mutablePinnedPublicKeys];
    } else {
        self.pinnedPublicKeys = nil;
    }
}

static id AFPublicKeyForCertificate(NSData *certificate) {
    id allowedPublicKey = nil;
    SecCertificateRef allowedCertificate;
    SecCertificateRef allowedCertificates[1];
    CFArrayRef tempCertificates = nil;
    SecPolicyRef policy = nil;
    SecTrustRef allowedTrust = nil;
    SecTrustResultType result;

    allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);

    allowedCertificates[0] = allowedCertificate;
    tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);

    policy = SecPolicyCreateBasicX509();
    SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust);
    SecTrustEvaluate(allowedTrust, &result);

    allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);

    if (allowedTrust) {
        CFRelease(allowedTrust);
    }

    if (policy) {
        CFRelease(policy);
    }

    if (tempCertificates) {
        CFRelease(tempCertificates);
    }

    if (allowedCertificate) {
        CFRelease(allowedCertificate);
    }

    return allowedPublicKey;
}

and in

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler 

method check it

NSMutableArray *policies = [NSMutableArray array];
    [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];

    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
    NSUInteger trustedPublicKeyCount = 0;
    NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);

    for (id trustChainPublicKey in publicKeys) {
        for (id pinnedPublicKey in self.pinnedPublicKeys) {
            if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
                trustedPublicKeyCount += 1;
            }
        }
    }
    return trustedPublicKeyCount > 0;


static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
    SecPolicyRef policy = SecPolicyCreateBasicX509();
    CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
    NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
    for (CFIndex i = 0; i < certificateCount; i++) {
        SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);

        SecCertificateRef someCertificates[] = {certificate};
        CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL);

        SecTrustRef trust;
        SecTrustCreateWithCertificates(certificates, policy, &trust);

        SecTrustResultType result;
        SecTrustEvaluate(trust, &result);

        [trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];

        if (trust) {
            CFRelease(trust);
        }

        if (certificates) {
            CFRelease(certificates);
        }

        continue;
    }
    CFRelease(policy);

    return [NSArray arrayWithArray:trustChain];
}

static BOOL AFSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) {
    return [(__bridge id)key1 isEqual:(__bridge id)key2];
}

This code form AFNetwking

For this way you no need update local certificates if it updated in remote server

Upvotes: 1

Related Questions