As far as I know - you're a little behind the wheel - with a paddle locked in the Appstore.
- iOS is missing the CMSEncoderAddSigners, CMSEncoderUpdateContent, CMSEncoderCopyEncodedContent that you need for this.
- Using openssl or Chilkat is also not ideal: since the iOS keychain API does not give you (more) access to the private key after import.
I have solved this in the past with both opensl and Chilkat.
In each case, however, I "cache" a copy of the private key - as soon as it falls into the key chain - all I can get is SecKeyRef (you need to enter an additional agreement / permission with the apple in order to be able to return it and that's it still be in the Appstore. Reverse engineer of any of the VPN (for example, juniper) applications to see the methods / framework for communication).
For openssl - just grab the smime.c code in openssl applications and change. For chilkat, things are much 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 in the identity field is used "save your own cache":
-(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 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 the above, I stripped most of the mngt / error management and / or replaced it with statements, because otherwise it took too much damage.
Thanks,
Dw.