What is the proper way to check the basic constraints of a certificate ? Below is the code I am currently using to mimic the icons show in the Keychain (the reason for below is that while we have a
SFChooseIdentityPanel * identityPanel = [SFChooseIdentityPanel sharedChooseIdentityPanel];
the equivalent for selecting a CA or a Host/Leaf cert does not exist. And that is useful when setting up/locking down SSL connections.
Unfortunately - I cannot find the OID strings in the header files to cleanly extract the CA:TRUE or false form a cert (or am using the API in the wrong way).
So the questions are
How do I cleanly check for CA:TRUE - while below works - I can imagine that it would get foiled by a malformed cert with text strings in the right places.
Secondly - I am using a heuristic of Issuer==Subject to detect self signed. Is there a cleaner way to do this ?
Finally - from trial and error - below seems to mimic apple her choices in the keychain - but the docs are rather hard to understand. Does kSecTrustResultProceed really mean that the user has set an override and kSecTrustResultUnspecified that in fact the trust is specified by the system basic trust ? While it 'work's - I cannot quite understand the exact interpretation of the docs.
Thanks a lot. Code below.
Dw.
@implementation NSImage (CertificateSelectionPanelExtensions)
+(NSImage *)iconForCertificate:(SecCertificateRef)certificateRef small:(BOOL)isSmall
{
BOOL isCA = FALSE, isInvalid = TRUE, isUserTrust = FALSE;
NSString * issuer = nil, * subject = nil;
const void *keys[] = { kSecOIDX509V1SubjectName, kSecOIDX509V1IssuerName, kSecOIDExtendedKeyUsage, kSecOIDBasicConstraints };
CFArrayRef keySelection = CFArrayCreate(NULL, keys , sizeof(keys)/sizeof(keys[0]), &kCFTypeArrayCallBacks);
CFDictionaryRef vals = SecCertificateCopyValues(certificateRef, keySelection, NULL);
CFArrayRef values;
CFDictionaryRef dict;
dict = CFDictionaryGetValue(vals, kSecOIDBasicConstraints );
values = dict ? CFDictionaryGetValue(dict, kSecPropertyKeyValue) : NULL;
if (values) {
for(int i = 0; i < CFArrayGetCount(values); i++) {
CFDictionaryRef subDict = CFArrayGetValueAtIndex(values, i);
// We cannot find OID defines for the CA - so rely on the lower libraries to give us a string
// of sorts. Not a good idea - as now this code can be foiled by a actual string.
//
NSString *k = [NSString stringWithFormat:@"%@", CFDictionaryGetValue(subDict, kSecPropertyKeyLabel)];
NSString *v = [NSString stringWithFormat:@"%@", CFDictionaryGetValue(subDict, kSecPropertyKeyValue)];
if ([@"Certificate Authority" isEqualToString:k] && [@"Yes" isEqualToString:v]) {
isCA = TRUE;
}
}
};
// Fall back on a simple self-sign check if there where no kSecOIDBasicConstraints.
// set on the cert. Note that it is a DN is equal check - in some cases
// doing a 509v3 Subject/Authority Key Identifier may be better ?? XXXX
//
if (!isCA && !values) {
dict = CFDictionaryGetValue(vals, kSecOIDX509V1SubjectName);
values = dict ? CFDictionaryGetValue(dict, kSecPropertyKeyValue) : NULL;
subject = [NSString stringWithFormat:@"%@", values];
dict = CFDictionaryGetValue(vals, kSecOIDX509V1IssuerName);
values = dict ? CFDictionaryGetValue(dict, kSecPropertyKeyValue) : NULL;
issuer = [NSString stringWithFormat:@"%@", values];
// Crap way of secondgessing CA ness.
if ([issuer isEqualToString:subject])
isCA = TRUE;
};
SecPolicyRef policy = SecPolicyCreateBasicX509(); // SecPolicyCreateSSL(YES,nil);
CFArrayRef chain = CFArrayCreate(NULL, (const void**)(&certificateRef), 1, NULL);
SecTrustRef trustRef;
SecTrustCreateWithCertificates(chain, policy, &trustRef);
SecTrustResultType result;
SecTrustEvaluate (trustRef, &result);
if(result == kSecTrustResultProceed) {
isUserTrust = TRUE;
isInvalid = FALSE;
} else
if (result == kSecTrustResultUnspecified)
isInvalid = FALSE;
CFRelease(trustRef);
CFRelease(chain);
// Images as per /System/Library/Frameworks/SecurityInterface.framework/Versions/A/Resources
// Cert <Small | Large> <Personal | Root> [_Invalid | _UserTrust ]
//
return [NSImage imageNamed:[NSString stringWithFormat:@"Cert%@%@%@",
isSmall ? @"Small" : @"Large",
isCA ? @"Root" : @"Personal",
isInvalid ? @"_Invalid" : (isUserTrust ? @"_UserTrust" : @"")]];
}
@end
The basicConstraints extension is defined in X.509 as follows:
basicConstraints EXTENSION ::= {
SYNTAX BasicConstraintsSyntax
IDENTIFIED BY id-ce-basicConstraints }
BasicConstraintsSyntax ::= SEQUENCE {
cA BOOLEAN DEFAULT FALSE,
pathLenConstraint INTEGER (0..MAX) OPTIONAL }
This, in turn, is encoded according to the Distinguished Encoding Rules (X.690). The individual parts of the BasicConstraintsSyntax
sequence do not have their own OIDs.
I wouldn’t claim to be an expert, but I don’t think there is a problem with the test for the cA
flag that you are doing. I don’t think the [NSString stringWithFormat:@"%@", ...]
part is necessary, mind.
As for checking for self-signed certificates, it seems not unreasonable to test the subject and issuer names; obviously that really tells you whether the certificate claims to be self-signed, and to actually test that it is you’d need to check the signature yourself (whether that’s something you want to do, I don’t know). FYI, on the topic of the key identifiers, according to RFC3280, in the specific case of a self-signed certificate, the authority key identifier can be omitted, so a certificate without an authority key identifier might be an indication that the certificate was self-signed, but it’s entirely possible for someone to deliberately issue a malformed certificate without authority key identifier.
The final question appears to be cleared up by looking at the docs, which indicate that the values mean roughly what you say.
The other thing worth saying is that there is code out there that can help with this kind of thing; for instance, Jens Alfke’s MYCrypto library
According to the X.509 Style Guide, it’s possible for the subject name to be empty, in which case you would need to look at the subjectAltName extension instead. It’s very unlikely that this would break a test for self-signed certificates that relied on comparing the subject and issuer names — it seems unreasonable to issue a certificate to yourself providing a DN for the issuer name but then leaving the subject name empty. However, it’s worth bearing in mind.