How to pin the Public key of a certificate on iOS

后端 未结 7 964
执笔经年
执笔经年 2020-11-30 18:15

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-

7条回答
  •  一个人的身影
    2020-11-30 19:00

    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:

    1. Use a NSURLConnectionDelegate to retrieve the data, and implement willSendRequestForAuthenticationChallenge.
    2. Include a reference certificate in DER format. In the example I've used a simple resource file.
    3. Extract the public key presented by the server
    4. Extract the public key from your reference certificate
    5. Compare the two
    6. If they match, continue with the regular checks (hostname, certificate signing, etc)
    7. If they don't match, fail.

    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.

提交回复
热议问题