#include "nameconstraints.h"
#include <AssertMacros.h>
#include <utilities/SecCFWrappers.h>
#include <Security/SecCertificateInternal.h>
#include "trust/trustd/SecPolicyServer.h"
#include <libDER/asn1Types.h>
#include <libDER/oids.h>
static bool SecDNSNameConstraintsMatch(CFStringRef DNSName, CFStringRef constraint) {
CFIndex clength = CFStringGetLength(constraint);
CFIndex dlength = CFStringGetLength(DNSName);
if (dlength < clength) return false;
if ((dlength != clength) && ('.' != CFStringGetCharacterAtIndex(constraint, 0)) &&
('.' != CFStringGetCharacterAtIndex(DNSName, dlength - clength -1))) {
return false;
}
CFRange compareRange = { dlength - clength, clength};
if (!CFStringCompareWithOptions(DNSName, constraint, compareRange, kCFCompareCaseInsensitive)) {
return true;
}
return false;
}
static bool SecURIMatch(CFStringRef URI, CFStringRef hostname) {
bool result = false;
CFStringRef URI_hostname = NULL;
CFCharacterSetRef port_or_path_separator = NULL;
CFRange URI_scheme = CFStringFind(URI, CFSTR("://"), 0);
require_quiet(URI_scheme.location != kCFNotFound, out);
CFRange URI_hostname_range = { URI_scheme.location + URI_scheme.length,
CFStringGetLength(URI) - URI_scheme.location - URI_scheme.length };
port_or_path_separator = CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault, CFSTR(":/"));
CFRange separator = {kCFNotFound, 0};
if(CFStringFindCharacterFromSet(URI, port_or_path_separator, URI_hostname_range, 0, &separator)) {
URI_hostname_range.length -= (CFStringGetLength(URI) - separator.location);
}
URI_hostname = CFStringCreateWithSubstring(kCFAllocatorDefault, URI, URI_hostname_range);
require_quiet('.' != CFStringGetCharacterAtIndex(URI_hostname, 0), out);
CFIndex ulength = CFStringGetLength(URI_hostname);
CFIndex hlength = CFStringGetLength(hostname);
require_quiet(ulength >= hlength, out);
CFRange compare_range = { 0, hlength };
if ('.' == CFStringGetCharacterAtIndex(hostname, 0)) {
compare_range.location = ulength - hlength;
}
if(kCFCompareEqualTo == CFStringCompareWithOptions(URI_hostname,
hostname,
compare_range,
kCFCompareCaseInsensitive)) {
result = true;
}
out:
CFReleaseNull(port_or_path_separator);
CFReleaseNull(URI_hostname);
return result;
}
static bool SecRFC822NameMatch(CFStringRef emailAddress, CFStringRef constraint) {
CFRange mailbox_range = CFStringFind(constraint,CFSTR("@"),0);
if (mailbox_range.location != kCFNotFound) {
if (!CFStringCompare(emailAddress, constraint, kCFCompareCaseInsensitive)) {
return true;
}
else return false;
}
mailbox_range = CFStringFind(emailAddress, CFSTR("@"), 0);
require_quiet(mailbox_range.location != kCFNotFound, out);
CFRange hostname_range = {mailbox_range.location + 1,
CFStringGetLength(emailAddress) - mailbox_range.location - 1 };
if ('.' != CFStringGetCharacterAtIndex(constraint, 0)) {
if (!CFStringCompareWithOptions(emailAddress, constraint, hostname_range, kCFCompareCaseInsensitive)) {
return true;
}
else return false;
}
require_quiet('.' != CFStringGetCharacterAtIndex(emailAddress, mailbox_range.location +1), out);
if (CFStringHasSuffix(emailAddress, constraint)) {
return true;
}
out:
return false;
}
static bool nc_compare_directoryNames(const DERItem *certName, const DERItem *subtreeName) {
DERDecodedInfo certName_content;
require_noerr_quiet(DERDecodeItem(certName, &certName_content), out);
DERDecodedInfo subtreeName_content;
require_noerr_quiet(DERDecodeItem(subtreeName, &subtreeName_content), out);
if (certName->length > subtreeName->length) {
if(0 == memcmp(certName_content.content.data,
subtreeName_content.content.data,
subtreeName_content.content.length)) {
return true;
}
}
out:
return false;
}
static bool nc_compare_DNSNames(const DERItem *certName, const DERItem *subtreeName) {
bool result = false;
CFStringRef certName_str = CFStringCreateWithBytes(kCFAllocatorDefault,
certName->data, certName->length,
kCFStringEncodingUTF8, FALSE);
CFStringRef subtreeName_str = CFStringCreateWithBytes(kCFAllocatorDefault,
subtreeName->data, subtreeName->length,
kCFStringEncodingUTF8, FALSE);
require_quiet(certName_str, out);
require_quiet(subtreeName_str, out);
if (SecDNSNameConstraintsMatch(certName_str, subtreeName_str)) {
result = true;
}
out:
CFReleaseNull(certName_str) ;
CFReleaseNull(subtreeName_str);
return result;
}
static bool nc_compare_URIs(const DERItem *certName, const DERItem *subtreeName) {
bool result = false;
CFStringRef certName_str = CFStringCreateWithBytes(kCFAllocatorDefault,
certName->data, certName->length,
kCFStringEncodingUTF8, FALSE);
CFStringRef subtreeName_str = CFStringCreateWithBytes(kCFAllocatorDefault,
subtreeName->data, subtreeName->length,
kCFStringEncodingUTF8, FALSE);
require_quiet(certName_str, out);
require_quiet(subtreeName_str, out);
if (SecURIMatch(certName_str, subtreeName_str)) {
result = true;
}
out:
CFReleaseNull(certName_str);
CFReleaseNull(subtreeName_str);
return result;
}
static bool nc_compare_RFC822Names(const DERItem *certName, const DERItem *subtreeName) {
bool result = false;
CFStringRef certName_str = CFStringCreateWithBytes(kCFAllocatorDefault,
certName->data, certName->length,
kCFStringEncodingUTF8, FALSE);
CFStringRef subtreeName_str = CFStringCreateWithBytes(kCFAllocatorDefault,
subtreeName->data, subtreeName->length,
kCFStringEncodingUTF8, FALSE);
require_quiet(certName_str, out);
require_quiet(subtreeName_str, out);
if (SecRFC822NameMatch(certName_str, subtreeName_str)) {
result = true;
}
out:
CFReleaseNull(certName_str);
CFReleaseNull(subtreeName_str);
return result;
}
static bool nc_compare_IPAddresses(const DERItem *certAddr, const DERItem *subtreeAddr) {
bool result = false;
require_quiet((subtreeAddr->length == 8) || (subtreeAddr->length == 32), out);
require_quiet((certAddr->length == 4) || (certAddr->length ==16), out);
require_quiet(subtreeAddr->length == 2*certAddr->length, out);
DERByte * mask = subtreeAddr->data + certAddr->length;
for (DERSize i = 0; i < certAddr->length; i++) {
if((subtreeAddr->data[i] & mask[i]) != (certAddr->data[i] & mask[i])) {
return false;
}
}
return true;
out:
return result;
}
typedef struct {
bool present;
bool isMatch;
} match_t;
typedef struct {
const SecCEGeneralNameType gnType;
const DERItem *cert_item;
match_t *match;
} nc_match_context_t;
typedef struct {
const CFArrayRef subtrees;
match_t *match;
bool permit;
} nc_san_match_context_t;
static OSStatus nc_compare_subtree(void *context, SecCEGeneralNameType gnType, const DERItem *generalName) {
nc_match_context_t *item_context = context;
if (item_context && gnType == item_context->gnType
&& item_context->match && item_context->cert_item) {
item_context->match->present = true;
switch (gnType) {
case GNT_DirectoryName: {
item_context->match->isMatch |= nc_compare_directoryNames(item_context->cert_item, generalName);
return errSecSuccess;
}
case GNT_DNSName: {
item_context->match->isMatch |= nc_compare_DNSNames(item_context->cert_item, generalName);
return errSecSuccess;
}
case GNT_URI: {
item_context->match->isMatch |= nc_compare_URIs(item_context->cert_item, generalName);
return errSecSuccess;
}
case GNT_RFC822Name: {
item_context->match->isMatch |= nc_compare_RFC822Names(item_context->cert_item, generalName);
return errSecSuccess;
}
case GNT_IPAddress: {
item_context->match->isMatch |= nc_compare_IPAddresses(item_context->cert_item, generalName);
return errSecSuccess;
}
default: {
return errSecInvalidCertificate;
}
}
}
return errSecInvalidCertificate;
}
static void nc_decode_and_compare_subtree(const void *value, void *context) {
CFDataRef subtree = value;
nc_match_context_t *match_context = context;
if (subtree) {
const DERItem general_name = { (unsigned char *)CFDataGetBytePtr(subtree), CFDataGetLength(subtree) };
DERDecodedInfo general_name_content;
require_noerr_quiet(DERDecodeItem(&general_name, &general_name_content),out);
OSStatus status = SecCertificateParseGeneralNameContentProperty(general_name_content.tag,
&general_name_content.content,
match_context,
nc_compare_subtree);
if (status == errSecInvalidCertificate) {
secnotice("policy","can't parse general name or not a type we support");
}
}
out:
return;
}
static bool isEmptySubject(CFDataRef subject) {
const DERItem subject_der = { (unsigned char *)CFDataGetBytePtr(subject), CFDataGetLength(subject) };
DERDecodedInfo subject_content;
require_noerr_quiet(DERDecodeItem(&subject_der, &subject_content), out);
if (subject_content.content.length) return false;
out:
return true;
}
static void update_match(bool permit, match_t *input_match, match_t *output_match) {
if (!input_match || !output_match) {
return;
}
if (input_match->present) {
output_match->present = true;
if (permit) {
output_match->isMatch &= input_match->isMatch;
} else {
output_match->isMatch |= input_match->isMatch;
}
}
}
static void nc_compare_RFC822Name_to_subtrees(const void *value, void *context) {
CFStringRef rfc822Name = (CFStringRef)value;
char *rfc822NameString = NULL;
nc_san_match_context_t *san_context = context;
CFArrayRef subtrees = NULL;
if (san_context) {
subtrees = san_context->subtrees;
}
if (subtrees) {
CFIndex num_trees = CFArrayGetCount(subtrees);
CFRange range = { 0, num_trees };
match_t match = { false, false };
rfc822NameString = CFStringToCString(rfc822Name);
if (!rfc822NameString) { return; }
const DERItem addr = { (unsigned char *)rfc822NameString,
CFStringGetLength(rfc822Name) };
nc_match_context_t match_context = {GNT_RFC822Name, &addr, &match};
CFArrayApplyFunction(subtrees, range, nc_decode_and_compare_subtree, &match_context);
free(rfc822NameString);
update_match(san_context->permit, &match, san_context->match);
}
}
static void nc_compare_subject_to_subtrees(SecCertificateRef certificate, CFArrayRef subtrees,
bool permit, match_t *match) {
CFDataRef subject = SecCertificateCopySubjectSequence(certificate);
if (!subject || isEmptySubject(subject)) {
CFReleaseNull(subject);
return;
}
CFIndex num_trees = CFArrayGetCount(subtrees);
CFRange range = { 0, num_trees };
match_t x500_match = { false, false };
const DERItem subject_der = { (unsigned char *)CFDataGetBytePtr(subject), CFDataGetLength(subject) };
nc_match_context_t context = {GNT_DirectoryName, &subject_der, &x500_match};
CFArrayApplyFunction(subtrees, range, nc_decode_and_compare_subtree, &context);
CFReleaseNull(subject);
update_match(permit, &x500_match, match);
match_t email_match = { false, permit };
CFArrayRef rfc822Names = SecCertificateCopyRFC822NamesFromSubject(certificate);
if (rfc822Names) {
CFRange emailRange = { 0, CFArrayGetCount(rfc822Names) };
nc_san_match_context_t emailContext = { subtrees, &email_match, permit };
CFArrayApplyFunction(rfc822Names, emailRange, nc_compare_RFC822Name_to_subtrees, &emailContext);
}
CFReleaseNull(rfc822Names);
update_match(permit, &email_match, match);
}
static OSStatus nc_compare_subjectAltName_to_subtrees(void *context, SecCEGeneralNameType gnType, const DERItem *generalName) {
nc_san_match_context_t *san_context = context;
CFArrayRef subtrees = NULL;
if (san_context) {
subtrees = san_context->subtrees;
}
if (subtrees) {
CFIndex num_trees = CFArrayGetCount(subtrees);
CFRange range = { 0, num_trees };
match_t match = { false, false };
nc_match_context_t match_context = {gnType, generalName, &match};
CFArrayApplyFunction(subtrees, range, nc_decode_and_compare_subtree, &match_context);
update_match(san_context->permit, &match, san_context->match);
return errSecSuccess;
}
return errSecInvalidCertificate;
}
OSStatus SecNameContraintsMatchSubtrees(SecCertificateRef certificate, CFArrayRef subtrees, bool *matched, bool permit) {
CFDataRef subject = NULL;
OSStatus status = errSecSuccess;
require_action_quiet(subject = SecCertificateCopySubjectSequence(certificate),
out,
status = errSecInvalidCertificate);
const DERItem *subjectAltNames = SecCertificateGetSubjectAltName(certificate);
require_action_quiet(!isEmptySubject(subject) || subjectAltNames, out, status = errSecInvalidCertificate);
match_t subject_match = { false, permit };
nc_compare_subject_to_subtrees(certificate, subtrees, permit, &subject_match);
match_t san_match = { false, permit };
nc_san_match_context_t san_context = {subtrees, &san_match, permit};
if (subjectAltNames) {
status = SecCertificateParseGeneralNames(subjectAltNames,
&san_context,
nc_compare_subjectAltName_to_subtrees);
require_action_quiet(status == errSecSuccess, out, *matched = false);
}
if (subject_match.present) {
if (san_match.present &&
((subject_match.isMatch && !san_match.isMatch) ||
(!subject_match.isMatch && san_match.isMatch))) {
*matched = permit ? false : true;
}
else {
*matched = subject_match.isMatch;
}
}
else if (san_match.present) {
*matched = san_match.isMatch;
}
else {
*matched = permit ? true : false;
}
out:
CFReleaseNull(subject);
return status;
}
typedef struct {
CFMutableArrayRef existing_trees;
CFMutableArrayRef trees_to_add;
} nc_intersect_context_t;
static SecCEGeneralNameType nc_gn_type_convert (DERTag tag) {
switch (tag) {
case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 0:
return GNT_OtherName;
case ASN1_CONTEXT_SPECIFIC | 1:
return GNT_RFC822Name;
case ASN1_CONTEXT_SPECIFIC | 2:
return GNT_DNSName;
case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 3:
return GNT_X400Address;
case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 4:
return GNT_DirectoryName;
case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 5:
return GNT_EdiPartyName;
case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 6:
case ASN1_CONTEXT_SPECIFIC | 6:
return GNT_URI;
case ASN1_CONTEXT_SPECIFIC | 7:
return GNT_IPAddress;
case ASN1_CONTEXT_SPECIFIC | 8:
return GNT_RegisteredID;
default:
return GNT_OtherName;
}
}
static void nc_intersect_tree_with_subtrees (const void *value, void *context) {
CFDataRef new_subtree = value;
nc_intersect_context_t *intersect_context = context;
CFMutableArrayRef existing_subtrees = intersect_context->existing_trees;
CFMutableArrayRef trees_to_append = intersect_context->trees_to_add;
if (!new_subtree || !existing_subtrees) return;
const DERItem general_name = { (unsigned char *)CFDataGetBytePtr(new_subtree), CFDataGetLength(new_subtree) };
DERDecodedInfo general_name_content;
if(DR_Success != DERDecodeItem(&general_name, &general_name_content)) return;
SecCEGeneralNameType gnType;
DERItem *new_subtree_item = &general_name_content.content;
gnType = nc_gn_type_convert(general_name_content.tag);
if (!(gnType == GNT_DirectoryName || gnType == GNT_DNSName)) {
CFArrayAppendValue(trees_to_append, new_subtree);
}
CFIndex subtreeIX;
CFIndex num_existing_subtrees = CFArrayGetCount(existing_subtrees);
match_t match = { false, false };
nc_match_context_t match_context = { gnType, new_subtree_item, &match};
for (subtreeIX = 0; subtreeIX < num_existing_subtrees; subtreeIX++) {
CFDataRef candidate_subtree = CFArrayGetValueAtIndex(existing_subtrees, subtreeIX);
const DERItem candidate = { (unsigned char *)CFDataGetBytePtr(candidate_subtree), CFDataGetLength(candidate_subtree) };
DERDecodedInfo candidate_content;
if(DR_Success != DERDecodeItem(&candidate, &candidate_content)) continue;
OSStatus status = SecCertificateParseGeneralNameContentProperty(candidate_content.tag,
&candidate_content.content,
&match_context,
nc_compare_subtree);
if((status == errSecSuccess) && match.present && match.isMatch) {
break;
}
match_t local_match = { false , false };
nc_match_context_t local_match_context = { nc_gn_type_convert(candidate_content.tag),
&candidate_content.content,
&local_match };
status = SecCertificateParseGeneralNameContentProperty(general_name_content.tag,
&general_name_content.content,
&local_match_context,
nc_compare_subtree);
if((status == errSecSuccess) && local_match.present && local_match.isMatch) {
break;
}
}
if (subtreeIX == num_existing_subtrees) {
CFArrayAppendValue(trees_to_append, new_subtree);
}
else if (match.present && match.isMatch) {
CFArraySetValueAtIndex(existing_subtrees, subtreeIX, new_subtree);
}
return;
}
void SecNameConstraintsIntersectSubtrees(CFMutableArrayRef subtrees_state, CFArrayRef subtrees_new) {
assert(subtrees_state);
assert(subtrees_new);
CFIndex num_new_trees = CFArrayGetCount(subtrees_new);
CFRange range = { 0, num_new_trees };
if (!CFArrayGetCount(subtrees_state)) {
CFArrayAppendArray(subtrees_state, subtrees_new, range);
return;
}
CFMutableArrayRef trees_to_append = NULL;
trees_to_append = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
nc_intersect_context_t context = { subtrees_state , trees_to_append };
CFArrayApplyFunction(subtrees_new, range, nc_intersect_tree_with_subtrees, &context);
num_new_trees = CFArrayGetCount(trees_to_append);
if (trees_to_append && num_new_trees) {
range.length = num_new_trees;
CFArrayAppendArray(subtrees_state, trees_to_append, range);
}
CFReleaseNull(trees_to_append);
}