Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective C: Unable to fetch SecKeyRef from PEM private key

I'm new to Objective C & iOS programming.

I'm using a simple public / private keys (PEM format) generated using openssl to encrypt and decrypt data that needs to be exchanged between a Server and a Client. I got this successfully working in Java Server & Client.

The trouble started when I was encrypting data using public key in Java and decrypting using private key in Objective C / iOS. I've looked around a few examples and put some code together but I'm getting an error -25300 when I call the SecItemCopyMatching all the time as part of creating a SecKeyRef from the private key.

BTW, there are no certificates involved here and it's just plain keys. Here is what I'm doing:

  1. Read the PEM private key and Base64 decode.
  2. Generate a SecKeyRef from the decoded string using SecItemCopyMatching.
  3. Decrypt using the SecKeyDecrypt.

My problem is step #2 which returns a status of -25300 (errSecItemNotFound –25300
The item cannot be found. Available in iOS 2.0 and later.)

Here is my code for generating the SecKeyRef:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSString *challenge = @"2KFqc46DNSWrizzv69lJN25o62xEYQw/QLcMiT2V1XLER9uJbOu+xH2qgTuNWa1HZ9SW3Lq+HovtkhFmjmf08QkVQohHmxCJXVyCgVhPBleScAgQ8AoP3tmV0RqGb2mJrb19ybeYP7uZ2piVtF4cRwU1gO3VTooCUK3cX4wS7Tc=";
NSLog(@"challenge, %@", challenge);

NSData *incomingData = [self base64DataFromString:challenge];
uint8_t *challengeBuffer = (uint8_t*)[incomingData bytes];
NSLog(@"challengeBuffer: %s", challengeBuffer);

[self decryptWithPrivateKey:challengeBuffer];

free(challengeBuffer);

return YES;
}

