diff options
Diffstat (limited to 'security/nss/lib/certdb/certdb.c')
-rw-r--r-- | security/nss/lib/certdb/certdb.c | 3411 |
1 files changed, 3411 insertions, 0 deletions
diff --git a/security/nss/lib/certdb/certdb.c b/security/nss/lib/certdb/certdb.c new file mode 100644 index 0000000000..0d7cdab768 --- /dev/null +++ b/security/nss/lib/certdb/certdb.c @@ -0,0 +1,3411 @@ +/* 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.len ? tmpitem.data[0] : 0; + cert->keyUsagePresent = PR_TRUE; + cert->keyUsage = cert->rawKeyUsage; + + 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 || tmpitem.len == 0) { + 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; + + if (!certs) { + return; + } + + 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); +} + +/* return true if cert is in the list */ +PRBool +CERT_IsInList(const CERTCertificate *cert, const CERTCertList *certList) +{ + CERTCertListNode *node; + for (node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node, certList); + node = CERT_LIST_NEXT(node)) { + if (node->cert == cert) { + return PR_TRUE; + } + } + return PR_FALSE; +} + +/* returned certList is the intersection of the certs on certList and the + * certs on filterList */ +SECStatus +CERT_FilterCertListByCertList(CERTCertList *certList, + const CERTCertList *filterList) +{ + CERTCertListNode *node, *freenode; + CERTCertificate *cert; + + if (!certList) { + return SECFailure; + } + + if (!filterList || CERT_LIST_EMPTY(certList)) { + /* if the filterList is empty, just clear out certList and return */ + for (node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node, certList);) { + freenode = node; + node = CERT_LIST_NEXT(node); + CERT_RemoveCertListNode(freenode); + } + return SECSuccess; + } + + node = CERT_LIST_HEAD(certList); + + while (!CERT_LIST_END(node, certList)) { + cert = node->cert; + if (!CERT_IsInList(cert, filterList)) { + // no matching cert on filter list, remove it from certlist */ + freenode = node; + node = CERT_LIST_NEXT(node); + CERT_RemoveCertListNode(freenode); + } else { + /* matching cert, keep it around */ + node = CERT_LIST_NEXT(node); + } + } + + return (SECSuccess); +} + +SECStatus +CERT_FilterCertListByNickname(CERTCertList *certList, char *nickname, + void *pwarg) +{ + CERTCertList *nameList; + SECStatus rv; + + if (!certList) { + return SECFailure; + } + + /* we could try to match the nickname to the individual cert, + * but nickname parsing is quite complicated, so it's best just + * to use the existing code and get a list of certs that match the + * nickname. We can then compare that list with our input cert list + * and return only those certs that are on both. */ + nameList = PK11_FindCertsFromNickname(nickname, pwarg); + + /* namelist could be NULL, this will force certList to become empty */ + rv = CERT_FilterCertListByCertList(certList, nameList); + /* CERT_DestroyCertList can now accept a NULL pointer */ + CERT_DestroyCertList(nameList); + return rv; +} + +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; +} |