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.