| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| /* |
| * Certificate handling code |
| */ |
| |
| #include "nssilock.h" |
| #include "prmon.h" |
| #include "prtime.h" |
| #include "cert.h" |
| #include "certi.h" |
| #include "secder.h" |
| #include "secoid.h" |
| #include "secasn1.h" |
| #include "genname.h" |
| #include "keyhi.h" |
| #include "secitem.h" |
| #include "certdb.h" |
| #include "prprf.h" |
| #include "sechash.h" |
| #include "prlong.h" |
| #include "certxutl.h" |
| #include "portreg.h" |
| #include "secerr.h" |
| #include "sslerr.h" |
| #include "pk11func.h" |
| #include "xconst.h" /* for CERT_DecodeAltNameExtension */ |
| |
| #include "pki.h" |
| #include "pki3hack.h" |
| |
| SEC_ASN1_MKSUB(CERT_TimeChoiceTemplate) |
| SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate) |
| SEC_ASN1_MKSUB(SEC_BitStringTemplate) |
| SEC_ASN1_MKSUB(SEC_IntegerTemplate) |
| SEC_ASN1_MKSUB(SEC_SkipTemplate) |
| |
| /* |
| * Certificate database handling code |
| */ |
| |
| const SEC_ASN1Template CERT_CertExtensionTemplate[] = { |
| { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertExtension) }, |
| { SEC_ASN1_OBJECT_ID, offsetof(CERTCertExtension, id) }, |
| { SEC_ASN1_OPTIONAL | SEC_ASN1_BOOLEAN, /* XXX DER_DEFAULT */ |
| offsetof(CERTCertExtension, critical) }, |
| { SEC_ASN1_OCTET_STRING, offsetof(CERTCertExtension, value) }, |
| { 0 } |
| }; |
| |
| const SEC_ASN1Template CERT_SequenceOfCertExtensionTemplate[] = { |
| { SEC_ASN1_SEQUENCE_OF, 0, CERT_CertExtensionTemplate } |
| }; |
| |
| const SEC_ASN1Template CERT_TimeChoiceTemplate[] = { |
| { SEC_ASN1_CHOICE, offsetof(SECItem, type), 0, sizeof(SECItem) }, |
| { SEC_ASN1_UTC_TIME, 0, 0, siUTCTime }, |
| { SEC_ASN1_GENERALIZED_TIME, 0, 0, siGeneralizedTime }, |
| { 0 } |
| }; |
| |
| const SEC_ASN1Template CERT_ValidityTemplate[] = { |
| { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTValidity) }, |
| { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTValidity, notBefore), |
| SEC_ASN1_SUB(CERT_TimeChoiceTemplate), 0 }, |
| { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTValidity, notAfter), |
| SEC_ASN1_SUB(CERT_TimeChoiceTemplate), 0 }, |
| { 0 } |
| }; |
| |
| const SEC_ASN1Template CERT_CertificateTemplate[] = { |
| { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertificate) }, |
| { SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | |
| SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, /* XXX DER_DEFAULT */ |
| offsetof(CERTCertificate, version), |
| SEC_ASN1_SUB(SEC_IntegerTemplate) }, |
| { SEC_ASN1_INTEGER, offsetof(CERTCertificate, serialNumber) }, |
| { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCertificate, signature), |
| SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, |
| { SEC_ASN1_SAVE, offsetof(CERTCertificate, derIssuer) }, |
| { SEC_ASN1_INLINE, offsetof(CERTCertificate, issuer), CERT_NameTemplate }, |
| { SEC_ASN1_INLINE, offsetof(CERTCertificate, validity), |
| CERT_ValidityTemplate }, |
| { SEC_ASN1_SAVE, offsetof(CERTCertificate, derSubject) }, |
| { SEC_ASN1_INLINE, offsetof(CERTCertificate, subject), CERT_NameTemplate }, |
| { SEC_ASN1_SAVE, offsetof(CERTCertificate, derPublicKey) }, |
| { SEC_ASN1_INLINE, offsetof(CERTCertificate, subjectPublicKeyInfo), |
| CERT_SubjectPublicKeyInfoTemplate }, |
| { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 1, |
| offsetof(CERTCertificate, issuerID), |
| SEC_ASN1_SUB(SEC_BitStringTemplate) }, |
| { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 2, |
| offsetof(CERTCertificate, subjectID), |
| SEC_ASN1_SUB(SEC_BitStringTemplate) }, |
| { SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | |
| SEC_ASN1_CONTEXT_SPECIFIC | 3, |
| offsetof(CERTCertificate, extensions), |
| CERT_SequenceOfCertExtensionTemplate }, |
| { 0 } |
| }; |
| |
| const SEC_ASN1Template SEC_SignedCertificateTemplate[] = { |
| { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertificate) }, |
| { SEC_ASN1_SAVE, offsetof(CERTCertificate, signatureWrap.data) }, |
| { SEC_ASN1_INLINE, 0, CERT_CertificateTemplate }, |
| { SEC_ASN1_INLINE | SEC_ASN1_XTRN, |
| offsetof(CERTCertificate, signatureWrap.signatureAlgorithm), |
| SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, |
| { SEC_ASN1_BIT_STRING, offsetof(CERTCertificate, signatureWrap.signature) }, |
| { 0 } |
| }; |
| |
| /* |
| * Find the subjectName in a DER encoded certificate |
| */ |
| const SEC_ASN1Template SEC_CertSubjectTemplate[] = { |
| { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECItem) }, |
| { SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | |
| SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, |
| 0, SEC_ASN1_SUB(SEC_SkipTemplate) }, /* version */ |
| { SEC_ASN1_SKIP }, /* serial number */ |
| { SEC_ASN1_SKIP }, /* signature algorithm */ |
| { SEC_ASN1_SKIP }, /* issuer */ |
| { SEC_ASN1_SKIP }, /* validity */ |
| { SEC_ASN1_ANY, 0, NULL }, /* subject */ |
| { SEC_ASN1_SKIP_REST }, |
| { 0 } |
| }; |
| |
| /* |
| * Find the issuerName in a DER encoded certificate |
| */ |
| const SEC_ASN1Template SEC_CertIssuerTemplate[] = { |
| { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECItem) }, |
| { SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | |
| SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, |
| 0, SEC_ASN1_SUB(SEC_SkipTemplate) }, /* version */ |
| { SEC_ASN1_SKIP }, /* serial number */ |
| { SEC_ASN1_SKIP }, /* signature algorithm */ |
| { SEC_ASN1_ANY, 0, NULL }, /* issuer */ |
| { SEC_ASN1_SKIP_REST }, |
| { 0 } |
| }; |
| /* |
| * Find the subjectName in a DER encoded certificate |
| */ |
| const SEC_ASN1Template SEC_CertSerialNumberTemplate[] = { |
| { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECItem) }, |
| { SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | |
| SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, |
| 0, SEC_ASN1_SUB(SEC_SkipTemplate) }, /* version */ |
| { SEC_ASN1_ANY, 0, NULL }, /* serial number */ |
| { SEC_ASN1_SKIP_REST }, |
| { 0 } |
| }; |
| |
| /* |
| * Find the issuer and serialNumber in a DER encoded certificate. |
| * This data is used as the database lookup key since its the unique |
| * identifier of a certificate. |
| */ |
| const SEC_ASN1Template CERT_CertKeyTemplate[] = { |
| { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertKey) }, |
| { SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | |
| SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, |
| 0, SEC_ASN1_SUB(SEC_SkipTemplate) }, /* version */ |
| { SEC_ASN1_INTEGER, offsetof(CERTCertKey, serialNumber) }, |
| { SEC_ASN1_SKIP }, /* signature algorithm */ |
| { SEC_ASN1_ANY, offsetof(CERTCertKey, derIssuer) }, |
| { SEC_ASN1_SKIP_REST }, |
| { 0 } |
| }; |
| |
| SEC_ASN1_CHOOSER_IMPLEMENT(CERT_TimeChoiceTemplate) |
| SEC_ASN1_CHOOSER_IMPLEMENT(CERT_CertificateTemplate) |
| SEC_ASN1_CHOOSER_IMPLEMENT(SEC_SignedCertificateTemplate) |
| SEC_ASN1_CHOOSER_IMPLEMENT(CERT_SequenceOfCertExtensionTemplate) |
| |
| SECStatus |
| CERT_KeyFromIssuerAndSN(PLArenaPool *arena, SECItem *issuer, SECItem *sn, |
| SECItem *key) |
| { |
| key->len = sn->len + issuer->len; |
| |
| if ((sn->data == NULL) || (issuer->data == NULL)) { |
| goto loser; |
| } |
| |
| key->data = (unsigned char *)PORT_ArenaAlloc(arena, key->len); |
| if (!key->data) { |
| goto loser; |
| } |
| |
| /* copy the serialNumber */ |
| PORT_Memcpy(key->data, sn->data, sn->len); |
| |
| /* copy the issuer */ |
| PORT_Memcpy(&key->data[sn->len], issuer->data, issuer->len); |
| |
| return (SECSuccess); |
| |
| loser: |
| return (SECFailure); |
| } |
| |
| /* |
| * Extract the subject name from a DER certificate |
| */ |
| SECStatus |
| CERT_NameFromDERCert(SECItem *derCert, SECItem *derName) |
| { |
| int rv; |
| PLArenaPool *arena; |
| CERTSignedData sd; |
| void *tmpptr; |
| |
| arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); |
| |
| if (!arena) { |
| return (SECFailure); |
| } |
| |
| PORT_Memset(&sd, 0, sizeof(CERTSignedData)); |
| rv = SEC_QuickDERDecodeItem(arena, &sd, CERT_SignedDataTemplate, derCert); |
| |
| if (rv) { |
| goto loser; |
| } |
| |
| PORT_Memset(derName, 0, sizeof(SECItem)); |
| rv = SEC_QuickDERDecodeItem(arena, derName, SEC_CertSubjectTemplate, |
| &sd.data); |
| |
| if (rv) { |
| goto loser; |
| } |
| |
| tmpptr = derName->data; |
| derName->data = (unsigned char *)PORT_Alloc(derName->len); |
| if (derName->data == NULL) { |
| goto loser; |
| } |
| |
| PORT_Memcpy(derName->data, tmpptr, derName->len); |
| |
| PORT_FreeArena(arena, PR_FALSE); |
| return (SECSuccess); |
| |
| loser: |
| PORT_FreeArena(arena, PR_FALSE); |
| return (SECFailure); |
| } |
| |
| SECStatus |
| CERT_IssuerNameFromDERCert(SECItem *derCert, SECItem *derName) |
| { |
| int rv; |
| PORTCheapArenaPool tmpArena; |
| CERTSignedData sd; |
| void *tmpptr; |
| |
| PORT_InitCheapArena(&tmpArena, DER_DEFAULT_CHUNKSIZE); |
| |
| PORT_Memset(&sd, 0, sizeof(CERTSignedData)); |
| rv = SEC_QuickDERDecodeItem(&tmpArena.arena, &sd, CERT_SignedDataTemplate, |
| derCert); |
| if (rv) { |
| goto loser; |
| } |
| |
| PORT_Memset(derName, 0, sizeof(SECItem)); |
| rv = SEC_QuickDERDecodeItem(&tmpArena.arena, derName, |
| SEC_CertIssuerTemplate, &sd.data); |
| if (rv) { |
| goto loser; |
| } |
| |
| tmpptr = derName->data; |
| derName->data = (unsigned char *)PORT_Alloc(derName->len); |
| if (derName->data == NULL) { |
| goto loser; |
| } |
| |
| PORT_Memcpy(derName->data, tmpptr, derName->len); |
| |
| PORT_DestroyCheapArena(&tmpArena); |
| return (SECSuccess); |
| |
| loser: |
| PORT_DestroyCheapArena(&tmpArena); |
| return (SECFailure); |
| } |
| |
| SECStatus |
| CERT_SerialNumberFromDERCert(SECItem *derCert, SECItem *derName) |
| { |
| int rv; |
| PORTCheapArenaPool tmpArena; |
| CERTSignedData sd; |
| void *tmpptr; |
| |
| PORT_InitCheapArena(&tmpArena, DER_DEFAULT_CHUNKSIZE); |
| |
| PORT_Memset(&sd, 0, sizeof(CERTSignedData)); |
| rv = SEC_QuickDERDecodeItem(&tmpArena.arena, &sd, CERT_SignedDataTemplate, |
| derCert); |
| if (rv) { |
| goto loser; |
| } |
| |
| PORT_Memset(derName, 0, sizeof(SECItem)); |
| rv = SEC_QuickDERDecodeItem(&tmpArena.arena, derName, |
| SEC_CertSerialNumberTemplate, &sd.data); |
| if (rv) { |
| goto loser; |
| } |
| |
| tmpptr = derName->data; |
| derName->data = (unsigned char *)PORT_Alloc(derName->len); |
| if (derName->data == NULL) { |
| goto loser; |
| } |
| |
| PORT_Memcpy(derName->data, tmpptr, derName->len); |
| |
| PORT_DestroyCheapArena(&tmpArena); |
| return (SECSuccess); |
| |
| loser: |
| PORT_DestroyCheapArena(&tmpArena); |
| return (SECFailure); |
| } |
| |
| /* |
| * Generate a database key, based on serial number and issuer, from a |
| * DER certificate. |
| */ |
| SECStatus |
| CERT_KeyFromDERCert(PLArenaPool *reqArena, SECItem *derCert, SECItem *key) |
| { |
| int rv; |
| CERTSignedData sd; |
| CERTCertKey certkey; |
| |
| if (!reqArena) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return SECFailure; |
| } |
| |
| PORT_Memset(&sd, 0, sizeof(CERTSignedData)); |
| rv = |
| SEC_QuickDERDecodeItem(reqArena, &sd, CERT_SignedDataTemplate, derCert); |
| |
| if (rv) { |
| goto loser; |
| } |
| |
| PORT_Memset(&certkey, 0, sizeof(CERTCertKey)); |
| rv = SEC_QuickDERDecodeItem(reqArena, &certkey, CERT_CertKeyTemplate, |
| &sd.data); |
| |
| if (rv) { |
| goto loser; |
| } |
| |
| return (CERT_KeyFromIssuerAndSN(reqArena, &certkey.derIssuer, |
| &certkey.serialNumber, key)); |
| loser: |
| return (SECFailure); |
| } |
| |
| /* |
| * fill in keyUsage field of the cert based on the cert extension |
| * if the extension is not critical, then we allow all uses |
| */ |
| static SECStatus |
| GetKeyUsage(CERTCertificate *cert) |
| { |
| SECStatus rv; |
| SECItem tmpitem; |
| |
| rv = CERT_FindKeyUsageExtension(cert, &tmpitem); |
| if (rv == SECSuccess) { |
| /* remember the actual value of the extension */ |
| cert->rawKeyUsage = tmpitem.data[0]; |
| cert->keyUsagePresent = PR_TRUE; |
| cert->keyUsage = tmpitem.data[0]; |
| |
| PORT_Free(tmpitem.data); |
| tmpitem.data = NULL; |
| } else { |
| /* if the extension is not present, then we allow all uses */ |
| cert->keyUsage = KU_ALL; |
| cert->rawKeyUsage = KU_ALL; |
| cert->keyUsagePresent = PR_FALSE; |
| } |
| |
| if (CERT_GovtApprovedBitSet(cert)) { |
| cert->keyUsage |= KU_NS_GOVT_APPROVED; |
| cert->rawKeyUsage |= KU_NS_GOVT_APPROVED; |
| } |
| |
| return (SECSuccess); |
| } |
| |
| static SECStatus |
| findOIDinOIDSeqByTagNum(CERTOidSequence *seq, SECOidTag tagnum) |
| { |
| SECItem **oids; |
| SECItem *oid; |
| SECStatus rv = SECFailure; |
| |
| if (seq != NULL) { |
| oids = seq->oids; |
| while (oids != NULL && *oids != NULL) { |
| oid = *oids; |
| if (SECOID_FindOIDTag(oid) == tagnum) { |
| rv = SECSuccess; |
| break; |
| } |
| oids++; |
| } |
| } |
| return rv; |
| } |
| |
| /* |
| * fill in nsCertType field of the cert based on the cert extension |
| */ |
| SECStatus |
| cert_GetCertType(CERTCertificate *cert) |
| { |
| PRUint32 nsCertType; |
| |
| if (cert->nsCertType) { |
| /* once set, no need to recalculate */ |
| return SECSuccess; |
| } |
| nsCertType = cert_ComputeCertType(cert); |
| |
| /* Assert that it is safe to cast &cert->nsCertType to "PRInt32 *" */ |
| PORT_Assert(sizeof(cert->nsCertType) == sizeof(PRInt32)); |
| PR_ATOMIC_SET((PRInt32 *)&cert->nsCertType, nsCertType); |
| return SECSuccess; |
| } |
| |
| PRBool |
| cert_IsIPsecOID(CERTOidSequence *extKeyUsage) |
| { |
| if (findOIDinOIDSeqByTagNum( |
| extKeyUsage, SEC_OID_EXT_KEY_USAGE_IPSEC_IKE) == SECSuccess) { |
| return PR_TRUE; |
| } |
| if (findOIDinOIDSeqByTagNum( |
| extKeyUsage, SEC_OID_IPSEC_IKE_END) == SECSuccess) { |
| return PR_TRUE; |
| } |
| if (findOIDinOIDSeqByTagNum( |
| extKeyUsage, SEC_OID_IPSEC_IKE_INTERMEDIATE) == SECSuccess) { |
| return PR_TRUE; |
| } |
| /* these are now deprecated, but may show up. Treat them the same as IKE */ |
| if (findOIDinOIDSeqByTagNum( |
| extKeyUsage, SEC_OID_EXT_KEY_USAGE_IPSEC_END) == SECSuccess) { |
| return PR_TRUE; |
| } |
| if (findOIDinOIDSeqByTagNum( |
| extKeyUsage, SEC_OID_EXT_KEY_USAGE_IPSEC_TUNNEL) == SECSuccess) { |
| return PR_TRUE; |
| } |
| if (findOIDinOIDSeqByTagNum( |
| extKeyUsage, SEC_OID_EXT_KEY_USAGE_IPSEC_USER) == SECSuccess) { |
| return PR_TRUE; |
| } |
| /* this one should probably be in cert_ComputeCertType and set all usages? */ |
| if (findOIDinOIDSeqByTagNum( |
| extKeyUsage, SEC_OID_X509_ANY_EXT_KEY_USAGE) == SECSuccess) { |
| return PR_TRUE; |
| } |
| return PR_FALSE; |
| } |
| |
| PRUint32 |
| cert_ComputeCertType(CERTCertificate *cert) |
| { |
| SECStatus rv; |
| SECItem tmpitem; |
| SECItem encodedExtKeyUsage; |
| CERTOidSequence *extKeyUsage = NULL; |
| CERTBasicConstraints basicConstraint; |
| PRUint32 nsCertType = 0; |
| PRBool isCA = PR_FALSE; |
| |
| tmpitem.data = NULL; |
| CERT_FindNSCertTypeExtension(cert, &tmpitem); |
| encodedExtKeyUsage.data = NULL; |
| rv = CERT_FindCertExtension(cert, SEC_OID_X509_EXT_KEY_USAGE, |
| &encodedExtKeyUsage); |
| if (rv == SECSuccess) { |
| extKeyUsage = CERT_DecodeOidSequence(&encodedExtKeyUsage); |
| } |
| rv = CERT_FindBasicConstraintExten(cert, &basicConstraint); |
| if (rv == SECSuccess) { |
| isCA = basicConstraint.isCA; |
| } |
| if (tmpitem.data != NULL || extKeyUsage != NULL) { |
| if (tmpitem.data == NULL) { |
| nsCertType = 0; |
| } else { |
| nsCertType = tmpitem.data[0]; |
| } |
| |
| /* free tmpitem data pointer to avoid memory leak */ |
| PORT_Free(tmpitem.data); |
| tmpitem.data = NULL; |
| |
| /* |
| * for this release, we will allow SSL certs with an email address |
| * to be used for email |
| */ |
| if ((nsCertType & NS_CERT_TYPE_SSL_CLIENT) && cert->emailAddr && |
| cert->emailAddr[0]) { |
| nsCertType |= NS_CERT_TYPE_EMAIL; |
| } |
| /* |
| * for this release, we will allow SSL intermediate CAs to be |
| * email intermediate CAs too. |
| */ |
| if (nsCertType & NS_CERT_TYPE_SSL_CA) { |
| nsCertType |= NS_CERT_TYPE_EMAIL_CA; |
| } |
| /* |
| * allow a cert with the extended key usage of EMail Protect |
| * to be used for email or as an email CA, if basic constraints |
| * indicates that it is a CA. |
| */ |
| if (findOIDinOIDSeqByTagNum(extKeyUsage, |
| SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT) == |
| SECSuccess) { |
| nsCertType |= isCA ? NS_CERT_TYPE_EMAIL_CA : NS_CERT_TYPE_EMAIL; |
| } |
| if (findOIDinOIDSeqByTagNum( |
| extKeyUsage, SEC_OID_EXT_KEY_USAGE_SERVER_AUTH) == SECSuccess) { |
| nsCertType |= isCA ? NS_CERT_TYPE_SSL_CA : NS_CERT_TYPE_SSL_SERVER; |
| } |
| /* |
| * Treat certs with step-up OID as also having SSL server type. |
| * COMODO needs this behaviour until June 2020. See Bug 737802. |
| */ |
| if (findOIDinOIDSeqByTagNum(extKeyUsage, |
| SEC_OID_NS_KEY_USAGE_GOVT_APPROVED) == |
| SECSuccess) { |
| nsCertType |= isCA ? NS_CERT_TYPE_SSL_CA : NS_CERT_TYPE_SSL_SERVER; |
| } |
| if (findOIDinOIDSeqByTagNum( |
| extKeyUsage, SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH) == SECSuccess) { |
| nsCertType |= isCA ? NS_CERT_TYPE_SSL_CA : NS_CERT_TYPE_SSL_CLIENT; |
| } |
| if (cert_IsIPsecOID(extKeyUsage)) { |
| nsCertType |= isCA ? NS_CERT_TYPE_IPSEC_CA : NS_CERT_TYPE_IPSEC; |
| } |
| if (findOIDinOIDSeqByTagNum( |
| extKeyUsage, SEC_OID_EXT_KEY_USAGE_CODE_SIGN) == SECSuccess) { |
| nsCertType |= isCA ? NS_CERT_TYPE_OBJECT_SIGNING_CA : NS_CERT_TYPE_OBJECT_SIGNING; |
| } |
| if (findOIDinOIDSeqByTagNum( |
| extKeyUsage, SEC_OID_EXT_KEY_USAGE_TIME_STAMP) == SECSuccess) { |
| nsCertType |= EXT_KEY_USAGE_TIME_STAMP; |
| } |
| if (findOIDinOIDSeqByTagNum(extKeyUsage, SEC_OID_OCSP_RESPONDER) == |
| SECSuccess) { |
| nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER; |
| } |
| } else { |
| /* If no NS Cert Type extension and no EKU extension, then */ |
| nsCertType = 0; |
| if (CERT_IsCACert(cert, &nsCertType)) |
| nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER; |
| /* if the basic constraint extension says the cert is a CA, then |
| allow SSL CA and EMAIL CA and Status Responder */ |
| if (isCA) { |
| nsCertType |= (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA | |
| EXT_KEY_USAGE_STATUS_RESPONDER); |
| } |
| /* allow any ssl or email (no ca or object signing. */ |
| nsCertType |= NS_CERT_TYPE_SSL_CLIENT | NS_CERT_TYPE_SSL_SERVER | |
| NS_CERT_TYPE_EMAIL; |
| } |
| |
| /* IPSEC is allowed to use SSL client and server certs as well as email certs */ |
| if (nsCertType & (NS_CERT_TYPE_SSL_CLIENT | NS_CERT_TYPE_SSL_SERVER | NS_CERT_TYPE_EMAIL)) { |
| nsCertType |= NS_CERT_TYPE_IPSEC; |
| } |
| if (nsCertType & (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA)) { |
| nsCertType |= NS_CERT_TYPE_IPSEC_CA; |
| } |
| |
| if (encodedExtKeyUsage.data != NULL) { |
| PORT_Free(encodedExtKeyUsage.data); |
| } |
| if (extKeyUsage != NULL) { |
| CERT_DestroyOidSequence(extKeyUsage); |
| } |
| return nsCertType; |
| } |
| |
| /* |
| * cert_GetKeyID() - extract or generate the subjectKeyID from a certificate |
| */ |
| SECStatus |
| cert_GetKeyID(CERTCertificate *cert) |
| { |
| SECItem tmpitem; |
| SECStatus rv; |
| |
| cert->subjectKeyID.len = 0; |
| |
| /* see of the cert has a key identifier extension */ |
| rv = CERT_FindSubjectKeyIDExtension(cert, &tmpitem); |
| if (rv == SECSuccess) { |
| cert->subjectKeyID.data = |
| (unsigned char *)PORT_ArenaAlloc(cert->arena, tmpitem.len); |
| if (cert->subjectKeyID.data != NULL) { |
| PORT_Memcpy(cert->subjectKeyID.data, tmpitem.data, tmpitem.len); |
| cert->subjectKeyID.len = tmpitem.len; |
| cert->keyIDGenerated = PR_FALSE; |
| } |
| |
| PORT_Free(tmpitem.data); |
| } |
| |
| /* if the cert doesn't have a key identifier extension, then generate one*/ |
| if (cert->subjectKeyID.len == 0) { |
| /* |
| * pkix says that if the subjectKeyID is not present, then we should |
| * use the SHA-1 hash of the DER-encoded publicKeyInfo from the cert |
| */ |
| cert->subjectKeyID.data = |
| (unsigned char *)PORT_ArenaAlloc(cert->arena, SHA1_LENGTH); |
| if (cert->subjectKeyID.data != NULL) { |
| rv = PK11_HashBuf(SEC_OID_SHA1, cert->subjectKeyID.data, |
| cert->derPublicKey.data, cert->derPublicKey.len); |
| if (rv == SECSuccess) { |
| cert->subjectKeyID.len = SHA1_LENGTH; |
| } |
| } |
| } |
| |
| if (cert->subjectKeyID.len == 0) { |
| return (SECFailure); |
| } |
| return (SECSuccess); |
| } |
| |
| static PRBool |
| cert_IsRootCert(CERTCertificate *cert) |
| { |
| SECStatus rv; |
| SECItem tmpitem; |
| |
| /* cache the authKeyID extension, if present */ |
| cert->authKeyID = CERT_FindAuthKeyIDExten(cert->arena, cert); |
| |
| /* it MUST be self-issued to be a root */ |
| if (cert->derIssuer.len == 0 || |
| !SECITEM_ItemsAreEqual(&cert->derIssuer, &cert->derSubject)) { |
| return PR_FALSE; |
| } |
| |
| /* check the authKeyID extension */ |
| if (cert->authKeyID) { |
| /* authority key identifier is present */ |
| if (cert->authKeyID->keyID.len > 0) { |
| /* the keyIdentifier field is set, look for subjectKeyID */ |
| rv = CERT_FindSubjectKeyIDExtension(cert, &tmpitem); |
| if (rv == SECSuccess) { |
| PRBool match; |
| /* also present, they MUST match for it to be a root */ |
| match = |
| SECITEM_ItemsAreEqual(&cert->authKeyID->keyID, &tmpitem); |
| PORT_Free(tmpitem.data); |
| if (!match) |
| return PR_FALSE; /* else fall through */ |
| } else { |
| /* the subject key ID is required when AKI is present */ |
| return PR_FALSE; |
| } |
| } |
| if (cert->authKeyID->authCertIssuer) { |
| SECItem *caName; |
| caName = (SECItem *)CERT_GetGeneralNameByType( |
| cert->authKeyID->authCertIssuer, certDirectoryName, PR_TRUE); |
| if (caName) { |
| if (!SECITEM_ItemsAreEqual(&cert->derIssuer, caName)) { |
| return PR_FALSE; |
| } /* else fall through */ |
| } /* else ??? could not get general name as directory name? */ |
| } |
| if (cert->authKeyID->authCertSerialNumber.len > 0) { |
| if (!SECITEM_ItemsAreEqual( |
| &cert->serialNumber, |
| &cert->authKeyID->authCertSerialNumber)) { |
| return PR_FALSE; |
| } /* else fall through */ |
| } |
| /* all of the AKI fields that were present passed the test */ |
| return PR_TRUE; |
| } |
| /* else the AKI was not present, so this is a root */ |
| return PR_TRUE; |
| } |
| |
| /* |
| * take a DER certificate and decode it into a certificate structure |
| */ |
| CERTCertificate * |
| CERT_DecodeDERCertificate(SECItem *derSignedCert, PRBool copyDER, |
| char *nickname) |
| { |
| CERTCertificate *cert; |
| PLArenaPool *arena; |
| void *data; |
| int rv; |
| int len; |
| char *tmpname; |
| |
| /* make a new arena */ |
| arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); |
| |
| if (!arena) { |
| return 0; |
| } |
| |
| /* allocate the certificate structure */ |
| cert = (CERTCertificate *)PORT_ArenaZAlloc(arena, sizeof(CERTCertificate)); |
| |
| if (!cert) { |
| goto loser; |
| } |
| |
| cert->arena = arena; |
| |
| if (copyDER) { |
| /* copy the DER data for the cert into this arena */ |
| data = (void *)PORT_ArenaAlloc(arena, derSignedCert->len); |
| if (!data) { |
| goto loser; |
| } |
| cert->derCert.data = (unsigned char *)data; |
| cert->derCert.len = derSignedCert->len; |
| PORT_Memcpy(data, derSignedCert->data, derSignedCert->len); |
| } else { |
| /* point to passed in DER data */ |
| cert->derCert = *derSignedCert; |
| } |
| |
| /* decode the certificate info */ |
| rv = SEC_QuickDERDecodeItem(arena, cert, SEC_SignedCertificateTemplate, |
| &cert->derCert); |
| |
| if (rv) { |
| goto loser; |
| } |
| |
| if (cert_HasUnknownCriticalExten(cert->extensions) == PR_TRUE) { |
| cert->options.bits.hasUnsupportedCriticalExt = PR_TRUE; |
| } |
| |
| /* generate and save the database key for the cert */ |
| rv = CERT_KeyFromIssuerAndSN(arena, &cert->derIssuer, &cert->serialNumber, |
| &cert->certKey); |
| if (rv) { |
| goto loser; |
| } |
| |
| /* set the nickname */ |
| if (nickname == NULL) { |
| cert->nickname = NULL; |
| } else { |
| /* copy and install the nickname */ |
| len = PORT_Strlen(nickname) + 1; |
| cert->nickname = (char *)PORT_ArenaAlloc(arena, len); |
| if (cert->nickname == NULL) { |
| goto loser; |
| } |
| |
| PORT_Memcpy(cert->nickname, nickname, len); |
| } |
| |
| /* set the email address */ |
| cert->emailAddr = cert_GetCertificateEmailAddresses(cert); |
| |
| /* initialize the subjectKeyID */ |
| rv = cert_GetKeyID(cert); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| |
| /* initialize keyUsage */ |
| rv = GetKeyUsage(cert); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| |
| /* determine if this is a root cert */ |
| cert->isRoot = cert_IsRootCert(cert); |
| |
| /* initialize the certType */ |
| rv = cert_GetCertType(cert); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| |
| tmpname = CERT_NameToAscii(&cert->subject); |
| if (tmpname != NULL) { |
| cert->subjectName = PORT_ArenaStrdup(cert->arena, tmpname); |
| PORT_Free(tmpname); |
| } |
| |
| tmpname = CERT_NameToAscii(&cert->issuer); |
| if (tmpname != NULL) { |
| cert->issuerName = PORT_ArenaStrdup(cert->arena, tmpname); |
| PORT_Free(tmpname); |
| } |
| |
| cert->referenceCount = 1; |
| cert->slot = NULL; |
| cert->pkcs11ID = CK_INVALID_HANDLE; |
| cert->dbnickname = NULL; |
| |
| return (cert); |
| |
| loser: |
| |
| if (arena) { |
| PORT_FreeArena(arena, PR_FALSE); |
| } |
| |
| return (0); |
| } |
| |
| CERTCertificate * |
| __CERT_DecodeDERCertificate(SECItem *derSignedCert, PRBool copyDER, |
| char *nickname) |
| { |
| return CERT_DecodeDERCertificate(derSignedCert, copyDER, nickname); |
| } |
| |
| CERTValidity * |
| CERT_CreateValidity(PRTime notBefore, PRTime notAfter) |
| { |
| CERTValidity *v; |
| int rv; |
| PLArenaPool *arena; |
| |
| if (notBefore > notAfter) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return NULL; |
| } |
| arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); |
| |
| if (!arena) { |
| return (0); |
| } |
| |
| v = (CERTValidity *)PORT_ArenaZAlloc(arena, sizeof(CERTValidity)); |
| if (v) { |
| v->arena = arena; |
| rv = DER_EncodeTimeChoice(arena, &v->notBefore, notBefore); |
| if (rv) |
| goto loser; |
| rv = DER_EncodeTimeChoice(arena, &v->notAfter, notAfter); |
| if (rv) |
| goto loser; |
| } |
| return v; |
| |
| loser: |
| CERT_DestroyValidity(v); |
| return 0; |
| } |
| |
| SECStatus |
| CERT_CopyValidity(PLArenaPool *arena, CERTValidity *to, CERTValidity *from) |
| { |
| SECStatus rv; |
| |
| CERT_DestroyValidity(to); |
| to->arena = arena; |
| |
| rv = SECITEM_CopyItem(arena, &to->notBefore, &from->notBefore); |
| if (rv) |
| return rv; |
| rv = SECITEM_CopyItem(arena, &to->notAfter, &from->notAfter); |
| return rv; |
| } |
| |
| void |
| CERT_DestroyValidity(CERTValidity *v) |
| { |
| if (v && v->arena) { |
| PORT_FreeArena(v->arena, PR_FALSE); |
| } |
| return; |
| } |
| |
| /* |
| ** Amount of time that a certifiate is allowed good before it is actually |
| ** good. This is used for pending certificates, ones that are about to be |
| ** valid. The slop is designed to allow for some variance in the clocks |
| ** of the machine checking the certificate. |
| */ |
| #define PENDING_SLOP (24L * 60L * 60L) /* seconds per day */ |
| static PRInt32 pendingSlop = PENDING_SLOP; /* seconds */ |
| |
| PRInt32 |
| CERT_GetSlopTime(void) |
| { |
| return pendingSlop; /* seconds */ |
| } |
| |
| SECStatus CERT_SetSlopTime(PRInt32 slop) /* seconds */ |
| { |
| if (slop < 0) |
| return SECFailure; |
| pendingSlop = slop; |
| return SECSuccess; |
| } |
| |
| SECStatus |
| CERT_GetCertTimes(const CERTCertificate *c, PRTime *notBefore, PRTime *notAfter) |
| { |
| SECStatus rv; |
| |
| if (!c || !notBefore || !notAfter) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return SECFailure; |
| } |
| |
| /* convert DER not-before time */ |
| rv = DER_DecodeTimeChoice(notBefore, &c->validity.notBefore); |
| if (rv) { |
| return (SECFailure); |
| } |
| |
| /* convert DER not-after time */ |
| rv = DER_DecodeTimeChoice(notAfter, &c->validity.notAfter); |
| if (rv) { |
| return (SECFailure); |
| } |
| |
| return (SECSuccess); |
| } |
| |
| /* |
| * Check the validity times of a certificate |
| */ |
| SECCertTimeValidity |
| CERT_CheckCertValidTimes(const CERTCertificate *c, PRTime t, |
| PRBool allowOverride) |
| { |
| PRTime notBefore, notAfter, llPendingSlop, tmp1; |
| SECStatus rv; |
| |
| if (!c) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return (secCertTimeUndetermined); |
| } |
| /* if cert is already marked OK, then don't bother to check */ |
| if (allowOverride && c->timeOK) { |
| return (secCertTimeValid); |
| } |
| |
| rv = CERT_GetCertTimes(c, ¬Before, ¬After); |
| |
| if (rv) { |
| return (secCertTimeExpired); /*XXX is this the right thing to do here?*/ |
| } |
| |
| LL_I2L(llPendingSlop, pendingSlop); |
| /* convert to micro seconds */ |
| LL_UI2L(tmp1, PR_USEC_PER_SEC); |
| LL_MUL(llPendingSlop, llPendingSlop, tmp1); |
| LL_SUB(notBefore, notBefore, llPendingSlop); |
| if (LL_CMP(t, <, notBefore)) { |
| PORT_SetError(SEC_ERROR_EXPIRED_CERTIFICATE); |
| return (secCertTimeNotValidYet); |
| } |
| if (LL_CMP(t, >, notAfter)) { |
| PORT_SetError(SEC_ERROR_EXPIRED_CERTIFICATE); |
| return (secCertTimeExpired); |
| } |
| |
| return (secCertTimeValid); |
| } |
| |
| SECStatus |
| SEC_GetCrlTimes(CERTCrl *date, PRTime *notBefore, PRTime *notAfter) |
| { |
| int rv; |
| |
| /* convert DER not-before time */ |
| rv = DER_DecodeTimeChoice(notBefore, &date->lastUpdate); |
| if (rv) { |
| return (SECFailure); |
| } |
| |
| /* convert DER not-after time */ |
| if (date->nextUpdate.data) { |
| rv = DER_DecodeTimeChoice(notAfter, &date->nextUpdate); |
| if (rv) { |
| return (SECFailure); |
| } |
| } else { |
| LL_I2L(*notAfter, 0L); |
| } |
| return (SECSuccess); |
| } |
| |
| /* These routines should probably be combined with the cert |
| * routines using an common extraction routine. |
| */ |
| SECCertTimeValidity |
| SEC_CheckCrlTimes(CERTCrl *crl, PRTime t) |
| { |
| PRTime notBefore, notAfter, llPendingSlop, tmp1; |
| SECStatus rv; |
| |
| if (!crl) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return (secCertTimeUndetermined); |
| } |
| |
| rv = SEC_GetCrlTimes(crl, ¬Before, ¬After); |
| |
| if (rv) { |
| return (secCertTimeExpired); |
| } |
| |
| LL_I2L(llPendingSlop, pendingSlop); |
| /* convert to micro seconds */ |
| LL_I2L(tmp1, PR_USEC_PER_SEC); |
| LL_MUL(llPendingSlop, llPendingSlop, tmp1); |
| LL_SUB(notBefore, notBefore, llPendingSlop); |
| if (LL_CMP(t, <, notBefore)) { |
| PORT_SetError(SEC_ERROR_CRL_EXPIRED); |
| return (secCertTimeNotValidYet); |
| } |
| |
| /* If next update is omitted and the test for notBefore passes, then |
| we assume that the crl is up to date. |
| */ |
| if (LL_IS_ZERO(notAfter)) { |
| return (secCertTimeValid); |
| } |
| |
| if (LL_CMP(t, >, notAfter)) { |
| PORT_SetError(SEC_ERROR_CRL_EXPIRED); |
| return (secCertTimeExpired); |
| } |
| |
| return (secCertTimeValid); |
| } |
| |
| PRBool |
| SEC_CrlIsNewer(CERTCrl *inNew, CERTCrl *old) |
| { |
| PRTime newNotBefore, newNotAfter; |
| PRTime oldNotBefore, oldNotAfter; |
| SECStatus rv; |
| |
| /* problems with the new CRL? reject it */ |
| rv = SEC_GetCrlTimes(inNew, &newNotBefore, &newNotAfter); |
| if (rv) |
| return PR_FALSE; |
| |
| /* problems with the old CRL? replace it */ |
| rv = SEC_GetCrlTimes(old, &oldNotBefore, &oldNotAfter); |
| if (rv) |
| return PR_TRUE; |
| |
| /* Question: what about the notAfter's? */ |
| return ((PRBool)LL_CMP(oldNotBefore, <, newNotBefore)); |
| } |
| |
| /* |
| * return required key usage and cert type based on cert usage |
| */ |
| SECStatus |
| CERT_KeyUsageAndTypeForCertUsage(SECCertUsage usage, PRBool ca, |
| unsigned int *retKeyUsage, |
| unsigned int *retCertType) |
| { |
| unsigned int requiredKeyUsage = 0; |
| unsigned int requiredCertType = 0; |
| |
| if (ca) { |
| switch (usage) { |
| case certUsageSSLServerWithStepUp: |
| requiredKeyUsage = KU_NS_GOVT_APPROVED | KU_KEY_CERT_SIGN; |
| requiredCertType = NS_CERT_TYPE_SSL_CA; |
| break; |
| case certUsageSSLClient: |
| requiredKeyUsage = KU_KEY_CERT_SIGN; |
| requiredCertType = NS_CERT_TYPE_SSL_CA; |
| break; |
| case certUsageSSLServer: |
| requiredKeyUsage = KU_KEY_CERT_SIGN; |
| requiredCertType = NS_CERT_TYPE_SSL_CA; |
| break; |
| case certUsageIPsec: |
| requiredKeyUsage = KU_KEY_CERT_SIGN; |
| requiredCertType = NS_CERT_TYPE_IPSEC_CA; |
| break; |
| case certUsageSSLCA: |
| requiredKeyUsage = KU_KEY_CERT_SIGN; |
| requiredCertType = NS_CERT_TYPE_SSL_CA; |
| break; |
| case certUsageEmailSigner: |
| requiredKeyUsage = KU_KEY_CERT_SIGN; |
| requiredCertType = NS_CERT_TYPE_EMAIL_CA; |
| break; |
| case certUsageEmailRecipient: |
| requiredKeyUsage = KU_KEY_CERT_SIGN; |
| requiredCertType = NS_CERT_TYPE_EMAIL_CA; |
| break; |
| case certUsageObjectSigner: |
| requiredKeyUsage = KU_KEY_CERT_SIGN; |
| requiredCertType = NS_CERT_TYPE_OBJECT_SIGNING_CA; |
| break; |
| case certUsageAnyCA: |
| case certUsageVerifyCA: |
| case certUsageStatusResponder: |
| requiredKeyUsage = KU_KEY_CERT_SIGN; |
| requiredCertType = NS_CERT_TYPE_OBJECT_SIGNING_CA | |
| NS_CERT_TYPE_EMAIL_CA | NS_CERT_TYPE_SSL_CA; |
| break; |
| default: |
| PORT_Assert(0); |
| goto loser; |
| } |
| } else { |
| switch (usage) { |
| case certUsageSSLClient: |
| /* |
| * RFC 5280 lists digitalSignature and keyAgreement for |
| * id-kp-clientAuth. NSS does not support the *_fixed_dh and |
| * *_fixed_ecdh client certificate types. |
| */ |
| requiredKeyUsage = KU_DIGITAL_SIGNATURE; |
| requiredCertType = NS_CERT_TYPE_SSL_CLIENT; |
| break; |
| case certUsageSSLServer: |
| requiredKeyUsage = KU_KEY_AGREEMENT_OR_ENCIPHERMENT; |
| requiredCertType = NS_CERT_TYPE_SSL_SERVER; |
| break; |
| case certUsageIPsec: |
| /* RFC 4945 Section 5.1.3.2 */ |
| requiredKeyUsage = KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION; |
| requiredCertType = NS_CERT_TYPE_IPSEC; |
| break; |
| case certUsageSSLServerWithStepUp: |
| requiredKeyUsage = |
| KU_KEY_AGREEMENT_OR_ENCIPHERMENT | KU_NS_GOVT_APPROVED; |
| requiredCertType = NS_CERT_TYPE_SSL_SERVER; |
| break; |
| case certUsageSSLCA: |
| requiredKeyUsage = KU_KEY_CERT_SIGN; |
| requiredCertType = NS_CERT_TYPE_SSL_CA; |
| break; |
| case certUsageEmailSigner: |
| requiredKeyUsage = KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION; |
| requiredCertType = NS_CERT_TYPE_EMAIL; |
| break; |
| case certUsageEmailRecipient: |
| requiredKeyUsage = KU_KEY_AGREEMENT_OR_ENCIPHERMENT; |
| requiredCertType = NS_CERT_TYPE_EMAIL; |
| break; |
| case certUsageObjectSigner: |
| /* RFC 5280 lists only digitalSignature for id-kp-codeSigning. |
| */ |
| requiredKeyUsage = KU_DIGITAL_SIGNATURE; |
| requiredCertType = NS_CERT_TYPE_OBJECT_SIGNING; |
| break; |
| case certUsageStatusResponder: |
| requiredKeyUsage = KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION; |
| requiredCertType = EXT_KEY_USAGE_STATUS_RESPONDER; |
| break; |
| default: |
| PORT_Assert(0); |
| goto loser; |
| } |
| } |
| |
| if (retKeyUsage != NULL) { |
| *retKeyUsage = requiredKeyUsage; |
| } |
| if (retCertType != NULL) { |
| *retCertType = requiredCertType; |
| } |
| |
| return (SECSuccess); |
| loser: |
| return (SECFailure); |
| } |
| |
| /* |
| * check the key usage of a cert against a set of required values |
| */ |
| SECStatus |
| CERT_CheckKeyUsage(CERTCertificate *cert, unsigned int requiredUsage) |
| { |
| if (!cert) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return SECFailure; |
| } |
| /* choose between key agreement or key encipherment based on key |
| * type in cert |
| */ |
| if (requiredUsage & KU_KEY_AGREEMENT_OR_ENCIPHERMENT) { |
| KeyType keyType = CERT_GetCertKeyType(&cert->subjectPublicKeyInfo); |
| /* turn off the special bit */ |
| requiredUsage &= (~KU_KEY_AGREEMENT_OR_ENCIPHERMENT); |
| |
| switch (keyType) { |
| case rsaKey: |
| requiredUsage |= KU_KEY_ENCIPHERMENT; |
| break; |
| case rsaPssKey: |
| case dsaKey: |
| requiredUsage |= KU_DIGITAL_SIGNATURE; |
| break; |
| case dhKey: |
| requiredUsage |= KU_KEY_AGREEMENT; |
| break; |
| case ecKey: |
| /* Accept either signature or agreement. */ |
| if (!(cert->keyUsage & |
| (KU_DIGITAL_SIGNATURE | KU_KEY_AGREEMENT))) |
| goto loser; |
| break; |
| default: |
| goto loser; |
| } |
| } |
| |
| /* Allow either digital signature or non-repudiation */ |
| if (requiredUsage & KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION) { |
| /* turn off the special bit */ |
| requiredUsage &= (~KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION); |
| |
| if (!(cert->keyUsage & (KU_DIGITAL_SIGNATURE | KU_NON_REPUDIATION))) |
| goto loser; |
| } |
| |
| if ((cert->keyUsage & requiredUsage) == requiredUsage) |
| return SECSuccess; |
| |
| loser: |
| PORT_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE); |
| return SECFailure; |
| } |
| |
| CERTCertificate * |
| CERT_DupCertificate(CERTCertificate *c) |
| { |
| if (c) { |
| NSSCertificate *tmp = STAN_GetNSSCertificate(c); |
| nssCertificate_AddRef(tmp); |
| } |
| return c; |
| } |
| |
| SECStatus |
| CERT_GetCertificateDer(const CERTCertificate *c, SECItem *der) |
| { |
| if (!c || !der) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return SECFailure; |
| } |
| *der = c->derCert; |
| return SECSuccess; |
| } |
| |
| /* |
| * Allow use of default cert database, so that apps(such as mozilla) don't |
| * have to pass the handle all over the place. |
| */ |
| static CERTCertDBHandle *default_cert_db_handle = 0; |
| |
| void |
| CERT_SetDefaultCertDB(CERTCertDBHandle *handle) |
| { |
| default_cert_db_handle = handle; |
| |
| return; |
| } |
| |
| CERTCertDBHandle * |
| CERT_GetDefaultCertDB(void) |
| { |
| return (default_cert_db_handle); |
| } |
| |
| /* XXX this would probably be okay/better as an xp routine? */ |
| static void |
| sec_lower_string(char *s) |
| { |
| if (s == NULL) { |
| return; |
| } |
| |
| while (*s) { |
| *s = PORT_Tolower(*s); |
| s++; |
| } |
| |
| return; |
| } |
| |
| static PRBool |
| cert_IsIPAddr(const char *hn) |
| { |
| PRBool isIPaddr = PR_FALSE; |
| PRNetAddr netAddr; |
| isIPaddr = (PR_SUCCESS == PR_StringToNetAddr(hn, &netAddr)); |
| return isIPaddr; |
| } |
| |
| /* |
| ** Add a domain name to the list of names that the user has explicitly |
| ** allowed (despite cert name mismatches) for use with a server cert. |
| */ |
| SECStatus |
| CERT_AddOKDomainName(CERTCertificate *cert, const char *hn) |
| { |
| CERTOKDomainName *domainOK; |
| int newNameLen; |
| |
| if (!hn || !(newNameLen = strlen(hn))) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return SECFailure; |
| } |
| domainOK = (CERTOKDomainName *)PORT_ArenaZAlloc(cert->arena, sizeof(*domainOK)); |
| if (!domainOK) { |
| return SECFailure; /* error code is already set. */ |
| } |
| domainOK->name = (char *)PORT_ArenaZAlloc(cert->arena, newNameLen + 1); |
| if (!domainOK->name) { |
| return SECFailure; /* error code is already set. */ |
| } |
| |
| PORT_Strncpy(domainOK->name, hn, newNameLen + 1); |
| sec_lower_string(domainOK->name); |
| |
| /* put at head of list. */ |
| domainOK->next = cert->domainOK; |
| cert->domainOK = domainOK; |
| return SECSuccess; |
| } |
| |
| /* returns SECSuccess if hn matches pattern cn, |
| ** returns SECFailure with SSL_ERROR_BAD_CERT_DOMAIN if no match, |
| ** returns SECFailure with some other error code if another error occurs. |
| ** |
| ** This function may modify string cn, so caller must pass a modifiable copy. |
| */ |
| static SECStatus |
| cert_TestHostName(char *cn, const char *hn) |
| { |
| static int useShellExp = -1; |
| |
| if (useShellExp < 0) { |
| useShellExp = (NULL != PR_GetEnvSecure("NSS_USE_SHEXP_IN_CERT_NAME")); |
| } |
| if (useShellExp) { |
| /* Backward compatible code, uses Shell Expressions (SHEXP). */ |
| int regvalid = PORT_RegExpValid(cn); |
| if (regvalid != NON_SXP) { |
| SECStatus rv; |
| /* cn is a regular expression, try to match the shexp */ |
| int match = PORT_RegExpCaseSearch(hn, cn); |
| |
| if (match == 0) { |
| rv = SECSuccess; |
| } else { |
| PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN); |
| rv = SECFailure; |
| } |
| return rv; |
| } |
| } else { |
| /* New approach conforms to RFC 6125. */ |
| char *wildcard = PORT_Strchr(cn, '*'); |
| char *firstcndot = PORT_Strchr(cn, '.'); |
| char *secondcndot = |
| firstcndot ? PORT_Strchr(firstcndot + 1, '.') : NULL; |
| char *firsthndot = PORT_Strchr(hn, '.'); |
| |
| /* For a cn pattern to be considered valid, the wildcard character... |
| * - may occur only in a DNS name with at least 3 components, and |
| * - may occur only as last character in the first component, and |
| * - may be preceded by additional characters, and |
| * - must not be preceded by an IDNA ACE prefix (xn--) |
| */ |
| if (wildcard && secondcndot && secondcndot[1] && firsthndot && |
| firstcndot - wildcard == 1 /* wildcard is last char in first component */ |
| && secondcndot - firstcndot > 1 /* second component is non-empty */ |
| && PORT_Strrchr(cn, '*') == wildcard /* only one wildcard in cn */ |
| && !PORT_Strncasecmp(cn, hn, wildcard - cn) && |
| !PORT_Strcasecmp(firstcndot, firsthndot) |
| /* If hn starts with xn--, then cn must start with wildcard */ |
| && (PORT_Strncasecmp(hn, "xn--", 4) || wildcard == cn)) { |
| /* valid wildcard pattern match */ |
| return SECSuccess; |
| } |
| } |
| /* String cn has no wildcard or shell expression. |
| * Compare entire string hn with cert name. |
| */ |
| if (PORT_Strcasecmp(hn, cn) == 0) { |
| return SECSuccess; |
| } |
| |
| PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN); |
| return SECFailure; |
| } |
| |
| SECStatus |
| cert_VerifySubjectAltName(const CERTCertificate *cert, const char *hn) |
| { |
| PLArenaPool *arena = NULL; |
| CERTGeneralName *nameList = NULL; |
| CERTGeneralName *current; |
| char *cn; |
| int cnBufLen; |
| int DNSextCount = 0; |
| int IPextCount = 0; |
| PRBool isIPaddr = PR_FALSE; |
| SECStatus rv = SECFailure; |
| SECItem subAltName; |
| PRNetAddr netAddr; |
| char cnbuf[128]; |
| |
| subAltName.data = NULL; |
| cn = cnbuf; |
| cnBufLen = sizeof cnbuf; |
| |
| rv = CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME, |
| &subAltName); |
| if (rv != SECSuccess) { |
| goto fail; |
| } |
| isIPaddr = (PR_SUCCESS == PR_StringToNetAddr(hn, &netAddr)); |
| arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); |
| if (!arena) |
| goto fail; |
| |
| nameList = current = CERT_DecodeAltNameExtension(arena, &subAltName); |
| if (!current) |
| goto fail; |
| |
| do { |
| switch (current->type) { |
| case certDNSName: |
| if (!isIPaddr) { |
| /* DNS name current->name.other.data is not null terminated. |
| ** so must copy it. |
| */ |
| int cnLen = current->name.other.len; |
| rv = CERT_RFC1485_EscapeAndQuote( |
| cn, cnBufLen, (char *)current->name.other.data, cnLen); |
| if (rv != SECSuccess && |
| PORT_GetError() == SEC_ERROR_OUTPUT_LEN) { |
| cnBufLen = |
| cnLen * 3 + 3; /* big enough for worst case */ |
| cn = (char *)PORT_ArenaAlloc(arena, cnBufLen); |
| if (!cn) |
| goto fail; |
| rv = CERT_RFC1485_EscapeAndQuote( |
| cn, cnBufLen, (char *)current->name.other.data, |
| cnLen); |
| } |
| if (rv == SECSuccess) |
| rv = cert_TestHostName(cn, hn); |
| if (rv == SECSuccess) |
| goto finish; |
| } |
| DNSextCount++; |
| break; |
| case certIPAddress: |
| if (isIPaddr) { |
| int match = 0; |
| PRIPv6Addr v6Addr; |
| if (current->name.other.len == 4 && /* IP v4 address */ |
| netAddr.inet.family == PR_AF_INET) { |
| match = !memcmp(&netAddr.inet.ip, |
| current->name.other.data, 4); |
| } else if (current->name.other.len == |
| 16 && /* IP v6 address */ |
| netAddr.ipv6.family == PR_AF_INET6) { |
| match = !memcmp(&netAddr.ipv6.ip, |
| current->name.other.data, 16); |
| } else if (current->name.other.len == |
| 16 && /* IP v6 address */ |
| netAddr.inet.family == PR_AF_INET) { |
| /* convert netAddr to ipv6, then compare. */ |
| /* ipv4 must be in Network Byte Order on input. */ |
| PR_ConvertIPv4AddrToIPv6(netAddr.inet.ip, &v6Addr); |
| match = !memcmp(&v6Addr, current->name.other.data, 16); |
| } else if (current->name.other.len == 4 && /* IP v4 address */ |
| netAddr.inet.family == PR_AF_INET6) { |
| /* convert netAddr to ipv6, then compare. */ |
| PRUint32 ipv4 = (current->name.other.data[0] << 24) | |
| (current->name.other.data[1] << 16) | |
| (current->name.other.data[2] << 8) | |
| current->name.other.data[3]; |
| /* ipv4 must be in Network Byte Order on input. */ |
| PR_ConvertIPv4AddrToIPv6(PR_htonl(ipv4), &v6Addr); |
| match = !memcmp(&netAddr.ipv6.ip, &v6Addr, 16); |
| } |
| if (match) { |
| rv = SECSuccess; |
| goto finish; |
| } |
| } |
| IPextCount++; |
| break; |
| default: |
| break; |
| } |
| current = CERT_GetNextGeneralName(current); |
| } while (current != nameList); |
| |
| fail: |
| |
| if (!(isIPaddr ? IPextCount : DNSextCount)) { |
| /* no relevant value in the extension was found. */ |
| PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND); |
| } else { |
| PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN); |
| } |
| rv = SECFailure; |
| |
| finish: |
| |
| /* Don't free nameList, it's part of the arena. */ |
| if (arena) { |
| PORT_FreeArena(arena, PR_FALSE); |
| } |
| |
| if (subAltName.data) { |
| SECITEM_FreeItem(&subAltName, PR_FALSE); |
| } |
| |
| return rv; |
| } |
| |
| /* |
| * If found: |
| * - subAltName contains the extension (caller must free) |
| * - return value is the decoded namelist (allocated off arena) |
| * if not found, or if failure to decode: |
| * - return value is NULL |
| */ |
| CERTGeneralName * |
| cert_GetSubjectAltNameList(const CERTCertificate *cert, PLArenaPool *arena) |
| { |
| CERTGeneralName *nameList = NULL; |
| SECStatus rv = SECFailure; |
| SECItem subAltName; |
| |
| if (!cert || !arena) |
| return NULL; |
| |
| subAltName.data = NULL; |
| |
| rv = CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME, |
| &subAltName); |
| if (rv != SECSuccess) |
| return NULL; |
| |
| nameList = CERT_DecodeAltNameExtension(arena, &subAltName); |
| SECITEM_FreeItem(&subAltName, PR_FALSE); |
| return nameList; |
| } |
| |
| PRUint32 |
| cert_CountDNSPatterns(CERTGeneralName *firstName) |
| { |
| CERTGeneralName *current; |
| PRUint32 count = 0; |
| |
| if (!firstName) |
| return 0; |
| |
| current = firstName; |
| do { |
| switch (current->type) { |
| case certDNSName: |
| case certIPAddress: |
| ++count; |
| break; |
| default: |
| break; |
| } |
| current = CERT_GetNextGeneralName(current); |
| } while (current != firstName); |
| |
| return count; |
| } |
| |
| #ifndef INET6_ADDRSTRLEN |
| #define INET6_ADDRSTRLEN 46 |
| #endif |
| |
| /* will fill nickNames, |
| * will allocate all data from nickNames->arena, |
| * numberOfGeneralNames should have been obtained from cert_CountDNSPatterns, |
| * will ensure the numberOfGeneralNames matches the number of output entries. |
| */ |
| SECStatus |
| cert_GetDNSPatternsFromGeneralNames(CERTGeneralName *firstName, |
| PRUint32 numberOfGeneralNames, |
| CERTCertNicknames *nickNames) |
| { |
| CERTGeneralName *currentInput; |
| char **currentOutput; |
| |
| if (!firstName || !nickNames || !numberOfGeneralNames) |
| return SECFailure; |
| |
| nickNames->numnicknames = numberOfGeneralNames; |
| nickNames->nicknames = PORT_ArenaAlloc( |
| nickNames->arena, sizeof(char *) * numberOfGeneralNames); |
| if (!nickNames->nicknames) |
| return SECFailure; |
| |
| currentInput = firstName; |
| currentOutput = nickNames->nicknames; |
| do { |
| char *cn = NULL; |
| char ipbuf[INET6_ADDRSTRLEN]; |
| PRNetAddr addr; |
| |
| if (numberOfGeneralNames < 1) { |
| /* internal consistency error */ |
| return SECFailure; |
| } |
| |
| switch (currentInput->type) { |
| case certDNSName: |
| /* DNS name currentInput->name.other.data is not null |
| *terminated. |
| ** so must copy it. |
| */ |
| cn = (char *)PORT_ArenaAlloc(nickNames->arena, |
| currentInput->name.other.len + 1); |
| if (!cn) |
| return SECFailure; |
| PORT_Memcpy(cn, currentInput->name.other.data, |
| currentInput->name.other.len); |
| cn[currentInput->name.other.len] = 0; |
| break; |
| case certIPAddress: |
| if (currentInput->name.other.len == 4) { |
| addr.inet.family = PR_AF_INET; |
| memcpy(&addr.inet.ip, currentInput->name.other.data, |
| currentInput->name.other.len); |
| } else if (currentInput->name.other.len == 16) { |
| addr.ipv6.family = PR_AF_INET6; |
| memcpy(&addr.ipv6.ip, currentInput->name.other.data, |
| currentInput->name.other.len); |
| } |
| if (PR_NetAddrToString(&addr, ipbuf, sizeof(ipbuf)) == |
| PR_FAILURE) |
| return SECFailure; |
| cn = PORT_ArenaStrdup(nickNames->arena, ipbuf); |
| if (!cn) |
| return SECFailure; |
| break; |
| default: |
| break; |
| } |
| if (cn) { |
| *currentOutput = cn; |
| nickNames->totallen += PORT_Strlen(cn); |
| ++currentOutput; |
| --numberOfGeneralNames; |
| } |
| currentInput = CERT_GetNextGeneralName(currentInput); |
| } while (currentInput != firstName); |
| |
| return (numberOfGeneralNames == 0) ? SECSuccess : SECFailure; |
| } |
| |
| /* |
| * Collect all valid DNS names from the given cert. |
| * The output arena will reference some temporaray data, |
| * but this saves us from dealing with two arenas. |
| * The caller may free all data by freeing CERTCertNicknames->arena. |
| */ |
| CERTCertNicknames * |
| CERT_GetValidDNSPatternsFromCert(CERTCertificate *cert) |
| { |
| CERTGeneralName *generalNames; |
| CERTCertNicknames *nickNames; |
| PLArenaPool *arena; |
| char *singleName; |
| |
| arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); |
| if (!arena) { |
| return NULL; |
| } |
| |
| nickNames = PORT_ArenaAlloc(arena, sizeof(CERTCertNicknames)); |
| if (!nickNames) { |
| PORT_FreeArena(arena, PR_FALSE); |
| return NULL; |
| } |
| |
| /* init the structure */ |
| nickNames->arena = arena; |
| nickNames->head = NULL; |
| nickNames->numnicknames = 0; |
| nickNames->nicknames = NULL; |
| nickNames->totallen = 0; |
| |
| generalNames = cert_GetSubjectAltNameList(cert, arena); |
| if (generalNames) { |
| SECStatus rv_getnames = SECFailure; |
| PRUint32 numNames = cert_CountDNSPatterns(generalNames); |
| |
| if (numNames) { |
| rv_getnames = cert_GetDNSPatternsFromGeneralNames( |
| generalNames, numNames, nickNames); |
| } |
| |
| /* if there were names, we'll exit now, either with success or failure |
| */ |
| if (numNames) { |
| if (rv_getnames == SECSuccess) { |
| return nickNames; |
| } |
| |
| /* failure to produce output */ |
| PORT_FreeArena(arena, PR_FALSE); |
| return NULL; |
| } |
| } |
| |
| /* no SAN extension or no names found in extension */ |
| singleName = CERT_GetCommonName(&cert->subject); |
| if (singleName) { |
| nickNames->numnicknames = 1; |
| nickNames->nicknames = PORT_ArenaAlloc(arena, sizeof(char *)); |
| if (nickNames->nicknames) { |
| *nickNames->nicknames = PORT_ArenaStrdup(arena, singleName); |
| } |
| PORT_Free(singleName); |
| |
| /* Did we allocate both the buffer of pointers and the string? */ |
| if (nickNames->nicknames && *nickNames->nicknames) { |
| return nickNames; |
| } |
| } |
| |
| PORT_FreeArena(arena, PR_FALSE); |
| return NULL; |
| } |
| |
| /* Make sure that the name of the host we are connecting to matches the |
| * name that is incoded in the common-name component of the certificate |
| * that they are using. |
| */ |
| SECStatus |
| CERT_VerifyCertName(const CERTCertificate *cert, const char *hn) |
| { |
| char *cn; |
| SECStatus rv; |
| CERTOKDomainName *domainOK; |
| |
| if (!hn || !strlen(hn)) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return SECFailure; |
| } |
| |
| /* if the name is one that the user has already approved, it's OK. */ |
| for (domainOK = cert->domainOK; domainOK; domainOK = domainOK->next) { |
| if (0 == PORT_Strcasecmp(hn, domainOK->name)) { |
| return SECSuccess; |
| } |
| } |
| |
| /* Per RFC 2818, if the SubjectAltName extension is present, it must |
| ** be used as the cert's identity. |
| */ |
| rv = cert_VerifySubjectAltName(cert, hn); |
| if (rv == SECSuccess || PORT_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND) |
| return rv; |
| |
| cn = CERT_GetCommonName(&cert->subject); |
| if (cn) { |
| PRBool isIPaddr = cert_IsIPAddr(hn); |
| if (isIPaddr) { |
| if (PORT_Strcasecmp(hn, cn) == 0) { |
| rv = SECSuccess; |
| } else { |
| PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN); |
| rv = SECFailure; |
| } |
| } else { |
| rv = cert_TestHostName(cn, hn); |
| } |
| PORT_Free(cn); |
| } else |
| PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN); |
| return rv; |
| } |
| |
| PRBool |
| CERT_CompareCerts(const CERTCertificate *c1, const CERTCertificate *c2) |
| { |
| SECComparison comp; |
| |
| comp = SECITEM_CompareItem(&c1->derCert, &c2->derCert); |
| if (comp == SECEqual) { /* certs are the same */ |
| return (PR_TRUE); |
| } else { |
| return (PR_FALSE); |
| } |
| } |
| |
| static SECStatus |
| StringsEqual(char *s1, char *s2) |
| { |
| if ((s1 == NULL) || (s2 == NULL)) { |
| if (s1 != s2) { /* only one is null */ |
| return (SECFailure); |
| } |
| return (SECSuccess); /* both are null */ |
| } |
| |
| if (PORT_Strcmp(s1, s2) != 0) { |
| return (SECFailure); /* not equal */ |
| } |
| |
| return (SECSuccess); /* strings are equal */ |
| } |
| |
| PRBool |
| CERT_CompareCertsForRedirection(CERTCertificate *c1, CERTCertificate *c2) |
| { |
| SECComparison comp; |
| char *c1str, *c2str; |
| SECStatus eq; |
| |
| comp = SECITEM_CompareItem(&c1->derCert, &c2->derCert); |
| if (comp == SECEqual) { /* certs are the same */ |
| return (PR_TRUE); |
| } |
| |
| /* check if they are issued by the same CA */ |
| comp = SECITEM_CompareItem(&c1->derIssuer, &c2->derIssuer); |
| if (comp != SECEqual) { /* different issuer */ |
| return (PR_FALSE); |
| } |
| |
| /* check country name */ |
| c1str = CERT_GetCountryName(&c1->subject); |
| c2str = CERT_GetCountryName(&c2->subject); |
| eq = StringsEqual(c1str, c2str); |
| PORT_Free(c1str); |
| PORT_Free(c2str); |
| if (eq != SECSuccess) { |
| return (PR_FALSE); |
| } |
| |
| /* check locality name */ |
| c1str = CERT_GetLocalityName(&c1->subject); |
| c2str = CERT_GetLocalityName(&c2->subject); |
| eq = StringsEqual(c1str, c2str); |
| PORT_Free(c1str); |
| PORT_Free(c2str); |
| if (eq != SECSuccess) { |
| return (PR_FALSE); |
| } |
| |
| /* check state name */ |
| c1str = CERT_GetStateName(&c1->subject); |
| c2str = CERT_GetStateName(&c2->subject); |
| eq = StringsEqual(c1str, c2str); |
| PORT_Free(c1str); |
| PORT_Free(c2str); |
| if (eq != SECSuccess) { |
| return (PR_FALSE); |
| } |
| |
| /* check org name */ |
| c1str = CERT_GetOrgName(&c1->subject); |
| c2str = CERT_GetOrgName(&c2->subject); |
| eq = StringsEqual(c1str, c2str); |
| PORT_Free(c1str); |
| PORT_Free(c2str); |
| if (eq != SECSuccess) { |
| return (PR_FALSE); |
| } |
| |
| #ifdef NOTDEF |
| /* check orgUnit name */ |
| /* |
| * We need to revisit this and decide which fields should be allowed to be |
| * different |
| */ |
| c1str = CERT_GetOrgUnitName(&c1->subject); |
| c2str = CERT_GetOrgUnitName(&c2->subject); |
| eq = StringsEqual(c1str, c2str); |
| PORT_Free(c1str); |
| PORT_Free(c2str); |
| if (eq != SECSuccess) { |
| return (PR_FALSE); |
| } |
| #endif |
| |
| return (PR_TRUE); /* all fields but common name are the same */ |
| } |
| |
| /* CERT_CertChainFromCert and CERT_DestroyCertificateList moved |
| to certhigh.c */ |
| |
| CERTIssuerAndSN * |
| CERT_GetCertIssuerAndSN(PLArenaPool *arena, CERTCertificate *cert) |
| { |
| CERTIssuerAndSN *result; |
| SECStatus rv; |
| |
| if (arena == NULL) { |
| arena = cert->arena; |
| } |
| |
| result = (CERTIssuerAndSN *)PORT_ArenaZAlloc(arena, sizeof(*result)); |
| if (result == NULL) { |
| PORT_SetError(SEC_ERROR_NO_MEMORY); |
| return NULL; |
| } |
| |
| rv = SECITEM_CopyItem(arena, &result->derIssuer, &cert->derIssuer); |
| if (rv != SECSuccess) |
| return NULL; |
| |
| rv = CERT_CopyName(arena, &result->issuer, &cert->issuer); |
| if (rv != SECSuccess) |
| return NULL; |
| |
| rv = SECITEM_CopyItem(arena, &result->serialNumber, &cert->serialNumber); |
| if (rv != SECSuccess) |
| return NULL; |
| |
| return result; |
| } |
| |
| char * |
| CERT_MakeCANickname(CERTCertificate *cert) |
| { |
| char *firstname = NULL; |
| char *org = NULL; |
| char *nickname = NULL; |
| int count; |
| CERTCertificate *dummycert; |
| |
| firstname = CERT_GetCommonName(&cert->subject); |
| if (firstname == NULL) { |
| firstname = CERT_GetOrgUnitName(&cert->subject); |
| } |
| |
| org = CERT_GetOrgName(&cert->issuer); |
| if (org == NULL) { |
| org = CERT_GetDomainComponentName(&cert->issuer); |
| if (org == NULL) { |
| if (firstname) { |
| org = firstname; |
| firstname = NULL; |
| } else { |
| org = PORT_Strdup("Unknown CA"); |
| } |
| } |
| } |
| |
| /* can only fail if PORT_Strdup fails, in which case |
| * we're having memory problems. */ |
| if (org == NULL) { |
| goto done; |
| } |
| |
| count = 1; |
| while (1) { |
| |
| if (firstname) { |
| if (count == 1) { |
| nickname = PR_smprintf("%s - %s", firstname, org); |
| } else { |
| nickname = PR_smprintf("%s - %s #%d", firstname, org, count); |
| } |
| } else { |
| if (count == 1) { |
| nickname = PR_smprintf("%s", org); |
| } else { |
| nickname = PR_smprintf("%s #%d", org, count); |
| } |
| } |
| if (nickname == NULL) { |
| goto done; |
| } |
| |
| /* look up the nickname to make sure it isn't in use already */ |
| dummycert = CERT_FindCertByNickname(cert->dbhandle, nickname); |
| |
| if (dummycert == NULL) { |
| goto done; |
| } |
| |
| /* found a cert, destroy it and loop */ |
| CERT_DestroyCertificate(dummycert); |
| |
| /* free the nickname */ |
| PORT_Free(nickname); |
| |
| count++; |
| } |
| |
| done: |
| if (firstname) { |
| PORT_Free(firstname); |
| } |
| if (org) { |
| PORT_Free(org); |
| } |
| |
| return (nickname); |
| } |
| |
| /* CERT_Import_CAChain moved to certhigh.c */ |
| |
| void |
| CERT_DestroyCrl(CERTSignedCrl *crl) |
| { |
| SEC_DestroyCrl(crl); |
| } |
| |
| static int |
| cert_Version(CERTCertificate *cert) |
| { |
| int version = 0; |
| if (cert && cert->version.data && cert->version.len) { |
| version = DER_GetInteger(&cert->version); |
| if (version < 0) |
| version = 0; |
| } |
| return version; |
| } |
| |
| static unsigned int |
| cert_ComputeTrustOverrides(CERTCertificate *cert, unsigned int cType) |
| { |
| CERTCertTrust trust; |
| SECStatus rv = SECFailure; |
| |
| rv = CERT_GetCertTrust(cert, &trust); |
| |
| if (rv == SECSuccess && |
| (trust.sslFlags | trust.emailFlags | trust.objectSigningFlags)) { |
| |
| if (trust.sslFlags & (CERTDB_TERMINAL_RECORD | CERTDB_TRUSTED)) |
| cType |= NS_CERT_TYPE_SSL_SERVER | NS_CERT_TYPE_SSL_CLIENT; |
| if (trust.sslFlags & (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) |
| cType |= NS_CERT_TYPE_SSL_CA; |
| #if defined(CERTDB_NOT_TRUSTED) |
| if (trust.sslFlags & CERTDB_NOT_TRUSTED) |
| cType &= ~(NS_CERT_TYPE_SSL_SERVER | NS_CERT_TYPE_SSL_CLIENT | |
| NS_CERT_TYPE_SSL_CA); |
| #endif |
| if (trust.emailFlags & (CERTDB_TERMINAL_RECORD | CERTDB_TRUSTED)) |
| cType |= NS_CERT_TYPE_EMAIL; |
| if (trust.emailFlags & (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) |
| cType |= NS_CERT_TYPE_EMAIL_CA; |
| #if defined(CERTDB_NOT_TRUSTED) |
| if (trust.emailFlags & CERTDB_NOT_TRUSTED) |
| cType &= ~(NS_CERT_TYPE_EMAIL | NS_CERT_TYPE_EMAIL_CA); |
| #endif |
| if (trust.objectSigningFlags & |
| (CERTDB_TERMINAL_RECORD | CERTDB_TRUSTED)) |
| cType |= NS_CERT_TYPE_OBJECT_SIGNING; |
| if (trust.objectSigningFlags & (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) |
| cType |= NS_CERT_TYPE_OBJECT_SIGNING_CA; |
| #if defined(CERTDB_NOT_TRUSTED) |
| if (trust.objectSigningFlags & CERTDB_NOT_TRUSTED) |
| cType &= |
| ~(NS_CERT_TYPE_OBJECT_SIGNING | NS_CERT_TYPE_OBJECT_SIGNING_CA); |
| #endif |
| } |
| return cType; |
| } |
| |
| /* |
| * Does a cert belong to a CA? We decide based on perm database trust |
| * flags, Netscape Cert Type Extension, and KeyUsage Extension. |
| */ |
| PRBool |
| CERT_IsCACert(CERTCertificate *cert, unsigned int *rettype) |
| { |
| unsigned int cType = cert->nsCertType; |
| PRBool ret = PR_FALSE; |
| |
| /* |
| * Check if the constraints are available and it's a CA, OR if it's |
| * a X.509 v1 Root CA. |
| */ |
| CERTBasicConstraints constraints; |
| if ((CERT_FindBasicConstraintExten(cert, &constraints) == SECSuccess && |
| constraints.isCA) || |
| (cert->isRoot && cert_Version(cert) < SEC_CERTIFICATE_VERSION_3)) |
| cType |= (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA); |
| |
| /* |
| * Apply trust overrides, if any. |
| */ |
| cType = cert_ComputeTrustOverrides(cert, cType); |
| ret = (cType & (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA | |
| NS_CERT_TYPE_OBJECT_SIGNING_CA)) |
| ? PR_TRUE |
| : PR_FALSE; |
| |
| if (rettype) { |
| *rettype = cType; |
| } |
| |
| return ret; |
| } |
| |
| PRBool |
| CERT_IsCADERCert(SECItem *derCert, unsigned int *type) |
| { |
| CERTCertificate *cert; |
| PRBool isCA; |
| |
| /* This is okay -- only looks at extensions */ |
| cert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL); |
| if (cert == NULL) |
| return PR_FALSE; |
| |
| isCA = CERT_IsCACert(cert, type); |
| CERT_DestroyCertificate(cert); |
| return isCA; |
| } |
| |
| PRBool |
| CERT_IsRootDERCert(SECItem *derCert) |
| { |
| CERTCertificate *cert; |
| PRBool isRoot; |
| |
| /* This is okay -- only looks at extensions */ |
| cert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL); |
| if (cert == NULL) |
| return PR_FALSE; |
| |
| isRoot = cert->isRoot; |
| CERT_DestroyCertificate(cert); |
| return isRoot; |
| } |
| |
| CERTCompareValidityStatus |
| CERT_CompareValidityTimes(CERTValidity *val_a, CERTValidity *val_b) |
| { |
| PRTime notBeforeA, notBeforeB, notAfterA, notAfterB; |
| |
| if (!val_a || !val_b) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return certValidityUndetermined; |
| } |
| |
| if (SECSuccess != DER_DecodeTimeChoice(¬BeforeA, &val_a->notBefore) || |
| SECSuccess != DER_DecodeTimeChoice(¬BeforeB, &val_b->notBefore) || |
| SECSuccess != DER_DecodeTimeChoice(¬AfterA, &val_a->notAfter) || |
| SECSuccess != DER_DecodeTimeChoice(¬AfterB, &val_b->notAfter)) { |
| return certValidityUndetermined; |
| } |
| |
| /* sanity check */ |
| if (LL_CMP(notBeforeA, >, notAfterA) || LL_CMP(notBeforeB, >, notAfterB)) { |
| PORT_SetError(SEC_ERROR_INVALID_TIME); |
| return certValidityUndetermined; |
| } |
| |
| if (LL_CMP(notAfterA, !=, notAfterB)) { |
| /* one cert validity goes farther into the future, select it */ |
| return LL_CMP(notAfterA, <, notAfterB) ? certValidityChooseB |
| : certValidityChooseA; |
| } |
| /* the two certs have the same expiration date */ |
| PORT_Assert(LL_CMP(notAfterA, ==, notAfterB)); |
| /* do they also have the same start date ? */ |
| if (LL_CMP(notBeforeA, ==, notBeforeB)) { |
| return certValidityEqual; |
| } |
| /* choose cert with the later start date */ |
| return LL_CMP(notBeforeA, <, notBeforeB) ? certValidityChooseB |
| : certValidityChooseA; |
| } |
| |
| /* |
| * is certa newer than certb? If one is expired, pick the other one. |
| */ |
| PRBool |
| CERT_IsNewer(CERTCertificate *certa, CERTCertificate *certb) |
| { |
| PRTime notBeforeA, notAfterA, notBeforeB, notAfterB, now; |
| SECStatus rv; |
| PRBool newerbefore, newerafter; |
| |
| rv = CERT_GetCertTimes(certa, ¬BeforeA, ¬AfterA); |
| if (rv != SECSuccess) { |
| return (PR_FALSE); |
| } |
| |
| rv = CERT_GetCertTimes(certb, ¬BeforeB, ¬AfterB); |
| if (rv != SECSuccess) { |
| return (PR_TRUE); |
| } |
| |
| newerbefore = PR_FALSE; |
| if (LL_CMP(notBeforeA, >, notBeforeB)) { |
| newerbefore = PR_TRUE; |
| } |
| |
| newerafter = PR_FALSE; |
| if (LL_CMP(notAfterA, >, notAfterB)) { |
| newerafter = PR_TRUE; |
| } |
| |
| if (newerbefore && newerafter) { |
| return (PR_TRUE); |
| } |
| |
| if ((!newerbefore) && (!newerafter)) { |
| return (PR_FALSE); |
| } |
| |
| /* get current time */ |
| now = PR_Now(); |
| |
| if (newerbefore) { |
| /* cert A was issued after cert B, but expires sooner */ |
| /* if A is expired, then pick B */ |
| if (LL_CMP(notAfterA, <, now)) { |
| return (PR_FALSE); |
| } |
| return (PR_TRUE); |
| } else { |
| /* cert B was issued after cert A, but expires sooner */ |
| /* if B is expired, then pick A */ |
| if (LL_CMP(notAfterB, <, now)) { |
| return (PR_TRUE); |
| } |
| return (PR_FALSE); |
| } |
| } |
| |
| void |
| CERT_DestroyCertArray(CERTCertificate **certs, unsigned int ncerts) |
| { |
| unsigned int i; |
| |
| if (certs) { |
| for (i = 0; i < ncerts; i++) { |
| if (certs[i]) { |
| CERT_DestroyCertificate(certs[i]); |
| } |
| } |
| |
| PORT_Free(certs); |
| } |
| |
| return; |
| } |
| |
| char * |
| CERT_FixupEmailAddr(const char *emailAddr) |
| { |
| char *retaddr; |
| char *str; |
| |
| if (emailAddr == NULL) { |
| return (NULL); |
| } |
| |
| /* copy the string */ |
| str = retaddr = PORT_Strdup(emailAddr); |
| if (str == NULL) { |
| return (NULL); |
| } |
| |
| /* make it lower case */ |
| while (*str) { |
| *str = tolower(*str); |
| str++; |
| } |
| |
| return (retaddr); |
| } |
| |
| /* |
| * NOTE - don't allow encode of govt-approved or invisible bits |
| */ |
| SECStatus |
| CERT_DecodeTrustString(CERTCertTrust *trust, const char *trusts) |
| { |
| unsigned int i; |
| unsigned int *pflags; |
| |
| if (!trust) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return SECFailure; |
| } |
| trust->sslFlags = 0; |
| trust->emailFlags = 0; |
| trust->objectSigningFlags = 0; |
| if (!trusts) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return SECFailure; |
| } |
| |
| pflags = &trust->sslFlags; |
| |
| for (i = 0; i < PORT_Strlen(trusts); i++) { |
| switch (trusts[i]) { |
| case 'p': |
| *pflags = *pflags | CERTDB_TERMINAL_RECORD; |
| break; |
| |
| case 'P': |
| *pflags = *pflags | CERTDB_TRUSTED | CERTDB_TERMINAL_RECORD; |
| break; |
| |
| case 'w': |
| *pflags = *pflags | CERTDB_SEND_WARN; |
| break; |
| |
| case 'c': |
| *pflags = *pflags | CERTDB_VALID_CA; |
| break; |
| |
| case 'T': |
| *pflags = *pflags | CERTDB_TRUSTED_CLIENT_CA | CERTDB_VALID_CA; |
| break; |
| |
| case 'C': |
| *pflags = *pflags | CERTDB_TRUSTED_CA | CERTDB_VALID_CA; |
| break; |
| |
| case 'u': |
| *pflags = *pflags | CERTDB_USER; |
| break; |
| |
| case 'i': |
| *pflags = *pflags | CERTDB_INVISIBLE_CA; |
| break; |
| case 'g': |
| *pflags = *pflags | CERTDB_GOVT_APPROVED_CA; |
| break; |
| |
| case ',': |
| if (pflags == &trust->sslFlags) { |
| pflags = &trust->emailFlags; |
| } else { |
| pflags = &trust->objectSigningFlags; |
| } |
| break; |
| default: |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return SECFailure; |
| } |
| } |
| |
| return SECSuccess; |
| } |
| |
| static void |
| EncodeFlags(char *trusts, unsigned int flags) |
| { |
| if (flags & CERTDB_VALID_CA) |
| if (!(flags & CERTDB_TRUSTED_CA) && !(flags & CERTDB_TRUSTED_CLIENT_CA)) |
| PORT_Strcat(trusts, "c"); |
| if (flags & CERTDB_TERMINAL_RECORD) |
| if (!(flags & CERTDB_TRUSTED)) |
| PORT_Strcat(trusts, "p"); |
| if (flags & CERTDB_TRUSTED_CA) |
| PORT_Strcat(trusts, "C"); |
| if (flags & CERTDB_TRUSTED_CLIENT_CA) |
| PORT_Strcat(trusts, "T"); |
| if (flags & CERTDB_TRUSTED) |
| PORT_Strcat(trusts, "P"); |
| if (flags & CERTDB_USER) |
| PORT_Strcat(trusts, "u"); |
| if (flags & CERTDB_SEND_WARN) |
| PORT_Strcat(trusts, "w"); |
| if (flags & CERTDB_INVISIBLE_CA) |
| PORT_Strcat(trusts, "I"); |
| if (flags & CERTDB_GOVT_APPROVED_CA) |
| PORT_Strcat(trusts, "G"); |
| return; |
| } |
| |
| char * |
| CERT_EncodeTrustString(CERTCertTrust *trust) |
| { |
| char tmpTrustSSL[32]; |
| char tmpTrustEmail[32]; |
| char tmpTrustSigning[32]; |
| char *retstr = NULL; |
| |
| if (trust) { |
| tmpTrustSSL[0] = '\0'; |
| tmpTrustEmail[0] = '\0'; |
| tmpTrustSigning[0] = '\0'; |
| |
| EncodeFlags(tmpTrustSSL, trust->sslFlags); |
| EncodeFlags(tmpTrustEmail, trust->emailFlags); |
| EncodeFlags(tmpTrustSigning, trust->objectSigningFlags); |
| |
| retstr = PR_smprintf("%s,%s,%s", tmpTrustSSL, tmpTrustEmail, |
| tmpTrustSigning); |
| } |
| |
| return (retstr); |
| } |
| |
| SECStatus |
| CERT_ImportCerts(CERTCertDBHandle *certdb, SECCertUsage usage, |
| unsigned int ncerts, SECItem **derCerts, |
| CERTCertificate ***retCerts, PRBool keepCerts, PRBool caOnly, |
| char *nickname) |
| { |
| unsigned int i; |
| CERTCertificate **certs = NULL; |
| unsigned int fcerts = 0; |
| |
| if (ncerts) { |
| certs = PORT_ZNewArray(CERTCertificate *, ncerts); |
| if (certs == NULL) { |
| return (SECFailure); |
| } |
| |
| /* decode all of the certs into the temporary DB */ |
| for (i = 0, fcerts = 0; i < ncerts; i++) { |
| certs[fcerts] = CERT_NewTempCertificate(certdb, derCerts[i], NULL, |
| PR_FALSE, PR_TRUE); |
| if (certs[fcerts]) { |
| SECItem subjKeyID = { siBuffer, NULL, 0 }; |
| if (CERT_FindSubjectKeyIDExtension(certs[fcerts], &subjKeyID) == |
| SECSuccess) { |
| if (subjKeyID.data) { |
| cert_AddSubjectKeyIDMapping(&subjKeyID, certs[fcerts]); |
| } |
| SECITEM_FreeItem(&subjKeyID, PR_FALSE); |
| } |
| fcerts++; |
| } |
| } |
| |
| if (keepCerts) { |
| for (i = 0; i < fcerts; i++) { |
| char *canickname = NULL; |
| PRBool isCA; |
| |
| SECKEY_UpdateCertPQG(certs[i]); |
| |
| isCA = CERT_IsCACert(certs[i], NULL); |
| if (isCA) { |
| canickname = CERT_MakeCANickname(certs[i]); |
| } |
| |
| if (isCA && (fcerts > 1)) { |
| /* if we are importing only a single cert and specifying |
| * a nickname, we want to use that nickname if it a CA, |
| * otherwise if there are more than one cert, we don't |
| * know which cert it belongs to. But we still may try |
| * the individual canickname from the cert itself. |
| */ |
| /* Bug 1192442 - propagate errors from these calls. */ |
| (void)CERT_AddTempCertToPerm(certs[i], canickname, NULL); |
| } else { |
| (void)CERT_AddTempCertToPerm( |
| certs[i], nickname ? nickname : canickname, NULL); |
| } |
| |
| PORT_Free(canickname); |
| /* don't care if it fails - keep going */ |
| } |
| } |
| } |
| |
| if (retCerts) { |
| *retCerts = certs; |
| } else { |
| if (certs) { |
| CERT_DestroyCertArray(certs, fcerts); |
| } |
| } |
| |
| return (fcerts || !ncerts) ? SECSuccess : SECFailure; |
| } |
| |
| /* |
| * a real list of certificates - need to convert CERTCertificateList |
| * stuff and ASN 1 encoder/decoder over to using this... |
| */ |
| CERTCertList * |
| CERT_NewCertList(void) |
| { |
| PLArenaPool *arena = NULL; |
| CERTCertList *ret = NULL; |
| |
| arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); |
| if (arena == NULL) { |
| goto loser; |
| } |
| |
| ret = (CERTCertList *)PORT_ArenaZAlloc(arena, sizeof(CERTCertList)); |
| if (ret == NULL) { |
| goto loser; |
| } |
| |
| ret->arena = arena; |
| |
| PR_INIT_CLIST(&ret->list); |
| |
| return (ret); |
| |
| loser: |
| if (arena != NULL) { |
| PORT_FreeArena(arena, PR_FALSE); |
| } |
| |
| return (NULL); |
| } |
| |
| void |
| CERT_DestroyCertList(CERTCertList *certs) |
| { |
| PRCList *node; |
| |
| while (!PR_CLIST_IS_EMPTY(&certs->list)) { |
| node = PR_LIST_HEAD(&certs->list); |
| CERT_DestroyCertificate(((CERTCertListNode *)node)->cert); |
| PR_REMOVE_LINK(node); |
| } |
| |
| PORT_FreeArena(certs->arena, PR_FALSE); |
| |
| return; |
| } |
| |
| void |
| CERT_RemoveCertListNode(CERTCertListNode *node) |
| { |
| CERT_DestroyCertificate(node->cert); |
| PR_REMOVE_LINK(&node->links); |
| return; |
| } |
| |
| SECStatus |
| CERT_AddCertToListTailWithData(CERTCertList *certs, CERTCertificate *cert, |
| void *appData) |
| { |
| CERTCertListNode *node; |
| |
| node = (CERTCertListNode *)PORT_ArenaZAlloc(certs->arena, |
| sizeof(CERTCertListNode)); |
| if (node == NULL) { |
| goto loser; |
| } |
| |
| PR_INSERT_BEFORE(&node->links, &certs->list); |
| /* certs->count++; */ |
| node->cert = cert; |
| node->appData = appData; |
| return (SECSuccess); |
| |
| loser: |
| return (SECFailure); |
| } |
| |
| SECStatus |
| CERT_AddCertToListTail(CERTCertList *certs, CERTCertificate *cert) |
| { |
| return CERT_AddCertToListTailWithData(certs, cert, NULL); |
| } |
| |
| SECStatus |
| CERT_AddCertToListHeadWithData(CERTCertList *certs, CERTCertificate *cert, |
| void *appData) |
| { |
| CERTCertListNode *node; |
| CERTCertListNode *head; |
| |
| head = CERT_LIST_HEAD(certs); |
| if (head == NULL) { |
| goto loser; |
| } |
| |
| node = (CERTCertListNode *)PORT_ArenaZAlloc(certs->arena, |
| sizeof(CERTCertListNode)); |
| if (node == NULL) { |
| goto loser; |
| } |
| |
| PR_INSERT_BEFORE(&node->links, &head->links); |
| /* certs->count++; */ |
| node->cert = cert; |
| node->appData = appData; |
| return (SECSuccess); |
| |
| loser: |
| return (SECFailure); |
| } |
| |
| SECStatus |
| CERT_AddCertToListHead(CERTCertList *certs, CERTCertificate *cert) |
| { |
| return CERT_AddCertToListHeadWithData(certs, cert, NULL); |
| } |
| |
| /* |
| * Sort callback function to determine if cert a is newer than cert b. |
| * Not valid certs are considered older than valid certs. |
| */ |
| PRBool |
| CERT_SortCBValidity(CERTCertificate *certa, CERTCertificate *certb, void *arg) |
| { |
| PRTime sorttime; |
| PRTime notBeforeA, notAfterA, notBeforeB, notAfterB; |
| SECStatus rv; |
| PRBool newerbefore, newerafter; |
| PRBool aNotValid = PR_FALSE, bNotValid = PR_FALSE; |
| |
| sorttime = *(PRTime *)arg; |
| |
| rv = CERT_GetCertTimes(certa, ¬BeforeA, ¬AfterA); |
| if (rv != SECSuccess) { |
| return (PR_FALSE); |
| } |
| |
| rv = CERT_GetCertTimes(certb, ¬BeforeB, ¬AfterB); |
| if (rv != SECSuccess) { |
| return (PR_TRUE); |
| } |
| newerbefore = PR_FALSE; |
| if (LL_CMP(notBeforeA, >, notBeforeB)) { |
| newerbefore = PR_TRUE; |
| } |
| newerafter = PR_FALSE; |
| if (LL_CMP(notAfterA, >, notAfterB)) { |
| newerafter = PR_TRUE; |
| } |
| |
| /* check if A is valid at sorttime */ |
| if (CERT_CheckCertValidTimes(certa, sorttime, PR_FALSE) != |
| secCertTimeValid) { |
| aNotValid = PR_TRUE; |
| } |
| |
| /* check if B is valid at sorttime */ |
| if (CERT_CheckCertValidTimes(certb, sorttime, PR_FALSE) != |
| secCertTimeValid) { |
| bNotValid = PR_TRUE; |
| } |
| |
| /* a is valid, b is not */ |
| if (bNotValid && (!aNotValid)) { |
| return (PR_TRUE); |
| } |
| |
| /* b is valid, a is not */ |
| if (aNotValid && (!bNotValid)) { |
| return (PR_FALSE); |
| } |
| |
| /* a and b are either valid or not valid */ |
| if (newerbefore && newerafter) { |
| return (PR_TRUE); |
| } |
| |
| if ((!newerbefore) && (!newerafter)) { |
| return (PR_FALSE); |
| } |
| |
| if (newerbefore) { |
| /* cert A was issued after cert B, but expires sooner */ |
| return (PR_TRUE); |
| } else { |
| /* cert B was issued after cert A, but expires sooner */ |
| return (PR_FALSE); |
| } |
| } |
| |
| SECStatus |
| CERT_AddCertToListSorted(CERTCertList *certs, CERTCertificate *cert, |
| CERTSortCallback f, void *arg) |
| { |
| CERTCertListNode *node; |
| CERTCertListNode *head; |
| PRBool ret; |
| |
| node = (CERTCertListNode *)PORT_ArenaZAlloc(certs->arena, |
| sizeof(CERTCertListNode)); |
| if (node == NULL) { |
| goto loser; |
| } |
| |
| head = CERT_LIST_HEAD(certs); |
| |
| while (!CERT_LIST_END(head, certs)) { |
| |
| /* if cert is already in the list, then don't add it again */ |
| if (cert == head->cert) { |
| /*XXX*/ |
| /* don't keep a reference */ |
| CERT_DestroyCertificate(cert); |
| goto done; |
| } |
| |
| ret = (*f)(cert, head->cert, arg); |
| /* if sort function succeeds, then insert before current node */ |
| if (ret) { |
| PR_INSERT_BEFORE(&node->links, &head->links); |
| goto done; |
| } |
| |
| head = CERT_LIST_NEXT(head); |
| } |
| /* if we get to the end, then just insert it at the tail */ |
| PR_INSERT_BEFORE(&node->links, &certs->list); |
| |
| done: |
| /* certs->count++; */ |
| node->cert = cert; |
| return (SECSuccess); |
| |
| loser: |
| return (SECFailure); |
| } |
| |
| /* This routine is here because pcertdb.c still has a call to it. |
| * The SMIME profile code in pcertdb.c should be split into high (find |
| * the email cert) and low (store the profile) code. At that point, we |
| * can move this to certhigh.c where it belongs. |
| * |
| * remove certs from a list that don't have keyUsage and certType |
| * that match the given usage. |
| */ |
| SECStatus |
| CERT_FilterCertListByUsage(CERTCertList *certList, SECCertUsage usage, |
| PRBool ca) |
| { |
| unsigned int requiredKeyUsage; |
| unsigned int requiredCertType; |
| CERTCertListNode *node, *savenode; |
| SECStatus rv; |
| |
| if (certList == NULL) |
| goto loser; |
| |
| rv = CERT_KeyUsageAndTypeForCertUsage(usage, ca, &requiredKeyUsage, |
| &requiredCertType); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| |
| node = CERT_LIST_HEAD(certList); |
| |
| while (!CERT_LIST_END(node, certList)) { |
| |
| PRBool bad = (PRBool)(!node->cert); |
| |
| /* bad key usage ? */ |
| if (!bad && |
| CERT_CheckKeyUsage(node->cert, requiredKeyUsage) != SECSuccess) { |
| bad = PR_TRUE; |
| } |
| /* bad cert type ? */ |
| if (!bad) { |
| unsigned int certType = 0; |
| if (ca) { |
| /* This function returns a more comprehensive cert type that |
| * takes trust flags into consideration. Should probably |
| * fix the cert decoding code to do this. |
| */ |
| (void)CERT_IsCACert(node->cert, &certType); |
| } else { |
| certType = node->cert->nsCertType; |
| } |
| if (!(certType & requiredCertType)) { |
| bad = PR_TRUE; |
| } |
| } |
| |
| if (bad) { |
| /* remove the node if it is bad */ |
| savenode = CERT_LIST_NEXT(node); |
| CERT_RemoveCertListNode(node); |
| node = savenode; |
| } else { |
| node = CERT_LIST_NEXT(node); |
| } |
| } |
| return (SECSuccess); |
| |
| loser: |
| return (SECFailure); |
| } |
| |
| PRBool |
| CERT_IsUserCert(CERTCertificate *cert) |
| { |
| CERTCertTrust trust; |
| SECStatus rv = SECFailure; |
| |
| rv = CERT_GetCertTrust(cert, &trust); |
| if (rv == SECSuccess && |
| ((trust.sslFlags & CERTDB_USER) || (trust.emailFlags & CERTDB_USER) || |
| (trust.objectSigningFlags & CERTDB_USER))) { |
| return PR_TRUE; |
| } else { |
| return PR_FALSE; |
| } |
| } |
| |
| SECStatus |
| CERT_FilterCertListForUserCerts(CERTCertList *certList) |
| { |
| CERTCertListNode *node, *freenode; |
| CERTCertificate *cert; |
| |
| if (!certList) { |
| return SECFailure; |
| } |
| |
| node = CERT_LIST_HEAD(certList); |
| |
| while (!CERT_LIST_END(node, certList)) { |
| cert = node->cert; |
| if (PR_TRUE != CERT_IsUserCert(cert)) { |
| /* Not a User Cert, so remove this cert from the list */ |
| freenode = node; |
| node = CERT_LIST_NEXT(node); |
| CERT_RemoveCertListNode(freenode); |
| } else { |
| /* Is a User cert, so leave it in the list */ |
| node = CERT_LIST_NEXT(node); |
| } |
| } |
| |
| return (SECSuccess); |
| } |
| |
| static PZLock *certRefCountLock = NULL; |
| |
| /* |
| * Acquire the cert reference count lock |
| * There is currently one global lock for all certs, but I'm putting a cert |
| * arg here so that it will be easy to make it per-cert in the future if |
| * that turns out to be necessary. |
| */ |
| void |
| CERT_LockCertRefCount(CERTCertificate *cert) |
| { |
| PORT_Assert(certRefCountLock != NULL); |
| PZ_Lock(certRefCountLock); |
| return; |
| } |
| |
| /* |
| * Free the cert reference count lock |
| */ |
| void |
| CERT_UnlockCertRefCount(CERTCertificate *cert) |
| { |
| PORT_Assert(certRefCountLock != NULL); |
| PRStatus prstat = PZ_Unlock(certRefCountLock); |
| PORT_AssertArg(prstat == PR_SUCCESS); |
| } |
| |
| static PZLock *certTrustLock = NULL; |
| |
| /* |
| * Acquire the cert trust lock |
| * There is currently one global lock for all certs, but I'm putting a cert |
| * arg here so that it will be easy to make it per-cert in the future if |
| * that turns out to be necessary. |
| */ |
| void |
| CERT_LockCertTrust(const CERTCertificate *cert) |
| { |
| PORT_Assert(certTrustLock != NULL); |
| PZ_Lock(certTrustLock); |
| } |
| |
| static PZLock *certTempPermCertLock = NULL; |
| |
| /* |
| * Acquire the cert temp/perm/nssCert lock |
| */ |
| void |
| CERT_LockCertTempPerm(const CERTCertificate *cert) |
| { |
| PORT_Assert(certTempPermCertLock != NULL); |
| PZ_Lock(certTempPermCertLock); |
| } |
| |
| /* Maybe[Lock, Unlock] variants are only to be used by |
| * CERT_DestroyCertificate, since an application could |
| * call this after NSS_Shutdown destroys cert locks. */ |
| void |
| CERT_MaybeLockCertTempPerm(const CERTCertificate *cert) |
| { |
| if (certTempPermCertLock) { |
| PZ_Lock(certTempPermCertLock); |
| } |
| } |
| |
| SECStatus |
| cert_InitLocks(void) |
| { |
| if (certRefCountLock == NULL) { |
| certRefCountLock = PZ_NewLock(nssILockRefLock); |
| PORT_Assert(certRefCountLock != NULL); |
| if (!certRefCountLock) { |
| return SECFailure; |
| } |
| } |
| |
| if (certTrustLock == NULL) { |
| certTrustLock = PZ_NewLock(nssILockCertDB); |
| PORT_Assert(certTrustLock != NULL); |
| if (!certTrustLock) { |
| PZ_DestroyLock(certRefCountLock); |
| certRefCountLock = NULL; |
| return SECFailure; |
| } |
| } |
| |
| if (certTempPermCertLock == NULL) { |
| certTempPermCertLock = PZ_NewLock(nssILockCertDB); |
| PORT_Assert(certTempPermCertLock != NULL); |
| if (!certTempPermCertLock) { |
| PZ_DestroyLock(certTrustLock); |
| PZ_DestroyLock(certRefCountLock); |
| certRefCountLock = NULL; |
| certTrustLock = NULL; |
| return SECFailure; |
| } |
| } |
| |
| return SECSuccess; |
| } |
| |
| SECStatus |
| cert_DestroyLocks(void) |
| { |
| SECStatus rv = SECSuccess; |
| |
| PORT_Assert(certRefCountLock != NULL); |
| if (certRefCountLock) { |
| PZ_DestroyLock(certRefCountLock); |
| certRefCountLock = NULL; |
| } else { |
| rv = SECFailure; |
| } |
| |
| PORT_Assert(certTrustLock != NULL); |
| if (certTrustLock) { |
| PZ_DestroyLock(certTrustLock); |
| certTrustLock = NULL; |
| } else { |
| rv = SECFailure; |
| } |
| |
| PORT_Assert(certTempPermCertLock != NULL); |
| if (certTempPermCertLock) { |
| PZ_DestroyLock(certTempPermCertLock); |
| certTempPermCertLock = NULL; |
| } else { |
| rv = SECFailure; |
| } |
| return rv; |
| } |
| |
| /* |
| * Free the cert trust lock |
| */ |
| void |
| CERT_UnlockCertTrust(const CERTCertificate *cert) |
| { |
| PORT_Assert(certTrustLock != NULL); |
| PRStatus prstat = PZ_Unlock(certTrustLock); |
| PORT_AssertArg(prstat == PR_SUCCESS); |
| } |
| |
| /* |
| * Free the temp/perm/nssCert lock |
| */ |
| void |
| CERT_UnlockCertTempPerm(const CERTCertificate *cert) |
| { |
| PORT_Assert(certTempPermCertLock != NULL); |
| PRStatus prstat = PZ_Unlock(certTempPermCertLock); |
| PORT_AssertArg(prstat == PR_SUCCESS); |
| } |
| |
| void |
| CERT_MaybeUnlockCertTempPerm(const CERTCertificate *cert) |
| { |
| if (certTempPermCertLock) { |
| PZ_Unlock(certTempPermCertLock); |
| } |
| } |
| |
| /* |
| * Get the StatusConfig data for this handle |
| */ |
| CERTStatusConfig * |
| CERT_GetStatusConfig(CERTCertDBHandle *handle) |
| { |
| return handle->statusConfig; |
| } |
| |
| /* |
| * Set the StatusConfig data for this handle. There |
| * should not be another configuration set. |
| */ |
| void |
| CERT_SetStatusConfig(CERTCertDBHandle *handle, CERTStatusConfig *statusConfig) |
| { |
| PORT_Assert(handle->statusConfig == NULL); |
| handle->statusConfig = statusConfig; |
| } |
| |
| /* |
| * Code for dealing with subjKeyID to cert mappings. |
| */ |
| |
| static PLHashTable *gSubjKeyIDHash = NULL; |
| static PRLock *gSubjKeyIDLock = NULL; |
| static PLHashTable *gSubjKeyIDSlotCheckHash = NULL; |
| static PRLock *gSubjKeyIDSlotCheckLock = NULL; |
| |
| static void * |
| cert_AllocTable(void *pool, PRSize size) |
| { |
| return PORT_Alloc(size); |
| } |
| |
| static void |
| cert_FreeTable(void *pool, void *item) |
| { |
| PORT_Free(item); |
| } |
| |
| static PLHashEntry * |
| cert_AllocEntry(void *pool, const void *key) |
| { |
| return PORT_New(PLHashEntry); |
| } |
| |
| static void |
| cert_FreeEntry(void *pool, PLHashEntry *he, PRUintn flag) |
| { |
| SECITEM_FreeItem((SECItem *)(he->value), PR_TRUE); |
| if (flag == HT_FREE_ENTRY) { |
| SECITEM_FreeItem((SECItem *)(he->key), PR_TRUE); |
| PORT_Free(he); |
| } |
| } |
| |
| static PLHashAllocOps cert_AllocOps = { cert_AllocTable, cert_FreeTable, |
| cert_AllocEntry, cert_FreeEntry }; |
| |
| SECStatus |
| cert_CreateSubjectKeyIDSlotCheckHash(void) |
| { |
| /* |
| * This hash is used to remember the series of a slot |
| * when we last checked for user certs |
| */ |
| gSubjKeyIDSlotCheckHash = |
| PL_NewHashTable(0, SECITEM_Hash, SECITEM_HashCompare, |
| SECITEM_HashCompare, &cert_AllocOps, NULL); |
| if (!gSubjKeyIDSlotCheckHash) { |
| PORT_SetError(SEC_ERROR_NO_MEMORY); |
| return SECFailure; |
| } |
| gSubjKeyIDSlotCheckLock = PR_NewLock(); |
| if (!gSubjKeyIDSlotCheckLock) { |
| PL_HashTableDestroy(gSubjKeyIDSlotCheckHash); |
| gSubjKeyIDSlotCheckHash = NULL; |
| PORT_SetError(SEC_ERROR_NO_MEMORY); |
| return SECFailure; |
| } |
| return SECSuccess; |
| } |
| |
| SECStatus |
| cert_CreateSubjectKeyIDHashTable(void) |
| { |
| gSubjKeyIDHash = PL_NewHashTable(0, SECITEM_Hash, SECITEM_HashCompare, |
| SECITEM_HashCompare, &cert_AllocOps, NULL); |
| if (!gSubjKeyIDHash) { |
| PORT_SetError(SEC_ERROR_NO_MEMORY); |
| return SECFailure; |
| } |
| gSubjKeyIDLock = PR_NewLock(); |
| if (!gSubjKeyIDLock) { |
| PL_HashTableDestroy(gSubjKeyIDHash); |
| gSubjKeyIDHash = NULL; |
| PORT_SetError(SEC_ERROR_NO_MEMORY); |
| return SECFailure; |
| } |
| /* initialize the companion hash (for remembering slot series) */ |
| if (cert_CreateSubjectKeyIDSlotCheckHash() != SECSuccess) { |
| cert_DestroySubjectKeyIDHashTable(); |
| return SECFailure; |
| } |
| return SECSuccess; |
| } |
| |
| SECStatus |
| cert_AddSubjectKeyIDMapping(SECItem *subjKeyID, CERTCertificate *cert) |
| { |
| SECItem *newKeyID, *oldVal, *newVal; |
| SECStatus rv = SECFailure; |
| |
| if (!gSubjKeyIDLock) { |
| /* If one is created, then both are there. So only check for one. */ |
| return SECFailure; |
| } |
| |
| newVal = SECITEM_DupItem(&cert->derCert); |
| if (!newVal) { |
| PORT_SetError(SEC_ERROR_NO_MEMORY); |
| goto done; |
| } |
| newKeyID = SECITEM_DupItem(subjKeyID); |
| if (!newKeyID) { |
| SECITEM_FreeItem(newVal, PR_TRUE); |
| PORT_SetError(SEC_ERROR_NO_MEMORY); |
| goto done; |
| } |
| |
| PR_Lock(gSubjKeyIDLock); |
| /* The hash table implementation does not free up the memory |
| * associated with the key of an already existing entry if we add a |
| * duplicate, so we would wind up leaking the previously allocated |
| * key if we don't remove before adding. |
| */ |
| oldVal = (SECItem *)PL_HashTableLookup(gSubjKeyIDHash, subjKeyID); |
| if (oldVal) { |
| PL_HashTableRemove(gSubjKeyIDHash, subjKeyID); |
| } |
| |
| rv = (PL_HashTableAdd(gSubjKeyIDHash, newKeyID, newVal)) ? SECSuccess |
| : SECFailure; |
| PR_Unlock(gSubjKeyIDLock); |
| done: |
| return rv; |
| } |
| |
| SECStatus |
| cert_RemoveSubjectKeyIDMapping(SECItem *subjKeyID) |
| { |
| SECStatus rv; |
| if (!gSubjKeyIDLock) |
| return SECFailure; |
| |
| PR_Lock(gSubjKeyIDLock); |
| rv = (PL_HashTableRemove(gSubjKeyIDHash, subjKeyID)) ? SECSuccess |
| : SECFailure; |
| PR_Unlock(gSubjKeyIDLock); |
| return rv; |
| } |
| |
| SECStatus |
| cert_UpdateSubjectKeyIDSlotCheck(SECItem *slotid, int series) |
| { |
| SECItem *oldSeries, *newSlotid, *newSeries; |
| SECStatus rv = SECFailure; |
| |
| if (!gSubjKeyIDSlotCheckLock) { |
| return rv; |
| } |
| |
| newSlotid = SECITEM_DupItem(slotid); |
| newSeries = SECITEM_AllocItem(NULL, NULL, sizeof(int)); |
| if (!newSlotid || !newSeries) { |
| PORT_SetError(SEC_ERROR_NO_MEMORY); |
| goto loser; |
| } |
| PORT_Memcpy(newSeries->data, &series, sizeof(int)); |
| |
| PR_Lock(gSubjKeyIDSlotCheckLock); |
| oldSeries = (SECItem *)PL_HashTableLookup(gSubjKeyIDSlotCheckHash, slotid); |
| if (oldSeries) { |
| /* |
| * make sure we don't leak the key of an existing entry |
| * (similar to cert_AddSubjectKeyIDMapping, see comment there) |
| */ |
| PL_HashTableRemove(gSubjKeyIDSlotCheckHash, slotid); |
| } |
| rv = (PL_HashTableAdd(gSubjKeyIDSlotCheckHash, newSlotid, newSeries)) |
| ? SECSuccess |
| : SECFailure; |
| PR_Unlock(gSubjKeyIDSlotCheckLock); |
| if (rv == SECSuccess) { |
| return rv; |
| } |
| |
| loser: |
| if (newSlotid) { |
| SECITEM_FreeItem(newSlotid, PR_TRUE); |
| } |
| if (newSeries) { |
| SECITEM_FreeItem(newSeries, PR_TRUE); |
| } |
| return rv; |
| } |
| |
| int |
| cert_SubjectKeyIDSlotCheckSeries(SECItem *slotid) |
| { |
| SECItem *seriesItem = NULL; |
| int series; |
| |
| if (!gSubjKeyIDSlotCheckLock) { |
| PORT_SetError(SEC_ERROR_NOT_INITIALIZED); |
| return -1; |
| } |
| |
| PR_Lock(gSubjKeyIDSlotCheckLock); |
| seriesItem = (SECItem *)PL_HashTableLookup(gSubjKeyIDSlotCheckHash, slotid); |
| PR_Unlock(gSubjKeyIDSlotCheckLock); |
| /* getting a null series just means we haven't registered one yet, |
| * just return 0 */ |
| if (seriesItem == NULL) { |
| return 0; |
| } |
| /* if we got a series back, assert if it's not the proper length. */ |
| PORT_Assert(seriesItem->len == sizeof(int)); |
| if (seriesItem->len != sizeof(int)) { |
| PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); |
| return -1; |
| } |
| PORT_Memcpy(&series, seriesItem->data, sizeof(int)); |
| return series; |
| } |
| |
| SECStatus |
| cert_DestroySubjectKeyIDSlotCheckHash(void) |
| { |
| if (gSubjKeyIDSlotCheckHash) { |
| PR_Lock(gSubjKeyIDSlotCheckLock); |
| PL_HashTableDestroy(gSubjKeyIDSlotCheckHash); |
| gSubjKeyIDSlotCheckHash = NULL; |
| PR_Unlock(gSubjKeyIDSlotCheckLock); |
| PR_DestroyLock(gSubjKeyIDSlotCheckLock); |
| gSubjKeyIDSlotCheckLock = NULL; |
| } |
| return SECSuccess; |
| } |
| |
| SECStatus |
| cert_DestroySubjectKeyIDHashTable(void) |
| { |
| if (gSubjKeyIDHash) { |
| PR_Lock(gSubjKeyIDLock); |
| PL_HashTableDestroy(gSubjKeyIDHash); |
| gSubjKeyIDHash = NULL; |
| PR_Unlock(gSubjKeyIDLock); |
| PR_DestroyLock(gSubjKeyIDLock); |
| gSubjKeyIDLock = NULL; |
| } |
| cert_DestroySubjectKeyIDSlotCheckHash(); |
| return SECSuccess; |
| } |
| |
| SECItem * |
| cert_FindDERCertBySubjectKeyID(SECItem *subjKeyID) |
| { |
| SECItem *val; |
| |
| if (!gSubjKeyIDLock) |
| return NULL; |
| |
| PR_Lock(gSubjKeyIDLock); |
| val = (SECItem *)PL_HashTableLookup(gSubjKeyIDHash, subjKeyID); |
| if (val) { |
| val = SECITEM_DupItem(val); |
| } |
| PR_Unlock(gSubjKeyIDLock); |
| return val; |
| } |
| |
| CERTCertificate * |
| CERT_FindCertBySubjectKeyID(CERTCertDBHandle *handle, SECItem *subjKeyID) |
| { |
| CERTCertificate *cert = NULL; |
| SECItem *derCert; |
| |
| derCert = cert_FindDERCertBySubjectKeyID(subjKeyID); |
| if (derCert) { |
| cert = CERT_FindCertByDERCert(handle, derCert); |
| SECITEM_FreeItem(derCert, PR_TRUE); |
| } |
| return cert; |
| } |