2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
20 policies.cpp - TP module policy implementation
22 Created 10/9/2000 by Doug Mitchell.
25 #include <Security/cssmtype.h>
26 #include <Security/cssmapi.h>
27 #include "tpPolicies.h"
28 #include <Security/oidsattr.h>
29 #include <Security/cssmerr.h>
30 #include "tpdebugging.h"
31 #include "rootCerts.h"
32 #include "certGroupUtils.h"
33 #include <Security/x509defs.h>
34 #include <Security/oidscert.h>
35 #include <Security/certextensions.h>
36 #include <Security/cssmapple.h>
43 * Our private per-extension info. One of these per (understood) extension per
49 CE_Data
*extnData
; // mallocd by CL
50 CSSM_DATA
*valToFree
; // the data we pass to freeField()
54 * Struct to keep track of info pertinent to one cert.
58 /* extensions we're interested in */
59 iSignExtenInfo authorityId
;
60 iSignExtenInfo subjectId
;
61 iSignExtenInfo keyUsage
;
62 iSignExtenInfo extendKeyUsage
;
63 iSignExtenInfo basicConstraints
;
64 iSignExtenInfo netscapeCertType
;
65 iSignExtenInfo subjectAltName
;
67 /* flag indicating presence of a critical extension we don't understand */
68 CSSM_BOOL foundUnknownCritical
;
74 * Setup a single iSignExtenInfo. Called once per known extension
77 static CSSM_RETURN
tpSetupExtension(
80 iSignExtenInfo
*extnInfo
) // which component of certInfo
82 if(extnData
->Length
!= sizeof(CSSM_X509_EXTENSION
)) {
83 tpPolicyError("tpSetupExtension: malformed CSSM_FIELD");
84 return CSSMERR_TP_UNKNOWN_FORMAT
;
86 CSSM_X509_EXTENSION
*cssmExt
= (CSSM_X509_EXTENSION
*)extnData
->Data
;
87 extnInfo
->present
= CSSM_TRUE
;
88 extnInfo
->critical
= cssmExt
->critical
;
89 extnInfo
->extnData
= (CE_Data
*)cssmExt
->value
.parsedValue
;
90 extnInfo
->valToFree
= extnData
;
95 * Fetch a known extension, set up associated iSignExtenInfo if present.
97 static CSSM_RETURN
iSignFetchExtension(
100 const CSSM_OID
*fieldOid
, // which extension to fetch
101 iSignExtenInfo
*extnInfo
) // where the info goes
103 CSSM_DATA_PTR fieldValue
; // mallocd by CL
106 crtn
= tpCert
->fetchField(fieldOid
, &fieldValue
);
110 case CSSMERR_CL_NO_FIELD_VALUES
:
111 /* field not present, OK */
116 return tpSetupExtension(alloc
,
122 * Search for al unknown extensions. If we find one which is flagged critical,
123 * flag certInfo->foundUnknownCritical. Only returns error on gross errors.
125 static CSSM_RETURN
iSignSearchUnknownExtensions(
127 iSignCertInfo
*certInfo
)
130 CSSM_DATA_PTR fieldValue
= NULL
;
131 CSSM_HANDLE searchHand
= CSSM_INVALID_HANDLE
;
132 uint32 numFields
= 0;
134 crtn
= CSSM_CL_CertGetFirstCachedFieldValue(tpCert
->clHand(),
136 &CSSMOID_X509V3CertificateExtensionCStruct
,
142 /* found one, proceed */
144 case CSSMERR_CL_NO_FIELD_VALUES
:
145 /* no unknown extensions present, OK */
151 if(fieldValue
->Length
!= sizeof(CSSM_X509_EXTENSION
)) {
152 tpPolicyError("iSignSearchUnknownExtensions: malformed CSSM_FIELD");
153 return CSSMERR_TP_UNKNOWN_FORMAT
;
155 CSSM_X509_EXTENSION
*cssmExt
= (CSSM_X509_EXTENSION
*)fieldValue
->Data
;
156 if(cssmExt
->critical
) {
157 /* BRRZAPP! Found an unknown extension marked critical */
158 certInfo
->foundUnknownCritical
= CSSM_TRUE
;
161 CSSM_CL_FreeFieldValue(tpCert
->clHand(),
162 &CSSMOID_X509V3CertificateExtensionCStruct
,
166 /* process remaining unknown extensions */
167 for(unsigned i
=1; i
<numFields
; i
++) {
168 crtn
= CSSM_CL_CertGetNextCachedFieldValue(tpCert
->clHand(),
172 /* should never happen */
173 tpPolicyError("searchUnknownExtensions: GetNextCachedFieldValue"
177 if(fieldValue
->Length
!= sizeof(CSSM_X509_EXTENSION
)) {
178 tpPolicyError("iSignSearchUnknownExtensions: "
179 "malformed CSSM_FIELD");
180 crtn
= CSSMERR_TP_UNKNOWN_FORMAT
;
183 CSSM_X509_EXTENSION
*cssmExt
= (CSSM_X509_EXTENSION
*)fieldValue
->Data
;
184 if(cssmExt
->critical
) {
185 /* BRRZAPP! Found an unknown extension marked critical */
186 certInfo
->foundUnknownCritical
= CSSM_TRUE
;
189 CSSM_CL_FreeFieldValue(tpCert
->clHand(),
190 &CSSMOID_X509V3CertificateExtensionCStruct
,
193 } /* for additional fields */
197 CSSM_CL_FreeFieldValue(tpCert
->clHand(),
198 &CSSMOID_X509V3CertificateExtensionCStruct
,
201 if(searchHand
!= CSSM_INVALID_HANDLE
) {
202 CSSM_CL_CertAbortQuery(tpCert
->clHand(), searchHand
);
207 * Given a TPCertInfo, fetch the associated iSignCertInfo fields.
208 * Returns CSSM_FAIL on error.
210 static CSSM_RETURN
iSignGetCertInfo(
211 CssmAllocator
&alloc
,
213 iSignCertInfo
*certInfo
)
217 /* first grind thru the extensions we're interested in */
218 crtn
= iSignFetchExtension(alloc
,
220 &CSSMOID_AuthorityKeyIdentifier
,
221 &certInfo
->authorityId
);
225 crtn
= iSignFetchExtension(alloc
,
227 &CSSMOID_SubjectKeyIdentifier
,
228 &certInfo
->subjectId
);
232 crtn
= iSignFetchExtension(alloc
,
235 &certInfo
->keyUsage
);
239 crtn
= iSignFetchExtension(alloc
,
241 &CSSMOID_ExtendedKeyUsage
,
242 &certInfo
->extendKeyUsage
);
246 crtn
= iSignFetchExtension(alloc
,
248 &CSSMOID_BasicConstraints
,
249 &certInfo
->basicConstraints
);
253 crtn
= iSignFetchExtension(alloc
,
255 &CSSMOID_NetscapeCertType
,
256 &certInfo
->netscapeCertType
);
260 crtn
= iSignFetchExtension(alloc
,
262 &CSSMOID_SubjectAltName
,
263 &certInfo
->subjectAltName
);
268 /* now look for extensions we don't understand - the only thing we're interested
269 * in is the critical flag. */
270 return iSignSearchUnknownExtensions(tpCert
, certInfo
);
274 * Free (via CL) the fields allocated in iSignGetCertInfo().
276 static void iSignFreeCertInfo(
277 CSSM_CL_HANDLE clHand
,
278 iSignCertInfo
*certInfo
)
280 if(certInfo
->authorityId
.present
) {
281 CSSM_CL_FreeFieldValue(clHand
, &CSSMOID_AuthorityKeyIdentifier
,
282 certInfo
->authorityId
.valToFree
);
284 if(certInfo
->subjectId
.present
) {
285 CSSM_CL_FreeFieldValue(clHand
, &CSSMOID_SubjectKeyIdentifier
,
286 certInfo
->subjectId
.valToFree
);
288 if(certInfo
->keyUsage
.present
) {
289 CSSM_CL_FreeFieldValue(clHand
, &CSSMOID_KeyUsage
,
290 certInfo
->keyUsage
.valToFree
);
292 if(certInfo
->extendKeyUsage
.present
) {
293 CSSM_CL_FreeFieldValue(clHand
, &CSSMOID_ExtendedKeyUsage
,
294 certInfo
->extendKeyUsage
.valToFree
);
296 if(certInfo
->basicConstraints
.present
) {
297 CSSM_CL_FreeFieldValue(clHand
, &CSSMOID_BasicConstraints
,
298 certInfo
->basicConstraints
.valToFree
);
300 if(certInfo
->netscapeCertType
.present
) {
301 CSSM_CL_FreeFieldValue(clHand
, &CSSMOID_NetscapeCertType
,
302 certInfo
->netscapeCertType
.valToFree
);
304 if(certInfo
->subjectAltName
.present
) {
305 CSSM_CL_FreeFieldValue(clHand
, &CSSMOID_SubjectAltName
,
306 certInfo
->subjectAltName
.valToFree
);
311 * See if cert's Subject.{commonName,EmailAddress} matches caller-specified
312 * string. Returns CSSM_TRUE if match, else returns CSSM_FALSE.
313 * Also indicates whether *any* of the specified fields were found, regardless
317 SN_CommonName
, // CSSMOID_CommonName, host name format
318 SN_Email
// CSSMOID_EmailAddress
319 } SubjSubjNameSearchType
;
321 static CSSM_BOOL
tpCompareSubjectName(
323 SubjSubjNameSearchType searchType
,
324 const char *callerStr
, // already tpToLower'd
328 char *certName
= NULL
; // from cert's subject name
329 uint32 certNameLen
= 0;
330 CSSM_DATA_PTR subjNameData
= NULL
;
332 CSSM_BOOL ourRtn
= CSSM_FALSE
;
333 const CSSM_OID
*oidSrch
;
338 oidSrch
= &CSSMOID_CommonName
;
341 oidSrch
= &CSSMOID_EmailAddress
;
347 crtn
= cert
.fetchField(&CSSMOID_X509V1SubjectNameCStruct
, &subjNameData
);
349 /* should never happen, we shouldn't be here if there is no subject */
350 tpPolicyError("tp_verifySslOpts: error retrieving subject name");
353 CSSM_X509_NAME_PTR x509name
= (CSSM_X509_NAME_PTR
)subjNameData
->Data
;
354 if((x509name
== NULL
) || (subjNameData
->Length
!= sizeof(CSSM_X509_NAME
))) {
355 tpPolicyError("tp_verifySslOpts: malformed CSSM_X509_NAME");
356 cert
.freeField(&CSSMOID_X509V1SubjectNameCStruct
, subjNameData
);
360 /* Now grunge thru the X509 name looking for a common name */
361 CSSM_X509_TYPE_VALUE_PAIR
*ptvp
;
362 CSSM_X509_RDN_PTR rdnp
;
366 for(rdnDex
=0; rdnDex
<x509name
->numberOfRDNs
; rdnDex
++) {
367 rdnp
= &x509name
->RelativeDistinguishedName
[rdnDex
];
368 for(pairDex
=0; pairDex
<rdnp
->numberOfPairs
; pairDex
++) {
369 ptvp
= &rdnp
->AttributeTypeAndValue
[pairDex
];
370 if(tpCompareOids(&ptvp
->type
, oidSrch
)) {
372 certName
= (char *)ptvp
->value
.Data
;
373 certNameLen
= ptvp
->value
.Length
;
376 ourRtn
= tpCompareHostNames(callerStr
, callerStrLen
,
377 certName
, certNameLen
);
380 ourRtn
= tpCompareEmailAddr(callerStr
, callerStrLen
,
381 certName
, certNameLen
);
388 /* else keep going, maybe there's another common name */
395 cert
.freeField(&CSSMOID_X509V1SubjectNameCStruct
, subjNameData
);
400 * Compare ASCII form of an IP address to a CSSM_DATA containing
401 * the IP address's numeric components. Returns true on match.
403 static CSSM_BOOL
tpCompIpAddrStr(
406 const CSSM_DATA
*numeric
)
408 const char *cp
= str
;
412 if((numeric
== NULL
) || (numeric
->Length
== 0) || (str
== NULL
)) {
415 if(cp
[strLen
- 1] == '\0') {
416 /* ignore NULL terminator */
419 for(unsigned dex
=0; dex
<numeric
->Length
; dex
++) {
420 /* cp points to start of current string digit */
422 const char *lastChar
= cp
+ strLen
;
424 for( ; nextDot
<lastChar
; nextDot
++) {
425 if(*nextDot
== '.') {
429 if(nextDot
== lastChar
) {
430 /* legal and required on last digit */
431 if(dex
!= (numeric
->Length
- 1)) {
435 else if(dex
== (numeric
->Length
- 1)) {
438 unsigned digLen
= nextDot
- cp
;
439 if(digLen
>= sizeof(buf
)) {
443 memmove(buf
, cp
, digLen
);
445 /* incr digLen to include the next dot */
449 int digVal
= atoi(buf
);
450 if(digVal
!= numeric
->Data
[dex
]) {
458 * See if cert's subjectAltName contains an element matching caller-specified
459 * string, hostname, in the following forms:
461 * SAN_HostName : dnsName, iPAddress
462 * SAN_Email : RFC822Name
464 * Returns CSSM_TRUE if match, else returns CSSM_FALSE.
466 * Also indicates whether or not a dnsName (search type HostName) or
467 * RFC822Name (search type SAM_Email) was found, regardless of result
470 * The appStr/appStrLen args are optional - if NULL/0, only the
471 * search for dnsName/RFC822Name is done.
476 } SubjAltNameSearchType
;
478 static CSSM_BOOL
tpCompareSubjectAltName(
479 const iSignExtenInfo
&subjAltNameInfo
,
482 SubjAltNameSearchType searchType
,
483 bool &dnsNameFound
, // RETURNED, SAN_HostName case
484 bool &emailFound
) // RETURNED, SAN_Email case
486 dnsNameFound
= false;
488 if(!subjAltNameInfo
.present
) {
489 /* common failure, no subjectAltName found */
493 CE_GeneralNames
*names
= &subjAltNameInfo
.extnData
->subjectAltName
;
494 CSSM_BOOL ourRtn
= CSSM_FALSE
;
496 unsigned certNameLen
;
498 /* Search thru the CE_GeneralNames looking for the appropriate attribute */
499 for(unsigned dex
=0; dex
<names
->numNames
; dex
++) {
500 CE_GeneralName
*name
= &names
->generalName
[dex
];
503 switch(name
->nameType
) {
506 /* nothing to do here */
509 ourRtn
= tpCompIpAddrStr(appStr
, appStrLen
, &name
->name
);
513 if(name
->berEncoded
) {
514 tpErrorLog("tpCompareSubjectAltName: malformed "
515 "CE_GeneralName (1)\n");
518 certName
= (char *)name
->name
.Data
;
519 if(certName
== NULL
) {
520 tpErrorLog("tpCompareSubjectAltName: malformed "
521 "CE_GeneralName (2)\n");
524 certNameLen
= name
->name
.Length
;
527 /* skip if caller passed in NULL */
528 ourRtn
= tpCompareHostNames(appStr
, appStrLen
,
529 certName
, certNameLen
);
534 /* not interested, proceed to next name */
537 break; /* from case HostName */
540 if(name
->nameType
!= GNT_RFC822Name
) {
544 certName
= (char *)name
->name
.Data
;
545 if(certName
== NULL
) {
546 tpErrorLog("tpCompareSubjectAltName: malformed "
550 certNameLen
= name
->name
.Length
;
553 ourRtn
= tpCompareEmailAddr(appStr
, appStrLen
, certName
,
566 /* is host name in the form of a.b.c.d, where a,b,c, and d are digits? */
567 static CSSM_BOOL
tpIsNumeric(
568 const char *hostName
,
569 unsigned hostNameLen
)
571 if(hostName
[hostNameLen
- 1] == '\0') {
572 /* ignore NULL terminator */
575 for(unsigned i
=0; i
<hostNameLen
; i
++) {
576 char c
= *hostName
++;
588 * Verify SSL options. Currently this just consists of matching the
589 * leaf cert's subject common name against the caller's (optional)
592 static CSSM_RETURN
tp_verifySslOpts(
593 TPCertGroup
&certGroup
,
594 const CSSM_DATA
*sslFieldOpts
,
595 const iSignCertInfo
&leafCertInfo
)
597 /* first validate optional SSL options */
598 if((sslFieldOpts
== NULL
) || (sslFieldOpts
->Data
== NULL
)) {
602 CSSM_APPLE_TP_SSL_OPTIONS
*sslOpts
;
603 sslOpts
= (CSSM_APPLE_TP_SSL_OPTIONS
*)sslFieldOpts
->Data
;
604 switch(sslOpts
->Version
) {
605 case CSSM_APPLE_TP_SSL_OPTS_VERSION
:
606 if(sslFieldOpts
->Length
!= sizeof(CSSM_APPLE_TP_SSL_OPTIONS
)) {
607 return CSSMERR_TP_INVALID_POLICY_IDENTIFIERS
;
610 /* handle backwards compatibility here if necessary */
612 return CSSMERR_TP_INVALID_POLICY_IDENTIFIERS
;
615 unsigned hostNameLen
= sslOpts
->ServerNameLen
;
617 if(hostNameLen
== 0) {
621 if(sslOpts
->ServerName
== NULL
) {
622 return CSSMERR_TP_INVALID_POINTER
;
625 /* convert caller's hostname string to lower case */
626 char *hostName
= (char *)certGroup
.alloc().malloc(hostNameLen
);
627 memmove(hostName
, sslOpts
->ServerName
, hostNameLen
);
628 tpToLower(hostName
, hostNameLen
);
630 TPCertInfo
*leaf
= certGroup
.certAtIndex(0);
631 assert(leaf
!= NULL
);
633 CSSM_BOOL match
= CSSM_FALSE
;
635 /* First check subjectAltName... */
636 bool dnsNameFound
= false;
638 match
= tpCompareSubjectAltName(leafCertInfo
.subjectAltName
,
639 hostName
, hostNameLen
,
640 SAN_HostName
, dnsNameFound
, dummy
);
643 * Then common name, if
644 * -- no match from subjectAltName, AND
645 * -- dnsName was NOT found, AND
646 * -- hostName is not strictly numeric form (1.2.3.4)
648 if(!match
&& !dnsNameFound
&& !tpIsNumeric(hostName
, hostNameLen
)) {
650 match
= tpCompareSubjectName(*leaf
, SN_CommonName
, hostName
, hostNameLen
,
653 certGroup
.alloc().free(hostName
);
658 leaf
->addStatusCode(CSSMERR_APPLETP_HOSTNAME_MISMATCH
);
659 return CSSMERR_TP_VERIFY_ACTION_FAILED
;
664 * Verify SMIME options.
666 #define CE_CIPHER_MASK (~(CE_KU_EncipherOnly | CE_KU_DecipherOnly))
668 static CSSM_RETURN
tp_verifySmimeOpts(
669 TPCertGroup
&certGroup
,
670 const CSSM_DATA
*smimeFieldOpts
,
671 const iSignCertInfo
&leafCertInfo
)
674 * First validate optional S/MIME options.
676 CSSM_APPLE_TP_SMIME_OPTIONS
*smimeOpts
= NULL
;
677 if(smimeFieldOpts
!= NULL
) {
678 smimeOpts
= (CSSM_APPLE_TP_SMIME_OPTIONS
*)smimeFieldOpts
->Data
;
680 if(smimeOpts
!= NULL
) {
681 switch(smimeOpts
->Version
) {
682 case CSSM_APPLE_TP_SMIME_OPTS_VERSION
:
683 if(smimeFieldOpts
->Length
!=
684 sizeof(CSSM_APPLE_TP_SMIME_OPTIONS
)) {
685 return CSSMERR_TP_INVALID_POLICY_IDENTIFIERS
;
688 /* handle backwards compatibility here if necessary */
690 return CSSMERR_TP_INVALID_POLICY_IDENTIFIERS
;
694 TPCertInfo
*leaf
= certGroup
.certAtIndex(0);
695 assert(leaf
!= NULL
);
697 /* Verify optional email address */
698 unsigned emailLen
= 0;
699 if(smimeOpts
!= NULL
) {
700 emailLen
= smimeOpts
->SenderEmailLen
;
702 bool emailFoundInSAN
= false;
704 if(smimeOpts
->SenderEmail
== NULL
) {
705 return CSSMERR_TP_INVALID_POINTER
;
708 /* normalize caller's email string */
709 char *email
= (char *)certGroup
.alloc().malloc(emailLen
);
710 memmove(email
, smimeOpts
->SenderEmail
, emailLen
);
711 tpNormalizeAddrSpec(email
, emailLen
);
713 CSSM_BOOL match
= false;
716 * First check subjectAltName. The emailFound bool indicates
717 * that *some* email address was found, regardless of a match
721 match
= tpCompareSubjectAltName(leafCertInfo
.subjectAltName
,
723 SAN_Email
, dummy
, emailFoundInSAN
);
726 * Then subject DN, CSSMOID_EmailAddress, if no match from
729 bool emailFoundInDn
= false;
731 match
= tpCompareSubjectName(*leaf
, SN_Email
, email
, emailLen
,
734 certGroup
.alloc().free(email
);
737 * Error here only if no match found but there was indeed *some*
738 * email address in the cert.
740 if(!match
&& (emailFoundInSAN
|| emailFoundInDn
)) {
741 leaf
->addStatusCode(CSSMERR_APPLETP_SMIME_EMAIL_ADDRS_NOT_FOUND
);
742 tpPolicyError("SMIME email addrs in cert but no match");
743 return CSSMERR_TP_VERIFY_ACTION_FAILED
;
748 * Going by the letter of the law, here's what RFC 2632 has to say
749 * about the legality of an empty Subject Name:
751 * ...the subject DN in a user's (i.e. end-entity) certificate MAY
752 * be an empty SEQUENCE in which case the subjectAltName extension
753 * will include the subject's identifier and MUST be marked as
756 * OK, first examine the leaf cert's subject name.
759 CSSM_DATA_PTR subjNameData
= NULL
;
760 crtn
= leaf
->fetchField(&CSSMOID_X509V1SubjectNameCStruct
, &subjNameData
);
762 /* This should really never happen */
763 tpPolicyError("SMIME policy: error fetching subjectName");
764 leaf
->addStatusCode(CSSMERR_TP_INVALID_CERTIFICATE
);
765 return CSSMERR_TP_INVALID_CERTIFICATE
;
767 /* must do a leaf->freeField(&CSSMOID_X509V1SubjectNameCStruct on exit */
769 const CSSM_X509_NAME
*x509Name
= (const CSSM_X509_NAME
*)subjNameData
->Data
;
770 if(x509Name
->numberOfRDNs
== 0) {
772 * Empty subject name. If we haven't already seen a valid
773 * email address in the subject alternate name (by looking
774 * for a specific address specified by app), try to find
777 if(!emailFoundInSAN
&& // haven't found one, and
778 (emailLen
== 0)) { // didn't even look yet
780 tpCompareSubjectAltName(leafCertInfo
.subjectAltName
,
781 NULL
, 0, // email, emailLen,
783 emailFoundInSAN
); // the variable we're updating
785 if(!emailFoundInSAN
) {
786 tpPolicyError("SMIME policy fail: empty subject name and "
787 "no Email Addrs in SubjectAltName");
788 leaf
->addStatusCode(CSSMERR_APPLETP_SMIME_NO_EMAIL_ADDRS
);
789 leaf
->freeField(&CSSMOID_X509V1SubjectNameCStruct
, subjNameData
);
790 return CSSMERR_TP_VERIFY_ACTION_FAILED
;
794 * One more thing: this leaf must indeed have a subjAltName
795 * extension and it must be critical. We would not have gotten this
796 * far if the subjAltName extension was not actually present....
798 assert(leafCertInfo
.subjectAltName
.present
);
799 if(!leafCertInfo
.subjectAltName
.critical
) {
800 tpPolicyError("SMIME policy fail: empty subject name and "
801 "no Email Addrs in SubjectAltName");
802 leaf
->addStatusCode(CSSMERR_APPLETP_SMIME_SUBJ_ALT_NAME_NOT_CRIT
);
803 leaf
->freeField(&CSSMOID_X509V1SubjectNameCStruct
, subjNameData
);
804 return CSSMERR_TP_VERIFY_ACTION_FAILED
;
807 leaf
->freeField(&CSSMOID_X509V1SubjectNameCStruct
, subjNameData
);
810 * Enforce the usage of the key associated with the leaf cert.
811 * Cert's KeyUsage must be a superset of what the app is trying to do.
812 * Note the {en,de}cipherONly flags are handledÊseparately....
814 const iSignExtenInfo
&kuInfo
= leafCertInfo
.keyUsage
;
816 CE_KeyUsage certKu
= *((CE_KeyUsage
*)kuInfo
.extnData
);
817 CE_KeyUsage appKu
= smimeOpts
->IntendedUsage
;
818 CE_KeyUsage intersection
= certKu
& appKu
;
819 if((intersection
& CE_CIPHER_MASK
) != (appKu
& CE_CIPHER_MASK
)) {
820 tpPolicyError("SMIME KeyUsage err: appKu 0x%x certKu 0x%x",
822 leaf
->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_KEY_USE
);
823 return CSSMERR_TP_VERIFY_ACTION_FAILED
;
826 /* Now the en/de cipher only bits - for keyAgreement only */
827 if(appKu
& CE_KU_KeyAgreement
) {
829 * 1. App wants to use this for key agreement; it must
830 * say what it wants to do with the derived key.
831 * In this context, the app's XXXonly bit means that
832 * it wants to use the key for that op - not necessarliy
835 if((appKu
& (CE_KU_EncipherOnly
| CE_KU_DecipherOnly
)) == 0) {
836 tpPolicyError("SMIME KeyUsage err: KeyAgreement with "
837 "no Encipher or Decipher");
838 leaf
->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_KEY_USE
);
839 return CSSMERR_TP_VERIFY_ACTION_FAILED
;
843 * 2. If cert restricts to encipher only make sure the
844 * app isn't trying to decipher.
846 if((certKu
& CE_KU_EncipherOnly
) &&
847 (appKu
& CE_KU_DecipherOnly
)) {
848 tpPolicyError("SMIME KeyUsage err: cert EncipherOnly, "
849 "app wants to decipher");
850 leaf
->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_KEY_USE
);
851 return CSSMERR_TP_VERIFY_ACTION_FAILED
;
855 * 3. If cert restricts to decipher only make sure the
856 * app isn't trying to encipher.
858 if((certKu
& CE_KU_DecipherOnly
) &&
859 (appKu
& CE_KU_EncipherOnly
)) {
860 tpPolicyError("SMIME KeyUsage err: cert DecipherOnly, "
861 "app wants to encipher");
862 leaf
->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_KEY_USE
);
863 return CSSMERR_TP_VERIFY_ACTION_FAILED
;
869 * Ensure that, if an extendedKeyUsage extension is present in the
870 * leaf, that either emailProtection or anyExtendedKeyUsage usages is present
872 const iSignExtenInfo
&ekuInfo
= leafCertInfo
.extendKeyUsage
;
873 if(ekuInfo
.present
) {
874 bool foundGoodEku
= false;
875 CE_ExtendedKeyUsage
*eku
= (CE_ExtendedKeyUsage
*)ekuInfo
.extnData
;
877 for(unsigned i
=0; i
<eku
->numPurposes
; i
++) {
878 if(tpCompareOids(&eku
->purposes
[i
], &CSSMOID_EmailProtection
)) {
882 if(tpCompareOids(&eku
->purposes
[i
], &CSSMOID_ExtendedKeyUsageAny
)) {
888 leaf
->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_EXT_KEY_USE
);
889 return CSSMERR_TP_VERIFY_ACTION_FAILED
;
897 * RFC2459 says basicConstraints must be flagged critical for
898 * CA certs, but Verisign doesn't work that way.
900 #define BASIC_CONSTRAINTS_MUST_BE_CRITICAL 0
903 * TP iSign spec says Extended Key Usage required for leaf certs,
904 * but Verisign doesn't work that way.
906 #define EXTENDED_KEY_USAGE_REQUIRED_FOR_LEAF 0
909 * TP iSign spec says Subject Alternate Name required for leaf certs,
910 * but Verisign doesn't work that way.
912 #define SUBJECT_ALT_NAME_REQUIRED_FOR_LEAF 0
915 * TP iSign spec originally required KeyUsage for all certs, but
916 * Verisign doesn't have that in their roots.
918 #define KEY_USAGE_REQUIRED_FOR_ROOT 0
921 * RFC 2632, "S/MIME Version 3 Certificate Handling", section
922 * 4.4.2, says that KeyUsage extensions MUST be flagged critical,
923 * but Thawte's intermediate cert (common namd "Thawte Personal
924 * Freemail Issuing CA" does not meet this requirement.
926 #define SMIME_KEY_USAGE_MUST_BE_CRITICAL 0
929 * Public routine to perform TP verification on a constructed
931 * Returns CSSM_TRUE on success.
932 * Asumes the chain has passed basic subject/issuer verification. First cert of
933 * incoming certGroup is end-entity (leaf).
935 * Per-policy details:
936 * iSign: Assumes that last cert in incoming certGroup is a root cert.
937 * Also assumes a cert group of more than one cert.
938 * kTPx509Basic: CertGroup of length one allowed.
940 CSSM_RETURN
tp_policyVerify(
942 CssmAllocator
&alloc
,
943 CSSM_CL_HANDLE clHand
,
944 CSSM_CSP_HANDLE cspHand
,
945 TPCertGroup
*certGroup
,
946 CSSM_BOOL verifiedToRoot
, // last cert is good root
947 CSSM_APPLE_TP_ACTION_FLAGS actionFlags
,
948 const CSSM_DATA
*policyFieldData
, // optional
949 void *policyOpts
) // future options
951 iSignCertInfo
*certInfo
= NULL
;
953 iSignCertInfo
*thisCertInfo
;
957 CSSM_BOOL cA
= CSSM_FALSE
; // init for compiler warning
958 bool isLeaf
; // end entity
959 bool isRoot
; // root cert
960 CE_ExtendedKeyUsage
*extendUsage
;
961 CE_AuthorityKeyID
*authorityId
;
962 CSSM_RETURN outErr
= CSSM_OK
; // for gross, non-policy errors
963 CSSM_BOOL policyFail
= CSSM_FALSE
;
965 /* First, kTPDefault is a nop here */
966 if(policy
== kTPDefault
) {
970 if(certGroup
== NULL
) {
971 return CSSMERR_TP_INVALID_CERTGROUP
;
973 numCerts
= certGroup
->numCerts();
975 return CSSMERR_TP_INVALID_CERTGROUP
;
977 if(policy
== kTPiSign
) {
978 if(!verifiedToRoot
) {
979 /* no way, this requires a root cert */
980 return CSSMERR_TP_VERIFY_ACTION_FAILED
;
983 /* nope, not for iSign */
984 return CSSMERR_TP_VERIFY_ACTION_FAILED
;
988 /* cook up an iSignCertInfo array */
989 certInfo
= (iSignCertInfo
*)tpCalloc(alloc
, numCerts
, sizeof(iSignCertInfo
));
990 /* subsequent errors to errOut: */
992 /* fill it with interesting info from parsed certs */
993 for(certDex
=0; certDex
<numCerts
; certDex
++) {
994 if(iSignGetCertInfo(alloc
,
995 certGroup
->certAtIndex(certDex
),
996 &certInfo
[certDex
])) {
997 (certGroup
->certAtIndex(certDex
))->addStatusCode(
998 CSSMERR_TP_INVALID_CERTIFICATE
);
999 /* this one is fatal */
1000 outErr
= CSSMERR_TP_INVALID_CERTIFICATE
;
1006 * OK, the heart of TP enforcement.
1007 * First check for presence of required extensions and
1008 * critical extensions we don't understand.
1010 for(certDex
=0; certDex
<numCerts
; certDex
++) {
1011 thisCertInfo
= &certInfo
[certDex
];
1012 TPCertInfo
*thisTpCertInfo
= certGroup
->certAtIndex(certDex
);
1014 if(thisCertInfo
->foundUnknownCritical
) {
1015 /* illegal for all policies */
1016 tpPolicyError("tp_policyVerify: critical flag in unknown "
1018 thisTpCertInfo
->addStatusCode(CSSMERR_APPLETP_UNKNOWN_CRITICAL_EXTEN
);
1019 policyFail
= CSSM_TRUE
;
1023 * Note it's possible for both of these to be true, for a
1024 * of length one (kTPx509Basic, kCrlPolicy only!)
1025 * FIXME: should this code work of the last cert in the chain is
1028 isLeaf
= thisTpCertInfo
->isLeaf();
1029 isRoot
= thisTpCertInfo
->isSelfSigned();
1032 * BasicConstraints.cA
1033 * iSign: required in all but leaf and root,
1034 * for which it is optional (with default values of false
1035 * for leaf and true for root).
1038 * kTP_SMIME always optional, default of false for leaf and
1040 * All: cA must be false for leaf, true for others
1042 if(!thisCertInfo
->basicConstraints
.present
) {
1044 * No basicConstraints present; infer a cA value if appropriate.
1047 /* cool, use default; note that kTPx509Basic with
1048 * certGroup length of one may take this case */
1052 /* cool, use default */
1062 * not present, not leaf, not root....
1063 * ....RFC2459 says this can not be a CA
1068 /* required for iSign in this position */
1069 tpPolicyError("tp_policyVerify: no "
1070 "basicConstraints");
1071 policyFail
= CSSM_TRUE
;
1072 thisTpCertInfo
->addStatusCode(
1073 CSSMERR_APPLETP_NO_BASIC_CONSTRAINTS
);
1083 /* basicConstraints present */
1084 #if BASIC_CONSTRAINTS_MUST_BE_CRITICAL
1085 /* disabled for verisign compatibility */
1086 if(!thisCertInfo
->basicConstraints
.critical
) {
1088 tpPolicyError("tp_policyVerify: basicConstraints marked "
1090 policyFail
= CSSM_TRUE
;
1091 thisTpCertInfo
->addStatusCode(CSSMERR_TP_VERIFY_ACTION_FAILED
);
1093 #endif /* BASIC_CONSTRAINTS_MUST_BE_CRITICAL */
1095 const CE_BasicConstraints
*bcp
=
1096 &thisCertInfo
->basicConstraints
.extnData
->basicConstraints
;
1100 /* Verify pathLenConstraint if present */
1101 if(!isLeaf
&& // leaf, certDex=0, don't care
1102 cA
&& // p.l.c. only valid for CAs
1103 bcp
->pathLenConstraintPresent
) { // present?
1105 * pathLenConstraint=0 legal for certDex 1 only
1106 * pathLenConstraint=1 legal for certDex {1,2}
1109 if(certDex
> (bcp
->pathLenConstraint
+ 1)) {
1110 tpPolicyError("tp_policyVerify: pathLenConstraint "
1112 policyFail
= CSSM_TRUE
;
1113 thisTpCertInfo
->addStatusCode(
1114 CSSMERR_APPLETP_PATH_LEN_CONSTRAINT
);
1121 * Special cases to allow a chain of length 1, leaf and root
1122 * both true, and for caller to override the "leaf can't be a CA"
1123 * requirement when a CA cert is explicitly being evaluated as the
1127 !(actionFlags
& CSSM_TP_ACTION_LEAF_IS_CA
)) {
1128 tpPolicyError("tp_policyVerify: cA true for leaf");
1129 policyFail
= CSSM_TRUE
;
1130 thisTpCertInfo
->addStatusCode(CSSMERR_APPLETP_INVALID_CA
);
1133 tpPolicyError("tp_policyVerify: cA false for non-leaf");
1134 policyFail
= CSSM_TRUE
;
1135 thisTpCertInfo
->addStatusCode(CSSMERR_APPLETP_INVALID_CA
);
1139 * Authority Key Identifier optional
1140 * iSign : only allowed in !root.
1141 * If present, must not be critical.
1144 * kTP_SMIME : ignored (though used later for chain verification)
1146 if((policy
== kTPiSign
) && thisCertInfo
->authorityId
.present
) {
1148 tpPolicyError("tp_policyVerify: authorityId in root");
1149 policyFail
= CSSM_TRUE
;
1150 thisTpCertInfo
->addStatusCode(CSSMERR_APPLETP_INVALID_AUTHORITY_ID
);
1152 if(thisCertInfo
->authorityId
.critical
) {
1153 /* illegal per RFC 2459 */
1154 tpPolicyError("tp_policyVerify: authorityId marked "
1156 policyFail
= CSSM_TRUE
;
1157 thisTpCertInfo
->addStatusCode(CSSMERR_APPLETP_INVALID_AUTHORITY_ID
);
1162 * Subject Key Identifier optional
1163 * iSign : can't be critical.
1166 * kTP_SMIME : ignored (though used later for chain verification)
1168 if(thisCertInfo
->subjectId
.present
) {
1169 if((policy
== kTPiSign
) && thisCertInfo
->subjectId
.critical
) {
1170 tpPolicyError("tp_policyVerify: subjectId marked critical");
1171 policyFail
= CSSM_TRUE
;
1172 thisTpCertInfo
->addStatusCode(CSSMERR_APPLETP_INVALID_SUBJECT_ID
);
1177 * Key Usage optional except as noted required
1178 * iSign : required for non-root/non-leaf
1179 * Leaf cert : if present, usage = digitalSignature
1180 * Exception : if leaf, and keyUsage not present,
1181 * netscape-cert-type must be present, with
1182 * Object Signing bit set
1185 * kTP_SMIME, : non-leaf : usage = keyCertSign
1187 * kCrlPolicy : Leaf: usage = CRLSign
1188 * kTP_SMIME : if present, must be critical
1190 if(thisCertInfo
->keyUsage
.present
) {
1192 * Leaf cert: usage = digitalSignature
1193 * Others: usage = keyCertSign
1194 * We only require that one bit to be set, we ignore others.
1199 expUsage
= CE_KU_DigitalSignature
;
1202 /* if present, this bit must be set */
1203 expUsage
= CE_KU_CRLSign
;
1206 /* hack to accept whatever's there */
1207 expUsage
= thisCertInfo
->keyUsage
.extnData
->keyUsage
;
1212 /* this is true for all policies */
1213 expUsage
= CE_KU_KeyCertSign
;
1215 actUsage
= thisCertInfo
->keyUsage
.extnData
->keyUsage
;
1216 if(!(actUsage
& expUsage
)) {
1217 tpPolicyError("tp_policyVerify: bad keyUsage (leaf %s; "
1219 (certDex
== 0) ? "TRUE" : "FALSE", actUsage
);
1220 policyFail
= CSSM_TRUE
;
1221 thisTpCertInfo
->addStatusCode(CSSMERR_APPLETP_INVALID_KEY_USAGE
);
1224 if((policy
== kTP_SMIME
) && !thisCertInfo
->keyUsage
.critical
) {
1226 * Per Radar 3410245, allow this for intermediate certs.
1228 if(SMIME_KEY_USAGE_MUST_BE_CRITICAL
|| isLeaf
|| isRoot
) {
1229 tpPolicyError("tp_policyVerify: key usage, !critical, SMIME");
1230 policyFail
= CSSM_TRUE
;
1231 thisTpCertInfo
->addStatusCode(CSSMERR_APPLETP_SMIME_KEYUSAGE_NOT_CRITICAL
);
1236 else if(policy
== kTPiSign
) {
1238 * iSign requires keyUsage present for non root OR
1239 * netscape-cert-type/ObjectSigning for leaf
1241 if(isLeaf
&& thisCertInfo
->netscapeCertType
.present
) {
1242 CE_NetscapeCertType ct
=
1243 thisCertInfo
->netscapeCertType
.extnData
->netscapeCertType
;
1245 if(!(ct
& CE_NCT_ObjSign
)) {
1246 tpPolicyError("tp_policyVerify: netscape-cert-type, "
1248 policyFail
= CSSM_TRUE
;
1249 thisTpCertInfo
->addStatusCode(CSSMERR_APPLETP_INVALID_KEY_USAGE
);
1253 tpPolicyError("tp_policyVerify: !isRoot, no keyUsage, "
1254 "!(leaf and netscapeCertType)");
1255 policyFail
= CSSM_TRUE
;
1256 thisTpCertInfo
->addStatusCode(CSSMERR_APPLETP_INVALID_KEY_USAGE
);
1259 } /* for certDex, checking presence of extensions */
1262 * Special case checking for leaf (end entity) cert
1264 * iSign only: Extended key usage, optional for leaf,
1265 * value CSSMOID_ExtendedUseCodeSigning
1267 if((policy
== kTPiSign
) && certInfo
[0].extendKeyUsage
.present
) {
1268 extendUsage
= &certInfo
[0].extendKeyUsage
.extnData
->extendedKeyUsage
;
1269 if(extendUsage
->numPurposes
!= 1) {
1270 tpPolicyError("tp_policyVerify: bad extendUsage->numPurposes "
1272 (int)extendUsage
->numPurposes
);
1273 policyFail
= CSSM_TRUE
;
1274 (certGroup
->certAtIndex(0))->addStatusCode(
1275 CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE
);
1277 if(!tpCompareOids(extendUsage
->purposes
,
1278 &CSSMOID_ExtendedUseCodeSigning
)) {
1279 tpPolicyError("tp_policyVerify: bad extendKeyUsage");
1280 policyFail
= CSSM_TRUE
;
1281 (certGroup
->certAtIndex(0))->addStatusCode(
1282 CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE
);
1287 * Verify authorityId-->subjectId linkage.
1288 * All optional - skip if needed fields not present.
1289 * Also, always skip last (root) cert.
1291 for(certDex
=0; certDex
<(numCerts
-1); certDex
++) {
1292 if(!certInfo
[certDex
].authorityId
.present
||
1293 !certInfo
[certDex
+1].subjectId
.present
) {
1296 authorityId
= &certInfo
[certDex
].authorityId
.extnData
->authorityKeyID
;
1297 if(!authorityId
->keyIdentifierPresent
) {
1298 /* we only know how to compare keyIdentifier */
1301 if(!tpCompareCssmData(&authorityId
->keyIdentifier
,
1302 &certInfo
[certDex
+1].subjectId
.extnData
->subjectKeyID
)) {
1303 tpPolicyError("tp_policyVerify: bad key ID linkage");
1304 policyFail
= CSSM_TRUE
;
1305 (certGroup
->certAtIndex(certDex
))->addStatusCode(
1306 CSSMERR_APPLETP_INVALID_ID_LINKAGE
);
1311 * SSL: optionally verify common name.
1312 * FIXME - should this be before or after the root cert test? How can
1313 * we return both errors?
1315 if(policy
== kTP_SSL
) {
1316 CSSM_RETURN cerr
= tp_verifySslOpts(*certGroup
, policyFieldData
,
1319 policyFail
= CSSM_TRUE
;
1324 if(policy
== kTP_SMIME
) {
1325 CSSM_RETURN cerr
= tp_verifySmimeOpts(*certGroup
, policyFieldData
,
1328 policyFail
= CSSM_TRUE
;
1332 if(policyFail
&& (outErr
== CSSM_OK
)) {
1333 /* only error in this function was policy failure */
1334 outErr
= CSSMERR_TP_VERIFY_ACTION_FAILED
;
1337 /* free resources */
1338 for(certDex
=0; certDex
<numCerts
; certDex
++) {
1339 thisCertInfo
= &certInfo
[certDex
];
1340 iSignFreeCertInfo(clHand
, thisCertInfo
);
1342 tpFree(alloc
, certInfo
);