We are tasked with determining inside our app whether or not a specific configuration profile is installed on an iOS device. There exists no public API to check which configuration profiles are installed on a device, but this article http://blog.markhorgan.com/?p=701 describes a workaround.
Basically one includes a root CA inside the configuration profile and signs with this root CA a second certificate. This second certificate gets bundled in the app and then a trust evaluation of this second certificate in the app will reveal whether or not the root CA is present (and this means that the configuration profile is installed).
Now, we are not able to tamper with our configuration profile in question (because it is installed on many devices already), but it does already include 2 certificates which we do have access to:
- OurRootCA (issued by: OurRootCA)
- OurIntermediateCA (issued by: OurRootCA)
unfortunately, OurRootCA is not only included in the configuration profile in question, but also in other configuration profiles (so a check with this method could yield false positives), but fortunately OurIntermediateCA is solely used in the configurationProfile we want to check for.
Unfortunately, for some reasons, the tests fails (it always returns kSecTrustResultRecoverableTrustFailure) if performed with a test SSL certificate that was issued by OurIntermediateCA (it works though with a test SSL certificate that was issued by OurRootCA).
Here's the validation code:
//NSString* certPath = [[NSBundle mainBundle] pathForResource:@"TestCertificateSignedByRoot" ofType:@"cer"]; //with this cert, the validation works
NSString* certPath = [[NSBundle mainBundle] pathForResource:@"TestCertificateSignedByIntermediate" ofType:@"cer"];
NSData* certData = [NSData dataWithContentsOfFile:certPath];
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) certData);
SecPolicyRef policy = SecPolicyCreateBasicX509();
SecTrustRef trust;
OSStatus err = SecTrustCreateWithCertificates((__bridge CFArrayRef) [NSArray arrayWithObject:(__bridge id)cert], policy, &trust);
// set trust anchor certificates to empty array as advised in http://stackoverflow.com/questions/7900896/sectrustevaluate-always-returns-ksectrustresultrecoverabletrustfailure-with-secp
SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef) [NSArray array]);
SecTrustSetAnchorCertificatesOnly(trust, NO);
SecTrustResultType trustResult = -1;
err = SecTrustEvaluate(trust, &trustResult);
CFRelease(trust);
CFRelease(policy);
CFRelease(cert);
switch(trustResult) {
case kSecTrustResultUnspecified:
case kSecTrustResultProceed: {
return YES;
// this is the result if TestCertificateSignedByRoot is used
break;
}
case kSecTrustResultRecoverableTrustFailure: {
return NO;
// this is the result if TestCertificateSignedByIntermediate is used
break;
}
default: {
return NO;
break;
}
}
So, the technique seems to work - the (self-signed) rootCA in the users keychain is used to validate our test certificate. But if the intermediate CA is checked for, the test always returns kSecTrustResultRecoverableTrustFailure. What could be the problem here and how can it be debugged?