Search code examples
objective-cssl-certificatensurlconnectionnsurl

Objective-C / C pulling private key (modulus) from SecKeyRef


I need a clean way to pull out my servers public key and compare it to local data in order to defend against expiring / renewed keys in the future, but I can't seem to get at the 256 bit key or express it as useful data for comparison...

Here's what I have so far...

-(BOOL)trustCertFromChallenge:(NSURLAuthenticationChallenge *)challenge
{

    SecTrustResultType trustResult;
    SecTrustRef trust = challenge.protectionSpace.serverTrust;
    OSStatus status = SecTrustEvaluate(trust, &trustResult);


    NSString *localKey = @"MY_PUBLIC_KEY";
    NSData *localKeyData = [localKey dataUsingEncoding:NSUTF8StringEncoding];

    SecCertificateRef serverCertificate = SecTrustGetCertificateAtIndex(trust, 0);
    SecKeyRef key = SecTrustCopyPublicKey(trust);

    DLog(@"Cert: %@  Key:%@",serverCertificate,key);

    // this prints the correct cert information and key information
    // for clarity....
    // Key: <SecKeyRef algorithm id: 1, key type: RSAPublicKey, version: 3, block size: 2048 bits, exponent: {hex: 10001, decimal: 65537}, modulus: MY_PUBLIC_KEY, addr: 0x7fa78b80bc00>

    // so far so good.. now for grabbing the key
    NSData *keyData = [self getPublicKeyBitsFromKey:key];

    DLog(@"Local: %@ - %li Key: %@ - %li",[localKeyData description],[localKeyData length],[keyData description],[keyData length]);


    if ([localKeyData isEqualToData:keyData])
        DLog(@"ITS THE SAME!");
    else
        DLog(@"NOT THE SAME!");

}

I've had a go with Apples Crypto Exercise and got to the following...

- (NSData *)getPublicKeyBitsFromKey:(SecKeyRef)givenKey {

    static const uint8_t publicKeyIdentifier[] = "com.mydomain.publickey";
    NSData *publicTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:sizeof(publicKeyIdentifier)];

    OSStatus sanityCheck = noErr;
    NSData *publicKeyBits = nil;

    NSMutableDictionary *queryPublicKey = [[NSMutableDictionary alloc] init];
    [queryPublicKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
    [queryPublicKey setObject:publicTag forKey:(__bridge id)kSecAttrApplicationTag];
    [queryPublicKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];

    // Temporarily add key to the Keychain, return as data:
    NSMutableDictionary * attributes = [queryPublicKey mutableCopy];
    [attributes setObject:(__bridge id)givenKey forKey:(__bridge id)kSecValueRef];
    [attributes setObject:@YES forKey:(__bridge id)kSecReturnData];
    CFTypeRef result;
    sanityCheck = SecItemAdd((__bridge CFDictionaryRef) attributes, &result);
    if (sanityCheck == errSecSuccess) {
        publicKeyBits = CFBridgingRelease(result);

        // Remove from Keychain again:
        (void)SecItemDelete((__bridge CFDictionaryRef) queryPublicKey);
    }

    return publicKeyBits;
}

This returns 270 bytes instead of the expected 256.. which I can't express in a way to compare it to my localData

The local key is 512 bytes of ASCII (why?) 45323636 32323330 and the derived key is 270 bytes of UTF8 223b70a0 56f28f68

First I need to get to 256 bytes from getPublicKeyBitsFromKey and I also need to express my data in the same manner to compare it.

It's also worth noting that

NSString *keyString = [NSString stringWithUTF8String:[keyData bytes]];

and

NSString *keyString = [[NSString alloc] initWithBytes:[keyData bytes] length:[keyData length] encoding:NSUTF8StringEncoding];

return (null)

NSString *keyString = [NSString stringWithCharacters:[keyData bytes] length:[keyData length]];

doesn't even log

Any help would be greatly appreciated, so thanks in advance.


Solution

  • I solved this by having a copy of the .der locally and pinning just it's public key.

    -(BOOL)trustCertFromChallenge:(NSURLAuthenticationChallenge *)challenge
    {
        SecTrustResultType trustResult;
        SecTrustRef trust = challenge.protectionSpace.serverTrust;
        OSStatus status = SecTrustEvaluate(trust, &trustResult);
    
        //DLog(@"Failed: %@",error.localizedDescription);
        //DLog(@"Status: %li | Trust: %@ - %li",(long)status,trust,(long)trustResult);
    
        if (status == 0 && (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed)) {
    
            SecKeyRef serverKey = SecTrustCopyPublicKey(trust);
    
            NSString *certPath = [[NSBundle mainBundle] pathForResource:@"MYCert" ofType:@"der"];
            NSData *certData = [NSData dataWithContentsOfFile:certPath];
            SecCertificateRef localCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
    
            SecKeyRef localKey = NULL;
            SecTrustRef localTrust = NULL;
            SecCertificateRef certRefs[1] = {localCertificate};
            CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, (void *)certRefs, 1, NULL);
            SecPolicyRef policy = SecPolicyCreateBasicX509();
            OSStatus status = SecTrustCreateWithCertificates(certArray, policy, &localTrust);
    
            if (status == errSecSuccess)
                localKey = SecTrustCopyPublicKey(localTrust);
    
            CFRelease(localTrust);
            CFRelease(policy);
            CFRelease(certArray);
    
             if (serverKey != NULL && localKey != NULL && [(__bridge id)serverKey isEqual:(__bridge id)localKey])
                return YES;
            else
                return NO;
        }
    
        //DLog(@"Failed: %@",error.localizedDescription);
    
        return NO;
    }