/* 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; }