I'm developing an iOS application and struggling to extract an identity from a .p12 certificate. I'm still new to objective-c so I'm sure something major is missing. Here's the code:
#implementation P12Extractor
-(SecIdentityRef)getIdentity{
NSString *path = [[NSBundle mainBundle] pathForResource:#"ServerCert" ofType:#"p12"];
NSData *p12data = [NSData dataWithContentsOfFile:path];
CFDataRef inP12Data = (__bridge CFDataRef)p12data;
SecIdentityRef myIdentity;
OSStatus status = extractIdentity(inP12Data, &myIdentity);
if (status != 0) {
NSLog(#"%#",status);
}
return myIdentity;
}
OSStatus extractIdentity(CFDataRef inP12Data, SecIdentityRef *identity){
OSStatus securityError = errSecSuccess;
CFStringRef password = CFSTR("password");
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
securityError = SecPKCS12Import(inP12Data, options, &items);
if (securityError == 0) {
CFDictionaryRef ident = CFArrayGetValueAtIndex(items,0); // <<<at this point i get an EXC_BAD_ACCESS(code=2,adress=0x0) error
const void *tempIdentity = NULL;
tempIdentity = CFDictionaryGetValue(ident, kSecImportItemIdentity);
*identity = (SecIdentityRef)tempIdentity;
}
if (options) {
CFRelease(options);
}
return securityError;
}
#end
I've marked the point of error with a comment, I really have no idea why I keep getting this. This code should be the approved solution from Apple dev site.
Your array is still empty when you try to get its first element...
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
securityError = SecPKCS12Import(inP12Data, options, &items);
if (securityError == 0) {
CFDictionaryRef ident = CFArrayGetValueAtIndex(items,0); // <<<at this point i get an EXC_BAD_ACCESS(code=2,adress=0x0) error
I think the problem could lay with the certificate or with the options you pass in.
Related
I created a certificate (of p12 type) that I installed onto an iPad using Apple Configurator. I now want to access that certificate in an app I've written but I can't seem to find a source example for it. There are lots of examples on how to use certificates but I've been unable to find anything on how to import it into an app.
Can someone point me in the right direction? Thanks in advance.
* NEW *
So I tried following the tutorial here https://developer.apple.com/library/ios/documentation/Security/Conceptual/CertKeyTrustProgGuide/iPhone_Tasks/iPhone_Tasks.html#//apple_ref/doc/uid/TP40001358-CH208-SW13 using Listing 2-1 and Listing 2-2. For now I gave up on getting the certificate from the keychain and instead I load it from the main Bundle.
NSData *certData = [[NSData alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"myfile.com" ofType:#"pfx"]];
CFDataRef myCertData = (__bridge_retained CFDataRef)(certData);
I'm not certain whether my loading is correct but when I debug certData is not NULL and contains bytes.
Next I modified the code in the two listings since the method signatures don't really match an Objective C method signature. Someone help me out here why Apple would show it this way? No code inside the function was modified.
- (OSStatus) extractIdentityAndTrust: (CFDataRef) inPKCS12Data withIdentity:(SecIdentityRef *) outIdentity withTrust:(SecTrustRef *) outTrust withPassword:(CFStringRef) keyPassword
{
OSStatus securityError = errSecSuccess;
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { keyPassword };
CFDictionaryRef optionsDictionary = NULL;
optionsDictionary = CFDictionaryCreate(NULL, keys, values, (keyPassword ? 1 : 0), NULL, NULL);
CFArrayRef items = NULL;
securityError = SecPKCS12Import(inPKCS12Data, optionsDictionary, &items);
if (securityError == 0) {
CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex (items, 0);
const void *tempIdentity = NULL;
tempIdentity = CFDictionaryGetValue (myIdentityAndTrust,
kSecImportItemIdentity);
CFRetain(tempIdentity);
*outIdentity = (SecIdentityRef)tempIdentity;
const void *tempTrust = NULL;
tempTrust = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust);
CFRetain(tempTrust);
*outTrust = (SecTrustRef)tempTrust;
}
if (optionsDictionary)
CFRelease(optionsDictionary);
if (items)
CFRelease(items);
return securityError;
}
Next I modified the method signature for copySummaryString. I also had to add a deference inside the function for the 'identity' variable.
- (NSString *)copySummaryString:(SecIdentityRef *) identity
{
// Get the certificate from the identity.
SecCertificateRef myReturnedCertificate = NULL;
OSStatus status = SecIdentityCopyCertificate (*identity, &myReturnedCertificate);
if (status) {
NSLog(#"SecIdentityCopyCertificate failed.\n");
return NULL;
}
CFStringRef certSummary = SecCertificateCopySubjectSummary
(myReturnedCertificate);
NSString* summaryString = [[NSString alloc] initWithString:(__bridge NSString *)certSummary];
CFRelease(certSummary);
return summaryString;
}
Finally in my calling function I created a identity and trust variable that I pass around:
[self extractIdentityAndTrust:myCertData withIdentity:identity withTrust:trust withPassword:CFSTR("supersecret")];
[self copySummaryString:identity];
When I try to run this I get a Thread 1: EXC_BAD_ACCESS(code=1, adress=0x2b) error and XCode pauses on the following line:
CFStringRef certSummary = SecCertificateCopySubjectSummary
This code doesn't reference any variable I created so I'm really surprised it's crashing here. The ultimate goal with loading this certificate is to use it for HTTPS. I hope that I'm at least on the right path here.
Here is the documentation and example about finding and using of a pre-installed certificate and identity through your mobile app.
https://developer.apple.com/library/ios/documentation/Security/Conceptual/CertKeyTrustProgGuide/iPhone_Tasks/iPhone_Tasks.html#//apple_ref/doc/uid/TP40001358-CH208-DontLinkElementID_10
*NEW
I checked your update.
Both you methods works like charm.
I used below source to call your methods, and was able to extract the SummaryString properly.
SecIdentityRef identity = nil;
SecTrustRef trust = nil;
NSData *certData = [[NSData alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"[Dev] InHouse_Certificates" ofType:#"p12"]];
CFDataRef myCertData = (__bridge_retained CFDataRef)(certData);
[self extractIdentityAndTrust:myCertData withIdentity:&identity withTrust:&trust withPassword:CFSTR("1234")];
NSString* summaryString = [self copySummaryString:&identity];
My app using a self-signed ssl certificate to connect to tcp server. It worked before iOS7.1, after iOS8.0,it has a problem when off net weight, suggesting : CFNetwork SSLHandshake failed (-9806) and can not reconnect.
i am using the method:startTLS for setting the ssl, the code like below:
// Configure SSL/TLS settings
NSMutableDictionary *sslSettings = [[NSMutableDictionary alloc] init];
NSData *pkcs12data = [[NSData alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:certificate ofType:#"p12"]];
CFDataRef inPKCS12Data = (CFDataRef)CFBridgingRetain(pkcs12data);
CFStringRef password = CFSTR("123456");
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import(inPKCS12Data, options, &items);
CFRelease(options);
CFRelease(password);
[pkcs12data release];
if(securityError == errSecSuccess) {
NSLog(#"Success opening p12 certificate.");
}
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
SecIdentityRef myIdent = (SecIdentityRef)CFDictionaryGetValue(identityDict,
kSecImportItemIdentity);
SecIdentityRef certArray[1] = { myIdent };
CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL);
[sslSettings setObject:(id)CFBridgingRelease(myCerts) forKey:(NSString *)kCFStreamSSLCertificates];
[sslSettings setObject:NSStreamSocketSecurityLevelSSLv2 forKey:(NSString *)kCFStreamSSLLevel];
[sslSettings setObject:(id)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain];
I'm using this code: https://stackoverflow.com/a/19221754/849616, however not everything is clear for me.
I want to encrypt NSString *msg = "0000" using public key NSString *pubKey = "1111". Because of this, I'm updating constants:
static const UInt8 publicKeyIdentifier[] = 1111;
// i want to encrypt only, so private key doesn't matter and I'm not posting it here
In function testAsymmetricEncryptionAndDecryption I've updated:
const char inputString[] = 0000
However the result is wrong. Is publicKeyIdentifier a right place to put my key string..? How should I do it if my approach is wrong..?
Well the question is wrong. I shouldn't even try to convert it to NSString.
You should put both keys to your project and use something like:
- (SecKeyRef)getPrivateKeyRef {
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:#"rsaPrivate" ofType:#"p12"];
NSData *p12Data = [NSData dataWithContentsOfFile:resourcePath];
NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
SecKeyRef privateKeyRef = NULL;
//change to the actual password you used here
[options setObject:#"!##EWQ" forKey:(__bridge id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import((__bridge CFDataRef)p12Data, (__bridge CFDictionaryRef)options, &items);
if (securityError == noErr && CFArrayGetCount(items) > 0) {
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
SecIdentityRef identityApp = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
securityError = SecIdentityCopyPrivateKey(identityApp, &privateKeyRef);
if (securityError != noErr) {
privateKeyRef = NULL;
}
}
CFRelease(items);
return privateKeyRef;
}
- (SecKeyRef)getPublicKeyRef {
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:#"rsaCert" ofType:#"der"];
NSData *certData = [NSData dataWithContentsOfFile:resourcePath];
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
SecKeyRef key = NULL;
SecTrustRef trust = NULL;
SecPolicyRef policy = NULL;
if (cert != NULL) {
policy = SecPolicyCreateBasicX509();
if (policy) {
if (SecTrustCreateWithCertificates((CFTypeRef)cert, policy, &trust) == noErr) {
SecTrustResultType result;
if (SecTrustEvaluate(trust, &result) == noErr) {
key = SecTrustCopyPublicKey(trust);
}
}
}
}
if (policy) CFRelease(policy);
if (trust) CFRelease(trust);
if (cert) CFRelease(cert);
return key;
}
I didn't write it all by my own (just modified), it's mostly copied but really I have no idea where from - some open source community. Still, many thanks to the person who wrote it.
I have created a self signed certificate in keychain and i am using MultipeerConnectivity framework to transfer data between devices.
When we create a session we can pass securityIdentity when we invite peers in MCSession.
- (instancetype)initWithPeer:(MCPeerID *)myPeerID securityIdentity:(NSArray *)identity encryptionPreference:(MCEncryptionPreference)encryptionPreference;
but securityIdentity is NSArray, how can we pass a certificate in NSArray and how can we authenticate it in
- (void)session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void(^)(BOOL accept))certificateHandler;
It is an array containing information that can be used to identify the local peer to other nearby peers.
The array contains objects that helps identify the peer to others.
The first is a SecIdentityRef object that has a SecKeyRef object and the related SecCertificateRef object. (It is something like the apple developer certificate and the private key pair)
The other elements in the array can be SecCertificateRef objects representing intermediate certificates that may be needed for verifying the SecIdentityRef .
The receiving peer has to validate the identity represented by SecIdentityRef.
Below is the code to obtain SecIdentityRef from a p12 file
- (SecIdentityRef)getClientCertificate
{
SecIdentityRef identity = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectoryPath = [paths objectAtIndex:0];
NSString *myFilePath = [documentsDirectoryPath stringByAppendingPathComponent:#"cert_key_pair.p12"];
NSData *PKCS12Data = [NSData dataWithContentsOfFile:myFilePath];
CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data;
CFStringRef password = CFSTR("password");
const void *keys[] = { kSecImportExportPassphrase };//kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import(inPKCS12Data, options, &items);
CFRelease(options);
CFRelease(password);
if (securityError == errSecSuccess) {
NSLog(#"Success opening p12 certificate. Items: %ld", CFArrayGetCount(items));
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
identity = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
} else {
NSLog(#"Error opening Certificate.");
}
return identity;
}
Obtaining a policy reference object and evaluating trust
- (void)session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer: (MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
{
SecCertificateRef myCert;
myCert = [certificate objectAtIndex:0]; // 1
SecPolicyRef myPolicy = SecPolicyCreateBasicX509(); // 2
SecCertificateRef certArray[1] = { myCert };
CFArrayRef myCerts = CFArrayCreate(
NULL, (void *)certArray,
1, NULL);
SecTrustRef myTrust;
OSStatus status = SecTrustCreateWithCertificates(
myCerts,
myPolicy,
&myTrust); // 3
SecTrustResultType trustResult;
if (status == noErr) {
status = SecTrustEvaluate(myTrust, &trustResult); // 4
}
//...
if (trustResult == kSecTrustResultConfirm || trustResult == kSecTrustResultProceed || trustResult == kSecTrustResultUnspecified) // 5
{
certificateHandler(YES);
}
// ...
if (myPolicy)
CFRelease(myPolicy);
}
I implemented a category method on the NSData class which returns a signature of the data using an SHA-1 hash and subsequent encryption with a private key as follows:
- (NSData *)signatureWithKey:(SecKeyRef)keyRef {
if (keyRef == NULL) {
return nil;
}
NSData *sha1Digest = [self dataWithSHA1Digest];
size_t maxLength = SecKeyGetBlockSize(keyRef) - 11;
if ([sha1Digest length] > maxLength) {
NSString *reason = [NSString stringWithFormat:#"Digest is too long to sign with this key, max length is %ld and actual length is %ld", maxLength, (unsigned long)[self length]];
NSException *ex = [NSException exceptionWithName:#"BMInvalidArgumentException" reason:reason userInfo:nil];
#throw ex;
}
#if TARGET_OS_IPHONE
OSStatus status = noErr;
uint8_t *plainBuffer = (uint8_t *)[sha1Digest bytes];
size_t plainBufferSize = [sha1Digest length];
size_t cipherBufferSize = SecKeyGetBlockSize(keyRef);
uint8_t *cipherBuffer = malloc(cipherBufferSize * sizeof(uint8_t));
status = SecKeyRawSign(keyRef,
kSecPaddingPKCS1SHA1,
plainBuffer,
plainBufferSize,
&cipherBuffer[0],
&cipherBufferSize
);
if (status == noErr) {
return [NSData dataWithBytesNoCopy:cipherBuffer length:cipherBufferSize freeWhenDone:YES];
}
free(cipherBuffer);
return nil;
#else
CFErrorRef error = NULL;
SecTransformRef signer = NULL;
CFTypeRef signature = NULL;
if ((signer = SecSignTransformCreate(keyRef, &error))) {
if (SecTransformSetAttribute(
signer,
kSecTransformInputAttributeName,
(CFDataRef)sha1Digest,
&error)) {
signature = SecTransformExecute(signer, &error);
}
}
if (error) {
LogWarn(#"Could not sign: %#", error);
CFRelease(error);
}
if (signer) {
CFRelease(signer);
}
if (signature) {
NSData *data = [NSData dataWithData:(NSData *)signature];
CFRelease(signature);
return data;
} else {
return nil;
}
#endif
}
Now the strange thing is that with the same private key (loaded from a p12 file) I get two different results for iOS and MacOSX when signing the same data. I am completely puzzled by this. You may notice the method above uses a different implementation for MacOSX using security transforms, but even if I use the iOS implementation on MacOSX (which gives a compile warning but works fine) I get the same result.
The method used for loading the private key from file is below:
+ (SecKeyRef)newPrivateKeyRefWithPassword:(NSString *)password fromData:(NSData *)data {
NSMutableDictionary * options = [[NSMutableDictionary alloc] init];
SecKeyRef privateKeyRef = NULL;
// Set the public key query dictionary
//change to your .pfx password here
[options setObject:password forKey:(id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import((CFDataRef)data,
(CFDictionaryRef)options, &items);
if (securityError == noErr && CFArrayGetCount(items) > 0) {
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
SecIdentityRef identityApp =
(SecIdentityRef)CFDictionaryGetValue(identityDict,
kSecImportItemIdentity);
securityError = SecIdentityCopyPrivateKey(identityApp, &privateKeyRef);
if (securityError != noErr) {
privateKeyRef = NULL;
}
}
[options release];
if (items) CFRelease(items);
return privateKeyRef;
}
And this is the test case I use. Notice that two different strings are printed on iOS and MacOSX:
NSString *test = #"bla";
NSData *testData = [test dataUsingEncoding:NSUTF8StringEncoding];
NSString *p12Path= [[NSBundle mainBundle] pathForResource:#"private_key" ofType:#"p12"];
NSData *p12Data = [NSData dataWithContentsOfFile:p12Path];
SecKeyRef keyRef = [BMSecurityHelper newPrivateKeyRefWithPassword:#"xxxxxxxx" fromData:p12Data];
NSData *signatureData = [testData signatureWithKey:keyRef];
NSString *signatureString = [BMEncodingHelper base64EncodedStringForData:signatureData withLineLength:0];
if (keyRef) CFRelease(keyRef);
NSLog(#"signatureString: %#", signatureString);
It's always nice if you can answer your own question. I missed the following: under MacOSX the security transform also calculates the SHA-1 hash automatically, in contrast with the iOS implementation.
I fixed the problem by adding the following in the MacOSX implementation:
SecTransformSetAttribute(signer, kSecInputIsAttributeName, kSecInputIsDigest, &error)