]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/nameconstraints.c
Security-57336.10.29.tar.gz
[apple/security.git] / OSX / sec / securityd / nameconstraints.c
1 /*
2 * Copyright (c) 2015 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 /*
25 * nameconstraints.c - rfc5280 section 4.2.1.10 and later name constraints implementation.
26 */
27
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
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.
41 */
42 static bool SecDNSNameConstraintsMatch(CFStringRef DNSName, CFStringRef constraint) {
43 CFIndex clength = CFStringGetLength(constraint);
44 CFIndex dlength = CFStringGetLength(DNSName);
45
46 if (dlength < clength) return false;
47
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.
50 */
51 if ((dlength != clength) &&
52 ('.' != CFStringGetCharacterAtIndex(DNSName, dlength - clength -1))) {
53 return false;
54 }
55
56 CFRange compareRange = { dlength - clength, clength};
57
58 if (!CFStringCompareWithOptions(DNSName, constraint, compareRange, kCFCompareCaseInsensitive)) {
59 return true;
60 }
61
62 return false;
63 }
64
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.
75 */
76 static bool SecURIMatch(CFStringRef URI, CFStringRef hostname) {
77 bool result = false;
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);
83
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);
91 }
92 URI_hostname = CFStringCreateWithSubstring(kCFAllocatorDefault, URI, URI_hostname_range);
93
94 /* Hostname in URI must not begin with '.' */
95 require_quiet('.' != CFStringGetCharacterAtIndex(URI_hostname, 0), out);
96
97 CFIndex ulength = CFStringGetLength(URI_hostname);
98 CFIndex hlength = CFStringGetLength(hostname);
99 require_quiet(ulength >= hlength, out);
100 CFRange compare_range = { 0, hlength };
101
102 /* Allow one or more preceding labels */
103 if ('.' == CFStringGetCharacterAtIndex(hostname, 0)) {
104 compare_range.location = ulength - hlength;
105 }
106
107 if(kCFCompareEqualTo == CFStringCompareWithOptions(URI_hostname,
108 hostname,
109 compare_range,
110 kCFCompareCaseInsensitive)) {
111 result = true;
112 }
113
114 out:
115 CFReleaseNull(port_or_path_separator);
116 CFReleaseNull(URI_hostname);
117 return result;
118 }
119
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
131 URIs).
132 */
133 static bool SecRFC822NameMatch(CFStringRef emailAddress, CFStringRef constraint) {
134 CFRange mailbox_range = CFStringFind(constraint,CFSTR("@"),0);
135
136 /* Constraint specifies a particular mailbox. Perform full comparison. */
137 if (mailbox_range.location != kCFNotFound) {
138 if (!CFStringCompare(emailAddress, constraint, kCFCompareCaseInsensitive)) {
139 return true;
140 }
141 else return false;
142 }
143
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 };
148
149 /* Constraint specificies a particular host. Compare hostname of address. */
150 if ('.' != CFStringGetCharacterAtIndex(constraint, 0)) {
151 if (!CFStringCompareWithOptions(emailAddress, constraint, hostname_range, kCFCompareCaseInsensitive)) {
152 return true;
153 }
154 else return false;
155 }
156
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)) {
160 return true;
161 }
162
163 out:
164 return false;
165 }
166
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);
171
172 DERDecodedInfo subtreeName_content;
173 require_noerr_quiet(DERDecodeItem(subtreeName, &subtreeName_content), out);
174
175 if (certName->length > subtreeName->length) {
176 if(0 == memcmp(certName_content.content.data,
177 subtreeName_content.content.data,
178 subtreeName_content.content.length)) {
179 return true;
180 }
181 }
182
183 out:
184 return false;
185 }
186
187 static bool nc_compare_DNSNames(const DERItem *certName, const DERItem *subtreeName) {
188 bool result = false;
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);
197
198 if (SecDNSNameConstraintsMatch(certName_str, subtreeName_str)) {
199 result = true;
200 }
201
202 out:
203 CFReleaseNull(certName_str) ;
204 CFReleaseNull(subtreeName_str);
205 return result;
206 }
207
208 static bool nc_compare_URIs(const DERItem *certName, const DERItem *subtreeName) {
209 bool result = false;
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);
218
219 if (SecURIMatch(certName_str, subtreeName_str)) {
220 result = true;
221 }
222
223 out:
224 CFReleaseNull(certName_str);
225 CFReleaseNull(subtreeName_str);
226 return result;
227 }
228
229 static bool nc_compare_RFC822Names(const DERItem *certName, const DERItem *subtreeName) {
230 bool result = false;
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);
239
240 if (SecRFC822NameMatch(certName_str, subtreeName_str)) {
241 result = true;
242 }
243
244 out:
245 CFReleaseNull(certName_str);
246 CFReleaseNull(subtreeName_str);
247 return result;
248 }
249
250 static bool nc_compare_IPAddresses(const DERItem *certAddr, const DERItem *subtreeAddr) {
251 bool result = false;
252
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);
259
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])) {
263 return false;
264 }
265 }
266 return true;
267
268 out:
269 return result;
270 }
271
272 typedef struct {
273 bool present;
274 bool isMatch;
275 } match_t;
276
277 typedef struct {
278 const SecCEGeneralNameType gnType;
279 const DERItem *cert_item;
280 match_t *match;
281 } nc_match_context_t;
282
283 typedef struct {
284 const CFArrayRef subtrees;
285 match_t *match;
286 } nc_san_match_context_t;
287
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) {
292
293 item_context->match->present = true;
294 /*
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.
297 */
298 switch (gnType) {
299 case GNT_DirectoryName: {
300 item_context->match->isMatch |= nc_compare_directoryNames(item_context->cert_item, generalName);
301 return errSecSuccess;
302 }
303 case GNT_DNSName: {
304 item_context->match->isMatch |= nc_compare_DNSNames(item_context->cert_item, generalName);
305 return errSecSuccess;
306 }
307 case GNT_URI: {
308 item_context->match->isMatch |= nc_compare_URIs(item_context->cert_item, generalName);
309 return errSecSuccess;
310 }
311 case GNT_RFC822Name: {
312 item_context->match->isMatch |= nc_compare_RFC822Names(item_context->cert_item, generalName);
313 return errSecSuccess;
314 }
315 case GNT_IPAddress: {
316 item_context->match->isMatch |= nc_compare_IPAddresses(item_context->cert_item, generalName);
317 return errSecSuccess;
318 }
319 default: {
320 /* If the name form is not supported, reject the certificate. */
321 return errSecInvalidCertificate;
322 }
323 }
324 }
325
326 return errSecInvalidCertificate;
327 }
328
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;
332 if(subtree) {
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);
337
338 OSStatus status = SecCertificateParseGeneralNameContentProperty(general_name_content.tag,
339 &general_name_content.content,
340 match_context,
341 nc_compare_subtree);
342 if (status == errSecInvalidCertificate) {
343 secdebug("policy","can't parse general name or not a type we support");
344 }
345 }
346 out:
347 return;
348 }
349
350 static bool isEmptySubject(CFDataRef subject) {
351 const DERItem subject_der = { (unsigned char *)CFDataGetBytePtr(subject), CFDataGetLength(subject) };
352
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;
357
358 out:
359 return true;
360 }
361
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)) {
365 return;
366 }
367
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);
373 }
374
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;
379 if (san_context) {
380 subtrees = san_context->subtrees;
381 }
382 if (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);
390
391 /*
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.
396 */
397 if (match.present && san_context->match) {
398 san_context->match->present = true;
399 san_context->match->isMatch &= match.isMatch;
400 }
401 }
402
403 }
404
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;
408 if (san_context) {
409 subtrees = san_context->subtrees;
410 }
411 if (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);
417
418 /*
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.
423 */
424 if (match.present && san_context->match) {
425 san_context->match->present = true;
426 san_context->match->isMatch &= match.isMatch;
427 }
428
429 return errSecSuccess;
430 }
431
432 return errSecInvalidCertificate;
433 }
434
435 OSStatus SecNameContraintsMatchSubtrees(SecCertificateRef certificate, CFArrayRef subtrees, bool *matched, bool permit) {
436 CFDataRef subject = NULL;
437 OSStatus status = errSecSuccess;
438 CFArrayRef rfc822Names = NULL;
439
440 require_action_quiet(subject = SecCertificateCopySubjectSequence(certificate),
441 out,
442 status = errSecInvalidCertificate);
443 const DERItem *subjectAltNames = SecCertificateGetSubjectAltName(certificate);
444
445 /* Reject certificates with neither Subject Name nor SubjectAltName */
446 require_action_quiet(!isEmptySubject(subject) || subjectAltNames, out, status = errSecInvalidCertificate);
447
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);
451
452 match_t san_match = { false, true };
453 nc_san_match_context_t san_context = {subtrees, &san_match};
454
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. */
459 if (rfc822Names) {
460 CFRange range = { 0 , CFArrayGetCount(rfc822Names) };
461 CFArrayApplyFunction(rfc822Names, range, nc_compare_RFC822Name_to_subtrees, &san_context);
462 }
463 }
464 else {
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,
468 &san_context,
469 nc_compare_subjectAltName_to_subtrees);
470 /* If failed to parse */
471 require_action_quiet(status == errSecSuccess, out, *matched = false);
472 }
473
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.
476 This logic is unfortunately complicated and could be cleaned up with
477 two separate functions for excluded and permitted subtrees.
478 */
479 if (subject_match.present) {
480 if (san_match.present &&
481 ((subject_match.isMatch && !san_match.isMatch) ||
482 (!subject_match.isMatch && san_match.isMatch))) {
483 *matched = permit ? false : true;
484 }
485 else {
486 *matched = subject_match.isMatch;
487 }
488 }
489 else if (san_match.present) {
490 *matched = san_match.isMatch;
491 }
492 else {
493 *matched = permit ? true : false;
494 }
495
496 out:
497 CFReleaseNull(subject);
498 CFReleaseNull(rfc822Names);
499 return status;
500 }
501
502 /* The recommended processing algorithm states:
503 * If permittedSubtrees is present in the certificate, set the permitted_subtrees state variable to the intersection
504 * of its previous value and the value indicated in the extension field.
505 * However, in practice, certs are issued with permittedSubtrees whose intersection would be the empty set. Wherever
506 * a new permittedSubtree is a subset of an existing subtree, we'll replace the existing subtree; otherwise, we just
507 * append the new subtree.
508 */
509 static void nc_intersect_tree_with_subtrees (const void *value, void *context) {
510 CFDataRef new_subtree = value;
511 CFMutableArrayRef *existing_subtrees = context;
512
513 if (!new_subtree || !*existing_subtrees) return;
514
515 /* convert new subtree to DERItem */
516 const DERItem general_name = { (unsigned char *)CFDataGetBytePtr(new_subtree), CFDataGetLength(new_subtree) };
517 DERDecodedInfo general_name_content;
518 if(DR_Success != DERDecodeItem(&general_name, &general_name_content)) return;
519
520 SecCEGeneralNameType gnType;
521 DERItem *new_subtree_item = &general_name_content.content;
522
523 /* Attempt to intersect if one of the supported types: DirectoryName and DNSName.
524 * Otherwise, just append the new tree.
525 */
526 switch (general_name_content.tag) {
527 case ASN1_CONTEXT_SPECIFIC | 2: {
528 gnType = GNT_DNSName;
529 break;
530 }
531 case ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 4: {
532 gnType = GNT_DirectoryName;
533 break;
534 }
535 default: {
536 CFArrayAppendValue(*existing_subtrees, new_subtree);
537 return;
538 }
539 }
540
541 CFIndex subtreeIX;
542 CFIndex num_existing_subtrees = CFArrayGetCount(*existing_subtrees);
543 match_t match = { false, false };
544 nc_match_context_t match_context = { gnType, new_subtree_item, &match};
545 for (subtreeIX = 0; subtreeIX < num_existing_subtrees; subtreeIX++) {
546 CFDataRef candidate_subtree = CFArrayGetValueAtIndex(*existing_subtrees, subtreeIX);
547 /* Convert candidate subtree to DERItem */
548 const DERItem candidate = { (unsigned char *)CFDataGetBytePtr(candidate_subtree), CFDataGetLength(candidate_subtree) };
549 DERDecodedInfo candidate_content;
550 /* We could probably just delete any subtrees in the array that don't decode */
551 if(DR_Success != DERDecodeItem(&candidate, &candidate_content)) continue;
552
553 OSStatus status = SecCertificateParseGeneralNameContentProperty(candidate_content.tag,
554 &candidate_content.content,
555 &match_context,
556 nc_compare_subtree);
557 if((status == errSecSuccess) && match.present && match.isMatch) {
558 break;
559 }
560 }
561 if (subtreeIX == num_existing_subtrees) {
562 /* No matches found. Append new subtree */
563 CFArrayAppendValue(*existing_subtrees, new_subtree);
564 }
565 else {
566 CFArraySetValueAtIndex(*existing_subtrees, subtreeIX, new_subtree);
567 }
568 return;
569
570 }
571
572 void SecNameConstraintsIntersectSubtrees(CFMutableArrayRef subtrees_state, CFArrayRef subtrees_new) {
573 assert(subtrees_state);
574 assert(subtrees_new);
575
576 CFIndex num_new_trees = CFArrayGetCount(subtrees_new);
577 CFRange range = { 0, num_new_trees };
578 CFArrayApplyFunction(subtrees_new, range, nc_intersect_tree_with_subtrees, &subtrees_state);
579 }