I have objc function that return SecIdentityRef
+ (SecIdentityRef)getSecIdentity {
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:#"cert1" ofType:#"p12"];
NSData *p12Data = [NSData dataWithContentsOfFile:resourcePath];
NSMutableDictionary * options = [[NSMutableDictionary alloc] init];
[options setObject:#"123456" forKey:(id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import((CFDataRef) p12Data,
(CFDictionaryRef)options, &items);
if (securityError == noErr && CFArrayGetCount(items) > 0) {
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
SecIdentityRef identityApp =
(SecIdentityRef)CFDictionaryGetValue(identityDict,
kSecImportItemIdentity);
CFRelease(items);
return identityApp;
}
CFRelease(items);
return NULL;
}
I import class with this function in bridging header, then use it in swift code
let test = Certificate.getSecIdentity()
let secIdentity = test.takeUnretainedValue()
From Certificate.getSecIdentity() returns Unmanaged<SecIdentityRef> with correct (?) SecIdentity inside.
On test.takeUnretainedValue() (and test.takeRetainedValue()) I receive
Thread 1: EXC_BAD_ACCESS (code=1, address=0x2d13e474f3e0)
What I do wrong? How can I get SecIdentity?
When you retrieve an element from (i believe) any core foundation collection, the collection functions follows the Get-Rule. It means that you don't own the element until you explicitly retain it. Thus in this part of your code:
SecIdentityRef identityApp = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
CFRelease(items);
You essentially release both items and identityApp and return a dangling pointer (a dangling core foundation reference to be precise). Just retain the instance of SecIdentityRef before your release the items array like that:
CFRetain(identityApp);
CFRelease(items);
return identityApp;
P.S. since your function may return NULL, you better make the result nullable, especially when working with Swift, so it knows the result is an optional value:
+ (nullable SecIdentityRef)getSecIdentity
P.P.S. you also may want to rewrite you code in Swift directly, since it supports work with SecCertificate
Related
I'm trying to use Keychain Services to save a value, which would persist even if a user reinstalls the app. So I check if an item exists using SecItemCopyMatching, which returns errSecItemNotFound the first time and add a new item using SecItemAdd, which returns errSecSuccess, but the value of _attrs is nil. Also, when the code is called second time, the SecItemCopyMatching still returns errSecItemNotFound, as if SecItemAdd wasn't called. So what that could be related to?
CFMutableDictionaryRef _attrs = nil;
NSString* key = #"<unique key>";
NSMutableDictionary* query = [NSMutableDictionary dictionary];
query[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
query[(__bridge id)kSecAttrLabel] = key;
query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
query[(__bridge id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
OSStatus err = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef*)&_attrs);
if (err == errSecSuccess) {
return YES;
}
NSString* str = #"<some data>";
if (err == errSecItemNotFound) {
query[(__bridge id)kSecValueData] = NSData_from_string(string_from_NSString(str));
query[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAlways;
err = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef*)&_attrs);
assert(err == errSecSuccess);
}
You are re-using query for your call to SecItemAdd, and the kSecMatchLimit value being present in the dictionary for this function is breaking it. You should remove this key before calling SecItemAdd.
It's also worth noting that [str dataUsingEncoding:NSUTF8StringEncoding] might be a better choice than whatever NSData_from_string(string_from_NSString(str)) is, depending on what you're doing.
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];
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 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)
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.