2 * Copyright (c) 2015 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
;
286 } nc_san_match_context_t
;
288 static OSStatus
nc_compare_subtree(void *context
, SecCEGeneralNameType gnType
, const DERItem
*generalName
) {
289 nc_match_context_t
*item_context
= context
;
290 if (item_context
&& gnType
== item_context
->gnType
291 && item_context
->match
&& item_context
->cert_item
) {
293 item_context
->match
->present
= true;
295 * We set isMatch such that if there are multiple subtrees of the same type, matching to any one
296 * of them is considered a match.
299 case GNT_DirectoryName
: {
300 item_context
->match
->isMatch
|= nc_compare_directoryNames(item_context
->cert_item
, generalName
);
301 return errSecSuccess
;
304 item_context
->match
->isMatch
|= nc_compare_DNSNames(item_context
->cert_item
, generalName
);
305 return errSecSuccess
;
308 item_context
->match
->isMatch
|= nc_compare_URIs(item_context
->cert_item
, generalName
);
309 return errSecSuccess
;
311 case GNT_RFC822Name
: {
312 item_context
->match
->isMatch
|= nc_compare_RFC822Names(item_context
->cert_item
, generalName
);
313 return errSecSuccess
;
315 case GNT_IPAddress
: {
316 item_context
->match
->isMatch
|= nc_compare_IPAddresses(item_context
->cert_item
, generalName
);
317 return errSecSuccess
;
320 /* If the name form is not supported, reject the certificate. */
321 return errSecInvalidCertificate
;
326 return errSecInvalidCertificate
;
329 static void nc_decode_and_compare_subtree(const void *value
, void *context
) {
330 CFDataRef subtree
= value
;
331 nc_match_context_t
*match_context
= context
;
333 /* convert subtree to DERItem */
334 const DERItem general_name
= { (unsigned char *)CFDataGetBytePtr(subtree
), CFDataGetLength(subtree
) };
335 DERDecodedInfo general_name_content
;
336 require_noerr_quiet(DERDecodeItem(&general_name
, &general_name_content
),out
);
338 OSStatus status
= SecCertificateParseGeneralNameContentProperty(general_name_content
.tag
,
339 &general_name_content
.content
,
342 if (status
== errSecInvalidCertificate
) {
343 secnotice("policy","can't parse general name or not a type we support");
350 static bool isEmptySubject(CFDataRef subject
) {
351 const DERItem subject_der
= { (unsigned char *)CFDataGetBytePtr(subject
), CFDataGetLength(subject
) };
353 /* Get content of certificate name */
354 DERDecodedInfo subject_content
;
355 require_noerr_quiet(DERDecodeItem(&subject_der
, &subject_content
), out
);
356 if (subject_content
.content
.length
) return false;
362 static void nc_compare_subject_to_subtrees(CFDataRef subject
, CFArrayRef subtrees
, match_t
*match
) {
363 /* An empty subject name is considered not present */
364 if (isEmptySubject(subject
)) {
368 CFIndex num_trees
= CFArrayGetCount(subtrees
);
369 CFRange range
= { 0, num_trees
};
370 const DERItem subject_der
= { (unsigned char *)CFDataGetBytePtr(subject
), CFDataGetLength(subject
) };
371 nc_match_context_t context
= {GNT_DirectoryName
, &subject_der
, match
};
372 CFArrayApplyFunction(subtrees
, range
, nc_decode_and_compare_subtree
, &context
);
375 static void nc_compare_RFC822Name_to_subtrees(const void *value
, void *context
) {
376 CFStringRef rfc822Name
= value
;
377 nc_san_match_context_t
*san_context
= context
;
378 CFArrayRef subtrees
= NULL
;
380 subtrees
= san_context
->subtrees
;
383 CFIndex num_trees
= CFArrayGetCount(subtrees
);
384 CFRange range
= { 0, num_trees
};
385 match_t match
= { false, false };
386 const DERItem addr
= { (unsigned char *)CFStringGetCStringPtr(rfc822Name
, kCFStringEncodingUTF8
),
387 CFStringGetLength(rfc822Name
) };
388 nc_match_context_t match_context
= {GNT_RFC822Name
, &addr
, &match
};
389 CFArrayApplyFunction(subtrees
, range
, nc_decode_and_compare_subtree
, &match_context
);
392 * We set the SAN context match struct as follows:
393 * 'present' is true if there's any subtree of the same type as any SAN
394 * 'match' is false if the present type(s) is/are not supported or the subtree(s) and SAN(s) don't match.
395 * Note: the state of 'match' is meaningless without 'present' also being true.
397 if (match
.present
&& san_context
->match
) {
398 san_context
->match
->present
= true;
399 san_context
->match
->isMatch
&= match
.isMatch
;
405 static OSStatus
nc_compare_subjectAltName_to_subtrees(void *context
, SecCEGeneralNameType gnType
, const DERItem
*generalName
) {
406 nc_san_match_context_t
*san_context
= context
;
407 CFArrayRef subtrees
= NULL
;
409 subtrees
= san_context
->subtrees
;
412 CFIndex num_trees
= CFArrayGetCount(subtrees
);
413 CFRange range
= { 0, num_trees
};
414 match_t match
= { false, false };
415 nc_match_context_t match_context
= {gnType
, generalName
, &match
};
416 CFArrayApplyFunction(subtrees
, range
, nc_decode_and_compare_subtree
, &match_context
);
419 * We set the SAN context match struct as follows:
420 * 'present' is true if there's any subtree of the same type as any SAN
421 * 'match' is false if the present type(s) is/are not supported or the subtree(s) and SAN(s) don't match.
422 * Note: the state of 'match' is meaningless without 'present' also being true.
424 if (match
.present
&& san_context
->match
) {
425 san_context
->match
->present
= true;
426 san_context
->match
->isMatch
&= match
.isMatch
;
429 return errSecSuccess
;
432 return errSecInvalidCertificate
;
435 OSStatus
SecNameContraintsMatchSubtrees(SecCertificateRef certificate
, CFArrayRef subtrees
, bool *matched
, bool permit
) {
436 CFDataRef subject
= NULL
;
437 OSStatus status
= errSecSuccess
;
438 CFArrayRef rfc822Names
= NULL
;
440 require_action_quiet(subject
= SecCertificateCopySubjectSequence(certificate
),
442 status
= errSecInvalidCertificate
);
443 const DERItem
*subjectAltNames
= SecCertificateGetSubjectAltName(certificate
);
445 /* Reject certificates with neither Subject Name nor SubjectAltName */
446 require_action_quiet(!isEmptySubject(subject
) || subjectAltNames
, out
, status
= errSecInvalidCertificate
);
448 /* Verify that the subject name is within any of the subtrees for X.500 distinguished names */
449 match_t subject_match
= { false, false };
450 nc_compare_subject_to_subtrees(subject
,subtrees
,&subject_match
);
452 match_t san_match
= { false, true };
453 nc_san_match_context_t san_context
= {subtrees
, &san_match
};
455 /* If there are no subjectAltNames, then determine if there's a matching emailAddress in the Subject */
456 if (!subjectAltNames
) {
457 rfc822Names
= SecCertificateCopyRFC822Names(certificate
);
458 /* If there's also no emailAddress field then subject match is enough. */
460 CFRange range
= { 0 , CFArrayGetCount(rfc822Names
) };
461 CFArrayApplyFunction(rfc822Names
, range
, nc_compare_RFC822Name_to_subtrees
, &san_context
);
465 /* And verify that each of the alternative names in the subjectAltName extension (critical or non-critical)
466 * is within any of the subtrees for that name type. */
467 status
= SecCertificateParseGeneralNames(subjectAltNames
,
469 nc_compare_subjectAltName_to_subtrees
);
470 /* If failed to parse */
471 require_action_quiet(status
== errSecSuccess
, out
, *matched
= false);
474 /* If we are excluding based on the subtrees, lack of names of the
475 same type is not a match. But if we are permitting, it is.
477 if (subject_match
.present
) {
478 if (san_match
.present
&&
479 ((subject_match
.isMatch
&& !san_match
.isMatch
) ||
480 (!subject_match
.isMatch
&& san_match
.isMatch
))) {
481 /* If both san and subject types are present, but don't agree on match
482 * we should exclude on the basis of the match and not permit on the
483 * basis of the failed match. */
484 *matched
= permit
? false : true;
487 /* If san type wasn't present or both had the same result, use the
488 * result from matching against the subject. */
489 *matched
= subject_match
.isMatch
;
492 else if (san_match
.present
) {
493 *matched
= san_match
.isMatch
;
496 /* Neither subject nor san had same type as subtrees, permit and don't
497 * exclude the cert. */
498 *matched
= permit
? true : false;
502 CFReleaseNull(subject
);
503 CFReleaseNull(rfc822Names
);
508 CFMutableArrayRef existing_trees
;
509 CFMutableArrayRef trees_to_add
;
510 } nc_intersect_context_t
;
512 static SecCEGeneralNameType
nc_gn_type_convert (DERTag tag
) {
514 case ASN1_CONTEXT_SPECIFIC
| ASN1_CONSTRUCTED
| 0:
515 return GNT_OtherName
;
516 case ASN1_CONTEXT_SPECIFIC
| 1:
517 return GNT_RFC822Name
;
518 case ASN1_CONTEXT_SPECIFIC
| 2:
520 case ASN1_CONTEXT_SPECIFIC
| ASN1_CONSTRUCTED
| 3:
521 return GNT_X400Address
;
522 case ASN1_CONTEXT_SPECIFIC
| ASN1_CONSTRUCTED
| 4:
523 return GNT_DirectoryName
;
524 case ASN1_CONTEXT_SPECIFIC
| ASN1_CONSTRUCTED
| 5:
525 return GNT_EdiPartyName
;
526 case ASN1_CONTEXT_SPECIFIC
| ASN1_CONSTRUCTED
| 6:
527 case ASN1_CONTEXT_SPECIFIC
| 6:
529 case ASN1_CONTEXT_SPECIFIC
| 7:
530 return GNT_IPAddress
;
531 case ASN1_CONTEXT_SPECIFIC
| 8:
532 return GNT_RegisteredID
;
534 return GNT_OtherName
;
538 /* The recommended processing algorithm states:
539 * If permittedSubtrees is present in the certificate, set the permitted_subtrees state variable to the intersection
540 * of its previous value and the value indicated in the extension field.
541 * However, in practice, certs are issued with permittedSubtrees whose intersection would be the empty set. For now,
542 * wherever a new permittedSubtree is a subset of an existing subtree, we'll replace the existing subtree; otherwise,
543 * we just append the new subtree.
545 static void nc_intersect_tree_with_subtrees (const void *value
, void *context
) {
546 CFDataRef new_subtree
= value
;
547 nc_intersect_context_t
*intersect_context
= context
;
548 CFMutableArrayRef existing_subtrees
= intersect_context
->existing_trees
;
549 CFMutableArrayRef trees_to_append
= intersect_context
->trees_to_add
;
551 if (!new_subtree
|| !existing_subtrees
) return;
553 /* convert new subtree to DERItem */
554 const DERItem general_name
= { (unsigned char *)CFDataGetBytePtr(new_subtree
), CFDataGetLength(new_subtree
) };
555 DERDecodedInfo general_name_content
;
556 if(DR_Success
!= DERDecodeItem(&general_name
, &general_name_content
)) return;
558 SecCEGeneralNameType gnType
;
559 DERItem
*new_subtree_item
= &general_name_content
.content
;
561 /* Attempt to intersect if one of the supported types: DirectoryName and DNSName.
562 * Otherwise, just append the new tree. */
563 gnType
= nc_gn_type_convert(general_name_content
.tag
);
564 if (!(gnType
== GNT_DirectoryName
|| gnType
== GNT_DNSName
)) {
565 CFArrayAppendValue(trees_to_append
, new_subtree
);
569 CFIndex num_existing_subtrees
= CFArrayGetCount(existing_subtrees
);
570 match_t match
= { false, false };
571 nc_match_context_t match_context
= { gnType
, new_subtree_item
, &match
};
572 for (subtreeIX
= 0; subtreeIX
< num_existing_subtrees
; subtreeIX
++) {
573 CFDataRef candidate_subtree
= CFArrayGetValueAtIndex(existing_subtrees
, subtreeIX
);
574 /* Convert candidate subtree to DERItem */
575 const DERItem candidate
= { (unsigned char *)CFDataGetBytePtr(candidate_subtree
), CFDataGetLength(candidate_subtree
) };
576 DERDecodedInfo candidate_content
;
577 /* We could probably just delete any subtrees in the array that don't decode */
578 if(DR_Success
!= DERDecodeItem(&candidate
, &candidate_content
)) continue;
580 /* first test whether new tree matches the existing tree */
581 OSStatus status
= SecCertificateParseGeneralNameContentProperty(candidate_content
.tag
,
582 &candidate_content
.content
,
585 if((status
== errSecSuccess
) && match
.present
&& match
.isMatch
) {
589 /* then test whether existing tree matches the new tree*/
590 match_t local_match
= { false , false };
591 nc_match_context_t local_match_context
= { nc_gn_type_convert(candidate_content
.tag
),
592 &candidate_content
.content
,
594 status
= SecCertificateParseGeneralNameContentProperty(general_name_content
.tag
,
595 &general_name_content
.content
,
596 &local_match_context
,
598 if((status
== errSecSuccess
) && local_match
.present
&& local_match
.isMatch
) {
602 if (subtreeIX
== num_existing_subtrees
) {
603 /* No matches found. Append new subtree */
604 CFArrayAppendValue(trees_to_append
, new_subtree
);
606 else if (match
.present
&& match
.isMatch
) {
607 /* new subtree \subseteq existing subtree, replace existing tree */
608 CFArraySetValueAtIndex(existing_subtrees
, subtreeIX
, new_subtree
);
610 /* existing subtree \subset new subtree, drop the new tree so as not to broaden constraints*/
615 void SecNameConstraintsIntersectSubtrees(CFMutableArrayRef subtrees_state
, CFArrayRef subtrees_new
) {
616 assert(subtrees_state
);
617 assert(subtrees_new
);
619 CFIndex num_new_trees
= CFArrayGetCount(subtrees_new
);
620 CFRange range
= { 0, num_new_trees
};
622 /* if existing subtrees state contains no subtrees, append new subtrees whole */
623 if (!CFArrayGetCount(subtrees_state
)) {
624 CFArrayAppendArray(subtrees_state
, subtrees_new
, range
);
628 CFMutableArrayRef trees_to_append
= NULL
;
629 trees_to_append
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
630 nc_intersect_context_t context
= { subtrees_state
, trees_to_append
};
631 CFArrayApplyFunction(subtrees_new
, range
, nc_intersect_tree_with_subtrees
, &context
);
633 /* don't append to the state until we've processed all the new trees */
634 num_new_trees
= CFArrayGetCount(trees_to_append
);
635 if (trees_to_append
&& num_new_trees
) {
636 range
.length
= num_new_trees
;
637 CFArrayAppendArray(subtrees_state
, trees_to_append
, range
);
640 CFReleaseNull(trees_to_append
);