How to check x.509 revocation using SecPolicyCreateRevocation in iOS 7 - ios

I've been trying to check X.509 certificates revocation status in iOS 7.0 using both OCSP and CRL in different moments and the evaluation returns kSecTrustResultUnspecified (that means the certificate is to be trusted) without actually checking with OCSP or CRL sources, as long as I pass all certificates in the chain. I've put any code I think is relevant below, please help!
Thanks!
PS: ocspOnly and crlOnly are booleans that indicate if any of those revocation checking methods are to be used exclusively; certs is an NSArray that contains all certificates in chain except anchor certificates; anchor certificates are set properly before.
int evaluationMethod = kSecRevocationRequirePositiveResponse;
if (ocspOnly) {
evaluationMethod |= kSecRevocationOCSPMethod;
} else if (crlOnly) {
evaluationMethod |= kSecRevocationCRLMethod;
} else {
evaluationMethod |= kSecRevocationUseAnyAvailableMethod;
}
if ((status = SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, SecPolicyCreateRevocation(evaluationMethod), &trust)) != errSecSuccess) {
NSLog(#"Failed to create trust with certificate and policy: %ld", status);
return NO;
}
if ((status = SecTrustSetNetworkFetchAllowed(trust, YES)) != errSecSuccess) {
NSLog(#"Failed to activate network fetch: %ld", status);
}
status = SecTrustEvaluate(trust, &trustResult);
if (status != errSecSuccess) {
NSLog(#"Failed to evaluate trust: %ld", status);
return NO;
}
if (trustResult == kSecTrustResultProceed || trustResult == kSecTrustResultUnspecified)
return YES;
return NO;
PS-2: This question was also asked in iOS Developer Forums here.

Related

iOS checking if root certificate is installed *and* trusted

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

Certificate with password

I consume a web services client.
I have a certificate with password. I can retrieve the certificate from my store and i can use it in InternetSetOption().
But I don't know how to send the password with the certificate.
Here is an example:
LPWSTR pswzFirstCert = L"xxxxx";// Subject of the first certificate.
LPWSTR pPassword = L"yyyyyy";// Spassword
HCERTSTORE hSystemStore; // The system store handle
PCCERT_CONTEXT pDesiredCert = NULL;
hSystemStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL, CERT_SYSTEM_STORE_CURRENT_USER,L"MY");
if(hSystemStore == NULL)
{
ShowMessage("Not Opened the MY system store.");
return;
}
// Get a certificate that has the string "Full Test Cert" in its subject.
if(pDesiredCert)
CertFreeCertificateContext(pDesiredCert);
pDesiredCert=CertFindCertificateInStore(hSystemStore, MY_ENCODING_TYPE, 0, CERT_FIND_SUBJECT_STR, pswzFirstCert, NULL);
if(pDesiredCert == NULL)
{
ShowMessage("The certificate was not found.");
return;
}
if(hSystemStore)
CertCloseStore(hSystemStore, CERT_CLOSE_STORE_CHECK_FLAG);
if( InternetSetOption (Data, INTERNET_OPTION_CLIENT_CERT_CONTEXT, (LPVOID)pDesiredCert, sizeof (CERT_CONTEXT) ) == FALSE )
ShowMessage("InternetSetOption failed with error.");
if( InternetSetOption (Data, INTERNET_OPTION_PASSWORD, (LPVOID)pPassword, sizeof (pPassword) ) == FALSE )
ShowMessage("InternetSetOption failed with error.");
if(pDesiredCert)
CertFreeCertificateContext(pDesiredCert);

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

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.

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