iOS checking if root certificate is installed *and* trusted - ios

Our app is installing a root CA profile, and I want to verify it is installed and trusted by the user.
Currently this is roughly what we do (trimmed it for the core)
SecPolicyRef policyObj = SecPolicyCreateBasicX509();
SecTrustRef trustObj;
OSStatus error = SecTrustCreateWithCertificates((__bridge CFTypeRef _Nonnull)(fullChain), policyObj, &trustObj);
SecTrustResultType result;
error = SecTrustEvaluate(trustObj, &result);
CFRelease(trustObj);
CFRelease(policyObj);
return (kSecTrustResultUnspecified == result || kSecTrustResultProceed == result);
The problem is this, once the profile is installed the result is either kSecTrustResultUnspecified (iOS 10~) or kSecTrustResultProceed (iOS 11~)
But I want to check if user trusted it (under General->About->Trust Settings)
I dug around apple's docs and found nothing, moreover in the SecTrustEvaluate doc it says return value 'proceed' means user trusted the cert.
proceed— The user explicitly chose to trust a certificate in the chain (usually by clicking a button in a certificate trust panel).
Anyone has idea how this can be done? what am i missing?

So, after digging around i found out that SecPolicyCreateSSL is working as expect, still not 100% why SecPolicyCreateBasicX509 is not.
So for future ref if someone have this issue, this is what we did instead,
SecPolicyRef policy = SecPolicyCreateSSL(true, NULL);
SecTrustRef testTrust;
OSStatus status = SecTrustCreateWithCertificates((__bridge CFArrayRef)fullChain, policy, &testTrust);
status = SecTrustEvaluate(testTrust, &trustResult);
CFRelease(testTrust);
CFRelease(policy);
return (status == errSecSuccess) && (kSecTrustResultUnspecified == trustResult || kSecTrustResultProceed == trustResult);;
(basically using SecPolicyCreateSSL instead)

Improoved Objective-C code by #Al Ga, it tested and work on iOS 13/14
SecPolicyRef policyObj = SecPolicyCreateBasicX509();
SecTrustRef trustObj;
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"certName" ofType:#"crt"];
NSData *certData = [NSData dataWithContentsOfFile:filePath];
CFDataRef certCFR = (__bridge CFDataRef)certData;
SecCertificateRef certSCR = SecCertificateCreateWithData(NULL, certCFR);
NSArray* certArray = #[ (__bridge id)certSCR ];
OSStatus error = SecTrustCreateWithCertificates((__bridge CFTypeRef _Nonnull)certArray, policyObj, &trustObj);
SecTrustResultType result;
error = SecTrustEvaluate(trustObj, &result);
SecTrustResultType result will be contain uint32_t :
kSecTrustResultInvalid = 0
kSecTrustResultProceed = 1 //root cert Installed
kSecTrustResultConfirm = 2
kSecTrustResultDeny = 3
kSecTrustResultUnspecified = 4
kSecTrustResultRecoverableTrustFailure = 5 //root cert Doesn't installed
kSecTrustResultFatalTrustFailure = 6
kSecTrustResultOtherError = 7

Related

Get public/private key from certificate

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

RSA/ECB/PKCS1Padding iOS encryption

