i'm a totally newbie in objective-c and iOS programming.
I need to use a REST API, in which the request requires a mutual SSL authentication with a x509 PEM certificate file and a password for the certificate.
My problem is that i don't really know how to do it in objective C, in C# it would be as easy as:
X509Certificate2 myCertificate = new x509Certificate2(certificatePath, certificatePassword);
myRequest.ClientCertificate.Add(myCertificate);
Where "myRequest" just represents the variable of the http request.
I've been looking for a while in many forums and posts, including some old stackoverflow questions.
The main tip people give is "use OpenSSL", but i've read in few places (i think even in a stackoverflow question) that Apple has deprecated OpenSSL.
Question: How could i load a .pem x509 certificate with its password to send in a POST request to a external REST API Service, in objective-C language?
Thank you.
My Own Solution:
Using OpenSSL command to convert .pem file intro pkcs12 certificate:
openssl pkcs12 -export -in "certificateFile" -inkey "KeyFile" -out "certificate.p12"
Then setting up some code found in this tutorial: https://vanjakom.wordpress.com/tag/nsurlconnection/
Basically:
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSLog(#"Authentication challenge");
// load cert
NSString *path = [[NSBundle mainBundle] pathForResource:#"userA" ofType:#"p12"];
NSData *p12data = [NSData dataWithContentsOfFile:path];
CFDataRef inP12data = (__bridge CFDataRef)p12data;
SecIdentityRef myIdentity;
SecTrustRef myTrust;
OSStatus status = extractIdentityAndTrust(inP12data, &myIdentity, &myTrust);
SecCertificateRef myCertificate;
SecIdentityCopyCertificate(myIdentity, &myCertificate);
const void *certs[] = { myCertificate };
CFArrayRef certsArray = CFArrayCreate(NULL, certs, 1, NULL);
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity certificates:(__bridge NSArray*)certsArray persistence:NSURLCredentialPersistencePermanent];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
And
OSStatus extractIdentityAndTrust(CFDataRef inP12data, SecIdentityRef *identity, SecTrustRef *trust)
{
OSStatus securityError = errSecSuccess;
CFStringRef password = CFSTR("userA");
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 myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0);
const void *tempIdentity = NULL;
tempIdentity = CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemIdentity);
*identity = (SecIdentityRef)tempIdentity;
const void *tempTrust = NULL;
tempTrust = CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemTrust);
*trust = (SecTrustRef)tempTrust;
}
if (options) {
CFRelease(options);
}
return securityError;
}
"Code copied from the tutorial, did not copy my own modification for my own needs".
And it's solved
Related
I'm using NSURLSessionDelegate's (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler for challenging server authentication like this:
BOOL trusted = NO;
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if (!cert) {
NSURL* certURL = [[NSBundle mainBundle] URLForResource: #"cert_new" withExtension: #"der"];
NSData* certData = [NSData dataWithContentsOfURL: certURL];
cert = SecCertificateCreateWithData(kCFAllocatorDefault, CFBridgingRetain(certData));
}
SecPolicyRef policyRef = SecPolicyCreateBasicX509();
SecCertificateRef certArray[1] = { cert };
CFArrayRef certArrayRef = CFArrayCreate(NULL, (void *)certArray, 1, NULL);
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
// Create a policy that ignores the host name…
OSStatus err = SecTrustCreateWithCertificates(CFBridgingRetain((__bridge id _Nullable)(certArrayRef)), policyRef, &serverTrust);
CFRelease(policyRef);
if (err != noErr)
{
XLog(#"Error creating trust: %d", (int)err);
[challenge.sender cancelAuthenticationChallenge: challenge];
return;
}
err = SecTrustSetAnchorCertificates(serverTrust, certArrayRef);
if (err == noErr)
{
SecTrustResultType trustResult;
err = SecTrustEvaluate(serverTrust, &trustResult);
trusted = (err == noErr && (trustResult == kSecTrustResultProceed || trustResult == kSecTrustResultUnspecified));
}
CFRelease(certArrayRef);
CFRelease(policyRef);
CFRelease(cert);
}
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (trusted) {
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
return;
} else {
[challenge.sender cancelAuthenticationChallenge: challenge];
}
The certificate I have to use is in PEM format. So I converted it via openssl to DER format like this:
openssl x509 -in pem_file.pem -out cert_new.der -outform DER
Now it seems like every certificate (whether valid or not) of a certain bit length will be accepted: trusted will be evaluate to YES. And the valid certificate with lower bit length will be evaluated to trusted = NO because of kSecTrustResultRecoverableTrustFailure. Strange behavior... Can someone explain to me how to do it right?
Thanks!
Now I have a working version:
// Build a new trust based on the supplied trust, so that we can set the policy…
NSURLProtectionSpace* protectionSpace = challenge.protectionSpace;
SecTrustRef trust = protectionSpace.serverTrust;
CFIndex numCerts = SecTrustGetCertificateCount(trust);
NSMutableArray* certs = [NSMutableArray arrayWithCapacity: numCerts];
for (CFIndex idx = 0; idx < numCerts; ++idx)
{
SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, idx);
[certs addObject: CFBridgingRelease(cert)];
}
// Create a policy that ignores the host name…
SecPolicyRef policy = SecPolicyCreateSSL(true, NULL); // SecPolicyCreateBasicX509();
OSStatus err = SecTrustCreateWithCertificates(CFBridgingRetain(certs), policy, &trust);
CFRelease(policy);
if (err != noErr)
{
//NSLogDebug(#"Error creating trust: %d", err);
[challenge.sender cancelAuthenticationChallenge: challenge];
return;
}
// Set the root cert and evaluate the trust…
NSURL* certURL = [[NSBundle mainBundle] URLForResource: #"doorbird_certificate" withExtension: #"der"];
NSData* certData = [NSData dataWithContentsOfURL: certURL];
SecCertificateRef rootCert = SecCertificateCreateWithData(kCFAllocatorDefault, CFBridgingRetain(certData));
NSArray* rootCerts = #[ CFBridgingRelease(rootCert) ];
err = SecTrustSetAnchorCertificates(trust, CFBridgingRetain(rootCerts));
if (err == noErr)
{
SecTrustResultType trustResult;
err = SecTrustEvaluate(trust, &trustResult);
SecTrustSetAnchorCertificatesOnly(trust, YES);
NSURLCredential* credential = [NSURLCredential credentialForTrust: trust];
CFRelease(trust);
bool trusted = err == noErr;
trusted = trusted && (trustResult == kSecTrustResultProceed || trustResult == kSecTrustResultUnspecified);
if (trusted)
{
[challenge.sender useCredential: credential forAuthenticationChallenge: challenge];
return;
}
}
// An error occurred, or we don't trust the cert, so disallow it…
[challenge.sender cancelAuthenticationChallenge: challenge];
But only certificates with a minimum of 1024 bit are working. Certificates with 512 bit don't. Can someone confirm that?
Thanks
Edit:
I created the certificates like described here:
http://www.akadia.com/services/ssh_test_certificate.html
I have an application where I need to perform sort of a 2-Step-Authentication and long story short I'm getting per-user base64 encoded pem format certificates from the server and using them on each request.
First I generate a key pair, make a CSR, give them the CSR, they give me the certificate and this is where I have to use it and fail. I'm getting the following errors in the console for each individual request:
CFNetwork SSLHandshake failed (-4)
CFNetwork SSLHandshake failed (-9824)
CFNetwork SSLHandshake failed (-9824)
NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)
My approach is the following:
-grab the DER encoded data from the PEM formatted signed certificate they are sending me
-make a SecCertificateRef which I add to the keychain
-query for the SecIdentityRef in the keychain by label
-I then do some mostly needless stuff like grab the SecCertificateRef and private key from the identity mostly to be sure what's going on
-I also insert a CA Certificate which I have from the server and grab a reference to it from the keychain (not sure if I need to use it for the credential but I tried with or without it - the result was the same)
-I then initialize the credential with the identity and my certificates and use it when I get a NSURLAuthenticationMethodClientCertificate auth method (I don't do the check but that's all I get besides the server trust).
So up to this point nothing is NULL, everything gets initialized and looks good but the requests don't succeed. When I try to use the server trust credential on all requests, I get through and don't get the error but my server is giving me a security error as it should. As soon as I use the custom credential for any challenge I get the above errors.
note: I know the code is messy and I shouldn't be inserting certificates on each request but it's still very early work in progress and that's not the problem since the refs get instantiated correctly
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
SSLConnectionWrapper *wrapper = [self wrapperForConnection:connection];
NSString *certStringBase64 = [[NSUserDefaults standardUserDefaults] SSLCertificateForUserWithID:wrapper.userID];
NSData *certData = [[NSData alloc] initWithBase64EncodedString:certStringBase64 options:0];
NSString *certString = [[NSString alloc] initWithData:certData encoding:NSUTF8StringEncoding];
certString = [certString stringByReplacingOccurrencesOfString:#"-----BEGIN CERTIFICATE-----" withString:#""];
certString = [certString stringByReplacingOccurrencesOfString:#"-----END CERTIFICATE-----" withString:#""];
certString = [[certString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]] componentsJoinedByString:#""];
//at this point certString contains the DER encoded certificate data
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)([[NSData alloc] initWithBase64EncodedString:certString options:kNilOptions]));
OSStatus err = SecItemAdd((__bridge CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge id) kSecClassCertificate, kSecClass,
cert, kSecValueRef,
kCFBooleanTrue, kSecReturnPersistentRef,
[NSString stringWithFormat:#"CertLabel_UserID_%#", wrapper.userID], kSecAttrLabel,
nil], NULL);
const void *keys[] = { kSecClass, kSecReturnRef, kSecAttrLabel };
const void *values[] = { kSecClassIdentity, kCFBooleanTrue, (__bridge const void *)([NSString stringWithFormat:#"CertLabel_UserID_%#", wrapper.userID]) };
CFDictionaryRef queryForIdentityDict = CFDictionaryCreate(NULL, keys, values,
3, NULL, NULL);
SecIdentityRef identityKeychainRef = NULL;
OSStatus s = SecItemCopyMatching(queryForIdentityDict, (CFTypeRef *)&identityKeychainRef);
SecCertificateRef certKeychainRef = NULL;
OSStatus s2 = SecIdentityCopyCertificate(identityKeychainRef, &certKeychainRef);
SecKeyRef privateKey;
SecIdentityCopyPrivateKey(identityKeychainRef, &privateKey);
NSString *stringForCACert = [self stringForCACert];
SecCertificateRef caCert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)([[NSData alloc] initWithBase64EncodedString:stringForCACert options:kNilOptions]));
OSStatus s3 = SecItemAdd((__bridge CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge id) kSecClassCertificate, kSecClass,
caCert, kSecValueRef,
#"CACert", kSecAttrLabel,
nil], NULL);
const void *keys1[] = { kSecClass, kSecReturnRef, kSecAttrLabel };
const void *values1[] = { kSecClassCertificate, kCFBooleanTrue, #"CACert" };
CFDictionaryRef queryForCACert = CFDictionaryCreate(NULL, keys1, values1,
3, NULL, NULL);
SecCertificateRef caCertKeychainRef = NULL;
OSStatus s4 = SecItemCopyMatching(queryForCACert, (CFTypeRef *)&caCertKeychainRef);
NSURLCredential *credential = [[NSURLCredential alloc] initWithIdentity:identityKeychainRef certificates:#[ (__bridge id)certKeychainRef, (__bridge id) caCertKeychainRef] persistence:NSURLCredentialPersistencePermanent];
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}else{
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}
}
It's an authentication challenge faced by server. You can bypass by following code (Using NSURLCOnnection)
+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString *)host {
return YES;
}
Note: Don't use above if you uploading app on app store.
For iOS 9 above would not work, please edit plist as follows
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
I need to digitally sign on mac some data and then verify it on iOS. So I generated RSA keypair and certificate for public key in DER format with open ssl (tried generation with SecKeyGeneratePair but then it is harder to import Public key to iOS and SecKeyRawVerify still doesn't work with the same result), and signed my data on Mac app. Then if I verify this data on iOS verification fails with -9809 error code, but if execute the same code on mac verification succeeds.
Here is my code for verification:
NSString* certPath = [[NSBundle mainBundle] pathForResource: #"Public" ofType:#"der"];
NSData* certificateData = [NSData dataWithContentsOfFile: certPath];
SecCertificateRef certificateFromFile = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData); // load the certificate
SecPolicyRef secPolicy = SecPolicyCreateBasicX509();
SecTrustRef trust;
OSStatus statusTrust = SecTrustCreateWithCertificates( certificateFromFile, secPolicy, &trust);
SecTrustResultType resultType;
OSStatus statusTrustEval = SecTrustEvaluate(trust, &resultType);
SecKeyRef publicKey = SecTrustCopyPublicKey(trust);
NSString* licensingPolicyString = #"ZKL3YXtqtFcIeWRqSekNuCmtu/nvy3ApsbJ+8xad6cO/E8smLHGfDrTQ3h/38d0IMJcUThsVMyX8qtqILmPeTnBpZgJetBjb8kAfuPznzxOrIcYd27/50ThWv6guLqZL7j1apnfRZHAdMiozvEYH62sma1Q9qTl+W7qxEAxWs2AXDTQcF7nGciEM6MEohs8u879VNIE1VcPW8ahMoe25wf8pvBvrzE0z0MR4UFE3ZSWIeeQsiaUPYFwHbfQAOifaw/qIisjL5Su6WURoaSupWTMdQh3ZNyqZuYJaT70u8S7NgF3BzG8uBiYOUYsf6UayvkABmF0UuMdcvhPQefyhuXsiYWxsb3dFeGNoYW5nZSI6dHJ1ZSwiYWxsb3dTaGFmZXIiOnRydWUsInBvbGljeSBuYW1lIjp0cnVlfQ==";
size_t signedHashBytesSize = SecKeyGetBlockSize(publicKey);
NSData* messageData = [[NSData alloc] initWithBase64EncodedData:[licensingPolicyString dataUsingEncoding: NSUTF8StringEncoding] options:0];
NSData* signatureData = [messageData subdataWithRange:NSMakeRange(0, signedHashBytesSize)];
NSData* rawMessageData = [messageData subdataWithRange: NSMakeRange(signedHashBytesSize, messageData.length - signedHashBytesSize)];
uint8_t sha1HashDigest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1([rawMessageData bytes], (CC_LONG)[rawMessageData length], sha1HashDigest);
OSStatus verficationResult = SecKeyRawVerify(publicKey, kSecPaddingPKCS1SHA1, sha1HashDigest, CC_SHA1_DIGEST_LENGTH, [signatureData bytes], [signatureData length]);
CFRelease(publicKey);
CFRelease(trust);
CFRelease(secPolicy);
CFRelease(certificateFromFile);
if (verficationResult == errSecSuccess) NSLog(#"Verified");
Is there some difference in digital signature verification for Mac and iOS? I didn't manage to find anything about it in Apple's documentation.
Well after some experimenting with sign/verify, I've found out that changing padding agreement to SecKeyRawVerify/SecKeyRawSign from kSecPaddingPKCS1SHA1 to kSecPaddingPKCS1, solves my problem. Don't know why it doesn't work with kSecPaddingPKCS1SHA1, there is not deprecations described in Apple's documentation. Also I didn't try this code on iOS different from 8.3 so maybe it is iOs8.3 issue.
I'm using the iPhoneHTTPServer sample into my project for using HTTPServer, and host a .plist file with a .ipa to simulate an ad-hoc deployment.
As you may know, since iOS7, the server which hosts files must be secured, and so I'm trying to use a SSL authentication, but it failed.
First, the server seems to start correctly, but it failed when I'm trying to access to my server like this:
NSURL *plistUrl = [NSURL URLWithString:#"itms-services://?action=download-manifest&url=https://localhost:8080/TestMAJ2.plist"];
[[UIApplication sharedApplication] openURL:plistUrl];
I have this error :
NSAssert(NO, #"Security option unavailable - kCFStreamSSLLevel" #" - You must use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMax");
How can I bypass this error? I was trying to removed the kCFStreamSSLLevel of the TLS settings (why not ? ^^), but the connection still don't work, I've got a popup with "Unable to connect to localhost" or something like that ...
About the SSL authentication, the DDKeychain class from the sample was not good because it's Mac's API, so I use this code: How to make iPhoneHTTPServer secure server, and the certificate come from Keychain Access, and it's the certificate that I used for signing my app. Maybe it's not the correct certificate ? Or the correct piece of code ? Do you know a very simple example of using SecureHTTPServer in iOS?
I have the SSL certificate working perfectly with a .html file. I am having problems with the .manifest and the .ipa files.
What I did for the SSL is creating MyHTTPConnection class that implements the following:
- (NSArray *)sslIdentityAndCertificates
{
SecIdentityRef identityRef = NULL;
SecCertificateRef certificateRef = NULL;
SecTrustRef trustRef = NULL;
NSString *thePath = [[NSBundle mainBundle] pathForResource:#"server" ofType:#"pfx"];
NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:thePath];
CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data;
CFStringRef password = CFSTR("test123");
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = errSecSuccess;
securityError = SecPKCS12Import(inPKCS12Data, optionsDictionary, &items);
if (securityError == 0) {
CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex (items, 0);
const void *tempIdentity = NULL;
tempIdentity = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemIdentity);
identityRef = (SecIdentityRef)tempIdentity;
const void *tempTrust = NULL;
tempTrust = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust);
trustRef = (SecTrustRef)tempTrust;
} else {
NSLog(#"Failed with error code %d",(int)securityError);
return nil;
}
SecIdentityCopyCertificate(identityRef, &certificateRef);
NSArray *result = [[NSArray alloc] initWithObjects:(__bridge id)identityRef, (__bridge id)certificateRef, nil];
return result;
}
Then in the App Delegate I just setted this custom connection class to the httpServer:
[httpServer setConnectionClass:[MyHTTPConnection class]];
The last thing that you need to do is to add the server.pfx certificate to the bundle. I created the certificate following this tutorial. I just did until you have the server.crt file. Then I converted it to pfx12 in this website. I did this because p12 certificate was not working, I was having this problem.
Chris, could you edit your entry explaining a little bit how you did the manifest thing. I want to do exactly the same as you but I am having troubles with the URLs that you need to open.
I am clearly missing the Aha! moment because I have been doing research on using CA or Self signed Certificates and such for gaining access to a secure URL https and I am still having trouble fully understanding it, I am mostly just blundering about with other people code and solutions to try and get mine working, i am clearly lacking a fundamental understanding so hopefully the denizens of the site can help.
Essentially, i have an app that communicates, with a https server using a self signed certificate.
What I believe to be the certificate and key needed to access the server are both stored in a p12 stored in the root bundle of the app. I then add this p12 to the phones or apps keychain through this code I found on the internet
NSString *p12Path = [[NSBundle mainBundle] pathForResource:p12Name ofType:#"p12"];
NSData *p12Data = [[NSData alloc] initWithContentsOfFile:p12Path];
NSError *error = nil;
NSData *data = [[SFHFKeychainUtils getPasswordForUsername:[[UIDevice currentDevice]name] andServiceName:serviceName error:&error] dataUsingEncoding:NSUTF8StringEncoding];
[self transform:data];
NSString *pass = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
CFStringRef password = (__bridge CFStringRef)pass;
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef p12Items;
OSStatus result = SecPKCS12Import((__bridge CFDataRef)p12Data, optionsDictionary,&p12Items);
if(result == noErr)
{
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(p12Items, 0);
SecIdentityRef identityApp =(SecIdentityRef)CFDictionaryGetValue(identityDict,kSecImportItemIdentity);
SecCertificateRef certRef;
SecIdentityCopyCertificate(identityApp,&certRef);
SecCertificateRef certArray[1] = { certRef };
CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL);
CFRelease(certRef);
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identityApp certificates:(__bridge NSArray *)myCerts persistence:NSURLCredentialPersistencePermanent];
CFRelease(myCerts);
[[RKObjectManager sharedManager].HTTPClient setDefaultCredential:credential];
}
And, well this seems to work, But i have to have this enabled
_httpClient = [RKObjectManager sharedManager].HTTPClient;
[_httpClient setAllowsInvalidSSLCertificate:YES];
Otherwise it does not connect, Now I have seen various posts saying, you need to have this set to yes to allow self signed certs, but at the same time i have seen other posts saying that it should only be for development as otherwise this makes using https entirely redundant. And obviously redundant security is bad so i set it to no...and it does not connect.
So Really does anyone have any links or could spare some time themselves filling up the gaps in my knowledge on how this stuff works? it would be much appreciated and would save me getting grief from the boss.
This needs to be handled by AFNetworking and is called 'SSL Pinning'. Check the docs here for details on how to enable the feature and supply your certificate. There is also useful information here.