Mercurial > trustbridge > nss-cmake-static
diff nss/lib/certdb/certdb.c @ 0:1e5118fa0cb1
This is NSS with a Cmake Buildsyste
To compile a static NSS library for Windows we've used the
Chromium-NSS fork and added a Cmake buildsystem to compile
it statically for Windows. See README.chromium for chromium
changes and README.trustbridge for our modifications.
author | Andre Heinecke <andre.heinecke@intevation.de> |
---|---|
date | Mon, 28 Jul 2014 10:47:06 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nss/lib/certdb/certdb.c Mon Jul 28 10:47:06 2014 +0200 @@ -0,0 +1,3275 @@ +/* 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; + 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_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_FreeArena(arena, PR_FALSE); + return(SECSuccess); + +loser: + PORT_FreeArena(arena, PR_FALSE); + return(SECFailure); +} + +SECStatus +CERT_SerialNumberFromDERCert(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_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_FreeArena(arena, PR_FALSE); + return(SECSuccess); + +loser: + PORT_FreeArena(arena, PR_FALSE); + 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; +} + +PRUint32 +cert_ComputeCertType(CERTCertificate *cert) +{ + SECStatus rv; + SECItem tmpitem; + SECItem encodedExtKeyUsage; + CERTOidSequence *extKeyUsage = NULL; + PRBool basicConstraintPresent = PR_FALSE; + CERTBasicConstraints basicConstraint; + PRUint32 nsCertType = 0; + + 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) { + basicConstraintPresent = PR_TRUE; + } + 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) { + if (basicConstraintPresent == PR_TRUE && + (basicConstraint.isCA)) { + nsCertType |= NS_CERT_TYPE_EMAIL_CA; + } else { + nsCertType |= NS_CERT_TYPE_EMAIL; + } + } + if (findOIDinOIDSeqByTagNum(extKeyUsage, + SEC_OID_EXT_KEY_USAGE_SERVER_AUTH) == + SECSuccess){ + if (basicConstraintPresent == PR_TRUE && + (basicConstraint.isCA)) { + nsCertType |= NS_CERT_TYPE_SSL_CA; + } else { + nsCertType |= 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){ + if (basicConstraintPresent == PR_TRUE && + (basicConstraint.isCA)) { + nsCertType |= NS_CERT_TYPE_SSL_CA; + } else { + nsCertType |= NS_CERT_TYPE_SSL_SERVER; + } + } + if (findOIDinOIDSeqByTagNum(extKeyUsage, + SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH) == + SECSuccess){ + if (basicConstraintPresent == PR_TRUE && + (basicConstraint.isCA)) { + nsCertType |= NS_CERT_TYPE_SSL_CA; + } else { + nsCertType |= NS_CERT_TYPE_SSL_CLIENT; + } + } + if (findOIDinOIDSeqByTagNum(extKeyUsage, + SEC_OID_EXT_KEY_USAGE_CODE_SIGN) == + SECSuccess) { + if (basicConstraintPresent == PR_TRUE && + (basicConstraint.isCA)) { + nsCertType |= NS_CERT_TYPE_OBJECT_SIGNING_CA; + } else { + nsCertType |= 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 (basicConstraintPresent && basicConstraint.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; + } + + 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; + + 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 ) ) { + 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) ) { + 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 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 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 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; +} + +/* + * 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) + newNameLen); + if (!domainOK) + return SECFailure; /* error code is already set. */ + + PORT_Strcpy(domainOK->name, hn); + 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_GetEnv("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; + unsigned int hnLen; + int DNSextCount = 0; + int IPextCount = 0; + PRBool isIPaddr = PR_FALSE; + SECStatus rv = SECFailure; + SECItem subAltName; + PRNetAddr netAddr; + char cnbuf[128]; + + subAltName.data = NULL; + hnLen = strlen(hn); + 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)); + rv = SECFailure; + 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; + + if (cType & (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA | + NS_CERT_TYPE_OBJECT_SIGNING_CA)) { + ret = PR_TRUE; + } else { + SECStatus rv; + CERTBasicConstraints constraints; + + rv = CERT_FindBasicConstraintExten(cert, &constraints); + if (rv == SECSuccess && constraints.isCA) { + ret = PR_TRUE; + cType |= (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA); + } + } + + /* finally check if it's an X.509 v1 root CA */ + if (!ret && + (cert->isRoot && cert_Version(cert) < SEC_CERTIFICATE_VERSION_3)) { + ret = PR_TRUE; + cType |= (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA); + } + /* Now 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 != NULL) { + *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: + 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; + SECStatus rv; + 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. + */ + rv = CERT_AddTempCertToPerm(certs[i], canickname, NULL); + } else { + rv = 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) return CERT_AddCertToListTail(certs,cert); + + 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) +{ + PRStatus prstat; + + PORT_Assert(certRefCountLock != NULL); + + prstat = PZ_Unlock(certRefCountLock); + + PORT_Assert(prstat == PR_SUCCESS); + + return; +} + +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); + return; +} + +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; + } + } + + 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; + } + return rv; +} + +/* + * Free the cert trust lock + */ +void +CERT_UnlockCertTrust(const CERTCertificate *cert) +{ + PRStatus prstat; + + PORT_Assert(certTrustLock != NULL); + + prstat = PZ_Unlock(certTrustLock); + + PORT_Assert(prstat == PR_SUCCESS); + + return; +} + + +/* + * 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; +}