The docs say: “If not all the certificates needed to verify the leaf certificate are included in the trust management object, then SecTrustEvaluate searches for certificates in the keychain search list (see SecTrustSetKeychains) and in the system’s store of anchor certificates (see SecTrustSetAnchorCertificates).”
However, since SecTrustSetKeychains() is not available on iOS, it’s not clear whether this function will also look in the application’s keychain.
Seems like it's been a while since you posted so I'm not sure if you still need the answer. If your use case is "I'm getting hit with connection:didReceiveAuthenticationChallenge:, and I'd like to make sure that exact certificate is being evaluated, then you can either use iOS built-in trust methods or do a bit more work via the Foundation APIs: (note that SecTrustEvaulate is not being called specifically here, but it could be added in quite easily)
#import <Security/Security.h>
#import <CommonCrypto/CommonDigest.h>
From there, you can iterate the full array of certs, and compare it to something like a SHA1 of the challenge's server trust reference:
// way #1 - iOS built-in ================================================ //
SecTrustRef trust = challenge.protectionSpace.serverTrust;
CFIndex cnt = SecTrustGetCertificateCount(trust);
// way #2 - build it in yourself from a file ============================ //
OSErr err;
NSString *path = [[NSBundle mainBundle] pathForResource:#"my.cert"
ofType:#"der"];
NSData *derData = [NSData dataWithContentsOfFile:path];
SecCertificateRef myCert =
SecCertificateCreateWithData(NULL, (CFDataRef)derData);
CFMutableArrayRef array = CFArrayCreateMutable(NULL, 1, NULL);
CFArrayInsertValueAtIndex(array, 0, myCert);
err = SecTrustSetAnchorCertificates(trust, array);
if (err != errSecSuccess) {
// do something smarter here, obviously, logging would be a start
abort();
}
CFArrayRef certs = NULL;
err = SecTrustCopyCustomAnchorCertificates(trust, &certs);
if (err != errSecSuccess) {
// again, better choices needed
abort();
}
CFIndex cnt = CFArrayGetCount(certs);
// loop and compare 'em
for (int i = 0; i < cnt; i++) {
SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i);
CFDataRef cdata = SecCertificateCopyData(cert);
NSData *data = [[NSData alloc] initWithData:(NSData *)cdata];
unsigned char digest_result[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(data.bytes, data.length, digest_result);
// compare each byte with your in-code SHA1 bytes
if (allBytesMatch) {
NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];
[challenge.sender useCredential:cred
forAuthenticationChallenge:challenge];
}
}
// don't forget to release & CFRelease all the alloc'ed stuff from above
It's written in the doc now:
Note: Although this function searches the user’s keychain (or the application keychain in iOS) for intermediate certificates, it does not search those keychains for anchor (root) certificates. To add an anchor certificate, you must call SecTrustSetAnchorCertificates.
Source: SecTrustEvaluate documentation
eskimo1 from Apple Devforums answered this so:
Does SecTrustEvaluate() look for root certificates in the
application keychain?
Not by default. However, it's easy to make it do this by getting the certificates out of your keychain (or from wherever) and applying them to the SecTrust object using SecTrustSetAnchorCertificates.
SecTrustEvaluation /will/ find intermediate certificates in your keychain.
Related
I'm trying to use RSA to sign a token within a HTTP header that I have to pass to a web service - the requirements are that I should encrypt using my own private key and give them my public key. This is used purely so verify that the message was sent by me.
I'm aware of many similar questions on SO but I've been through many attempts so far this morning and am still no closer to something I assumed would be trivial.
I can find many examples of using RSA and encrypting using a public key, but my requirement is for me to sign with the private key.
It looks (from initial attempts) that the apple sample security code and most other examples that borrow from it always generate a private/public keypair using keychain - I would prefer to generate my keys using OpenSSL so I can distribute the public key to my client.
I've managed to generate some keys and it looks like I can load the private key using this code (apologies, I can't recall the source as I've been mixing implementations for hours):
-(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:#"xxxx" 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;
}
which seems to return me a valid SecKeyRef but I don't know how to use this to sign an NSString.
There seems to be so many ways to tackle this question (build OpenSSL and link to it, use Apple's keychain methods, use a partially complete library on GitHub) but I keep reaching a dead end - it would be ideal to get a proper, complete solution to this one.
Something along the lines of:
NSString* signedString = [rsaHelper signString: #"Hello, World!" WithPrivateKey:#"rsaPrivate.p12"];
And given a string encrypted with the public key:
NSString* decryptedString = [rsaHelper decryptString: #"DK5tkgkfjkJJft=" WithPrivateKey:#"rsaPrivate.p12"];
would be valuable! Please note, I'm not afraid of writing this code myself in any way, but would appreciate a nudge in the right direction :-)
I need to integrate my iPhone app with a system, and they require to encrypt data by a given public key, there are 3 files in 3 different format .xml .der and .pem, I have researched and found some articles about getting SecKeyRef from DER/PEM, but they are always return nil. Below is my code:
NSString *pkFilePath = [[NSBundle mainBundle] pathForResource:#"PKFile" ofType:#"der"];
NSData *pkData = [NSData dataWithContentsOfFile:pkFilePath];
SecCertificateRef cert;
cert = SecCertificateCreateWithData(NULL, (CFDataRef) pkData);
assert(cert != NULL);
OSStatus err;
if (cert != NULL) {
err = SecItemAdd(
(CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
(id) kSecClassCertificate, kSecClass,
(id) cert, kSecValueRef,
nil
],
NULL
);
if ( (err == errSecSuccess) || (err == errSecDuplicateItem) ) {
CFArrayRef certs = CFArrayCreate(kCFAllocatorDefault, (const void **) &cert, 1, NULL);
SecPolicyRef policy = SecPolicyCreateBasicX509();
SecTrustRef trust;
SecTrustCreateWithCertificates(certs, policy, &trust);
SecTrustResultType trustResult;
SecTrustEvaluate(trust, &trustResult);
if (certs) {
CFRelease(certs);
}
if (trust) {
CFRelease(trust);
}
return SecTrustCopyPublicKey(trust);
}
}
return NULL;
Problem happens at SecCertificateCreateWithData, it always return nil even through read file is ok.
Anybody has done this please help me, thanks!
EDIT: The cert file was MD5 signature.
I struggled a lot with the same problem and finally found a solution. My problem was that I needed to use both an external private and public key for encrypting/decrypting data in an iOS app and didn't want to use the keychain.
It turns out you also need a signed certificate for the iOS security library to be able to read the key data and of course the files have to be in the correct format.
The procedure is basically as follows:
Say you have a private key in PEM format (with the -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY----- markers): rsaPrivate.pem
//Create a certificate signing request with the private key
openssl req -new -key rsaPrivate.pem -out rsaCertReq.csr
//Create a self-signed certificate with the private key and signing request
openssl x509 -req -days 3650 -in rsaCertReq.csr -signkey rsaPrivate.pem -out rsaCert.crt
//Convert the certificate to DER format: the certificate contains the public key
openssl x509 -outform der -in rsaCert.crt -out rsaCert.der
//Export the private key and certificate to p12 file
openssl pkcs12 -export -out rsaPrivate.p12 -inkey rsaPrivate.pem -in rsaCert.crt
Now you have two files which are compatible with the iOS security framework: rsaCert.der (public key) and rsaPrivate.p12 (private key). The code below reads in the public key assuming the file is added to your bundle:
- (SecKeyRef)getPublicKeyRef {
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:#"rsaCert" ofType:#"der"];
NSData *certData = [NSData dataWithContentsOfFile:resourcePath];
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (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;
OSStatus res = SecTrustEvaluate(trust, &result);
//Check the result of the trust evaluation rather than the result of the API invocation.
if (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) {
key = SecTrustCopyPublicKey(trust);
}
}
}
}
if (policy) CFRelease(policy);
if (trust) CFRelease(trust);
if (cert) CFRelease(cert);
return key;
}
To read in the private key use the following code:
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:#"password_for_the_key" 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);
securityError = SecIdentityCopyPrivateKey(identityApp, &privateKeyRef);
if (securityError != noErr) {
privateKeyRef = NULL;
}
}
[options release];
CFRelease(items);
return privateKeyRef;
}
Starting with iOS 10, it is actually possible to import PEM private keys w/o converting them to PKCS#12 (which is a very universal container format for everything related to cryptography) and thus also w/o using OpenSSL on command line or statically linking apps with it. On macOS it's even possible since 10.7 using a different function than the ones mentioned here (but so far it doesn't exist for iOS). Exactly the way described below will also work on macOS 10.12 and later, though.
To import a certificate, it's enough to just strip the
-----BEGIN CERTIFICATE-----
and
-----END CERTIFICATE-----
lines, then run base64 decoding over the data left, the result is a certificate in standard DER format, which can just be fed to SecCertificateCreateWithData() to get a SecCertificateRef. This has always been working, also prior to iOS 10.
To import a private key, a little bit of extra work may be required. If the private key is wrapped with
-----BEGIN RSA PRIVATE KEY-----
then it is very easy. Again, the first and last line needs to be stripped, the remaining data needs to be base64 decoded and the result is a RSA key in PKCS#1 format. This format can only hold RSA keys and it is directly readable, just feed the decoded data into SecKeyCreateWithData() to obtain a SecKeyRef. The attributes dictionary just need the following key/value pairs:
kSecAttrKeyType: kSecAttrKeyTypeRSA
kSecAttrKeyClass: kSecAttrKeyClassPrivate
kSecAttrKeySizeInBits: CFNumberRef with then number of bits in the key (e.g. 1024, 2048, etc.) If not known, this information can actually be read from the raw key data, which is ASN.1 data (it's a bit beyond the scope of this answer, but I will provide some helpful links below about how to parse that format). This value is maybe optional! In my tests it was actually not necessary to set this value; if absent, the API determined the value on its own and it was always set correctly later on.
In case the private key is wrapped by -----BEGIN PRIVATE KEY-----, then the base64 encoded data is not in PKCS#1 format but in PKCS#8 format, however, this is a just a more generic container that can also hold non-RSA keys but for RSA keys the inner data of that container is equal to PKCS#1, so one could say for RSA keys PKCS#8 is PKCS#1 with an extra header and all you need to do is stripping that extra header. Just strip the first 26 bytes of the base64 decoded data and you have PKCS#1 again. Yes, it's really that simple.
To learn more about PKCS#x formats in PEM encodings, have a look at this site. To learn more about ASN.1 format, here's a good site for that. And if you need a simple, yet powerful and interactive online ASN.1 parser to play around with different formats, one that can directly read PEM data, as well as ASN.1 in base64 and hexdump, try this site.
Very important: When adding a private key to keychain, that you created as above, please be aware that such a private key doesn't contain a public key hash, yet a public key hash is important for they keychain API to form an identity (SecIdentityRef), as using the public key hash is how the API finds the correct private key that belongs to an imported certificate (a SecIdentityRef is just a SecKeyRef of a private key and a SecCertificateRef of a cert forming a combined object and it's the public key hash, that binds them together). So when you plan to add the private key to keychain, be sure to set a public key hash manually, otherwise you won't ever be able to get an identity for it and without that you cannot use keychain API for tasks like signing or decrypting data. The public key hash must be stored in an attribute named kSecAttrApplicationLabel (stupid name, I know, but it's really not a label and nothing the user can ever see, check out the documentation). E.g.:
OSStatus error = SecItemAdd(
(__bridge CFDictionaryRef)#{
(__bridge NSString *)kSecClass:
(__bridge NSString *)kSecClassKey,
(__bridge NSString *)kSecAttrApplicationLabel:
hashOfPublicKey, // hashOfPublicKey is NSData *
#if TARGET_OS_IPHONE
(__bridge NSString *)kSecValueRef:
(__bridge id)privateKeyToAdd, // privateKeyToAdd is SecKeyRef
#else
(__bridge NSString *)kSecUseItemList:
#[(__bridge id)privateKeyToAdd], // privateKeyToAdd is SecKeyRef
// #[ ... ] wraps it into a NSArray object,
// as kSecUseItemList expects an array of items
#endif
},
&outReference // Can also be NULL,
// otherwise reference to added keychain entry
// that must be released with CFRelease()
);
After hours of effort researching online with the help of this post, I finally get it working perfectly. Here is the notes with working Swift code of the most current version. I hope it can help someone!
Received a certificate in the base64 encoded string sandwiched between header and tail like this (PEM format):
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
strip out the header and the tail, such as
// remove the header string
let offset = ("-----BEGIN CERTIFICATE-----").characters.count
let index = certStr.index(cerStr.startIndex, offsetBy: offset+1)
cerStr = cerStr.substring(from: index)
// remove the tail string
let tailWord = "-----END CERTIFICATE-----"
if let lowerBound = cerStr.range(of: tailWord)?.lowerBound {
cerStr = cerStr.substring(to: lowerBound)
}
decode base64 string to NSData:
let data = NSData(base64Encoded: cerStr,
options:NSData.Base64DecodingOptions.ignoreUnknownCharacters)!
Convert it from NSdata format to SecCertificate:
let cert = SecCertificateCreateWithData(kCFAllocatorDefault, data)
Now, this cert can be used to compare with the certificate received from the urlSession trust:
certificateFromUrl = SecTrustGetCertificateAtIndex(...)
if cert == certificate {
}
My app "streams" content (fixed sized files, hence quotation marks) from an HTTP server into a local file. Then there is another component of the app that opens that same file and displays it (plays it).
This is done for caching purposes, so that when the same file is requested next time, it will no longer need to be downloaded from the server.
App's spec requires that all local content is encrypted (even with the most light weight encryption)
Question: has there been done any work, allowing one to simply redirect the stream to a library which will then save the stream encrypted into a file? And then, when I request the stream from the local file, the library returns an on the fly decrypted stream?
I've been searching for a solution with no results so far
Thanks
I ended up writing a custom solution that uses RC4 encryption from the built in Crypt library. It was surprisingly straight forward. Basically it involved creating a function that encrypts/decrypts chunks of NSData and then read/write those chunks to files... Here's the function that does the encryption in case someone else is interested:
- (NSData*)RC4EncryptDecryptWithKey:(NSString *)key operation:(CCOperation)operation
{
// convert to C string..
int keySize = [key length];
char keyPtr[keySize];
bzero(keyPtr, sizeof(keyPtr));
[key getCString:keyPtr
maxLength:sizeof(keyPtr)
encoding:NSUTF8StringEncoding];
// encode/decode
NSUInteger dataLength = [self length];
size_t bufferSize = dataLength;
void *buffer = malloc(bufferSize);
size_t numBytesOut = 0;
CCCryptorStatus cryptStatus = CCCrypt(operation,
kCCAlgorithmRC4,
kCCOptionECBMode,
keyPtr,
8,
NULL,
[self bytes],
dataLength,
buffer,
bufferSize,
&numBytesOut);
if (cryptStatus == kCCSuccess) {
return [NSData dataWithBytesNoCopy:buffer
length:numBytesOut
freeWhenDone:YES];
}
free(buffer);
return nil;
}
- (NSData*)RC4EncryptWithKey:(NSString*)key {
return [self RC4EncryptDecryptWithKey:key operation:kCCEncrypt];
}
- (NSData*)RC4DecryptWithKey:(NSString*)key {
return [self RC4EncryptDecryptWithKey:key operation:kCCDecrypt];
}
Obviously one could create something more secure (eg AES) or whatever (in fact I used examples of other encryption wrappers to write this one)
I wouldn't worry about encryption just because Apple says so.
Make this work how you want it (without encryption, it sounds like) and submit it for approval. If approved, you're good. If not, worry about it then. If your design requires you to make a decision now, your design might be flawed.
I was to encrypt data on the device and send it by http to our web server then decrypt the data on out .net web app. Is this possible? If yes, which encryption method I should use? and if their are any articles out there?
Thanks
SSL should be the standard solution for HTTP encryption. NSURLConnection supports it out of the box (just load an https:// request), so you would just have to set up your server accordingly.
As you don't want to use SSL (and I agree there are many good reasons not to do so) you can use the built in CommonCrypto framework to encrypt just the data you need to. Here is a simple NSData category to encrypt arbitrary data:
#implementation NSData (AES256)
- (NSData*) encryptedWithKey: (NSString *) key;
{
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyBuffer[kCCKeySizeAES128+1]; // room for terminator (unused)
bzero( keyBuffer, sizeof(keyBuffer) ); // fill with zeroes (for padding)
[key getCString: keyBuffer maxLength: sizeof(keyBuffer) encoding: NSUTF8StringEncoding];
// encrypts in-place, since this is a mutable data object
size_t numBytesEncrypted = 0;
size_t returnLength = ([self length] + kCCKeySizeAES256) & ~(kCCKeySizeAES256 - 1);
// NSMutableData* returnBuffer = [NSMutableData dataWithLength:returnLength];
char* returnBuffer = malloc(returnLength * sizeof(uint8_t) );
CCCryptorStatus result = CCCrypt(kCCEncrypt, kCCAlgorithmAES128 , kCCOptionPKCS7Padding | kCCOptionECBMode,
keyBuffer, kCCKeySizeAES128, nil,
[self bytes], [self length],
returnBuffer, returnLength,
&numBytesEncrypted);
if(result == kCCSuccess)
return [NSData dataWithBytes:returnBuffer length:numBytesEncrypted];
else
return nil;
}
#end
Note that this also turns on ECB Mode which you may not want. Also remember that the data that comes back from this call is not suitable for use in URLs you will have to base 64 encode it.
If SSL is not an option use AES encryption in CBC mode. 128 encryption bit is all you need and you can use anything (0 is acceptable) as the IV.
im trying to get a call to amazon web service and im stuck on getting the signature, looked at this but i still have a question on it.
using this example what is the
NSData *keyData;
NSData *clearTextData
? what do i need to pass for these two values?
/*
inputs:
NSData *keyData;
NSData *clearTextData
*/
uint8_t digest[CC_SHA1_DIGEST_LENGTH] = {0};
CCHmacContext hmacContext;
CCHmacInit(&hmacContext, kCCHmacAlgSHA1, keyData.bytes, keyData.length);
CCHmacUpdate(&hmacContext, clearTextData.bytes, clearTextData.length);
CCHmacFinal(&hmacContext, digest);
NSData *out = [NSData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH]
I just spent like 4 hours Googling and looking for ways to calculate an unkeyed SHA1 on the iPhone that would match the results of the sha1() function in php. Here was the result:
#import <CommonCrypto/CommonDigest.h>
NSString *hashkey = <your data here>;
// PHP uses ASCII encoding, not UTF
const char *s = [hashkey cStringUsingEncoding:NSASCIIStringEncoding];
NSData *keyData = [NSData dataWithBytes:s length:strlen(s)];
// This is the destination
uint8_t digest[CC_SHA1_DIGEST_LENGTH] = {0};
// This one function does an unkeyed SHA1 hash of your hash data
CC_SHA1(keyData.bytes, keyData.length, digest);
// Now convert to NSData structure to make it usable again
NSData *out = [NSData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH];
// description converts to hex but puts <> around it and spaces every 4 bytes
NSString *hash = [out description];
hash = [hash stringByReplacingOccurrencesOfString:#" " withString:#""];
hash = [hash stringByReplacingOccurrencesOfString:#"<" withString:#""];
hash = [hash stringByReplacingOccurrencesOfString:#">" withString:#""];
// hash is now a string with just the 40char hash value in it
Hopefully this will help others who are struggling with SHA1 on the iPhone
If you are calling the Amazon web service too look up prices or product details, your Amazon web service key will be disabled and your app will stop working.
Look at the terms of service of the Amazon Web Services, use by mobile clients is strictly disallowed:
https://affiliate-program.amazon.com/gp/advertising/api/detail/agreement.html
I found this out the hard way when my own application had my AWS key disabled in a production app. I had read the TOS, but it was not really there as you can see by the link above to some other obscure detail of use. You wouldn't think the affiliate program would have anything to do with the API, but it does.
You can find details of other apps blocked at this TechCrunch article:
http://www.techcrunch.com/2009/07/07/amazon-killing-mobile-apps-that-use-its-data/
Just giving you a heads up and hopefully saving you a lot of work.
// This is my code used in my Twitter connection, and working well for me.
// KeithF's code was a big help!
//
// This is a category added to NSData.
#implementation NSData (EOUtil)
- (NSData*)dataByHmacSHA1EncryptingWithKey:(NSData*)key
{
void* buffer = malloc(CC_SHA1_DIGEST_LENGTH);
CCHmac(kCCHmacAlgSHA1, [key bytes], [key length], [self bytes], [self length], buffer);
return [NSData dataWithBytesNoCopy:buffer length:CC_SHA1_DIGEST_LENGTH freeWhenDone:YES];
}
#end
Take a look at CocoaCryptoHashing for the SHA1 encoding
I posted one solution to this here, that returns the Base64 encoded data that AWS requests.
Apple's iOS developer library has provided an excellent sample titled CryptoExercise which includes a simple function:
- (NSData *)getHashBytes:(NSData *)plainText" to get a SHA-1 hash.
You can see this
maybe it helps you.