I am currently stuck at a problem which involves encryption in iOS.
My client has given me the public key,
"-----BEGIN PUBLIC KEY-----
xxxx
-----END PUBLIC KEY-----"
The padding strategy that needs to be used is RSA/ECB/PKCS1Padding.
With android, it seems pretty straight forward
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
encryptedBytes = cipher.doFinal(plain.getBytes());
return encryptedBytes;
I dont see any direct methods to do this in iOS. Any of the common pods used like Commoncrypto doesnt allow me to force PKCS1 padding scheme. Being a pretty inexperienced guy with RSA and encryption, it would be very much appreciated if you could help me understand on how to approach this and guide me through this.
Using standard Security Framework - SecKeyEncrypt with kSecPaddingPKCS1 parameter
My issue was solved using non padding :
kSecPaddingNone
-(SecKeyRef)getPublicKeyForEncryption
{
NSString *thePath = [MAuthBundle pathForResource:#"certificate" ofType:#"der"];
//2. Get the contents of the certificate and load to NSData
NSData *certData = [[NSData alloc]
initWithContentsOfFile:thePath];
//3. Get CFDataRef of the certificate data
CFDataRef myCertData = (__bridge CFDataRef)certData;
SecCertificateRef myCert;
SecKeyRef aPublicKeyRef = NULL;
SecTrustRef aTrustRef = NULL;
//4. Create certificate with the data
myCert = SecCertificateCreateWithData(NULL, myCertData);
//5. Returns a policy object for the default X.509 policy
SecPolicyRef aPolicyRef = SecPolicyCreateBasicX509();
if (aPolicyRef) {
if (SecTrustCreateWithCertificates((CFTypeRef)myCert, aPolicyRef, &aTrustRef) == noErr) {
SecTrustResultType result;
if (SecTrustEvaluate(aTrustRef, &result) == noErr) {
//6. Returns the public key for a leaf certificate after it has been evaluated.
aPublicKeyRef = SecTrustCopyPublicKey(aTrustRef);
}
}
}
return aPublicKeyRef;
}
-(NSString*) rsaEncryptString:(NSString*) string
{
SecKeyRef publicKey = [self getPublicKeyForEncryption];
NSData* strData = [string dataUsingEncoding:NSUTF8StringEncoding];
CFErrorRef err ;
NSData * data = CFBridgingRelease(SecKeyCreateEncryptedData(publicKey, kSecKeyAlgorithmRSAEncryptionPKCS1, ( __bridge CFDataRef)strData, &err));
NSString *base64EncodedString = [data base64EncodedStringWithOptions:0];
return base64EncodedString;
}

How to evaluate trust of x509 certificate on iOS

I am currently developing an iOS app with end to end encryption. In order to let the users authenticate each other, every user generates a x509 Certificate Signing Request (CSR) and sends the CSR to our CA-server for signing.
A user can trust another user by verifying that the other users certificate is signed by the CA.
My question is:
On the iPhone, I currently have the CA-cert and the user-cert that needs to be verified. How do I verify that the user-cert is actually signed by the CA?
My best try is the code that follows, but it does not specify what to evaluate the clientCert against, which confuses me.
-(BOOL) evaluateTrust:(SecCertificateRef) clientCert{
SecPolicyRef myPolicy = SecPolicyCreateBasicX509();
SecCertificateRef certArray[1] = { clientCert };
CFArrayRef myCerts = CFArrayCreate(
NULL, (void *)certArray,
1, NULL);
SecTrustRef myTrust;
OSStatus status = SecTrustCreateWithCertificates(
myCerts,
myPolicy,
&myTrust);
SecTrustResultType trustResult;
if (status == noErr) {
status = SecTrustEvaluate(myTrust, &trustResult);
}
NSLog(#"trustresult %d", trustResult);
return trustResult == kSecTrustResultProceed || trustResult == kSecTrustResultUnspecified;
}
Your code evaluates your clientCert against anchor (trusted root) certificates present in the keychain. If you want to evaluate it against your caCert you need to register your caCert as an anchor certificate with SecTrustSetAnchorCertificates.
Alternatively you can add your caCert to certArray:
SecCertificateRef certArray[2] = { clientCert, caCert };
CFArrayRef myCerts = CFArrayCreate(
NULL, (void *)certArray,
2, NULL);
SecTrustRef myTrust;
OSStatus status = SecTrustCreateWithCertificates(
myCerts,
myPolicy,
&myTrust);
Check out: https://developer.apple.com/library/ios/documentation/security/conceptual/CertKeyTrustProgGuide/iPhone_Tasks/iPhone_Tasks.html#//apple_ref/doc/uid/TP40001358-CH208-SW13
It says:
4. ... If you have intermediate certificates or an anchor certificate for the certificate chain, you can include those in the certificate array passed to the SecTrustCreateWithCertificates function. Doing so speeds up the trust evaluation.

get SecKeyRef from base64 coded string

I'm working on an iOS app and I get a base64 coded public key such as:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3gn+tJ1+PbP0GHa6hmM35WsVyibpypWAwRuBYY4MGfh3VWoXgiyiLo5HJTW1eR9BUFq3z+yOG1rwzSabZ8I4zneWm0kH7xErSjNrMbmjirbL7e6TQNa1ujP/x4x9XVbqf3vIsNVs19kn/qSX/HGzd5Ct3TGAo0AT0T4JwkCfciwIDAQAB
I'd like to encode some text with this public key, but I cannot find a way to convert this string to a useful public key.
What do I need to do?
First, you must base64 decode your NSString to NSData:
See this answer for solutions. If you are developing for iOS 7, you can use initWithBase64EncodedString::options.
Once you have the string decoded as NSData, you can attempt to create a certificate from it. The format of the certificate you received matters - you can use DER (which is common) or PKCS12. You're likely to be getting it as DER, so that's what I'll assume you need guidance on.
Create a certificate and policy:
SecCertificateRef cert = NULL;
SecPolicyRef policy = NULL;
cert = SecCertificateCreateWithData(kCFAllocatorDefault, data);
policy = SecPolicyCreateBasicX509();
If the cerificate data was in an incorrect format when passed to SecCertificateCreateWithData you will get a NULL result.
At this point you have the certificate, but not the public key. To obtain the public key you must create a trust reference and evaluate the trust of the certificate.
OSStatus status = noErr;
SecKeyRef *publicKey = NULL;
SecTrustRef trust = NULL;
SecTrustResultType trustType = kSecTrustResultInvalid;
if (cert != NULL){
SecCertificateRef certArray[1] = {cert};
certs = CFArrayCreate(kCFAllocatorDefault, (void *)certArray, 1, NULL);
status = SecTrustCreateWithCertificates(certs, policy, &trust);
if (status == errSecSuccess){
status = SecTrustEvaluate(trust, &trustType);
// Evaulate the trust.
switch (trustType) {
case kSecTrustResultInvalid:
case kSecTrustResultConfirm:
case kSecTrustResultDeny:
case kSecTrustResultUnspecified:
case kSecTrustResultFatalTrustFailure:
case kSecTrustResultOtherError:
break;
case kSecTrustResultRecoverableTrustFailure:
*publicKey = SecTrustCopyPublicKey(trust);
break;
case kSecTrustResultProceed:
*publicKey = SecTrustCopyPublicKey(trust);
break;
}
}
}
If everything went well, you should now have a populated SecKeyRef with the public key. If it didn't go well, you will have a NULL SecKeyRef and an OSStatus indicating what went wrong. SecBase.h in the Security framework gives more detailed information on those error codes.
Now that you have a SecKeyRef with a public key, using it to encrypt data with a corresponding private key is covered well by the programming guide.
Note that you will have to release the things you allocated above (policy, certs) using ARC or CFRelease.

SecTrustEvaluate always returns kSecTrustResultRecoverableTrustFailure with SecPolicyCreateSSL

My application tries to evaluate a server trust certificate for a self signed certificate. This is working fine with SecPolicyCreateBasicX509 but not working for SecPolicyCreateSSL
Here is my code:
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
// create trust from protection space
SecTrustRef trustRef;
int trustCertificateCount = SecTrustGetCertificateCount(challenge.protectionSpace.serverTrust);
NSMutableArray* trustCertificates = [[NSMutableArray alloc] initWithCapacity:trustCertificateCount];
for (int i = 0; i < trustCertificateCount; i++) {
SecCertificateRef trustCertificate = SecTrustGetCertificateAtIndex(challenge.protectionSpace.serverTrust, i);
[trustCertificates addObject:(id) trustCertificate];
}
// set evaluation policy
SecPolicyRef policyRef;
// policyRef = SecPolicyCreateBasicX509(); this is working
policyRef = SecPolicyCreateSSL(NO, (CFStringRef)
SecTrustCreateWithCertificates((CFArrayRef) trustCertificates, policyRef, &trustRef);
[trustCertificates release];
// load known certificates from keychain and set as anchor certificates
NSMutableDictionary* secItemCopyCertificatesParams = [[NSMutableDictionary alloc] init];
[secItemCopyCertificatesParams setObject:(id)kSecClassCertificate forKey:(id)kSecClass];
[secItemCopyCertificatesParams setObject:#"Server_Cert_Label" forKey:(id)kSecAttrLabel];
[secItemCopyCertificatesParams setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnRef];
[secItemCopyCertificatesParams setObject:(id)kSecMatchLimitAll forKey:(id)kSecMatchLimit];
CFArrayRef certificates;
certificates = nil;
SecItemCopyMatching((CFDictionaryRef) secItemCopyCertificatesParams, (CFTypeRef*) &certificates);
if (certificates != nil && CFGetTypeID(certificates) == CFArrayGetTypeID()) {
SecTrustSetAnchorCertificates(trustRef, certificates);
SecTrustSetAnchorCertificatesOnly(trustRef, NO);
}
SecTrustResultType result;
OSStatus trustEvalStatus = SecTrustEvaluate(trustRef, &result);
if (trustEvalStatus == errSecSuccess) {
if (result == kSecTrustResultConfirm || result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) {
// evaluation OK
[challenge.sender useCredential:[NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
} else {
// evaluation failed
// ask user to add certificate to keychain
} else {
// evaluation failed - cancel authentication
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
After a lot of research i have already made changes to the self-signed certificate by adding extension like mentioned in this post: Unable to trust a self signed certificate on iphone
Does anyone have another hint what might be missing here?
After a lot of testing I have worked out this problem. The following has been changed.
The policy is set to NO for server evaluation. This means the certificate is checked for client authentication. Obviously the server certificate will not have this! Setting this to YES will actually check if extendedKeyUsage is set to serverAuth for the server certificate.
SecTrustSetAnchorCertificates and SecTrustSetAnchorCertificatesOnly should always be called before evaluation and not only if you are providing your own anchor certificates. You need to call this with an empty array, otherwise the system known anchor certificates are not used for evaluation. Even installed trusted root certificates from MDM are working then.
Here is a working sample based on the first code:
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
// create trust from protection space
SecTrustRef trustRef;
int trustCertificateCount = SecTrustGetCertificateCount(challenge.protectionSpace.serverTrust);
NSMutableArray* trustCertificates = [[NSMutableArray alloc] initWithCapacity:trustCertificateCount];
for (int i = 0; i < trustCertificateCount; i++) {
SecCertificateRef trustCertificate = SecTrustGetCertificateAtIndex(challenge.protectionSpace.serverTrust, i);
[trustCertificates addObject:(id) trustCertificate];
}
// set evaluation policy
SecPolicyRef policyRef;
// set to YES to verify certificate extendedKeyUsage is set to serverAuth
policyRef = SecPolicyCreateSSL(YES, (CFStringRef) challenge.protectionSpace.host);
SecTrustCreateWithCertificates((CFArrayRef) trustCertificates, policyRef, &trustRef);
[trustCertificates release];
// load known certificates from keychain and set as anchor certificates
NSMutableDictionary* secItemCopyCertificatesParams = [[NSMutableDictionary alloc] init];
[secItemCopyCertificatesParams setObject:(id)kSecClassCertificate forKey:(id)kSecClass];
[secItemCopyCertificatesParams setObject:#"Server_Cert_Label" forKey:(id)kSecAttrLabel];
[secItemCopyCertificatesParams setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnRef];
[secItemCopyCertificatesParams setObject:(id)kSecMatchLimitAll forKey:(id)kSecMatchLimit];
CFArrayRef certificates;
certificates = nil;
SecItemCopyMatching((CFDictionaryRef) secItemCopyCertificatesParams, (CFTypeRef*) &certificates);
if (certificates != nil && CFGetTypeID(certificates) == CFArrayGetTypeID()) {
SecTrustSetAnchorCertificates(trustRef, certificates);
SecTrustSetAnchorCertificatesOnly(trustRef, NO);
} else {
// set empty array as own anchor certificate so system anchos certificates are used too!
SecTrustSetAnchorCertificates(trustRef, (CFArrayRef) [NSArray array]);
SecTrustSetAnchorCertificatesOnly(trustRef, NO);
}
SecTrustResultType result;
OSStatus trustEvalStatus = SecTrustEvaluate(trustRef, &result);
if (trustEvalStatus == errSecSuccess) {
if (result == kSecTrustResultConfirm || result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) {
// evaluation OK
[challenge.sender useCredential:[NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}
else {
// evaluation failed
// ask user to add certificate to keychain
}
}
else {
// evaluation failed - cancel authentication
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
Hope this will help someone.
It may be a server certificate problem....
Check here, I solved my kSecTrustResultRecoverableTrustFailure problem, adding subjectAltName = DNS:example.com into openssl config file, specifically in server key generation...
If you are not using openssl to generate it, I'm sorry but I can help you.. Anyway if you want to use openssl, here is a good tutorial to generate those keys and sign then with your own root certificate authority.
From this tutorial, I just changed my openssl server config file to:
[ server ]
basicConstraints = critical,CA:FALSE
keyUsage = digitalSignature, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
nsCertType = server
subjectAltName = IP:10.0.1.5,DNS:office.totendev.com
Hope it helps !

Resources