// Generate a SecKeyRef from the private key in the private.pem file.
- (SecKeyRef)getPrivateKeyRef {
NSString *startPrivateKey = @"-----BEGIN RSA PRIVATE KEY-----";
NSString *endPrivateKey = @"-----END RSA PRIVATE KEY-----";
NSString* path = [[NSBundle mainBundle] pathForResource:@"private"
                                                 ofType:@"pem"];
NSString* content = [NSString stringWithContentsOfFile:path
                                              encoding:NSUTF8StringEncoding
                                                 error:NULL];
NSLog(@"Private Key: %@", content);

NSString *privateKey;
NSScanner *scanner = [NSScanner scannerWithString:content];
[scanner scanUpToString:startPrivateKey intoString:nil];
[scanner scanString:startPrivateKey intoString:nil];
[scanner scanUpToString:endPrivateKey intoString:&privateKey];

NSData *privateTag = [self dataWithBase64EncodedString:privateKey];
NSLog(@"Decoded String: %@", privateTag);

OSStatus status = noErr;
SecKeyRef privateKeyReference = NULL;

NSMutableDictionary * queryPrivateKey = [[NSMutableDictionary alloc] init];

// Set the private key query dictionary.
[queryPrivateKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
[queryPrivateKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
[queryPrivateKey setObject:privateTag forKey:(__bridge id)kSecAttrApplicationTag];
[queryPrivateKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
//[queryPrivateKey setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnRef];


// Get the key.
status = SecItemCopyMatching((__bridge CFDictionaryRef)queryPrivateKey, (CFTypeRef *)&privateKeyReference);
NSLog(@"status: %ld", status);

if(status != noErr)
{
    privateKeyReference = NULL;
}

return privateKeyReference;
}

// Decrypt data
- (void)decryptWithPrivateKey:(uint8_t *)cipherBuffer {
OSStatus status = noErr;

SecKeyRef privateKeyRef = [self getPrivateKeyRef];

size_t plainBufferSize = SecKeyGetBlockSize(privateKeyRef);
uint8_t *plainBuffer = malloc(plainBufferSize);

size_t cipherBufferSize = strlen((char *)cipherBuffer);
NSLog(@"decryptWithPrivateKey: length of input: %lu", cipherBufferSize);

//  Error handling
status = SecKeyDecrypt(privateKeyRef,
                       PADDING,
                       cipherBuffer,
                       cipherBufferSize,
                       &plainBuffer[0],
                       &plainBufferSize
                       );
NSLog(@"decryption result code: %ld (size: %lu)", status, plainBufferSize);
NSLog(@"FINAL decrypted text: %s", plainBuffer);
}

I've been breaking my head for a couple of days now and I felt like I need to get some help here. Any one any pointers? I could spend more time gaining the Crypto domain knowledge and support that iOS provides but I don't do any iOS programming at all and this is a one time thing.

I just need some direction and I can struggle to make it work.

TIA.

like image 617
user977505 Avatar asked Oct 31 '13 02:10

user977505


2 Answers

Unfortunately the Security framework on iOS requires that private keys be in the PKCS12 format, with a passphrase. Public keys can be in X509 armored DER or PKCS12, but private keys are required to be PKCS12. The private key you are trying to use is a PEM-formatted RSA key.

If you have access to the key it can be converted using the openssl command line tools:

openssl pkcs12 -export -nocerts -inkey privatekey.pem -out privatekey.p12

This will create a PKCS12 file with the private key, and requires a passphrase. If you do not have control of the private key (for example, if it is coming from an external source such as a server), you are out of luck.

But let's assume you were able to do the steps above to convert that troubesome PEM RSA private key to PKCS12. Extracting the private key from the PKCS12 data is not too difficult:

  1. Load the PKCS12 as NSData. You can do this using dataWithContentsOfURL: if this is a resource on the filesystem.
  2. Use SecPKCS12Import to import the PKCS12 data with the passphrase.
  3. Extract the SecIdentityRef from the imported items.
  4. Copy the private key from the SecIdentityRef

A function for doing so would be:

OSStatus    SecKeyPrivatePKCS12Import(CFDataRef keyData, CFStringRef passphrase, SecKeyRef *privateKey){
    OSStatus        status              = errSecSuccess;
    CFDictionaryRef secImportOptions    = NULL;
    CFArrayRef      secImportItems      = NULL;

    if ((keyData != NULL) && (CFStringGetLength(passphrase) > 0) ){
        const void *keys[] = { kSecImportExportPassphrase };
        const void *values[] = { passphrase };

        secImportOptions = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);

        status = SecPKCS12Import((CFDataRef) keyData, (CFDictionaryRef)secImportOptions, &secImportItems);
        if (CFArrayGetCount(secImportItems) > 0){
            CFDictionaryRef identityDict = CFArrayGetValueAtIndex(secImportItems, 0);
            SecIdentityRef identityApp = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
            SecIdentityCopyPrivateKey(identityApp, privateKey);
        }
    }

    return status;
}

Calling it from Objective-C would look like:

OSStatus status = errSecSuccess;

status = SecKeyPrivatePKCS12Import((_bridge CFDataRef)data, (_bridge CFStringRef)passphrase, &privateKey);
if (privateKey == NULL){
   // Check the status value for why it failed
}

Assuming that "data" is an instance of NSData that contains the PKCS12 data, and "passphrase" is an NSString instance representing the passphrase. On success "privateKey" is populated with the private key imported from the PKCS12 data.

like image 106
quellish Avatar answered Nov 08 '22 13:11

quellish


I have faced the same issue when I was working with java server and iPhone application and my work around was as below.

  1. Generate p12 on java server. [Remember to note down the password.]
  2. Convert raw bytes of p12 file into base 64 string.
  3. Send those data to iOS application no matter how you want.

    3.1 You can put base 64 in text file and send that to iOS. [Safest way and working fine in my case.]

    3.2 You can use JSON string to send that string. [This might corrupt your data.]

  4. Once you get the data on iPhone application convert base 64 string to NSData. NSData+Base64
  5. Use following method to get the SecKeyRef of your private key.

    - (SecKeyRef)getPrivateKeyFromData:(NSData *)p12Data withPassword:(NSString *)password {
        NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
        SecKeyRef privateKey = NULL;
        [options setObject:password forKey:(__bridge id)kSecImportExportPassphrase];
        CFArrayRef items = NULL;// = CFArrayCreate(NULL, 0, 0, NULL);
        OSStatus securityError = SecPKCS12Import((__bridge CFDataRef)p12Data,
                                              (__bridge CFDictionaryRef)options, &items);
        if (securityError == noErr && CFArrayGetCount(items) > 0) {
             CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
             SecIdentityRef identityApp =
             (SecIdentityRef)CFDictionaryGetValue(identityDict,
                                          kSecImportItemIdentity);
             securityError = SecIdentityCopyPrivateKey(identityApp, &privateKey);
        if (securityError != noErr) {
                privateKey = NULL;
            }
        }
        //NSLog(@"-------------------- Private Key Error %d",(int)securityError);
        CFRelease(items);
        options = nil;
        p12Data = nil;
        password = nil;
        return privateKey;
    }
    

Hope this helps!!!!!

like image 25
Pratik Mistry Avatar answered Nov 08 '22 12:11

Pratik Mistry