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>
35 /* RFC 5280 Section 4.2.1.10:
36 DNS name restrictions are expressed as host.example.com. Any DNS
37 name that can be constructed by simply adding zero or more labels to
38 the left-hand side of the name satisfies the name constraint. For
39 example, www.host.example.com would satisfy the constraint but
40 host1.example.com would not.
42 static bool SecDNSNameConstraintsMatch(CFStringRef DNSName
, CFStringRef constraint
) {
43 CFIndex clength
= CFStringGetLength(constraint
);
44 CFIndex dlength
= CFStringGetLength(DNSName
);
46 if (dlength
< clength
) return false;
48 /* Ensure that character to the left of the constraint in the DNSName is a '.'
49 so that badexample.com does not match example.com, but good.example.com does.
51 if ((dlength
!= clength
) && ('.' != CFStringGetCharacterAtIndex(constraint
, 0)) &&
52 ('.' != CFStringGetCharacterAtIndex(DNSName
, dlength
- clength
-1))) {
56 CFRange compareRange
= { dlength
- clength
, clength
};
58 if (!CFStringCompareWithOptions(DNSName
, constraint
, compareRange
, kCFCompareCaseInsensitive
)) {
65 /* RFC 5280 Section 4.2.1.10:
66 For URIs, the constraint applies to the host part of the name. The
67 constraint MUST be specified as a fully qualified domain name and MAY
68 specify a host or a domain. Examples would be "host.example.com" and
69 ".example.com". When the constraint begins with a period, it MAY be
70 expanded with one or more labels. That is, the constraint
71 ".example.com" is satisfied by both host.example.com and
72 my.host.example.com. However, the constraint ".example.com" is not
73 satisfied by "example.com". When the constraint does not begin with
74 a period, it specifies a host.
76 static bool SecURIMatch(CFStringRef URI
, CFStringRef hostname
) {
78 CFStringRef URI_hostname
= NULL
;
79 CFCharacterSetRef port_or_path_separator
= NULL
;
80 /* URI must have scheme specified */
81 CFRange URI_scheme
= CFStringFind(URI
, CFSTR("://"), 0);
82 require_quiet(URI_scheme
.location
!= kCFNotFound
, out
);
84 /* Remove scheme prefix and port or resource path suffix */
85 CFRange URI_hostname_range
= { URI_scheme
.location
+ URI_scheme
.length
,
86 CFStringGetLength(URI
) - URI_scheme
.location
- URI_scheme
.length
};
87 port_or_path_separator
= CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault
, CFSTR(":/"));
88 CFRange separator
= {kCFNotFound
, 0};
89 if(CFStringFindCharacterFromSet(URI
, port_or_path_separator
, URI_hostname_range
, 0, &separator
)) {
90 URI_hostname_range
.length
-= (CFStringGetLength(URI
) - separator
.location
);
92 URI_hostname
= CFStringCreateWithSubstring(kCFAllocatorDefault
, URI
, URI_hostname_range
);
94 /* Hostname in URI must not begin with '.' */
95 require_quiet('.' != CFStringGetCharacterAtIndex(URI_hostname
, 0), out
);
97 CFIndex ulength
= CFStringGetLength(URI_hostname
);
98 CFIndex hlength
= CFStringGetLength(hostname
);
99 require_quiet(ulength
>= hlength
, out
);
100 CFRange compare_range
= { 0, hlength
};
102 /* Allow one or more preceding labels */
103 if ('.' == CFStringGetCharacterAtIndex(hostname
, 0)) {
104 compare_range
.location
= ulength
- hlength
;
107 if(kCFCompareEqualTo
== CFStringCompareWithOptions(URI_hostname
,
110 kCFCompareCaseInsensitive
)) {
115 CFReleaseNull(port_or_path_separator
);
116 CFReleaseNull(URI_hostname
);
120 /* RFC 5280 Section 4.2.1.10:
121 A name constraint for Internet mail addresses MAY specify a
122 particular mailbox, all addresses at a particular host, or all
123 mailboxes in a domain. To indicate a particular mailbox, the
124 constraint is the complete mail address. For example,
125 "root@example.com" indicates the root mailbox on the host
126 "example.com". To indicate all Internet mail addresses on a
127 particular host, the constraint is specified as the host name. For
128 example, the constraint "example.com" is satisfied by any mail
129 address at the host "example.com". To specify any address within a
130 domain, the constraint is specified with a leading period (as with
133 static bool SecRFC822NameMatch(CFStringRef emailAddress
, CFStringRef constraint
) {
134 CFRange mailbox_range
= CFStringFind(constraint
,CFSTR("@"),0);
136 /* Constraint specifies a particular mailbox. Perform full comparison. */
137 if (mailbox_range
.location
!= kCFNotFound
) {
138 if (!CFStringCompare(emailAddress
, constraint
, kCFCompareCaseInsensitive
)) {
144 mailbox_range
= CFStringFind(emailAddress
, CFSTR("@"), 0);
145 require_quiet(mailbox_range
.location
!= kCFNotFound
, out
);
146 CFRange hostname_range
= {mailbox_range
.location
+ 1,
147 CFStringGetLength(emailAddress
) - mailbox_range
.location
- 1 };
149 /* Constraint specificies a particular host. Compare hostname of address. */
150 if ('.' != CFStringGetCharacterAtIndex(constraint
, 0)) {
151 if (!CFStringCompareWithOptions(emailAddress
, constraint
, hostname_range
, kCFCompareCaseInsensitive
)) {
157 /* Constraint specificies a domain. Match hostname of address to domain name. */
158 require_quiet('.' != CFStringGetCharacterAtIndex(emailAddress
, mailbox_range
.location
+1), out
);
159 if (CFStringHasSuffix(emailAddress
, constraint
)) {
167 static bool nc_compare_directoryNames(const DERItem
*certName
, const DERItem
*subtreeName
) {
168 /* Get content of certificate name and subtree name */
169 DERDecodedInfo certName_content
;
170 require_noerr_quiet(DERDecodeItem(certName
, &certName_content
), out
);
172 DERDecodedInfo subtreeName_content
;
173 require_noerr_quiet(DERDecodeItem(subtreeName
, &subtreeName_content
), out
);
175 if (certName
->length
> subtreeName
->length
) {
176 if(0 == memcmp(certName_content
.content
.data
,
177 subtreeName_content
.content
.data
,
178 subtreeName_content
.content
.length
)) {
187 static bool nc_compare_DNSNames(const DERItem
*certName
, const DERItem
*subtreeName
) {
189 CFStringRef certName_str
= CFStringCreateWithBytes(kCFAllocatorDefault
,
190 certName
->data
, certName
->length
,
191 kCFStringEncodingUTF8
, FALSE
);
192 CFStringRef subtreeName_str
= CFStringCreateWithBytes(kCFAllocatorDefault
,
193 subtreeName
->data
, subtreeName
->length
,
194 kCFStringEncodingUTF8
, FALSE
);
195 require_quiet(certName_str
, out
);
196 require_quiet(subtreeName_str
, out
);
198 if (SecDNSNameConstraintsMatch(certName_str
, subtreeName_str
)) {
203 CFReleaseNull(certName_str
) ;
204 CFReleaseNull(subtreeName_str
);
208 static bool nc_compare_URIs(const DERItem
*certName
, const DERItem
*subtreeName
) {
210 CFStringRef certName_str
= CFStringCreateWithBytes(kCFAllocatorDefault
,
211 certName
->data
, certName
->length
,
212 kCFStringEncodingUTF8
, FALSE
);
213 CFStringRef subtreeName_str
= CFStringCreateWithBytes(kCFAllocatorDefault
,
214 subtreeName
->data
, subtreeName
->length
,
215 kCFStringEncodingUTF8
, FALSE
);
216 require_quiet(certName_str
, out
);
217 require_quiet(subtreeName_str
, out
);
219 if (SecURIMatch(certName_str
, subtreeName_str
)) {
224 CFReleaseNull(certName_str
);
225 CFReleaseNull(subtreeName_str
);
229 static bool nc_compare_RFC822Names(const DERItem
*certName
, const DERItem
*subtreeName
) {
231 CFStringRef certName_str
= CFStringCreateWithBytes(kCFAllocatorDefault
,
232 certName
->data
, certName
->length
,
233 kCFStringEncodingUTF8
, FALSE
);
234 CFStringRef subtreeName_str
= CFStringCreateWithBytes(kCFAllocatorDefault
,
235 subtreeName
->data
, subtreeName
->length
,
236 kCFStringEncodingUTF8
, FALSE
);
237 require_quiet(certName_str
, out
);
238 require_quiet(subtreeName_str
, out
);
240 if (SecRFC822NameMatch(certName_str
, subtreeName_str
)) {
245 CFReleaseNull(certName_str
);
246 CFReleaseNull(subtreeName_str
);
250 static bool nc_compare_IPAddresses(const DERItem
*certAddr
, const DERItem
*subtreeAddr
) {
253 /* Verify Subtree Address has correct number of bytes for IP and mask */
254 require_quiet((subtreeAddr
->length
== 8) || (subtreeAddr
->length
== 32), out
);
255 /* Verify Cert Address has correct number of bytes for IP */
256 require_quiet((certAddr
->length
== 4) || (certAddr
->length
==16), out
);
257 /* Verify Subtree Address and Cert Address are the same version */
258 require_quiet(subtreeAddr
->length
== 2*certAddr
->length
, out
);
260 DERByte
* mask
= subtreeAddr
->data
+ certAddr
->length
;
261 for (DERSize i
= 0; i
< certAddr
->length
; i
++) {
262 if((subtreeAddr
->data
[i
] & mask
[i
]) != (certAddr
->data
[i
] & mask
[i
])) {
278 const SecCEGeneralNameType gnType
;
279 const DERItem
*cert_item
;
281 } nc_match_context_t
;
284 const CFArrayRef subtrees
;
287 } nc_san_match_context_t
;
289 static OSStatus
nc_compare_subtree(void *context
, SecCEGeneralNameType gnType
, const DERItem
*generalName
) {
290 nc_match_context_t
*item_context
= context
;
291 if (item_context
&& gnType
== item_context
->gnType
292 && item_context
->match
&& item_context
->cert_item
) {
294 item_context
->match
->present
= true;
296 * We set isMatch such that if there are multiple subtrees of the same type, matching to any one
297 * of them is considered a match.
300 case GNT_DirectoryName
: {
301 item_context
->match
->isMatch
|= nc_compare_directoryNames(item_context
->cert_item
, generalName
);
302 return errSecSuccess
;
305 item_context
->match
->isMatch
|= nc_compare_DNSNames(item_context
->cert_item
, generalName
);
306 return errSecSuccess
;
309 item_context
->match
->isMatch
|= nc_compare_URIs(item_context
->cert_item
, generalName
);
310 return errSecSuccess
;
312 case GNT_RFC822Name
: {
313 item_context
->match
->isMatch
|= nc_compare_RFC822Names(item_context
->cert_item
, generalName
);
314 return errSecSuccess
;
316 case GNT_IPAddress
: {
317 item_context
->match
->isMatch
|= nc_compare_IPAddresses(item_context
->cert_item
, generalName
);
318 return errSecSuccess
;
321 /* If the name form is not supported, reject the certificate. */
322 return errSecInvalidCertificate
;
327 return errSecInvalidCertificate
;
330 static void nc_decode_and_compare_subtree(const void *value
, void *context
) {
331 CFDataRef subtree
= value
;
332 nc_match_context_t
*match_context
= context
;
334 /* convert subtree to DERItem */
335 const DERItem general_name
= { (unsigned char *)CFDataGetBytePtr(subtree
), CFDataGetLength(subtree
) };
336 DERDecodedInfo general_name_content
;
337 require_noerr_quiet(DERDecodeItem(&general_name
, &general_name_content
),out
);
339 OSStatus status
= SecCertificateParseGeneralNameContentProperty(general_name_content
.tag
,
340 &general_name_content
.content
,
343 if (status
== errSecInvalidCertificate
) {
344 secnotice("policy","can't parse general name or not a type we support");
351 static bool isEmptySubject(CFDataRef subject
) {
352 const DERItem subject_der
= { (unsigned char *)CFDataGetBytePtr(subject
), CFDataGetLength(subject
) };
354 /* Get content of certificate name */
355 DERDecodedInfo subject_content
;
356 require_noerr_quiet(DERDecodeItem(&subject_der
, &subject_content
), out
);
357 if (subject_content
.content
.length
) return false;
364 * We update match structs as follows:
365 * 'present' is true if there's any subtree of the same type as any Subject/SAN
366 * 'match' is false if the subtree(s) and Subject(s)/SAN(s) don't match.
367 * Note: the state of 'match' is meaningless without 'present' also being true.
369 static void update_match(bool permit
, match_t
*input_match
, match_t
*output_match
) {
370 if (!input_match
|| !output_match
) {
373 if (input_match
->present
) {
374 output_match
->present
= true;
376 output_match
->isMatch
&= input_match
->isMatch
;
378 output_match
->isMatch
|= input_match
->isMatch
;
383 static void nc_compare_DNSName_to_subtrees(const void *value
, void *context
) {
384 CFStringRef dnsName
= (CFStringRef
)value
;
385 char *dnsNameString
= NULL
;
386 nc_san_match_context_t
*san_context
= context
;
387 CFArrayRef subtrees
= NULL
;
389 subtrees
= san_context
->subtrees
;
392 CFIndex num_trees
= CFArrayGetCount(subtrees
);
393 CFRange range
= { 0, num_trees
};
394 match_t match
= { false, false };
395 dnsNameString
= CFStringToCString(dnsName
);
396 if (!dnsNameString
) { return; }
397 const DERItem name
= { (unsigned char *)dnsNameString
,
398 CFStringGetLength(dnsName
) };
399 nc_match_context_t match_context
= {GNT_DNSName
, &name
, &match
};
400 CFArrayApplyFunction(subtrees
, range
, nc_decode_and_compare_subtree
, &match_context
);
403 update_match(san_context
->permit
, &match
, san_context
->match
);
407 static void nc_compare_IPAddress_to_subtrees(const void *value
, void *context
) {
408 CFDataRef ipAddr
= (CFDataRef
)value
;
409 nc_san_match_context_t
*san_context
= context
;
410 CFArrayRef subtrees
= NULL
;
412 subtrees
= san_context
->subtrees
;
415 CFIndex num_trees
= CFArrayGetCount(subtrees
);
416 CFRange range
= { 0, num_trees
};
417 match_t match
= { false, false };
418 const DERItem addr
= { (unsigned char *)CFDataGetBytePtr(ipAddr
), CFDataGetLength(ipAddr
) };
419 nc_match_context_t match_context
= {GNT_IPAddress
, &addr
, &match
};
420 CFArrayApplyFunction(subtrees
, range
, nc_decode_and_compare_subtree
, &match_context
);
422 update_match(san_context
->permit
, &match
, san_context
->match
);
426 static void nc_compare_RFC822Name_to_subtrees(const void *value
, void *context
) {
427 CFStringRef rfc822Name
= (CFStringRef
)value
;
428 char *rfc822NameString
= NULL
;
429 nc_san_match_context_t
*san_context
= context
;
430 CFArrayRef subtrees
= NULL
;
432 subtrees
= san_context
->subtrees
;
435 CFIndex num_trees
= CFArrayGetCount(subtrees
);
436 CFRange range
= { 0, num_trees
};
437 match_t match
= { false, false };
438 rfc822NameString
= CFStringToCString(rfc822Name
);
439 if (!rfc822NameString
) { return; }
440 const DERItem addr
= { (unsigned char *)rfc822NameString
,
441 CFStringGetLength(rfc822Name
) };
442 nc_match_context_t match_context
= {GNT_RFC822Name
, &addr
, &match
};
443 CFArrayApplyFunction(subtrees
, range
, nc_decode_and_compare_subtree
, &match_context
);
444 free(rfc822NameString
);
446 update_match(san_context
->permit
, &match
, san_context
->match
);
451 static void nc_compare_subject_to_subtrees(SecCertificateRef certificate
, CFArrayRef subtrees
,
452 bool permit
, match_t
*match
) {
453 CFDataRef subject
= SecCertificateCopySubjectSequence(certificate
);
454 /* An empty subject name is considered not present */
455 if (!subject
|| isEmptySubject(subject
)) {
456 CFReleaseNull(subject
);
460 /* Compare X.500 distinguished name constraints */
461 CFIndex num_trees
= CFArrayGetCount(subtrees
);
462 CFRange range
= { 0, num_trees
};
463 match_t x500_match
= { false, false };
464 const DERItem subject_der
= { (unsigned char *)CFDataGetBytePtr(subject
), CFDataGetLength(subject
) };
465 nc_match_context_t context
= {GNT_DirectoryName
, &subject_der
, &x500_match
};
466 CFArrayApplyFunction(subtrees
, range
, nc_decode_and_compare_subtree
, &context
);
467 CFReleaseNull(subject
);
468 update_match(permit
, &x500_match
, match
);
470 /* Compare DNSName constraints */
471 match_t dns_match
= { false, permit
};
472 CFArrayRef dnsNames
= SecCertificateCopyDNSNamesFromSubject(certificate
);
474 CFRange dnsRange
= { 0, CFArrayGetCount(dnsNames
) };
475 nc_san_match_context_t dnsContext
= { subtrees
, &dns_match
, permit
};
476 CFArrayApplyFunction(dnsNames
, dnsRange
, nc_compare_DNSName_to_subtrees
, &dnsContext
);
478 CFReleaseNull(dnsNames
);
479 update_match(permit
, &dns_match
, match
);
481 /* Compare IPAddresss constraints */
482 match_t ip_match
= { false, permit
};
483 CFArrayRef ipAddresses
= SecCertificateCopyIPAddressesFromSubject(certificate
);
485 CFRange ipRange
= { 0, CFArrayGetCount(ipAddresses
) };
486 nc_san_match_context_t ipContext
= { subtrees
, &ip_match
, permit
};
487 CFArrayApplyFunction(ipAddresses
, ipRange
, nc_compare_IPAddress_to_subtrees
, &ipContext
);
489 CFReleaseNull(ipAddresses
);
490 update_match(permit
, &ip_match
, match
);
492 /* Compare RFC822 constraints to subject email address */
493 match_t email_match
= { false, permit
};
494 CFArrayRef rfc822Names
= SecCertificateCopyRFC822NamesFromSubject(certificate
);
496 CFRange emailRange
= { 0, CFArrayGetCount(rfc822Names
) };
497 nc_san_match_context_t emailContext
= { subtrees
, &email_match
, permit
};
498 CFArrayApplyFunction(rfc822Names
, emailRange
, nc_compare_RFC822Name_to_subtrees
, &emailContext
);
500 CFReleaseNull(rfc822Names
);
501 update_match(permit
, &email_match
, match
);
504 static OSStatus
nc_compare_subjectAltName_to_subtrees(void *context
, SecCEGeneralNameType gnType
, const DERItem
*generalName
) {
505 nc_san_match_context_t
*san_context
= context
;
506 CFArrayRef subtrees
= NULL
;
508 subtrees
= san_context
->subtrees
;
511 CFIndex num_trees
= CFArrayGetCount(subtrees
);
512 CFRange range
= { 0, num_trees
};
513 match_t match
= { false, false };
514 nc_match_context_t match_context
= {gnType
, generalName
, &match
};
515 CFArrayApplyFunction(subtrees
, range
, nc_decode_and_compare_subtree
, &match_context
);
517 update_match(san_context
->permit
, &match
, san_context
->match
);
519 return errSecSuccess
;
522 return errSecInvalidCertificate
;
525 OSStatus
SecNameContraintsMatchSubtrees(SecCertificateRef certificate
, CFArrayRef subtrees
, bool *matched
, bool permit
) {
526 CFDataRef subject
= NULL
;
527 OSStatus status
= errSecSuccess
;
529 require_action_quiet(subject
= SecCertificateCopySubjectSequence(certificate
),
531 status
= errSecInvalidCertificate
);
532 const DERItem
*subjectAltNames
= SecCertificateGetSubjectAltName(certificate
);
534 /* Reject certificates with neither Subject Name nor SubjectAltName */
535 require_action_quiet(!isEmptySubject(subject
) || subjectAltNames
, out
, status
= errSecInvalidCertificate
);
537 /* Verify that the subject name is within all of the subtrees */
538 match_t subject_match
= { false, permit
};
539 nc_compare_subject_to_subtrees(certificate
, subtrees
, permit
, &subject_match
);
541 /* permit tells us whether to start with true or false. If we are looking at permitted
542 * subtrees, we are going to "and" the matching results because all present types must match
543 * to permit. For excluded subtrees, we are going to "or" the matching results because
544 * any matching present types causes exclusion. */
545 match_t san_match
= { false, permit
};
546 nc_san_match_context_t san_context
= {subtrees
, &san_match
, permit
};
548 /* And verify that each of the alternative names in the subjectAltName extension (critical or non-critical)
549 * is within any of the subtrees for that name type. */
550 if (subjectAltNames
) {
551 status
= SecCertificateParseGeneralNames(subjectAltNames
,
553 nc_compare_subjectAltName_to_subtrees
);
554 /* If failed to parse */
555 require_action_quiet(status
== errSecSuccess
, out
, *matched
= false);
558 /* If we are excluding based on the subtrees, lack of names of the
559 same type is not a match. But if we are permitting, it is.
561 if (subject_match
.present
) {
562 if (san_match
.present
&&
563 ((subject_match
.isMatch
&& !san_match
.isMatch
) ||
564 (!subject_match
.isMatch
&& san_match
.isMatch
))) {
565 /* If both san and subject types are present, but don't agree on match
566 * we should exclude on the basis of the match and not permit on the
567 * basis of the failed match. */
568 *matched
= permit
? false : true;
571 /* If san type wasn't present or both had the same result, use the
572 * result from matching against the subject. */
573 *matched
= subject_match
.isMatch
;
576 else if (san_match
.present
) {
577 *matched
= san_match
.isMatch
;
580 /* Neither subject nor san had same type as subtrees, permit and don't
581 * exclude the cert. */
582 *matched
= permit
? true : false;
586 CFReleaseNull(subject
);
591 CFMutableArrayRef existing_trees
;
592 CFMutableArrayRef trees_to_add
;
593 } nc_intersect_context_t
;
595 static SecCEGeneralNameType
nc_gn_type_convert (DERTag tag
) {
597 case ASN1_CONTEXT_SPECIFIC
| ASN1_CONSTRUCTED
| 0:
598 return GNT_OtherName
;
599 case ASN1_CONTEXT_SPECIFIC
| 1:
600 return GNT_RFC822Name
;
601 case ASN1_CONTEXT_SPECIFIC
| 2:
603 case ASN1_CONTEXT_SPECIFIC
| ASN1_CONSTRUCTED
| 3:
604 return GNT_X400Address
;
605 case ASN1_CONTEXT_SPECIFIC
| ASN1_CONSTRUCTED
| 4:
606 return GNT_DirectoryName
;
607 case ASN1_CONTEXT_SPECIFIC
| ASN1_CONSTRUCTED
| 5:
608 return GNT_EdiPartyName
;
609 case ASN1_CONTEXT_SPECIFIC
| ASN1_CONSTRUCTED
| 6:
610 case ASN1_CONTEXT_SPECIFIC
| 6:
612 case ASN1_CONTEXT_SPECIFIC
| 7:
613 return GNT_IPAddress
;
614 case ASN1_CONTEXT_SPECIFIC
| 8:
615 return GNT_RegisteredID
;
617 return GNT_OtherName
;
621 /* The recommended processing algorithm states:
622 * If permittedSubtrees is present in the certificate, set the permitted_subtrees state variable to the intersection
623 * of its previous value and the value indicated in the extension field.
624 * However, in practice, certs are issued with permittedSubtrees whose intersection would be the empty set. For now,
625 * wherever a new permittedSubtree is a subset of an existing subtree, we'll replace the existing subtree; otherwise,
626 * we just append the new subtree.
628 static void nc_intersect_tree_with_subtrees (const void *value
, void *context
) {
629 CFDataRef new_subtree
= value
;
630 nc_intersect_context_t
*intersect_context
= context
;
631 CFMutableArrayRef existing_subtrees
= intersect_context
->existing_trees
;
632 CFMutableArrayRef trees_to_append
= intersect_context
->trees_to_add
;
634 if (!new_subtree
|| !existing_subtrees
) return;
636 /* convert new subtree to DERItem */
637 const DERItem general_name
= { (unsigned char *)CFDataGetBytePtr(new_subtree
), CFDataGetLength(new_subtree
) };
638 DERDecodedInfo general_name_content
;
639 if(DR_Success
!= DERDecodeItem(&general_name
, &general_name_content
)) return;
641 SecCEGeneralNameType gnType
;
642 DERItem
*new_subtree_item
= &general_name_content
.content
;
644 /* Attempt to intersect if one of the supported types: DirectoryName and DNSName.
645 * Otherwise, just append the new tree. */
646 gnType
= nc_gn_type_convert(general_name_content
.tag
);
647 if (!(gnType
== GNT_DirectoryName
|| gnType
== GNT_DNSName
)) {
648 CFArrayAppendValue(trees_to_append
, new_subtree
);
652 CFIndex num_existing_subtrees
= CFArrayGetCount(existing_subtrees
);
653 match_t match
= { false, false };
654 nc_match_context_t match_context
= { gnType
, new_subtree_item
, &match
};
655 for (subtreeIX
= 0; subtreeIX
< num_existing_subtrees
; subtreeIX
++) {
656 CFDataRef candidate_subtree
= CFArrayGetValueAtIndex(existing_subtrees
, subtreeIX
);
657 /* Convert candidate subtree to DERItem */
658 const DERItem candidate
= { (unsigned char *)CFDataGetBytePtr(candidate_subtree
), CFDataGetLength(candidate_subtree
) };
659 DERDecodedInfo candidate_content
;
660 /* We could probably just delete any subtrees in the array that don't decode */
661 if(DR_Success
!= DERDecodeItem(&candidate
, &candidate_content
)) continue;
663 /* first test whether new tree matches the existing tree */
664 OSStatus status
= SecCertificateParseGeneralNameContentProperty(candidate_content
.tag
,
665 &candidate_content
.content
,
668 if((status
== errSecSuccess
) && match
.present
&& match
.isMatch
) {
672 /* then test whether existing tree matches the new tree*/
673 match_t local_match
= { false , false };
674 nc_match_context_t local_match_context
= { nc_gn_type_convert(candidate_content
.tag
),
675 &candidate_content
.content
,
677 status
= SecCertificateParseGeneralNameContentProperty(general_name_content
.tag
,
678 &general_name_content
.content
,
679 &local_match_context
,
681 if((status
== errSecSuccess
) && local_match
.present
&& local_match
.isMatch
) {
685 if (subtreeIX
== num_existing_subtrees
) {
686 /* No matches found. Append new subtree */
687 CFArrayAppendValue(trees_to_append
, new_subtree
);
689 else if (match
.present
&& match
.isMatch
) {
690 /* new subtree \subseteq existing subtree, replace existing tree */
691 CFArraySetValueAtIndex(existing_subtrees
, subtreeIX
, new_subtree
);
693 /* existing subtree \subset new subtree, drop the new tree so as not to broaden constraints*/
698 void SecNameConstraintsIntersectSubtrees(CFMutableArrayRef subtrees_state
, CFArrayRef subtrees_new
) {
699 assert(subtrees_state
);
700 assert(subtrees_new
);
702 CFIndex num_new_trees
= CFArrayGetCount(subtrees_new
);
703 CFRange range
= { 0, num_new_trees
};
705 /* if existing subtrees state contains no subtrees, append new subtrees whole */
706 if (!CFArrayGetCount(subtrees_state
)) {
707 CFArrayAppendArray(subtrees_state
, subtrees_new
, range
);
711 CFMutableArrayRef trees_to_append
= NULL
;
712 trees_to_append
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
713 nc_intersect_context_t context
= { subtrees_state
, trees_to_append
};
714 CFArrayApplyFunction(subtrees_new
, range
, nc_intersect_tree_with_subtrees
, &context
);
716 /* don't append to the state until we've processed all the new trees */
717 num_new_trees
= CFArrayGetCount(trees_to_append
);
718 if (trees_to_append
&& num_new_trees
) {
719 range
.length
= num_new_trees
;
720 CFArrayAppendArray(subtrees_state
, trees_to_append
, range
);
723 CFReleaseNull(trees_to_append
);