Replicate OpenSSL smime command on iPhone/Cocoa

What I am attempting to do is replicate the following command run through Terminal on a Mac, but on the iPhone/in Cocoa:

openssl smime -binary -sign -signer cert.pem -inkey key.pem -in file.txt -out encrypted -outform DER

where "encrypted" is the encrypted file that results from the command.

Although it specifies 2 separate keys (public and private key), it is possible to have these as a single .p12 file.

After following this cocoa snippet for encrypting a file using a .p12 certificate, I'm unsure if this is the right way to go.

What is the best approach for replicating the smime command on an iPhone (as per the Terminal command above), or is it not even possible at all through the available Security.framework/CommonCrypto methods?

Answers


As far as I know - you are a bit up a creek - with the paddle locked up in the appstore.

  • iOS lacks CMSEncoderAddSigners, CMSEncoderUpdateContent, CMSEncoderCopyEncodedContent which you need for this.
  • Using openssl or Chilkat is not ideal either - as the keychain API of iOS does not give you access (anymore) to the private key once imported.

I've solved this in the past with both openssl and Chilkat.

In each case however I 'cache' a copy of the private key - as once it goes into the keychain - all I can get back is a SecKeyRef (you need to enter into an additional agreement/permission with apple to be able to get it back out and still be in the appstore. Reverse engineer any of the VPN (e.g. the juniper one) apps to see the methods/framework to link).

For openssl - simply take the smime.c code in apps of openssl and modify. For chilkat things are a lot simpler:

    CkoCert * mine = [identity ckoCert];

    assert([mime AddEncryptCert: mine] == YES);

    for(id cc in backupCerts) {
        assert([mime AddEncryptCert:cc] == YES);
    }

    for(id key in [headers allKeys]) {
        [mime SetHeaderField:[NSString stringWithFormat:@"%s%@", X_HDR_PREFIX, key]
                       value:[headers objectForKey:key]
         ];
    };

    [mime SetBodyFromBinary:data];        
    assert([mime EncryptN] == YES);

    return  [mime GetMimeBytes];

and where the identity field has the 'keep your own cache' cheat:

-(id)initWithPKCS12:(NSData*)pkcs12der password:(NSString *)password {
    if (password == nil)
        password = [APPSETTINGS wellKnownPkcsPassword];

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             password, kSecImportExportPassphrase,
                             nil];

    CFArrayRef items;
    OSStatus status = SecPKCS12Import((__bridge CFDataRef)pkcs12der,  
        (__bridge CFDictionaryRef)options, &items);

    if (status != noErr) {
        NSLog(@"PKCS12 importAsDer failed: Error %ld",(long)status);
        ...
    }

    if (!items || CFArrayGetCount(items) < 1) {
        NSLog(@"PKCS12 importAsDer failed - nothing returned (%ld bytes DER)", 
              (long)[pkcs12der length]);
        ...
    }

    CFDictionaryRef dict0 = (CFDictionaryRef) CFArrayGetValueAtIndex(items, 0);
    if (!dict0)
        return nil;

    SecIdentityRef iRef = (SecIdentityRef) CFDictionaryGetValue(dict0, 
            kSecImportItemIdentity);
    CFArrayRef cRef = (CFArrayRef) CFDictionaryGetValue(dict0, kSecImportItemCertChain);

    self = [self initWithIdentityRef:iRef withChainArrayRef:cRef];
    CFRelease(items);

#if TARGET_OS_IPHONE
    // We lack SecPrivate* on iOS. So we cheat a bit - rather than
    // use the keychain we limt ourselves to our own *.p12's and
    // keep a copy of the private key in memory.
    //
#  ifdef WITH_OPENSSL

   const unsigned char * ptr = [pkcs12der bytes];
    PKCS12 * p12 = d2i_PKCS12(NULL, &ptr, len);
    char buff[1024];

    if (!p12) {
       NSLog(@"Could not decode PKCS#12: %s", ERR_error_string(ERR_get_error(), buff));
       ...
    };

    const char * pass = [password cStringUsingEncoding:NSASCIIStringEncoding];

   if (PKCS12_parse(p12, pass, &pkey, &x509, NULL) != 1) {
      NSLog(@"Could not parse PKCS#12: %s", ERR_error_string(ERR_get_error(), buff));
      ...
    };
    ....
#  else
    ckoCert = [[CkoCert alloc] init];

    if (!([ckoCert LoadPfxData:pkcs12der password:[APPSETTINGS wellKnownPkcsPassword]])) {
        NSLog(@"PKCS12 loadPfxData failed: %@", [ckoCert LastErrorText]);
        ...
    }

    ckoPrivateKey = [ckoCert ExportPrivateKey];
#  endif // chilkat or openssl
#endif // iOS

    return self;
}

Warning: in above i've stripped most mngt/error management and/or replaced it by asserts as otherwise it got a little too wieldly.

Thanks,

Dw.


Need Your Help