Importing private key into Keychain is not working as expected in iphone
Asked Answered
S

3

26

I need to sign requests before sending it to backend server. However the private key is given to me. So I need to import it and then use it to sign. I am able to import and sign in but that data is different from what I get by signing using openssl. I know its doing it wrong because when I import public key, i cannot verify it too. If there is a way i can avoid importing to keychain, that would be great too. Have been working hard on this for couple of days and this is a high pri work for us. Can some one please help.

- (SecKeyRef) getPrivateKey {
//RSA KEY BELOW IS DUMMY. 

key = @"-----BEGIN RSA PRIVATE KEY-----\nORtMei3ImKI2ZKI636I4+uNCwFfZv9pyJzXyfr1ZNo7iaiW7A0NjLxikNxrWpr/M\n6HD8B2j/CSjRPW3bhsgDXAx/AI1aSfJFxazjiTxx2Lk2Ke3jbhE=\n-----END RSA PRIVATE KEY-----\n";

NSString * tag = @"adpPrivateKey";

    NSString *s_key = [NSString string];
    NSArray  *a_key = [key componentsSeparatedByString:@"\n"];
    BOOL     f_key  = FALSE;

    for (NSString *a_line in a_key) {
        if ([a_line isEqualToString:@"-----BEGIN RSA PRIVATE KEY-----"]) {
            f_key = TRUE;
        }
        else if ([a_line isEqualToString:@"-----END RSA PRIVATE KEY-----"]) {
            f_key = FALSE;
        }
        else if (f_key) {
            s_key = [s_key stringByAppendingString:a_line];
        }
    }
    if (s_key.length == 0) return(nil);

    // This will be base64 encoded, decode it.
    NSData *d_key = [NSData dataFromBase64String:s_key];

if(d_key == nil) return nil;

    NSData *d_tag = [NSData dataWithBytes:[tag UTF8String] length:[tag length]];

    // Delete any old lingering key with the same tag
    NSMutableDictionary *privateKey = [[NSMutableDictionary alloc] init];
    [privateKey setObject:(id) kSecClassKey forKey:(id)kSecClass];
    [privateKey setObject:(id) kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
    [privateKey setObject:d_tag forKey:(id)kSecAttrApplicationTag];
    SecItemDelete((CFDictionaryRef)privateKey);

    CFTypeRef persistKey = nil;

    // Add persistent version of the key to system keychain
    [privateKey setObject:d_key forKey:(id)kSecValueData];
    [privateKey setObject:(id) kSecAttrKeyClassPrivate forKey:(id)
     kSecAttrKeyClass];
    [privateKey setObject:[NSNumber numberWithBool:YES] forKey:(id)
     kSecReturnPersistentRef];

    OSStatus secStatus = SecItemAdd((CFDictionaryRef)privateKey, &persistKey);
    if (persistKey != nil) CFRelease(persistKey);

    if ((secStatus != noErr) && (secStatus != errSecDuplicateItem)) {
        [privateKey release];
        return(nil);
    }

    // Now fetch the SecKeyRef version of the key
    SecKeyRef keyRef = nil;

    [privateKey removeObjectForKey:(id)kSecValueData];
    [privateKey removeObjectForKey:(id)kSecReturnPersistentRef];
    [privateKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnRef
     ];
    [privateKey setObject:(id) kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
    secStatus = SecItemCopyMatching((CFDictionaryRef)privateKey,
                                    (CFTypeRef *)&keyRef);

    if(secStatus != noErr)
        return nil;

    [privateKey release];

    return keyRef;
}

The Code below is used to sign. Part of code is from Apple example (http://developer.apple.com/library/ios/#samplecode/CryptoExercise/Listings/Classes_SecKeyWrapper_m.html#//apple_ref/doc/uid/DTS40008019-Classes_SecKeyWrapper_m-DontLinkElementID_17)

- (NSData *)getSignatureBytes:(NSString *)plainText {

OSStatus sanityCheck = noErr;
NSData * signedHash = nil;
uint8_t * signedHashBytes = NULL;
size_t signedHashBytesSize = 0;
SecKeyRef privateKey = NULL;

privateKey = [self getPrivateKey];

signedHashBytesSize = SecKeyGetBlockSize(privateKey);

//Create a SHA Encoded 
NSString * shaEncoded = [self sha256:plainText];
NSLog(@"%@", shaEncoded);


// Malloc a buffer to hold signature.

signedHashBytes = malloc( signedHashBytesSize * sizeof(uint8_t) );
memset((void *)signedHashBytes, 0x0, signedHashBytesSize);


NSData *inputData = [self getHashBytes:[plainText dataUsingEncoding:NSUTF8StringEncoding]];
int bytesLengthUINT8 = [inputData length]; 

sanityCheck =  SecKeyRawSign ( privateKey, kSecPaddingPKCS1, (const uint8_t *)inputData, CC_SHA256_DIGEST_LENGTH,(uint8_t *)signedHashBytes, &signedHashBytesSize);


if(sanityCheck != noErr)
    return nil;


signedHash = [NSData dataWithBytes:(const void *)signedHashBytes length:(NSUInteger)signedHashBytesSize];    
NSString *string = [signedHash base64EncodedString];

NSLog(@"%@", string);


if (signedHashBytes) free(signedHashBytes);
return signedHash;

}

I used the code sample in http://blog.flirble.org/2011/01/05/rsa-public-key-openssl-ios/ to import public key and verify and its failing.

Supportive answered 28/10, 2012 at 3:52 Comment(8)
What's the value of kSecPaddingPKCS1? Could you try and compare the moduli of the public and private key?Inclination
Hi @owlstead, Can you please elaborate as to what to do? Also do you see any issue in the way i am saving and retrieving private key. Please let me know if there are other libraries available that i can use.Supportive
Sorry, I'm not an ios expert. I do now quite a bit about crypto though, so I thought I give you some general recommendations. E.g. kSecPaddingPKCS1 does not specify a hash, and SHA-1 is likely to be the default. If the modulus of the private key and public key don't match, then they don't belong to the same key pair. In both cases the signature verification would fail.Inclination
Hi @owlstead, iOS does not support padding for SHA256 which i want. So i actually hased the input using SHA256 and then used kSecPaddingPKCS1 assuming it would not do any more hashing.Supportive
Ah, but the padding is different from the SHA-1 padding which is kind of the default; it requires a ASN.1 DER encoded structure around the hash with the OID of the hashing algorithm and the hash value. More info in the public PKCS#1 standard. Could you perform a raw RSA decrypt with the public key and post the result in hexadecimals?Inclination
And, did you have any progress, or are you still working on it?Inclination
I had a similar problem. See my answer here: https://mcmap.net/q/537592/-how-can-i-get-seckeyref-from-der-pem-fileElsie
@Kishore Bulusu two people below along with folks above have taken the time and effort to offer up answers. It would be courteous if you replied.Krysta
S
3

Take a look at the last method in the accepted answer for: Converting NSData to SecKeyRef

The problem is that iOS handles public and private keys slightly differently where the identification header that usually exist and is expected by other security APIs (in Java, for instance) is not expected in iOS. So you have to strip them out.

Shayn answered 20/5, 2014 at 5:23 Comment(0)
K
1

Perhaps instead of storing all your key in a keychain you can just store a simple character string in the key chain (as a secret_hash). Also, make the secure calls to the backend web services using the common, widely adopted AFNetworking library.

So if you need to sign your requests to a backend service using a private key, I suggest you do this by (a) using a robust wrapper library for service invocation (AFNetworking) and (b) storing the private key as a .pfx file at a location accessible to the app (I'd keep the .pfx file in the project root.)

So create your own subclass of AFHTTPClient and use the subclass to create AFHTTPRequestOperations with the challenge blocks set to use the credentials extracted from the .pfx.

This way, instead of creating AFHTTPRequestOperation's directly - create them using a MySignedAFHTTPRequestOperation method of your AFHTTPClient subclass. This method should create the AFHTTPRequestOperation and then set the challenge block like this ...

    [myOperationObject setAuthenticationChallengeBlock:^(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge)
    {
             NSString * pfxPath = [[NSBundle mainBundle]
                           pathForResource:@“pvtKeyFile” ofType:@"pfx"];

            NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile: pfxPath];
            CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data;    
            SecIdentityRef identity;
            SecTrustRef trust;
            myIdentityAndTrustExtractionHelper(inPKCS12Data, &identity, &trust);

            SecCertificateRef certificate = NULL;
            SecIdentityCopyCertificate (identity, &certificate); 

            const void *certs[] = {certificate};
            CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL);

            NSURLCredential *credential = [NSURLCredential
                                           credentialWithIdentity:identity
                                           certificates:(__bridge NSArray*)certArray
                                           persistence:NSURLCredentialPersistencePermanent];

            [[challenge sender] useCredential:credential
                forAuthenticationChallenge:challenge];
            CFRelease(certArray);
    } 

Here is some more detail on the identity extraction helper function used above ...

OSStatus myIdentityAndTrustExtractionHelper(CFDataRef inPKCS12Data,        
                                 SecIdentityRef *mySecIdentityRef,
                                 SecTrustRef *myTrustRef)
{
    //modify to get secret-hash from keychain 
    CFStringRef mySecretHash = CFSTR(secret_hash);
    const void *keys[] =   { kSecImportExportPassphrase };
    const void *values[] = { mySecretHash };


    CFArrayRef pkscItems = CFArrayCreate(NULL, 0, 0, NULL);
    OSStatus mySecurityError = SecPKCS12Import(inPKCS12Data,
                                CFDictionaryCreate(NULL,keys, values, 1,
                                NULL, NULL),
                                &pkscItems);
    if (mySecurityError == 0) 
    {                                 
        CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex (pkscItems, 0);
        const void *tempIdentity = NULL;
        tempIdentity = CFDictionaryGetValue (myIdentityAndTrust,
                                             kSecImportItemIdentity);
        *mySecIdentityRef = (SecIdentityRef)tempIdentity;
        const void *tempTrust = NULL;
        tempTrust = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust);
        *myTrustRef = (SecTrustRef)tempTrust;
    }

    return mySecurityError;
}

Once you create the AFHTTPRequest this way (i.e. via the MySignedAFHTTPRequestOperation method of your AFHTTPClient subclass), add it to a NSOperationsQueue for execution (of course you need to set the success and fail handler blocks appropriately when you create the operation)

In summary:

  • use the robust AFNetworking framework to make your webservice calls
  • store the cert private key as a .pfx file in the app and use it to create the credentials and keys
  • allow AFNetworking to do the encrypting for you by setting the credentials within each operation object you create.

Hope this helps.

Krysta answered 19/2, 2014 at 22:19 Comment(5)
Storing a private key in a plain file without a password sound like a bad idea. Suggesting it to be distributed as a file with the (mobile) application seems to defeat any security purposes for which it was cheated. KeyChain is meant exactly for the purpose of storing sensitive data and should be used.Geisha
Point taken. My solution focuses more on the invocation of the backend services and fitting the encryption into that framework (AFNetworking) I have updated the solution above to address your concern by storing not the key itself but the passphrase for the key in the keychain (specific calls to fetch the passphrase from the keychain needs to be substituted in the line where mySecretHash is fetched.Krysta
Even with that change, the keychain is still a better place to store sensitive information like keys. It's also not clear how your suggestions help the poster, whose question seems to be about the mechanics of generating the signature, not of making the HTTP requests.Cultivable
This responds to the part where the poster asks "If there is a way i can avoid importing to keychain, that would be great too."Krysta
And the mechanics of generating the signature (extracting the id and trust) are right there in the sample code ...Krysta
F
0

If you just want to store an AES-256 RSA PEM that you've got in standard PHP-compatible base64 string type format, into the system keychain, this works for me.

I'm posting this here because a lot of places, I read that you cannot just stick a PEM file directly into iOS at all; or else you have to strip some headers from it; etc. However, at least on iOS 9.3, this now works, and if I had seen this somewhere then it would have saved me a lot of time. (Note: the below is a heavily modified version of part of Objective-C-RSA from https://github.com/ideawu/Objective-C-RSA, see applicable license, I imply no endorsements. They also have a Swift version here:https://github.com/btnguyen2k/swift-rsautils that appears much more feature complete and would solve problems for a lot of folks.)

#define BR (__bridge id)
#define BRD (__bridge CFDictionaryRef)

+ (SecKeyRef)storePrivateKey:(NSString *)key inSystemKeychainWithTag:(NSString *)tag {
    NSRange spos;
    NSRange epos;
    spos = [key rangeOfString:@"-----BEGIN RSA PRIVATE KEY-----"];
    if(spos.length > 0) {
        epos = [key rangeOfString:@"-----END RSA PRIVATE KEY-----"];
    }
    else {
        spos = [key rangeOfString:@"-----BEGIN PRIVATE KEY-----"];
        epos = [key rangeOfString:@"-----END PRIVATE KEY-----"];
    }
    if(spos.location != NSNotFound && epos.location != NSNotFound){
        NSUInteger s = spos.location + spos.length;
        NSUInteger e = epos.location;
        NSRange range = NSMakeRange(s, e-s);
        key = [key substringWithRange:range];
    }
    key = [key stringByReplacingOccurrencesOfString:@"\r" withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@"\t" withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@" "  withString:@""];

    // This will be base64 encoded, decode it.
    NSData *data = [[NSData alloc] initWithBase64EncodedString:key options:0];

    if(data == nil){
        return nil;
    }

    //a tag to read/write keychain storage
    NSData *d_tag = [NSData dataWithBytes:[tag UTF8String] length:[tag length]];

    // Delete any old lingering key with the same tag
    NSMutableDictionary *options = [
    @{
      BR kSecClass: BR kSecClassKey,
      BR kSecAttrKeyType: BR kSecAttrKeyTypeRSA,
      BR kSecAttrApplicationTag: d_tag,
    }
    mutableCopy];

    SecItemDelete(BRD options);

    // Add persistent version of the key to system keychain
    [options addEntriesFromDictionary:
    @{
      BR kSecValueData:data,
      BR kSecAttrKeyClass: BR kSecAttrKeyClassPrivate,
      BR kSecReturnPersistentRef: @YES,
    }];

    CFTypeRef persistKey = nil;
    OSStatus status = SecItemAdd(BRD options, &persistKey);
    if (persistKey != nil){
        CFRelease(persistKey);
    }
    if ((status != noErr) && (status != errSecDuplicateItem)) {
        return nil;
    }

    [options removeObjectForKey:BR kSecValueData];
    [options removeObjectForKey:BR kSecReturnPersistentRef];

    [options addEntriesFromDictionary:
    @{
      BR kSecReturnRef:@YES,
      BR kSecAttrKeyType:BR kSecAttrKeyTypeRSA,
    }];

    // Now fetch the SecKeyRef version of the key
    SecKeyRef keyRef = nil;
    status = SecItemCopyMatching(BRD options, (CFTypeRef *)&keyRef);
    if(status != noErr){
        return nil;
    }
    return keyRef;
}
Fibrosis answered 13/8, 2016 at 2:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.