#include "nameconstraints.h"
#include <AssertMacros.h>
#include <utilities/SecCFWrappers.h>
#include <Security/SecCertificateInternal.h>
#include <securityd/SecPolicyServer.h>
#include <libDER/asn1Types.h>
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 subtreeName_with_wildcard = NULL;
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);
subtreeName_with_wildcard = CFStringCreateWithFormat(kCFAllocatorDefault,
NULL,
CFSTR("*.%s"),
CFStringGetCStringPtr(subtreeName_str,
kCFStringEncodingUTF8));
require_quiet(subtreeName_with_wildcard, out);
if (SecDNSMatch(certName_str, subtreeName_str) || SecDNSMatch(certName_str, subtreeName_with_wildcard)) {
result = true;
}
out:
CFReleaseNull(certName_str) ;
CFReleaseNull(subtreeName_str);
CFReleaseNull(subtreeName_with_wildcard);
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;
} 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) {
secdebug("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 bool nc_compare_subject_to_subtrees(CFDataRef subject, CFArrayRef subtrees) {
if (isEmptySubject(subject))
return true;
CFIndex num_trees = CFArrayGetCount(subtrees);
CFRange range = { 0, num_trees };
const DERItem subject_der = { (unsigned char *)CFDataGetBytePtr(subject), CFDataGetLength(subject) };
match_t match = { false, false };
nc_match_context_t context = {GNT_DirectoryName, &subject_der, &match};
CFArrayApplyFunction(subtrees, range, nc_decode_and_compare_subtree, &context);
if (!match.present) return true;
else return match.isMatch;
}
static void nc_compare_RFC822Name_to_subtrees(const void *value, void *context) {
CFStringRef rfc822Name = value;
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 };
const DERItem addr = { (unsigned char *)CFStringGetCStringPtr(rfc822Name, kCFStringEncodingUTF8),
CFStringGetLength(rfc822Name) };
nc_match_context_t match_context = {GNT_RFC822Name, &addr, &match};
CFArrayApplyFunction(subtrees, range, nc_decode_and_compare_subtree, &match_context);
if (match.present && san_context->match) {
san_context->match->present = true;
san_context->match->isMatch &= match.isMatch;
}
}
}
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);
if (match.present && san_context->match) {
san_context->match->present = true;
san_context->match->isMatch &= match.isMatch;
}
return errSecSuccess;
}
return errSecInvalidCertificate;
}
OSStatus SecNameContraintsMatchSubtrees(SecCertificateRef certificate, CFArrayRef subtrees, bool *matched) {
CFDataRef subject = NULL;
OSStatus status = errSecSuccess;
CFArrayRef rfc822Names = NULL;
require_action_quiet(subject = SecCertificateCopySubjectSequence(certificate),
out,
status = errSecInvalidCertificate);
const DERItem *subjectAltNames = SecCertificateGetSubjectAltName(certificate);
require_action_quiet(!isEmptySubject(subject) || subjectAltNames, out, *matched = false);
require_action_quiet(nc_compare_subject_to_subtrees(subject,subtrees), out, *matched = false);
match_t san_match = { false, true };
nc_san_match_context_t san_context = {subtrees, &san_match};
if (!subjectAltNames) {
rfc822Names = SecCertificateCopyRFC822Names(certificate);
require_action_quiet(rfc822Names, out, *matched = true);
CFRange range = { 0 , CFArrayGetCount(rfc822Names) };
CFArrayApplyFunction(rfc822Names, range, nc_compare_RFC822Name_to_subtrees, &san_context);
if (san_match.present && !san_match.isMatch) {
*matched = false;
}
else {
*matched = true;
}
}
else {
status = SecCertificateParseGeneralNames(subjectAltNames,
&san_context,
nc_compare_subjectAltName_to_subtrees);
if((status != errSecSuccess) || (san_match.present && !san_match.isMatch)) {
*matched = false;
}
else {
*matched = true;
}
}
out:
CFReleaseNull(subject);
CFReleaseNull(rfc822Names);
return status;
}
static void nc_intersect_tree_with_subtrees (const void *value, void *context) {
CFDataRef new_subtree = value;
CFMutableArrayRef *existing_subtrees = context;
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;
switch (general_name_content.tag) {
case ASN1_CONTEXT_SPECIFIC | 2: {
gnType = GNT_DNSName;
break;
}
case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 4: {
gnType = GNT_DirectoryName;
break;
}
default: {
CFArrayAppendValue(*existing_subtrees, new_subtree);
return;
}
}
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;
}
}
if (subtreeIX == num_existing_subtrees) {
CFArrayAppendValue(*existing_subtrees, new_subtree);
}
else {
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 };
CFArrayApplyFunction(subtrees_new, range, nc_intersect_tree_with_subtrees, &subtrees_state);
}