2 * Copyright (c) 2015-2017 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 * nameconstraints.c - rfc5280 section 4.2.1.10 and later name constraints implementation.
28 #include "nameconstraints.h"
29 #include <AssertMacros.h>
30 #include <utilities/SecCFWrappers.h>
31 #include <Security/SecCertificateInternal.h>
32 #include <securityd/SecPolicyServer.h>
33 #include <libDER/asn1Types.h>
34 #include <libDER/oids.h>
36 /* RFC 5280 Section 4.2.1.10:
37 DNS name restrictions are expressed as host.example.com. Any DNS
38 name that can be constructed by simply adding zero or more labels to
39 the left-hand side of the name satisfies the name constraint. For
40 example, www.host.example.com would satisfy the constraint but
41 host1.example.com would not.
43 static bool SecDNSNameConstraintsMatch(CFStringRef DNSName
, CFStringRef constraint
) {
44 CFIndex clength
= CFStringGetLength(constraint
);
45 CFIndex dlength
= CFStringGetLength(DNSName
);
47 if (dlength
< clength
) return false;
49 /* Ensure that character to the left of the constraint in the DNSName is a '.'
50 so that badexample.com does not match example.com, but good.example.com does.
52 if ((dlength
!= clength
) && ('.' != CFStringGetCharacterAtIndex(constraint
, 0)) &&
53 ('.' != CFStringGetCharacterAtIndex(DNSName
, dlength
- clength
-1))) {
57 CFRange compareRange
= { dlength
- clength
, clength
};
59 if (!CFStringCompareWithOptions(DNSName
, constraint
, compareRange
, kCFCompareCaseInsensitive
)) {
66 /* RFC 5280 Section 4.2.1.10:
67 For URIs, the constraint applies to the host part of the name. The
68 constraint MUST be specified as a fully qualified domain name and MAY
69 specify a host or a domain. Examples would be "host.example.com" and
70 ".example.com". When the constraint begins with a period, it MAY be
71 expanded with one or more labels. That is, the constraint
72 ".example.com" is satisfied by both host.example.com and
73 my.host.example.com. However, the constraint ".example.com" is not
74 satisfied by "example.com". When the constraint does not begin with
75 a period, it specifies a host.
77 static bool SecURIMatch(CFStringRef URI
, CFStringRef hostname
) {
79 CFStringRef URI_hostname
= NULL
;
80 CFCharacterSetRef port_or_path_separator
= NULL
;
81 /* URI must have scheme specified */
82 CFRange URI_scheme
= CFStringFind(URI
, CFSTR("://"), 0);
83 require_quiet(URI_scheme
.location
!= kCFNotFound
, out
);
85 /* Remove scheme prefix and port or resource path suffix */
86 CFRange URI_hostname_range
= { URI_scheme
.location
+ URI_scheme
.length
,
87 CFStringGetLength(URI
) - URI_scheme
.location
- URI_scheme
.length
};
88 port_or_path_separator
= CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault
, CFSTR(":/"));
89 CFRange separator
= {kCFNotFound
, 0};
90 if(CFStringFindCharacterFromSet(URI
, port_or_path_separator
, URI_hostname_range
, 0, &separator
)) {
91 URI_hostname_range
.length
-= (CFStringGetLength(URI
) - separator
.location
);
93 URI_hostname
= CFStringCreateWithSubstring(kCFAllocatorDefault
, URI
, URI_hostname_range
);
95 /* Hostname in URI must not begin with '.' */
96 require_quiet('.' != CFStringGetCharacterAtIndex(URI_hostname
, 0), out
);
98 CFIndex ulength
= CFStringGetLength(URI_hostname
);
99 CFIndex hlength
= CFStringGetLength(hostname
);
100 require_quiet(ulength
>= hlength
, out
);
101 CFRange compare_range
= { 0, hlength
};
103 /* Allow one or more preceding labels */
104 if ('.' == CFStringGetCharacterAtIndex(hostname
, 0)) {
105 compare_range
.location
= ulength
- hlength
;
108 if(kCFCompareEqualTo
== CFStringCompareWithOptions(URI_hostname
,
111 kCFCompareCaseInsensitive
)) {
116 CFReleaseNull(port_or_path_separator
);
117 CFReleaseNull(URI_hostname
);
121 /* RFC 5280 Section 4.2.1.10:
122 A name constraint for Internet mail addresses MAY specify a
123 particular mailbox, all addresses at a particular host, or all
124 mailboxes in a domain. To indicate a particular mailbox, the
125 constraint is the complete mail address. For example,
126 "root@example.com" indicates the root mailbox on the host
127 "example.com". To indicate all Internet mail addresses on a
128 particular host, the constraint is specified as the host name. For
129 example, the constraint "example.com" is satisfied by any mail
130 address at the host "example.com". To specify any address within a
131 domain, the constraint is specified with a leading period (as with
134 static bool SecRFC822NameMatch(CFStringRef emailAddress
, CFStringRef constraint
) {
135 CFRange mailbox_range
= CFStringFind(constraint
,CFSTR("@"),0);
137 /* Constraint specifies a particular mailbox. Perform full comparison. */
138 if (mailbox_range
.location
!= kCFNotFound
) {
139 if (!CFStringCompare(emailAddress
, constraint
, kCFCompareCaseInsensitive
)) {
145 mailbox_range
= CFStringFind(emailAddress
, CFSTR("@"), 0);
146 require_quiet(mailbox_range
.location
!= kCFNotFound
, out
);
147 CFRange hostname_range
= {mailbox_range
.location
+ 1,
148 CFStringGetLength(emailAddress
) - mailbox_range
.location
- 1 };
150 /* Constraint specificies a particular host. Compare hostname of address. */
151 if ('.' != CFStringGetCharacterAtIndex(constraint
, 0)) {
152 if (!CFStringCompareWithOptions(emailAddress
, constraint
, hostname_range
, kCFCompareCaseInsensitive
)) {
158 /* Constraint specificies a domain. Match hostname of address to domain name. */
159 require_quiet('.' != CFStringGetCharacterAtIndex(emailAddress
, mailbox_range
.location
+1), out
);
160 if (CFStringHasSuffix(emailAddress
, constraint
)) {
168 static bool nc_compare_directoryNames(const DERItem
*certName
, const DERItem
*subtreeName
) {
169 /* Get content of certificate name and subtree name */
170 DERDecodedInfo certName_content
;
171 require_noerr_quiet(DERDecodeItem(certName
, &certName_content
), out
);
173 DERDecodedInfo subtreeName_content
;
174 require_noerr_quiet(DERDecodeItem(subtreeName
, &subtreeName_content
), out
);
176 if (certName
->length
> subtreeName
->length
) {
177 if(0 == memcmp(certName_content
.content
.data
,
178 subtreeName_content
.content
.data
,
179 subtreeName_content
.content
.length
)) {
188 static bool nc_compare_DNSNames(const DERItem
*certName
, const DERItem
*subtreeName
) {
190 CFStringRef certName_str
= CFStringCreateWithBytes(kCFAllocatorDefault
,
191 certName
->data
, certName
->length
,
192 kCFStringEncodingUTF8
, FALSE
);
193 CFStringRef subtreeName_str
= CFStringCreateWithBytes(kCFAllocatorDefault
,
194 subtreeName
->data
, subtreeName
->length
,
195 kCFStringEncodingUTF8
, FALSE
);
196 require_quiet(certName_str
, out
);
197 require_quiet(subtreeName_str
, out
);
199 if (SecDNSNameConstraintsMatch(certName_str
, subtreeName_str
)) {
204 CFReleaseNull(certName_str
) ;
205 CFReleaseNull(subtreeName_str
);
209 static bool nc_compare_URIs(const DERItem
*certName
, const DERItem
*subtreeName
) {
211 CFStringRef certName_str
= CFStringCreateWithBytes(kCFAllocatorDefault
,
212 certName
->data
, certName
->length
,
213 kCFStringEncodingUTF8
, FALSE
);
214 CFStringRef subtreeName_str
= CFStringCreateWithBytes(kCFAllocatorDefault
,
215 subtreeName
->data
, subtreeName
->length
,
216 kCFStringEncodingUTF8
, FALSE
);
217 require_quiet(certName_str
, out
);
218 require_quiet(subtreeName_str
, out
);
220 if (SecURIMatch(certName_str
, subtreeName_str
)) {
225 CFReleaseNull(certName_str
);
226 CFReleaseNull(subtreeName_str
);
230 static bool nc_compare_RFC822Names(const DERItem
*certName
, const DERItem
*subtreeName
) {
232 CFStringRef certName_str
= CFStringCreateWithBytes(kCFAllocatorDefault
,
233 certName
->data
, certName
->length
,
234 kCFStringEncodingUTF8
, FALSE
);
235 CFStringRef subtreeName_str
= CFStringCreateWithBytes(kCFAllocatorDefault
,
236 subtreeName
->data
, subtreeName
->length
,
237 kCFStringEncodingUTF8
, FALSE
);
238 require_quiet(certName_str
, out
);
239 require_quiet(subtreeName_str
, out
);
241 if (SecRFC822NameMatch(certName_str
, subtreeName_str
)) {
246 CFReleaseNull(certName_str
);
247 CFReleaseNull(subtreeName_str
);
251 static bool nc_compare_IPAddresses(const DERItem
*certAddr
, const DERItem
*subtreeAddr
) {
254 /* Verify Subtree Address has correct number of bytes for IP and mask */
255 require_quiet((subtreeAddr
->length
== 8) || (subtreeAddr
->length
== 32), out
);
256 /* Verify Cert Address has correct number of bytes for IP */
257 require_quiet((certAddr
->length
== 4) || (certAddr
->length
==16), out
);
258 /* Verify Subtree Address and Cert Address are the same version */
259 require_quiet(subtreeAddr
->length
== 2*certAddr
->length
, out
);
261 DERByte
* mask
= subtreeAddr
->data
+ certAddr
->length
;
262 for (DERSize i
= 0; i
< certAddr
->length
; i
++) {
263 if((subtreeAddr
->data
[i
] & mask
[i
]) != (certAddr
->data
[i
] & mask
[i
])) {
279 const SecCEGeneralNameType gnType
;
280 const DERItem
*cert_item
;
282 } nc_match_context_t
;
285 const CFArrayRef subtrees
;
288 } nc_san_match_context_t
;
290 static OSStatus
nc_compare_subtree(void *context
, SecCEGeneralNameType gnType
, const DERItem
*generalName
) {
291 nc_match_context_t
*item_context
= context
;
292 if (item_context
&& gnType
== item_context
->gnType
293 && item_context
->match
&& item_context
->cert_item
) {
295 item_context
->match
->present
= true;
297 * We set isMatch such that if there are multiple subtrees of the same type, matching to any one
298 * of them is considered a match.
301 case GNT_DirectoryName
: {
302 item_context
->match
->isMatch
|= nc_compare_directoryNames(item_context
->cert_item
, generalName
);
303 return errSecSuccess
;
306 item_context
->match
->isMatch
|= nc_compare_DNSNames(item_context
->cert_item
, generalName
);
307 return errSecSuccess
;
310 item_context
->match
->isMatch
|= nc_compare_URIs(item_context
->cert_item
, generalName
);
311 return errSecSuccess
;
313 case GNT_RFC822Name
: {
314 item_context
->match
->isMatch
|= nc_compare_RFC822Names(item_context
->cert_item
, generalName
);
315 return errSecSuccess
;
317 case GNT_IPAddress
: {
318 item_context
->match
->isMatch
|= nc_compare_IPAddresses(item_context
->cert_item
, generalName
);
319 return errSecSuccess
;
322 /* If the name form is not supported, reject the certificate. */
323 return errSecInvalidCertificate
;
328 return errSecInvalidCertificate
;
331 static void nc_decode_and_compare_subtree(const void *value
, void *context
) {
332 CFDataRef subtree
= value
;
333 nc_match_context_t
*match_context
= context
;
335 /* convert subtree to DERItem */
336 const DERItem general_name
= { (unsigned char *)CFDataGetBytePtr(subtree
), CFDataGetLength(subtree
) };
337 DERDecodedInfo general_name_content
;
338 require_noerr_quiet(DERDecodeItem(&general_name
, &general_name_content
),out
);
340 OSStatus status
= SecCertificateParseGeneralNameContentProperty(general_name_content
.tag
,
341 &general_name_content
.content
,
344 if (status
== errSecInvalidCertificate
) {
345 secnotice("policy","can't parse general name or not a type we support");
352 static bool isEmptySubject(CFDataRef subject
) {
353 const DERItem subject_der
= { (unsigned char *)CFDataGetBytePtr(subject
), CFDataGetLength(subject
) };
355 /* Get content of certificate name */
356 DERDecodedInfo subject_content
;
357 require_noerr_quiet(DERDecodeItem(&subject_der
, &subject_content
), out
);
358 if (subject_content
.content
.length
) return false;
365 * We update match structs as follows:
366 * 'present' is true if there's any subtree of the same type as any Subject/SAN
367 * 'match' is false if the subtree(s) and Subject(s)/SAN(s) don't match.
368 * Note: the state of 'match' is meaningless without 'present' also being true.
370 static void update_match(bool permit
, match_t
*input_match
, match_t
*output_match
) {
371 if (!input_match
|| !output_match
) {
374 if (input_match
->present
) {
375 output_match
->present
= true;
377 output_match
->isMatch
&= input_match
->isMatch
;
379 output_match
->isMatch
|= input_match
->isMatch
;
384 static void nc_compare_DNSName_to_subtrees(const void *value
, void *context
) {
385 CFStringRef dnsName
= (CFStringRef
)value
;
386 char *dnsNameString
= NULL
;
387 nc_san_match_context_t
*san_context
= context
;
388 CFArrayRef subtrees
= NULL
;
390 subtrees
= san_context
->subtrees
;
393 CFIndex num_trees
= CFArrayGetCount(subtrees
);
394 CFRange range
= { 0, num_trees
};
395 match_t match
= { false, false };
396 dnsNameString
= CFStringToCString(dnsName
);
397 if (!dnsNameString
) { return; }
398 const DERItem name
= { (unsigned char *)dnsNameString
,
399 CFStringGetLength(dnsName
) };
400 nc_match_context_t match_context
= {GNT_DNSName
, &name
, &match
};
401 CFArrayApplyFunction(subtrees
, range
, nc_decode_and_compare_subtree
, &match_context
);
404 update_match(san_context
->permit
, &match
, san_context
->match
);
408 static void nc_compare_IPAddress_to_subtrees(const void *value
, void *context
) {
409 CFDataRef ipAddr
= (CFDataRef
)value
;
410 nc_san_match_context_t
*san_context
= context
;
411 CFArrayRef subtrees
= NULL
;
413 subtrees
= san_context
->subtrees
;
416 CFIndex num_trees
= CFArrayGetCount(subtrees
);
417 CFRange range
= { 0, num_trees
};
418 match_t match
= { false, false };
419 const DERItem addr
= { (unsigned char *)CFDataGetBytePtr(ipAddr
), CFDataGetLength(ipAddr
) };
420 nc_match_context_t match_context
= {GNT_IPAddress
, &addr
, &match
};
421 CFArrayApplyFunction(subtrees
, range
, nc_decode_and_compare_subtree
, &match_context
);
423 update_match(san_context
->permit
, &match
, san_context
->match
);
427 static void nc_compare_RFC822Name_to_subtrees(const void *value
, void *context
) {
428 CFStringRef rfc822Name
= (CFStringRef
)value
;
429 char *rfc822NameString
= NULL
;
430 nc_san_match_context_t
*san_context
= context
;
431 CFArrayRef subtrees
= NULL
;
433 subtrees
= san_context
->subtrees
;
436 CFIndex num_trees
= CFArrayGetCount(subtrees
);
437 CFRange range
= { 0, num_trees
};
438 match_t match
= { false, false };
439 rfc822NameString
= CFStringToCString(rfc822Name
);
440 if (!rfc822NameString
) { return; }
441 const DERItem addr
= { (unsigned char *)rfc822NameString
,
442 CFStringGetLength(rfc822Name
) };
443 nc_match_context_t match_context
= {GNT_RFC822Name
, &addr
, &match
};
444 CFArrayApplyFunction(subtrees
, range
, nc_decode_and_compare_subtree
, &match_context
);
445 free(rfc822NameString
);
447 update_match(san_context
->permit
, &match
, san_context
->match
);
452 static bool certAllowsSSL(SecCertificateRef certificate
) {
453 CFArrayRef ekus
= SecCertificateCopyExtendedKeyUsage(certificate
);
455 return true; // No EKU -> any purpose
458 const DERItem
*serverEkus
[] = {
459 &oidAnyExtendedKeyUsage
, &oidExtendedKeyUsageServerAuth
,
460 &oidExtendedKeyUsageMicrosoftSGC
, &oidExtendedKeyUsageNetscapeSGC
,
464 for (uint8_t ix
= 0; ix
< sizeof(serverEkus
)/sizeof(const DERItem
*); ix
++) {
465 CFDataRef ekuOid
= CFDataCreate(NULL
, serverEkus
[ix
]->data
, serverEkus
[ix
]->length
);
466 if (CFArrayContainsValue(ekus
, CFRangeMake(0, CFArrayGetCount(ekus
)), ekuOid
)) {
469 CFReleaseNull(ekuOid
);
479 static void nc_compare_subject_to_subtrees(SecCertificateRef certificate
, CFArrayRef subtrees
,
480 bool permit
, match_t
*match
) {
481 CFDataRef subject
= SecCertificateCopySubjectSequence(certificate
);
482 /* An empty subject name is considered not present */
483 if (!subject
|| isEmptySubject(subject
)) {
484 CFReleaseNull(subject
);
488 /* Compare X.500 distinguished name constraints */
489 CFIndex num_trees
= CFArrayGetCount(subtrees
);
490 CFRange range
= { 0, num_trees
};
491 match_t x500_match
= { false, false };
492 const DERItem subject_der
= { (unsigned char *)CFDataGetBytePtr(subject
), CFDataGetLength(subject
) };
493 nc_match_context_t context
= {GNT_DirectoryName
, &subject_der
, &x500_match
};
494 CFArrayApplyFunction(subtrees
, range
, nc_decode_and_compare_subtree
, &context
);
495 CFReleaseNull(subject
);
496 update_match(permit
, &x500_match
, match
);
498 /* These checks are unnecessary if Common Names aren't used to match SSLHostnames. (<rdar://31562470>)
499 * In the meantime, this hack makes non-SSL certs with DNS-like CNs work. (<rdar://41173883>) */
500 /* Compare DNSName constraints -- if there's no DNS names in the SAN and this cert can be used for SSL Server Auth*/
501 CFArrayRef sanDnsNames
= SecCertificateCopyDNSNamesFromSAN(certificate
);
502 if (certAllowsSSL(certificate
) && (!sanDnsNames
|| CFArrayGetCount(sanDnsNames
) == 0)) {
503 match_t dns_match
= { false, permit
};
504 CFArrayRef dnsNames
= SecCertificateCopyDNSNamesFromSubject(certificate
);
506 CFRange dnsRange
= { 0, CFArrayGetCount(dnsNames
) };
507 nc_san_match_context_t dnsContext
= { subtrees
, &dns_match
, permit
};
508 CFArrayApplyFunction(dnsNames
, dnsRange
, nc_compare_DNSName_to_subtrees
, &dnsContext
);
510 CFReleaseNull(dnsNames
);
511 update_match(permit
, &dns_match
, match
);
513 CFReleaseNull(sanDnsNames
);
515 /* Compare IPAddresss constraints */
516 match_t ip_match
= { false, permit
};
517 CFArrayRef ipAddresses
= SecCertificateCopyIPAddressesFromSubject(certificate
);
519 CFRange ipRange
= { 0, CFArrayGetCount(ipAddresses
) };
520 nc_san_match_context_t ipContext
= { subtrees
, &ip_match
, permit
};
521 CFArrayApplyFunction(ipAddresses
, ipRange
, nc_compare_IPAddress_to_subtrees
, &ipContext
);
523 CFReleaseNull(ipAddresses
);
524 update_match(permit
, &ip_match
, match
);
526 /* Compare RFC822 constraints to subject email address */
527 match_t email_match
= { false, permit
};
528 CFArrayRef rfc822Names
= SecCertificateCopyRFC822NamesFromSubject(certificate
);
530 CFRange emailRange
= { 0, CFArrayGetCount(rfc822Names
) };
531 nc_san_match_context_t emailContext
= { subtrees
, &email_match
, permit
};
532 CFArrayApplyFunction(rfc822Names
, emailRange
, nc_compare_RFC822Name_to_subtrees
, &emailContext
);
534 CFReleaseNull(rfc822Names
);
535 update_match(permit
, &email_match
, match
);
538 static OSStatus
nc_compare_subjectAltName_to_subtrees(void *context
, SecCEGeneralNameType gnType
, const DERItem
*generalName
) {
539 nc_san_match_context_t
*san_context
= context
;
540 CFArrayRef subtrees
= NULL
;
542 subtrees
= san_context
->subtrees
;
545 CFIndex num_trees
= CFArrayGetCount(subtrees
);
546 CFRange range
= { 0, num_trees
};
547 match_t match
= { false, false };
548 nc_match_context_t match_context
= {gnType
, generalName
, &match
};
549 CFArrayApplyFunction(subtrees
, range
, nc_decode_and_compare_subtree
, &match_context
);
551 update_match(san_context
->permit
, &match
, san_context
->match
);
553 return errSecSuccess
;
556 return errSecInvalidCertificate
;
559 OSStatus
SecNameContraintsMatchSubtrees(SecCertificateRef certificate
, CFArrayRef subtrees
, bool *matched
, bool permit
) {
560 CFDataRef subject
= NULL
;
561 OSStatus status
= errSecSuccess
;
563 require_action_quiet(subject
= SecCertificateCopySubjectSequence(certificate
),
565 status
= errSecInvalidCertificate
);
566 const DERItem
*subjectAltNames
= SecCertificateGetSubjectAltName(certificate
);
568 /* Reject certificates with neither Subject Name nor SubjectAltName */
569 require_action_quiet(!isEmptySubject(subject
) || subjectAltNames
, out
, status
= errSecInvalidCertificate
);
571 /* Verify that the subject name is within all of the subtrees */
572 match_t subject_match
= { false, permit
};
573 nc_compare_subject_to_subtrees(certificate
, subtrees
, permit
, &subject_match
);
575 /* permit tells us whether to start with true or false. If we are looking at permitted
576 * subtrees, we are going to "and" the matching results because all present types must match
577 * to permit. For excluded subtrees, we are going to "or" the matching results because
578 * any matching present types causes exclusion. */
579 match_t san_match
= { false, permit
};
580 nc_san_match_context_t san_context
= {subtrees
, &san_match
, permit
};
582 /* And verify that each of the alternative names in the subjectAltName extension (critical or non-critical)
583 * is within any of the subtrees for that name type. */
584 if (subjectAltNames
) {
585 status
= SecCertificateParseGeneralNames(subjectAltNames
,
587 nc_compare_subjectAltName_to_subtrees
);
588 /* If failed to parse */
589 require_action_quiet(status
== errSecSuccess
, out
, *matched
= false);
592 /* If we are excluding based on the subtrees, lack of names of the
593 same type is not a match. But if we are permitting, it is.
595 if (subject_match
.present
) {
596 if (san_match
.present
&&
597 ((subject_match
.isMatch
&& !san_match
.isMatch
) ||
598 (!subject_match
.isMatch
&& san_match
.isMatch
))) {
599 /* If both san and subject types are present, but don't agree on match
600 * we should exclude on the basis of the match and not permit on the
601 * basis of the failed match. */
602 *matched
= permit
? false : true;
605 /* If san type wasn't present or both had the same result, use the
606 * result from matching against the subject. */
607 *matched
= subject_match
.isMatch
;
610 else if (san_match
.present
) {
611 *matched
= san_match
.isMatch
;
614 /* Neither subject nor san had same type as subtrees, permit and don't
615 * exclude the cert. */
616 *matched
= permit
? true : false;
620 CFReleaseNull(subject
);
625 CFMutableArrayRef existing_trees
;
626 CFMutableArrayRef trees_to_add
;
627 } nc_intersect_context_t
;
629 static SecCEGeneralNameType
nc_gn_type_convert (DERTag tag
) {
631 case ASN1_CONTEXT_SPECIFIC
| ASN1_CONSTRUCTED
| 0:
632 return GNT_OtherName
;
633 case ASN1_CONTEXT_SPECIFIC
| 1:
634 return GNT_RFC822Name
;
635 case ASN1_CONTEXT_SPECIFIC
| 2:
637 case ASN1_CONTEXT_SPECIFIC
| ASN1_CONSTRUCTED
| 3:
638 return GNT_X400Address
;
639 case ASN1_CONTEXT_SPECIFIC
| ASN1_CONSTRUCTED
| 4:
640 return GNT_DirectoryName
;
641 case ASN1_CONTEXT_SPECIFIC
| ASN1_CONSTRUCTED
| 5:
642 return GNT_EdiPartyName
;
643 case ASN1_CONTEXT_SPECIFIC
| ASN1_CONSTRUCTED
| 6:
644 case ASN1_CONTEXT_SPECIFIC
| 6:
646 case ASN1_CONTEXT_SPECIFIC
| 7:
647 return GNT_IPAddress
;
648 case ASN1_CONTEXT_SPECIFIC
| 8:
649 return GNT_RegisteredID
;
651 return GNT_OtherName
;
655 /* The recommended processing algorithm states:
656 * If permittedSubtrees is present in the certificate, set the permitted_subtrees state variable to the intersection
657 * of its previous value and the value indicated in the extension field.
658 * However, in practice, certs are issued with permittedSubtrees whose intersection would be the empty set. For now,
659 * wherever a new permittedSubtree is a subset of an existing subtree, we'll replace the existing subtree; otherwise,
660 * we just append the new subtree.
662 static void nc_intersect_tree_with_subtrees (const void *value
, void *context
) {
663 CFDataRef new_subtree
= value
;
664 nc_intersect_context_t
*intersect_context
= context
;
665 CFMutableArrayRef existing_subtrees
= intersect_context
->existing_trees
;
666 CFMutableArrayRef trees_to_append
= intersect_context
->trees_to_add
;
668 if (!new_subtree
|| !existing_subtrees
) return;
670 /* convert new subtree to DERItem */
671 const DERItem general_name
= { (unsigned char *)CFDataGetBytePtr(new_subtree
), CFDataGetLength(new_subtree
) };
672 DERDecodedInfo general_name_content
;
673 if(DR_Success
!= DERDecodeItem(&general_name
, &general_name_content
)) return;
675 SecCEGeneralNameType gnType
;
676 DERItem
*new_subtree_item
= &general_name_content
.content
;
678 /* Attempt to intersect if one of the supported types: DirectoryName and DNSName.
679 * Otherwise, just append the new tree. */
680 gnType
= nc_gn_type_convert(general_name_content
.tag
);
681 if (!(gnType
== GNT_DirectoryName
|| gnType
== GNT_DNSName
)) {
682 CFArrayAppendValue(trees_to_append
, new_subtree
);
686 CFIndex num_existing_subtrees
= CFArrayGetCount(existing_subtrees
);
687 match_t match
= { false, false };
688 nc_match_context_t match_context
= { gnType
, new_subtree_item
, &match
};
689 for (subtreeIX
= 0; subtreeIX
< num_existing_subtrees
; subtreeIX
++) {
690 CFDataRef candidate_subtree
= CFArrayGetValueAtIndex(existing_subtrees
, subtreeIX
);
691 /* Convert candidate subtree to DERItem */
692 const DERItem candidate
= { (unsigned char *)CFDataGetBytePtr(candidate_subtree
), CFDataGetLength(candidate_subtree
) };
693 DERDecodedInfo candidate_content
;
694 /* We could probably just delete any subtrees in the array that don't decode */
695 if(DR_Success
!= DERDecodeItem(&candidate
, &candidate_content
)) continue;
697 /* first test whether new tree matches the existing tree */
698 OSStatus status
= SecCertificateParseGeneralNameContentProperty(candidate_content
.tag
,
699 &candidate_content
.content
,
702 if((status
== errSecSuccess
) && match
.present
&& match
.isMatch
) {
706 /* then test whether existing tree matches the new tree*/
707 match_t local_match
= { false , false };
708 nc_match_context_t local_match_context
= { nc_gn_type_convert(candidate_content
.tag
),
709 &candidate_content
.content
,
711 status
= SecCertificateParseGeneralNameContentProperty(general_name_content
.tag
,
712 &general_name_content
.content
,
713 &local_match_context
,
715 if((status
== errSecSuccess
) && local_match
.present
&& local_match
.isMatch
) {
719 if (subtreeIX
== num_existing_subtrees
) {
720 /* No matches found. Append new subtree */
721 CFArrayAppendValue(trees_to_append
, new_subtree
);
723 else if (match
.present
&& match
.isMatch
) {
724 /* new subtree \subseteq existing subtree, replace existing tree */
725 CFArraySetValueAtIndex(existing_subtrees
, subtreeIX
, new_subtree
);
727 /* existing subtree \subset new subtree, drop the new tree so as not to broaden constraints*/
732 void SecNameConstraintsIntersectSubtrees(CFMutableArrayRef subtrees_state
, CFArrayRef subtrees_new
) {
733 assert(subtrees_state
);
734 assert(subtrees_new
);
736 CFIndex num_new_trees
= CFArrayGetCount(subtrees_new
);
737 CFRange range
= { 0, num_new_trees
};
739 /* if existing subtrees state contains no subtrees, append new subtrees whole */
740 if (!CFArrayGetCount(subtrees_state
)) {
741 CFArrayAppendArray(subtrees_state
, subtrees_new
, range
);
745 CFMutableArrayRef trees_to_append
= NULL
;
746 trees_to_append
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
747 nc_intersect_context_t context
= { subtrees_state
, trees_to_append
};
748 CFArrayApplyFunction(subtrees_new
, range
, nc_intersect_tree_with_subtrees
, &context
);
750 /* don't append to the state until we've processed all the new trees */
751 num_new_trees
= CFArrayGetCount(trees_to_append
);
752 if (trees_to_append
&& num_new_trees
) {
753 range
.length
= num_new_trees
;
754 CFArrayAppendArray(subtrees_state
, trees_to_append
, range
);
757 CFReleaseNull(trees_to_append
);