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?
As far as I know - you are a bit up a creek - with the paddle locked up in the appstore.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With