i know is christmas but i have a huge problem that i need to solve, and i'm looking for my christmas miracle here...
I have read apples documentation, and there were only guides how to create RSA public and private keys from certificates. In my case, i have only RSA private key in .pem file. So my question is his: how should i sign data, using that key? I dont want to use openssl. i have tried it with no luck, and i think it's possible to sign data with RSA, by using apples API's.
This is how my key looks like:
-----BEGIN RSA PRIVATE KEY-----
..............................
-----END RSA PRIVATE KEY-----
This is what i have done so far:
-(NSString *)signing:(NSString *)dataString {
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"PrestaMobilekey" ofType:#"pem"];
NSData *data = [[NSData alloc]initWithContentsOfFile:filePath];
SecKeyRef privateKey = (__bridge SecKeyRef)(data);
uint8_t *signedHashBytes = NULL;
// calculate private key size
size_t signedHashBytesSize = SecKeyGetBlockSize(privateKey);
// create space to put signature
signedHashBytes = (uint8_t *)malloc(signedHashBytesSize * sizeof(uint8_t));
memset((void *)signedHashBytes, 0x0, signedHashBytesSize);
OSStatus status = NULL;
// sign data
status = SecKeyRawSign(privateKey,
kSecPaddingPKCS1SHA1,
[[[dataString dataUsingEncoding:NSUTF8StringEncoding] SHA1] bytes],
CC_SHA1_DIGEST_LENGTH,
signedHashBytes,
&signedHashBytesSize);
if (privateKey) {
CFRelease(privateKey);
}
// get signature hash
NSData *signedHash = [NSData dataWithBytes:(const void *)signedHashBytes length:(NSUInteger)signedHashBytesSize];
// release created space
if (signedHashBytes) {
free(signedHashBytes);
}
if (status != errSecSuccess) {
return #"";
}
// return Base64 encoded signature string
return [Base64 encode:signedHash];
}
I really hope that someone will help me, with some good information and answer.
Thank you.
You don't need to use OpenSSL. You can sign your data using your method with a few tweaks. I don't think you can simply bridge and cast an NSData object to a SecKeyRef. You most likely need to save it to the keychain first.
You can do so with this method:
- (SecKeyRef)saveKeyToKeychain:(NSData *)key keySize:(NSUInteger)keySize private:(BOOL)isPrivate {
OSStatus sanityCheck = noErr;
NSData *tag;
id keyClass;
if (isPrivate) {
tag = privateTag;
keyClass = (__bridge id) kSecAttrKeyClassPrivate;
}
else {
tag = publicTag;
keyClass = (__bridge id) kSecAttrKeyClassPublic;
}
NSDictionary *saveDict = #{
(__bridge id) kSecClass : (__bridge id) kSecClassKey,
(__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA,
(__bridge id) kSecAttrApplicationTag : tag,
(__bridge id) kSecAttrKeyClass : keyClass,
(__bridge id) kSecValueData : key,
(__bridge id) kSecAttrKeySizeInBits : [NSNumber numberWithUnsignedInteger:keySize],
(__bridge id) kSecAttrEffectiveKeySize : [NSNumber numberWithUnsignedInteger:keySize],
(__bridge id) kSecAttrCanDerive : (__bridge id) kCFBooleanFalse,
(__bridge id) kSecAttrCanEncrypt : (__bridge id) kCFBooleanTrue,
(__bridge id) kSecAttrCanDecrypt : (__bridge id) kCFBooleanFalse,
(__bridge id) kSecAttrCanVerify : (__bridge id) kCFBooleanTrue,
(__bridge id) kSecAttrCanSign : (__bridge id) kCFBooleanFalse,
(__bridge id) kSecAttrCanWrap : (__bridge id) kCFBooleanTrue,
(__bridge id) kSecAttrCanUnwrap : (__bridge id) kCFBooleanFalse
};
SecKeyRef savedKey = NULL;
sanityCheck = SecItemAdd((__bridge CFDictionaryRef) saveDict, (CFTypeRef *)&savedKey);
if (sanityCheck != errSecSuccess) {
LOGGING_FACILITY1(sanityCheck != noErr, #"Problem saving the key to keychain, OSStatus == %d.", sanityCheck);
}
return savedKey;
}
If you don't want to get the reference immediately, you can change the method type to void and remove the return statement. Change (CFTypeRef *)&savedKey to NULL.
You can then retrieve the saved key like so:
- (SecKeyRef)getKeyRef:(BOOL)isPrivate {
OSStatus sanityCheck = noErr;
NSData *tag;
id keyClass;
if (isPrivate) {
if (privateKeyRef != NULL) {
// already exists in memory, return
return privateKeyRef;
}
tag = privateTag;
keyClass = (__bridge id) kSecAttrKeyClassPrivate;
}
else {
if (publicKeyRef != NULL) {
// already exists in memory, return
return publicKeyRef;
}
tag = publicTag;
keyClass = (__bridge id) kSecAttrKeyClassPublic;
}
NSDictionary *queryDict = #{
(__bridge id) kSecClass : (__bridge id) kSecClassKey,
(__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA,
(__bridge id) kSecAttrApplicationTag : tag,
(__bridge id) kSecAttrKeyClass : keyClass,
(__bridge id) kSecReturnRef : (__bridge id) kCFBooleanTrue
};
SecKeyRef keyReference = NULL;
sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef) queryDict, (CFTypeRef *) &keyReference);
if (sanityCheck != errSecSuccess) {
NSLog(#"Error trying to retrieve key from server. isPrivate: %d. sanityCheck: %li", isPrivate, sanityCheck);
}
if (isPrivate) {
privateKeyRef = keyReference;
}
else {
publicKeyRef = keyReference;
}
return keyReference;
}
Also, an easier way to return a base64 encoded string is to do this:
NSString *signatureString = [signedHash base64EncodedStringWithOptions:nil];
About privateTag and publicTag
privateTag and publicTag are used to mark the kSecAttrApplicationTag which defines the application that uses this key. You want to have a separate privateTag and publicTag to differentiate between your private key and public key.
It's a bit convoluted because I followed the sample code, but I defined my privateTag and publicTag this way:
SecKeyWrapper.h
#define kPublicKeyTag "com.sample.app.publickey"
#define kPrivateKeyTag "com.sample.app.privatekey"
SecKeyWrapper.m
// just under #implementation or #synthesize lines
static const uint8_t publicKeyIdentifier[] = kPublicKeyTag;
static const uint8_t privateKeyIdentifier[] = kPrivateKeyTag;
- (id)init {
if (self = [super init]) {
// Tag data to search for keys.
privateTag = [[NSData alloc] initWithBytes:privateKeyIdentifier length:sizeof(privateKeyIdentifier)];
publicTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:sizeof(publicKeyIdentifier)];
}
return self;
}
Then use the privateTag and publicTag as you would in the code samples I provided above.
Ok, o found a solution for this problem my self. I hope that this will help to others... this is what helped me. I thought that i could do this without openssl, but i was wrong. But still, by doing like in that post, you won't need an extra library in you project. Use terminal
Related
I want to store in the keychain the value "MyKeyValue" and I do like this :
NSData* key = [#"MyKeyValue" dataUsingEncoding:NSUTF8StringEncoding];
NSData* tag = [#"com.example.MyKey" dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary* addquery = #{ (id)kSecValueRef: key,
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrApplicationTag: tag,
};
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addquery, NULL);
but this failed with error -50 (Invalid params)
What i did wrong ?
I would like to store in the keychain a string that can be retrieved if the user uninstall and reinstall my app.
The error is occurring because of kSecValueRef, as per Apple's guideline kSecValueRef accepts a cryptographic key which can be generated through SecKeyRef, Please find below,
NSData* tag = [#"com.example.keys.mykey" dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary* attributes =
#{ (id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
(id)kSecAttrKeySizeInBits: #2048,
(id)kSecPrivateKeyAttrs:
#{ (id)kSecAttrIsPermanent: #YES,
(id)kSecAttrApplicationTag: tag,
},
};
CFErrorRef error = NULL;
SecKeyRef privateKey = SecKeyCreateRandomKey((__bridge CFDictionaryRef)attributes,
&error);
SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey);
NSDictionary* addquery = #{ (id)kSecValueRef: (__bridge id)publicKey,
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrApplicationTag: tag,
};
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addquery, NULL);
For more info please refer Storing Keys in the Keychain
I try to get public or private key from certificate saved on device.
I'm using this method:
- (SecKeyRef)publicKeyFromFile:(NSString *)path
{
NSData * certificateData = [[NSData alloc] initWithData:[[NSFileManager defaultManager] contentsAtPath:path]];
if (certificateData != nil && certificateData.bytes != 0) {
CFDataRef cfDataPath = CFDataCreate(NULL, [certificateData bytes], [certificateData length]);
SecCertificateRef certificateFromFile = SecCertificateCreateWithData(NULL, cfDataPath);
if (certificateFromFile) {
SecPolicyRef secPolicy = SecPolicyCreateBasicX509();
SecTrustRef trust;
SecTrustCreateWithCertificates( certificateFromFile, secPolicy, &trust);
SecTrustResultType resultType;
SecTrustEvaluate(trust, &resultType);
SecKeyRef publicKeyObj = SecTrustCopyPublicKey(trust);
return publicKeyObj;
}
}
return nil;
}
There is data in cfDataPath, but certificateFromFile is always nil...
Does anyone know where's the problem?
Apple doc refers:
Obtaining a SecKeyRef Object for Public Key Cryptography
Extracting Keys from the Keychain If you are using existing public and private keys from your keychain, read Certificate, Key, and Trust Services Programming Guide to learn how to retrieve a SecKeychainItemRef object for that key.
Once you have obtained a SecKeychainItemRef, you can cast it to a SecKeyRef for use with this API.
Importing Existing Public and Private Keys Importing and exporting public and private key pairs is somewhat more complicated than generating new keys because of the number of different key formats in common use.
This example describes how to import and export a key pair in PEM (Privacy Enhanced Mail) format.
Read more : https://developer.apple.com/library/mac/documentation/Security/Conceptual/SecTransformPG/SigningandVerifying/SigningandVerifying.html and https://developer.apple.com/library/mac/documentation/Security/Conceptual/CertKeyTrustProgGuide/01introduction/introduction.html#//apple_ref/doc/uid/TP40001358
Try with this:
-(BOOL)trustCertFromChallenge:(NSURLAuthenticationChallenge *)challenge
{
SecTrustResultType trustResult;
SecTrustRef trust = challenge.protectionSpace.serverTrust;
OSStatus status = SecTrustEvaluate(trust, &trustResult);
//DLog(#"Failed: %#",error.localizedDescription);
//DLog(#"Status: %li | Trust: %# - %li",(long)status,trust,(long)trustResult);
if (status == 0 && (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed)) {
SecKeyRef serverKey = SecTrustCopyPublicKey(trust);
NSString *certPath = [[NSBundle mainBundle] pathForResource:#"MYCert" ofType:#"der"];
NSData *certData = [NSData dataWithContentsOfFile:certPath];
SecCertificateRef localCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
SecKeyRef localKey = NULL;
SecTrustRef localTrust = NULL;
SecCertificateRef certRefs[1] = {localCertificate};
CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, (void *)certRefs, 1, NULL);
SecPolicyRef policy = SecPolicyCreateBasicX509();
OSStatus status = SecTrustCreateWithCertificates(certArray, policy, &localTrust);
if (status == errSecSuccess)
localKey = SecTrustCopyPublicKey(localTrust);
CFRelease(localTrust);
CFRelease(policy);
CFRelease(certArray);
if (serverKey != NULL && localKey != NULL && [(__bridge id)serverKey isEqual:(__bridge id)localKey])
return YES;
else
return NO;
}
//DLog(#"Failed: %#",error.localizedDescription);
return NO;
}
Follow the accepted answer for more details: Objective-C / C pulling private key (modulus) from SecKeyRef
So I know that I can store a RSA key into the keychain using my following code:
+ (void)savePublicKeyToKeychain:(NSData *)key tag:(NSString *)tagString deleteExisting:(BOOL)deleteExisting {
NSData *tag = [SecKeyWrapper getKeyTag:tagString];
NSDictionary *saveDict = #{
(__bridge id) kSecClass : (__bridge id) kSecClassKey,
(__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA,
(__bridge id) kSecAttrApplicationTag : tag,
(__bridge id) kSecAttrKeyClass : (__bridge id) kSecAttrKeyClassPublic,
(__bridge id) kSecValueData : key
};
[self saveKeyToKeychain:saveDict tag:tagString deleteExisting:deleteExisting];
}
+ (void)saveKeyToKeychain:(NSDictionary *)saveDict tag:(NSString *)tagString deleteExisting:(BOOL)deleteExisting {
OSStatus sanityCheck = SecItemAdd((__bridge CFDictionaryRef) saveDict, NULL);
if (sanityCheck != errSecSuccess) {
if (sanityCheck == errSecDuplicateItem && deleteExisting) {
// delete the duplicate and save again
SecItemDelete((__bridge CFDictionaryRef) saveDict);
sanityCheck = SecItemAdd((__bridge CFDictionaryRef) saveDict, NULL);
}
if (sanityCheck != errSecSuccess) {
NSLog(#"Problem saving the key to keychain, OSStatus == %d.", (int) sanityCheck);
}
}
// remove from cache
[keyCache removeObjectForKey:tagString];
}
This I can save and retrieve correctly. If I try to set the kSecAttrAccessible value on save:
+ (void)savePublicKeyToKeychain:(NSData *)key tag:(NSString *)tagString deleteExisting:(BOOL)deleteExisting {
NSData *tag = [SecKeyWrapper getKeyTag:tagString];
NSDictionary *saveDict = #{
(__bridge id) kSecClass : (__bridge id) kSecClassKey,
(__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA,
(__bridge id) kSecAttrApplicationTag : tag,
(__bridge id) kSecAttrKeyClass : (__bridge id) kSecAttrKeyClassPublic,
(__bridge id) kSecAttrAccessible: (__bridge id) kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
(__bridge id) kSecValueData : key
};
[self saveKeyToKeychain:saveDict tag:tagString deleteExisting:deleteExisting];
}
and then trying to retrieve, I get junk. It's junk because when I retrieve it immediately after I save, the inserted value and the retrieved value are different.
Anyone have a code sample or know how to set the accessibility of the keychain item, specifically for an RSA key?
For public knowledge, it turns out that the attributes used to store the data to the keychain needs to be EXACTLY what is used to retrieve the data from the keychain. If you're missing one of the attributes, even though it seems like an attribute needed only for storing (like kSecAttrAccessible), you'll get the wrong data. You won't even get a errSecItemNotFound. It returns garbage.
I'm trying to work with KeyChain and without a wrapper. But my code crashes when I want to read the value.
Code:
CFDictionaryRef attributes = CFDictionaryCreate(NULL, keys, values, 5, NULL, NULL);
CFDataRef result;
OSStatus status = SecItemAdd(attributes, (CFTypeRef *)&result);
if (status == errSecSuccess) {
if (result && CFGetTypeID(result) == CFDataGetTypeID()) { //crashes here
NSLog(#"Data");
}
isSuccess = YES;
} else {
fprintf(stderr, "Error while inserting into keychain osstatus:%ld\n", status);
}
Error: EXC_BAD_ACCESS
What am I doing wrong? I thought SecItemAdd can return the newly add item
Edit:
const void *keys[] = {
kSecClass
, kSecAttrAccessible
, kSecAttrService
, kSecAttrAccount
, kSecValueData
};
const void *values[] = {
kSecClassGenericPassword
, kSecAttrAccessibleWhenUnlocked
, (__bridge CFStringRef)service
, (__bridge CFStringRef)account
, data //CFDataRef
};
From documentation:
To obtain the data of the added item as an object of type CFDataRef,
specify the return type key kSecReturnData with a value of
kCFBooleanTrue.
The answer to why you're getting EXC_BAD_ACCESS, is because the dictionary that you pass into SecItemAdd has to be mutable.
Try something like this:
CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(NULL, size,
&kCFCopyStringDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(attributes, keys[x], values[x]);
I wanted to know is there a way to access certificate pushed by MDM server through your app?
A few years later - is there still no way to access the MDM issued X.509 certificates?
I'm using this code, but getting zero results. And as far as google helps, there is also no way to see if there is any cert at all?
CFTypeRef certificateRef = NULL; // 1
const char *certLabelString = "XenMobile MDM";
CFStringRef certLabel = CFStringCreateWithCString(
NULL, certLabelString,
kCFStringEncodingUTF8);
const void *keys[] = { kSecClass, kSecAttrLabel, kSecReturnRef };
const void *values[] = { kSecClassCertificate, certLabel, kCFBooleanTrue };
CFDictionaryRef dict = CFDictionaryCreate(NULL, keys,
values, 3,
NULL, NULL);
status = SecItemCopyMatching(dict, &certificateRef);
if (status == errSecItemNotFound) {
_UILabelINFO.text = #"error The item cannot be found (errSecItemNotFound) :";
_UILabelINFO.text = [_UILabelINFO.text stringByAppendingString:(__bridge NSString *)(certLabel)];
} else {
_UILabelINFO.text = #"retrieved keychain reference";
}