зеркало из https://github.com/mozilla/gecko-dev.git
3281 строка
92 KiB
C
3281 строка
92 KiB
C
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/*
|
|
* Certificate handling code
|
|
*/
|
|
|
|
#include "nssilock.h"
|
|
#include "prmon.h"
|
|
#include "prtime.h"
|
|
#include "cert.h"
|
|
#include "certi.h"
|
|
#include "secder.h"
|
|
#include "secoid.h"
|
|
#include "secasn1.h"
|
|
#include "genname.h"
|
|
#include "keyhi.h"
|
|
#include "secitem.h"
|
|
#include "certdb.h"
|
|
#include "prprf.h"
|
|
#include "sechash.h"
|
|
#include "prlong.h"
|
|
#include "certxutl.h"
|
|
#include "portreg.h"
|
|
#include "secerr.h"
|
|
#include "sslerr.h"
|
|
#include "pk11func.h"
|
|
#include "xconst.h" /* for CERT_DecodeAltNameExtension */
|
|
|
|
#include "pki.h"
|
|
#include "pki3hack.h"
|
|
|
|
SEC_ASN1_MKSUB(CERT_TimeChoiceTemplate)
|
|
SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)
|
|
SEC_ASN1_MKSUB(SEC_BitStringTemplate)
|
|
SEC_ASN1_MKSUB(SEC_IntegerTemplate)
|
|
SEC_ASN1_MKSUB(SEC_SkipTemplate)
|
|
|
|
/*
|
|
* Certificate database handling code
|
|
*/
|
|
|
|
const SEC_ASN1Template CERT_CertExtensionTemplate[] = {
|
|
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertExtension) },
|
|
{ SEC_ASN1_OBJECT_ID, offsetof(CERTCertExtension, id) },
|
|
{ SEC_ASN1_OPTIONAL | SEC_ASN1_BOOLEAN, /* XXX DER_DEFAULT */
|
|
offsetof(CERTCertExtension, critical) },
|
|
{ SEC_ASN1_OCTET_STRING, offsetof(CERTCertExtension, value) },
|
|
{ 0 }
|
|
};
|
|
|
|
const SEC_ASN1Template CERT_SequenceOfCertExtensionTemplate[] = {
|
|
{ SEC_ASN1_SEQUENCE_OF, 0, CERT_CertExtensionTemplate }
|
|
};
|
|
|
|
const SEC_ASN1Template CERT_TimeChoiceTemplate[] = {
|
|
{ SEC_ASN1_CHOICE, offsetof(SECItem, type), 0, sizeof(SECItem) },
|
|
{ SEC_ASN1_UTC_TIME, 0, 0, siUTCTime },
|
|
{ SEC_ASN1_GENERALIZED_TIME, 0, 0, siGeneralizedTime },
|
|
{ 0 }
|
|
};
|
|
|
|
const SEC_ASN1Template CERT_ValidityTemplate[] = {
|
|
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTValidity) },
|
|
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTValidity, notBefore),
|
|
SEC_ASN1_SUB(CERT_TimeChoiceTemplate), 0 },
|
|
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTValidity, notAfter),
|
|
SEC_ASN1_SUB(CERT_TimeChoiceTemplate), 0 },
|
|
{ 0 }
|
|
};
|
|
|
|
const SEC_ASN1Template CERT_CertificateTemplate[] = {
|
|
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertificate) },
|
|
{ SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED |
|
|
SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, /* XXX DER_DEFAULT */
|
|
offsetof(CERTCertificate, version),
|
|
SEC_ASN1_SUB(SEC_IntegerTemplate) },
|
|
{ SEC_ASN1_INTEGER, offsetof(CERTCertificate, serialNumber) },
|
|
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCertificate, signature),
|
|
SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
|
|
{ SEC_ASN1_SAVE, offsetof(CERTCertificate, derIssuer) },
|
|
{ SEC_ASN1_INLINE, offsetof(CERTCertificate, issuer), CERT_NameTemplate },
|
|
{ SEC_ASN1_INLINE, offsetof(CERTCertificate, validity),
|
|
CERT_ValidityTemplate },
|
|
{ SEC_ASN1_SAVE, offsetof(CERTCertificate, derSubject) },
|
|
{ SEC_ASN1_INLINE, offsetof(CERTCertificate, subject), CERT_NameTemplate },
|
|
{ SEC_ASN1_SAVE, offsetof(CERTCertificate, derPublicKey) },
|
|
{ SEC_ASN1_INLINE, offsetof(CERTCertificate, subjectPublicKeyInfo),
|
|
CERT_SubjectPublicKeyInfoTemplate },
|
|
{ SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 1,
|
|
offsetof(CERTCertificate, issuerID),
|
|
SEC_ASN1_SUB(SEC_BitStringTemplate) },
|
|
{ SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 2,
|
|
offsetof(CERTCertificate, subjectID),
|
|
SEC_ASN1_SUB(SEC_BitStringTemplate) },
|
|
{ SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED |
|
|
SEC_ASN1_CONTEXT_SPECIFIC | 3,
|
|
offsetof(CERTCertificate, extensions),
|
|
CERT_SequenceOfCertExtensionTemplate },
|
|
{ 0 }
|
|
};
|
|
|
|
const SEC_ASN1Template SEC_SignedCertificateTemplate[] = {
|
|
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertificate) },
|
|
{ SEC_ASN1_SAVE, offsetof(CERTCertificate, signatureWrap.data) },
|
|
{ SEC_ASN1_INLINE, 0, CERT_CertificateTemplate },
|
|
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN,
|
|
offsetof(CERTCertificate, signatureWrap.signatureAlgorithm),
|
|
SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
|
|
{ SEC_ASN1_BIT_STRING, offsetof(CERTCertificate, signatureWrap.signature) },
|
|
{ 0 }
|
|
};
|
|
|
|
/*
|
|
* Find the subjectName in a DER encoded certificate
|
|
*/
|
|
const SEC_ASN1Template SEC_CertSubjectTemplate[] = {
|
|
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECItem) },
|
|
{ SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED |
|
|
SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
|
|
0, SEC_ASN1_SUB(SEC_SkipTemplate) }, /* version */
|
|
{ SEC_ASN1_SKIP }, /* serial number */
|
|
{ SEC_ASN1_SKIP }, /* signature algorithm */
|
|
{ SEC_ASN1_SKIP }, /* issuer */
|
|
{ SEC_ASN1_SKIP }, /* validity */
|
|
{ SEC_ASN1_ANY, 0, NULL }, /* subject */
|
|
{ SEC_ASN1_SKIP_REST },
|
|
{ 0 }
|
|
};
|
|
|
|
/*
|
|
* Find the issuerName in a DER encoded certificate
|
|
*/
|
|
const SEC_ASN1Template SEC_CertIssuerTemplate[] = {
|
|
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECItem) },
|
|
{ SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED |
|
|
SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
|
|
0, SEC_ASN1_SUB(SEC_SkipTemplate) }, /* version */
|
|
{ SEC_ASN1_SKIP }, /* serial number */
|
|
{ SEC_ASN1_SKIP }, /* signature algorithm */
|
|
{ SEC_ASN1_ANY, 0, NULL }, /* issuer */
|
|
{ SEC_ASN1_SKIP_REST },
|
|
{ 0 }
|
|
};
|
|
/*
|
|
* Find the subjectName in a DER encoded certificate
|
|
*/
|
|
const SEC_ASN1Template SEC_CertSerialNumberTemplate[] = {
|
|
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECItem) },
|
|
{ SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED |
|
|
SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
|
|
0, SEC_ASN1_SUB(SEC_SkipTemplate) }, /* version */
|
|
{ SEC_ASN1_ANY, 0, NULL }, /* serial number */
|
|
{ SEC_ASN1_SKIP_REST },
|
|
{ 0 }
|
|
};
|
|
|
|
/*
|
|
* Find the issuer and serialNumber in a DER encoded certificate.
|
|
* This data is used as the database lookup key since its the unique
|
|
* identifier of a certificate.
|
|
*/
|
|
const SEC_ASN1Template CERT_CertKeyTemplate[] = {
|
|
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertKey) },
|
|
{ SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED |
|
|
SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
|
|
0, SEC_ASN1_SUB(SEC_SkipTemplate) }, /* version */
|
|
{ SEC_ASN1_INTEGER, offsetof(CERTCertKey, serialNumber) },
|
|
{ SEC_ASN1_SKIP }, /* signature algorithm */
|
|
{ SEC_ASN1_ANY, offsetof(CERTCertKey, derIssuer) },
|
|
{ SEC_ASN1_SKIP_REST },
|
|
{ 0 }
|
|
};
|
|
|
|
SEC_ASN1_CHOOSER_IMPLEMENT(CERT_TimeChoiceTemplate)
|
|
SEC_ASN1_CHOOSER_IMPLEMENT(CERT_CertificateTemplate)
|
|
SEC_ASN1_CHOOSER_IMPLEMENT(SEC_SignedCertificateTemplate)
|
|
SEC_ASN1_CHOOSER_IMPLEMENT(CERT_SequenceOfCertExtensionTemplate)
|
|
|
|
SECStatus
|
|
CERT_KeyFromIssuerAndSN(PLArenaPool *arena, SECItem *issuer, SECItem *sn,
|
|
SECItem *key)
|
|
{
|
|
key->len = sn->len + issuer->len;
|
|
|
|
if ((sn->data == NULL) || (issuer->data == NULL)) {
|
|
goto loser;
|
|
}
|
|
|
|
key->data = (unsigned char *)PORT_ArenaAlloc(arena, key->len);
|
|
if (!key->data) {
|
|
goto loser;
|
|
}
|
|
|
|
/* copy the serialNumber */
|
|
PORT_Memcpy(key->data, sn->data, sn->len);
|
|
|
|
/* copy the issuer */
|
|
PORT_Memcpy(&key->data[sn->len], issuer->data, issuer->len);
|
|
|
|
return (SECSuccess);
|
|
|
|
loser:
|
|
return (SECFailure);
|
|
}
|
|
|
|
/*
|
|
* Extract the subject name from a DER certificate
|
|
*/
|
|
SECStatus
|
|
CERT_NameFromDERCert(SECItem *derCert, SECItem *derName)
|
|
{
|
|
int rv;
|
|
PLArenaPool *arena;
|
|
CERTSignedData sd;
|
|
void *tmpptr;
|
|
|
|
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
|
|
if (!arena) {
|
|
return (SECFailure);
|
|
}
|
|
|
|
PORT_Memset(&sd, 0, sizeof(CERTSignedData));
|
|
rv = SEC_QuickDERDecodeItem(arena, &sd, CERT_SignedDataTemplate, derCert);
|
|
|
|
if (rv) {
|
|
goto loser;
|
|
}
|
|
|
|
PORT_Memset(derName, 0, sizeof(SECItem));
|
|
rv = SEC_QuickDERDecodeItem(arena, derName, SEC_CertSubjectTemplate,
|
|
&sd.data);
|
|
|
|
if (rv) {
|
|
goto loser;
|
|
}
|
|
|
|
tmpptr = derName->data;
|
|
derName->data = (unsigned char *)PORT_Alloc(derName->len);
|
|
if (derName->data == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
PORT_Memcpy(derName->data, tmpptr, derName->len);
|
|
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
return (SECSuccess);
|
|
|
|
loser:
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
return (SECFailure);
|
|
}
|
|
|
|
SECStatus
|
|
CERT_IssuerNameFromDERCert(SECItem *derCert, SECItem *derName)
|
|
{
|
|
int rv;
|
|
PORTCheapArenaPool tmpArena;
|
|
CERTSignedData sd;
|
|
void *tmpptr;
|
|
|
|
PORT_InitCheapArena(&tmpArena, DER_DEFAULT_CHUNKSIZE);
|
|
|
|
PORT_Memset(&sd, 0, sizeof(CERTSignedData));
|
|
rv = SEC_QuickDERDecodeItem(&tmpArena.arena, &sd, CERT_SignedDataTemplate,
|
|
derCert);
|
|
if (rv) {
|
|
goto loser;
|
|
}
|
|
|
|
PORT_Memset(derName, 0, sizeof(SECItem));
|
|
rv = SEC_QuickDERDecodeItem(&tmpArena.arena, derName,
|
|
SEC_CertIssuerTemplate, &sd.data);
|
|
if (rv) {
|
|
goto loser;
|
|
}
|
|
|
|
tmpptr = derName->data;
|
|
derName->data = (unsigned char *)PORT_Alloc(derName->len);
|
|
if (derName->data == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
PORT_Memcpy(derName->data, tmpptr, derName->len);
|
|
|
|
PORT_DestroyCheapArena(&tmpArena);
|
|
return (SECSuccess);
|
|
|
|
loser:
|
|
PORT_DestroyCheapArena(&tmpArena);
|
|
return (SECFailure);
|
|
}
|
|
|
|
SECStatus
|
|
CERT_SerialNumberFromDERCert(SECItem *derCert, SECItem *derName)
|
|
{
|
|
int rv;
|
|
PORTCheapArenaPool tmpArena;
|
|
CERTSignedData sd;
|
|
void *tmpptr;
|
|
|
|
PORT_InitCheapArena(&tmpArena, DER_DEFAULT_CHUNKSIZE);
|
|
|
|
PORT_Memset(&sd, 0, sizeof(CERTSignedData));
|
|
rv = SEC_QuickDERDecodeItem(&tmpArena.arena, &sd, CERT_SignedDataTemplate,
|
|
derCert);
|
|
if (rv) {
|
|
goto loser;
|
|
}
|
|
|
|
PORT_Memset(derName, 0, sizeof(SECItem));
|
|
rv = SEC_QuickDERDecodeItem(&tmpArena.arena, derName,
|
|
SEC_CertSerialNumberTemplate, &sd.data);
|
|
if (rv) {
|
|
goto loser;
|
|
}
|
|
|
|
tmpptr = derName->data;
|
|
derName->data = (unsigned char *)PORT_Alloc(derName->len);
|
|
if (derName->data == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
PORT_Memcpy(derName->data, tmpptr, derName->len);
|
|
|
|
PORT_DestroyCheapArena(&tmpArena);
|
|
return (SECSuccess);
|
|
|
|
loser:
|
|
PORT_DestroyCheapArena(&tmpArena);
|
|
return (SECFailure);
|
|
}
|
|
|
|
/*
|
|
* Generate a database key, based on serial number and issuer, from a
|
|
* DER certificate.
|
|
*/
|
|
SECStatus
|
|
CERT_KeyFromDERCert(PLArenaPool *reqArena, SECItem *derCert, SECItem *key)
|
|
{
|
|
int rv;
|
|
CERTSignedData sd;
|
|
CERTCertKey certkey;
|
|
|
|
if (!reqArena) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return SECFailure;
|
|
}
|
|
|
|
PORT_Memset(&sd, 0, sizeof(CERTSignedData));
|
|
rv =
|
|
SEC_QuickDERDecodeItem(reqArena, &sd, CERT_SignedDataTemplate, derCert);
|
|
|
|
if (rv) {
|
|
goto loser;
|
|
}
|
|
|
|
PORT_Memset(&certkey, 0, sizeof(CERTCertKey));
|
|
rv = SEC_QuickDERDecodeItem(reqArena, &certkey, CERT_CertKeyTemplate,
|
|
&sd.data);
|
|
|
|
if (rv) {
|
|
goto loser;
|
|
}
|
|
|
|
return (CERT_KeyFromIssuerAndSN(reqArena, &certkey.derIssuer,
|
|
&certkey.serialNumber, key));
|
|
loser:
|
|
return (SECFailure);
|
|
}
|
|
|
|
/*
|
|
* fill in keyUsage field of the cert based on the cert extension
|
|
* if the extension is not critical, then we allow all uses
|
|
*/
|
|
static SECStatus
|
|
GetKeyUsage(CERTCertificate *cert)
|
|
{
|
|
SECStatus rv;
|
|
SECItem tmpitem;
|
|
|
|
rv = CERT_FindKeyUsageExtension(cert, &tmpitem);
|
|
if (rv == SECSuccess) {
|
|
/* remember the actual value of the extension */
|
|
cert->rawKeyUsage = tmpitem.data[0];
|
|
cert->keyUsagePresent = PR_TRUE;
|
|
cert->keyUsage = tmpitem.data[0];
|
|
|
|
PORT_Free(tmpitem.data);
|
|
tmpitem.data = NULL;
|
|
} else {
|
|
/* if the extension is not present, then we allow all uses */
|
|
cert->keyUsage = KU_ALL;
|
|
cert->rawKeyUsage = KU_ALL;
|
|
cert->keyUsagePresent = PR_FALSE;
|
|
}
|
|
|
|
if (CERT_GovtApprovedBitSet(cert)) {
|
|
cert->keyUsage |= KU_NS_GOVT_APPROVED;
|
|
cert->rawKeyUsage |= KU_NS_GOVT_APPROVED;
|
|
}
|
|
|
|
return (SECSuccess);
|
|
}
|
|
|
|
static SECStatus
|
|
findOIDinOIDSeqByTagNum(CERTOidSequence *seq, SECOidTag tagnum)
|
|
{
|
|
SECItem **oids;
|
|
SECItem *oid;
|
|
SECStatus rv = SECFailure;
|
|
|
|
if (seq != NULL) {
|
|
oids = seq->oids;
|
|
while (oids != NULL && *oids != NULL) {
|
|
oid = *oids;
|
|
if (SECOID_FindOIDTag(oid) == tagnum) {
|
|
rv = SECSuccess;
|
|
break;
|
|
}
|
|
oids++;
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* fill in nsCertType field of the cert based on the cert extension
|
|
*/
|
|
SECStatus
|
|
cert_GetCertType(CERTCertificate *cert)
|
|
{
|
|
PRUint32 nsCertType;
|
|
|
|
if (cert->nsCertType) {
|
|
/* once set, no need to recalculate */
|
|
return SECSuccess;
|
|
}
|
|
nsCertType = cert_ComputeCertType(cert);
|
|
|
|
/* Assert that it is safe to cast &cert->nsCertType to "PRInt32 *" */
|
|
PORT_Assert(sizeof(cert->nsCertType) == sizeof(PRInt32));
|
|
PR_ATOMIC_SET((PRInt32 *)&cert->nsCertType, nsCertType);
|
|
return SECSuccess;
|
|
}
|
|
|
|
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;
|
|
|
|
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 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 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;
|
|
}
|
|
|
|
/*
|
|
* Allow use of default cert database, so that apps(such as mozilla) don't
|
|
* have to pass the handle all over the place.
|
|
*/
|
|
static CERTCertDBHandle *default_cert_db_handle = 0;
|
|
|
|
void
|
|
CERT_SetDefaultCertDB(CERTCertDBHandle *handle)
|
|
{
|
|
default_cert_db_handle = handle;
|
|
|
|
return;
|
|
}
|
|
|
|
CERTCertDBHandle *
|
|
CERT_GetDefaultCertDB(void)
|
|
{
|
|
return (default_cert_db_handle);
|
|
}
|
|
|
|
/* XXX this would probably be okay/better as an xp routine? */
|
|
static void
|
|
sec_lower_string(char *s)
|
|
{
|
|
if (s == NULL) {
|
|
return;
|
|
}
|
|
|
|
while (*s) {
|
|
*s = PORT_Tolower(*s);
|
|
s++;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static PRBool
|
|
cert_IsIPAddr(const char *hn)
|
|
{
|
|
PRBool isIPaddr = PR_FALSE;
|
|
PRNetAddr netAddr;
|
|
isIPaddr = (PR_SUCCESS == PR_StringToNetAddr(hn, &netAddr));
|
|
return isIPaddr;
|
|
}
|
|
|
|
/*
|
|
** Add a domain name to the list of names that the user has explicitly
|
|
** allowed (despite cert name mismatches) for use with a server cert.
|
|
*/
|
|
SECStatus
|
|
CERT_AddOKDomainName(CERTCertificate *cert, const char *hn)
|
|
{
|
|
CERTOKDomainName *domainOK;
|
|
int newNameLen;
|
|
|
|
if (!hn || !(newNameLen = strlen(hn))) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return SECFailure;
|
|
}
|
|
domainOK = (CERTOKDomainName *)PORT_ArenaZAlloc(cert->arena, sizeof(*domainOK));
|
|
if (!domainOK) {
|
|
return SECFailure; /* error code is already set. */
|
|
}
|
|
domainOK->name = (char *)PORT_ArenaZAlloc(cert->arena, newNameLen + 1);
|
|
if (!domainOK->name) {
|
|
return SECFailure; /* error code is already set. */
|
|
}
|
|
|
|
PORT_Strncpy(domainOK->name, hn, newNameLen + 1);
|
|
sec_lower_string(domainOK->name);
|
|
|
|
/* put at head of list. */
|
|
domainOK->next = cert->domainOK;
|
|
cert->domainOK = domainOK;
|
|
return SECSuccess;
|
|
}
|
|
|
|
/* returns SECSuccess if hn matches pattern cn,
|
|
** returns SECFailure with SSL_ERROR_BAD_CERT_DOMAIN if no match,
|
|
** returns SECFailure with some other error code if another error occurs.
|
|
**
|
|
** This function may modify string cn, so caller must pass a modifiable copy.
|
|
*/
|
|
static SECStatus
|
|
cert_TestHostName(char *cn, const char *hn)
|
|
{
|
|
static int useShellExp = -1;
|
|
|
|
if (useShellExp < 0) {
|
|
useShellExp = (NULL != PR_GetEnvSecure("NSS_USE_SHEXP_IN_CERT_NAME"));
|
|
}
|
|
if (useShellExp) {
|
|
/* Backward compatible code, uses Shell Expressions (SHEXP). */
|
|
int regvalid = PORT_RegExpValid(cn);
|
|
if (regvalid != NON_SXP) {
|
|
SECStatus rv;
|
|
/* cn is a regular expression, try to match the shexp */
|
|
int match = PORT_RegExpCaseSearch(hn, cn);
|
|
|
|
if (match == 0) {
|
|
rv = SECSuccess;
|
|
} else {
|
|
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
|
|
rv = SECFailure;
|
|
}
|
|
return rv;
|
|
}
|
|
} else {
|
|
/* New approach conforms to RFC 6125. */
|
|
char *wildcard = PORT_Strchr(cn, '*');
|
|
char *firstcndot = PORT_Strchr(cn, '.');
|
|
char *secondcndot =
|
|
firstcndot ? PORT_Strchr(firstcndot + 1, '.') : NULL;
|
|
char *firsthndot = PORT_Strchr(hn, '.');
|
|
|
|
/* For a cn pattern to be considered valid, the wildcard character...
|
|
* - may occur only in a DNS name with at least 3 components, and
|
|
* - may occur only as last character in the first component, and
|
|
* - may be preceded by additional characters, and
|
|
* - must not be preceded by an IDNA ACE prefix (xn--)
|
|
*/
|
|
if (wildcard && secondcndot && secondcndot[1] && firsthndot &&
|
|
firstcndot - wildcard == 1 /* wildcard is last char in first component */
|
|
&& secondcndot - firstcndot > 1 /* second component is non-empty */
|
|
&& PORT_Strrchr(cn, '*') == wildcard /* only one wildcard in cn */
|
|
&& !PORT_Strncasecmp(cn, hn, wildcard - cn) &&
|
|
!PORT_Strcasecmp(firstcndot, firsthndot)
|
|
/* If hn starts with xn--, then cn must start with wildcard */
|
|
&& (PORT_Strncasecmp(hn, "xn--", 4) || wildcard == cn)) {
|
|
/* valid wildcard pattern match */
|
|
return SECSuccess;
|
|
}
|
|
}
|
|
/* String cn has no wildcard or shell expression.
|
|
* Compare entire string hn with cert name.
|
|
*/
|
|
if (PORT_Strcasecmp(hn, cn) == 0) {
|
|
return SECSuccess;
|
|
}
|
|
|
|
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
|
|
return SECFailure;
|
|
}
|
|
|
|
SECStatus
|
|
cert_VerifySubjectAltName(const CERTCertificate *cert, const char *hn)
|
|
{
|
|
PLArenaPool *arena = NULL;
|
|
CERTGeneralName *nameList = NULL;
|
|
CERTGeneralName *current;
|
|
char *cn;
|
|
int cnBufLen;
|
|
int DNSextCount = 0;
|
|
int IPextCount = 0;
|
|
PRBool isIPaddr = PR_FALSE;
|
|
SECStatus rv = SECFailure;
|
|
SECItem subAltName;
|
|
PRNetAddr netAddr;
|
|
char cnbuf[128];
|
|
|
|
subAltName.data = NULL;
|
|
cn = cnbuf;
|
|
cnBufLen = sizeof cnbuf;
|
|
|
|
rv = CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME,
|
|
&subAltName);
|
|
if (rv != SECSuccess) {
|
|
goto fail;
|
|
}
|
|
isIPaddr = (PR_SUCCESS == PR_StringToNetAddr(hn, &netAddr));
|
|
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
if (!arena)
|
|
goto fail;
|
|
|
|
nameList = current = CERT_DecodeAltNameExtension(arena, &subAltName);
|
|
if (!current)
|
|
goto fail;
|
|
|
|
do {
|
|
switch (current->type) {
|
|
case certDNSName:
|
|
if (!isIPaddr) {
|
|
/* DNS name current->name.other.data is not null terminated.
|
|
** so must copy it.
|
|
*/
|
|
int cnLen = current->name.other.len;
|
|
rv = CERT_RFC1485_EscapeAndQuote(
|
|
cn, cnBufLen, (char *)current->name.other.data, cnLen);
|
|
if (rv != SECSuccess &&
|
|
PORT_GetError() == SEC_ERROR_OUTPUT_LEN) {
|
|
cnBufLen =
|
|
cnLen * 3 + 3; /* big enough for worst case */
|
|
cn = (char *)PORT_ArenaAlloc(arena, cnBufLen);
|
|
if (!cn)
|
|
goto fail;
|
|
rv = CERT_RFC1485_EscapeAndQuote(
|
|
cn, cnBufLen, (char *)current->name.other.data,
|
|
cnLen);
|
|
}
|
|
if (rv == SECSuccess)
|
|
rv = cert_TestHostName(cn, hn);
|
|
if (rv == SECSuccess)
|
|
goto finish;
|
|
}
|
|
DNSextCount++;
|
|
break;
|
|
case certIPAddress:
|
|
if (isIPaddr) {
|
|
int match = 0;
|
|
PRIPv6Addr v6Addr;
|
|
if (current->name.other.len == 4 && /* IP v4 address */
|
|
netAddr.inet.family == PR_AF_INET) {
|
|
match = !memcmp(&netAddr.inet.ip,
|
|
current->name.other.data, 4);
|
|
} else if (current->name.other.len ==
|
|
16 && /* IP v6 address */
|
|
netAddr.ipv6.family == PR_AF_INET6) {
|
|
match = !memcmp(&netAddr.ipv6.ip,
|
|
current->name.other.data, 16);
|
|
} else if (current->name.other.len ==
|
|
16 && /* IP v6 address */
|
|
netAddr.inet.family == PR_AF_INET) {
|
|
/* convert netAddr to ipv6, then compare. */
|
|
/* ipv4 must be in Network Byte Order on input. */
|
|
PR_ConvertIPv4AddrToIPv6(netAddr.inet.ip, &v6Addr);
|
|
match = !memcmp(&v6Addr, current->name.other.data, 16);
|
|
} else if (current->name.other.len == 4 && /* IP v4 address */
|
|
netAddr.inet.family == PR_AF_INET6) {
|
|
/* convert netAddr to ipv6, then compare. */
|
|
PRUint32 ipv4 = (current->name.other.data[0] << 24) |
|
|
(current->name.other.data[1] << 16) |
|
|
(current->name.other.data[2] << 8) |
|
|
current->name.other.data[3];
|
|
/* ipv4 must be in Network Byte Order on input. */
|
|
PR_ConvertIPv4AddrToIPv6(PR_htonl(ipv4), &v6Addr);
|
|
match = !memcmp(&netAddr.ipv6.ip, &v6Addr, 16);
|
|
}
|
|
if (match) {
|
|
rv = SECSuccess;
|
|
goto finish;
|
|
}
|
|
}
|
|
IPextCount++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
current = CERT_GetNextGeneralName(current);
|
|
} while (current != nameList);
|
|
|
|
fail:
|
|
|
|
if (!(isIPaddr ? IPextCount : DNSextCount)) {
|
|
/* no relevant value in the extension was found. */
|
|
PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND);
|
|
} else {
|
|
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
|
|
}
|
|
rv = SECFailure;
|
|
|
|
finish:
|
|
|
|
/* Don't free nameList, it's part of the arena. */
|
|
if (arena) {
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
}
|
|
|
|
if (subAltName.data) {
|
|
SECITEM_FreeItem(&subAltName, PR_FALSE);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* If found:
|
|
* - subAltName contains the extension (caller must free)
|
|
* - return value is the decoded namelist (allocated off arena)
|
|
* if not found, or if failure to decode:
|
|
* - return value is NULL
|
|
*/
|
|
CERTGeneralName *
|
|
cert_GetSubjectAltNameList(const CERTCertificate *cert, PLArenaPool *arena)
|
|
{
|
|
CERTGeneralName *nameList = NULL;
|
|
SECStatus rv = SECFailure;
|
|
SECItem subAltName;
|
|
|
|
if (!cert || !arena)
|
|
return NULL;
|
|
|
|
subAltName.data = NULL;
|
|
|
|
rv = CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME,
|
|
&subAltName);
|
|
if (rv != SECSuccess)
|
|
return NULL;
|
|
|
|
nameList = CERT_DecodeAltNameExtension(arena, &subAltName);
|
|
SECITEM_FreeItem(&subAltName, PR_FALSE);
|
|
return nameList;
|
|
}
|
|
|
|
PRUint32
|
|
cert_CountDNSPatterns(CERTGeneralName *firstName)
|
|
{
|
|
CERTGeneralName *current;
|
|
PRUint32 count = 0;
|
|
|
|
if (!firstName)
|
|
return 0;
|
|
|
|
current = firstName;
|
|
do {
|
|
switch (current->type) {
|
|
case certDNSName:
|
|
case certIPAddress:
|
|
++count;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
current = CERT_GetNextGeneralName(current);
|
|
} while (current != firstName);
|
|
|
|
return count;
|
|
}
|
|
|
|
#ifndef INET6_ADDRSTRLEN
|
|
#define INET6_ADDRSTRLEN 46
|
|
#endif
|
|
|
|
/* will fill nickNames,
|
|
* will allocate all data from nickNames->arena,
|
|
* numberOfGeneralNames should have been obtained from cert_CountDNSPatterns,
|
|
* will ensure the numberOfGeneralNames matches the number of output entries.
|
|
*/
|
|
SECStatus
|
|
cert_GetDNSPatternsFromGeneralNames(CERTGeneralName *firstName,
|
|
PRUint32 numberOfGeneralNames,
|
|
CERTCertNicknames *nickNames)
|
|
{
|
|
CERTGeneralName *currentInput;
|
|
char **currentOutput;
|
|
|
|
if (!firstName || !nickNames || !numberOfGeneralNames)
|
|
return SECFailure;
|
|
|
|
nickNames->numnicknames = numberOfGeneralNames;
|
|
nickNames->nicknames = PORT_ArenaAlloc(
|
|
nickNames->arena, sizeof(char *) * numberOfGeneralNames);
|
|
if (!nickNames->nicknames)
|
|
return SECFailure;
|
|
|
|
currentInput = firstName;
|
|
currentOutput = nickNames->nicknames;
|
|
do {
|
|
char *cn = NULL;
|
|
char ipbuf[INET6_ADDRSTRLEN];
|
|
PRNetAddr addr;
|
|
|
|
if (numberOfGeneralNames < 1) {
|
|
/* internal consistency error */
|
|
return SECFailure;
|
|
}
|
|
|
|
switch (currentInput->type) {
|
|
case certDNSName:
|
|
/* DNS name currentInput->name.other.data is not null
|
|
*terminated.
|
|
** so must copy it.
|
|
*/
|
|
cn = (char *)PORT_ArenaAlloc(nickNames->arena,
|
|
currentInput->name.other.len + 1);
|
|
if (!cn)
|
|
return SECFailure;
|
|
PORT_Memcpy(cn, currentInput->name.other.data,
|
|
currentInput->name.other.len);
|
|
cn[currentInput->name.other.len] = 0;
|
|
break;
|
|
case certIPAddress:
|
|
if (currentInput->name.other.len == 4) {
|
|
addr.inet.family = PR_AF_INET;
|
|
memcpy(&addr.inet.ip, currentInput->name.other.data,
|
|
currentInput->name.other.len);
|
|
} else if (currentInput->name.other.len == 16) {
|
|
addr.ipv6.family = PR_AF_INET6;
|
|
memcpy(&addr.ipv6.ip, currentInput->name.other.data,
|
|
currentInput->name.other.len);
|
|
}
|
|
if (PR_NetAddrToString(&addr, ipbuf, sizeof(ipbuf)) ==
|
|
PR_FAILURE)
|
|
return SECFailure;
|
|
cn = PORT_ArenaStrdup(nickNames->arena, ipbuf);
|
|
if (!cn)
|
|
return SECFailure;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (cn) {
|
|
*currentOutput = cn;
|
|
nickNames->totallen += PORT_Strlen(cn);
|
|
++currentOutput;
|
|
--numberOfGeneralNames;
|
|
}
|
|
currentInput = CERT_GetNextGeneralName(currentInput);
|
|
} while (currentInput != firstName);
|
|
|
|
return (numberOfGeneralNames == 0) ? SECSuccess : SECFailure;
|
|
}
|
|
|
|
/*
|
|
* Collect all valid DNS names from the given cert.
|
|
* The output arena will reference some temporaray data,
|
|
* but this saves us from dealing with two arenas.
|
|
* The caller may free all data by freeing CERTCertNicknames->arena.
|
|
*/
|
|
CERTCertNicknames *
|
|
CERT_GetValidDNSPatternsFromCert(CERTCertificate *cert)
|
|
{
|
|
CERTGeneralName *generalNames;
|
|
CERTCertNicknames *nickNames;
|
|
PLArenaPool *arena;
|
|
char *singleName;
|
|
|
|
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
if (!arena) {
|
|
return NULL;
|
|
}
|
|
|
|
nickNames = PORT_ArenaAlloc(arena, sizeof(CERTCertNicknames));
|
|
if (!nickNames) {
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
return NULL;
|
|
}
|
|
|
|
/* init the structure */
|
|
nickNames->arena = arena;
|
|
nickNames->head = NULL;
|
|
nickNames->numnicknames = 0;
|
|
nickNames->nicknames = NULL;
|
|
nickNames->totallen = 0;
|
|
|
|
generalNames = cert_GetSubjectAltNameList(cert, arena);
|
|
if (generalNames) {
|
|
SECStatus rv_getnames = SECFailure;
|
|
PRUint32 numNames = cert_CountDNSPatterns(generalNames);
|
|
|
|
if (numNames) {
|
|
rv_getnames = cert_GetDNSPatternsFromGeneralNames(
|
|
generalNames, numNames, nickNames);
|
|
}
|
|
|
|
/* if there were names, we'll exit now, either with success or failure
|
|
*/
|
|
if (numNames) {
|
|
if (rv_getnames == SECSuccess) {
|
|
return nickNames;
|
|
}
|
|
|
|
/* failure to produce output */
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* no SAN extension or no names found in extension */
|
|
singleName = CERT_GetCommonName(&cert->subject);
|
|
if (singleName) {
|
|
nickNames->numnicknames = 1;
|
|
nickNames->nicknames = PORT_ArenaAlloc(arena, sizeof(char *));
|
|
if (nickNames->nicknames) {
|
|
*nickNames->nicknames = PORT_ArenaStrdup(arena, singleName);
|
|
}
|
|
PORT_Free(singleName);
|
|
|
|
/* Did we allocate both the buffer of pointers and the string? */
|
|
if (nickNames->nicknames && *nickNames->nicknames) {
|
|
return nickNames;
|
|
}
|
|
}
|
|
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
return NULL;
|
|
}
|
|
|
|
/* Make sure that the name of the host we are connecting to matches the
|
|
* name that is incoded in the common-name component of the certificate
|
|
* that they are using.
|
|
*/
|
|
SECStatus
|
|
CERT_VerifyCertName(const CERTCertificate *cert, const char *hn)
|
|
{
|
|
char *cn;
|
|
SECStatus rv;
|
|
CERTOKDomainName *domainOK;
|
|
|
|
if (!hn || !strlen(hn)) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return SECFailure;
|
|
}
|
|
|
|
/* if the name is one that the user has already approved, it's OK. */
|
|
for (domainOK = cert->domainOK; domainOK; domainOK = domainOK->next) {
|
|
if (0 == PORT_Strcasecmp(hn, domainOK->name)) {
|
|
return SECSuccess;
|
|
}
|
|
}
|
|
|
|
/* Per RFC 2818, if the SubjectAltName extension is present, it must
|
|
** be used as the cert's identity.
|
|
*/
|
|
rv = cert_VerifySubjectAltName(cert, hn);
|
|
if (rv == SECSuccess || PORT_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND)
|
|
return rv;
|
|
|
|
cn = CERT_GetCommonName(&cert->subject);
|
|
if (cn) {
|
|
PRBool isIPaddr = cert_IsIPAddr(hn);
|
|
if (isIPaddr) {
|
|
if (PORT_Strcasecmp(hn, cn) == 0) {
|
|
rv = SECSuccess;
|
|
} else {
|
|
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
|
|
rv = SECFailure;
|
|
}
|
|
} else {
|
|
rv = cert_TestHostName(cn, hn);
|
|
}
|
|
PORT_Free(cn);
|
|
} else
|
|
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
|
|
return rv;
|
|
}
|
|
|
|
PRBool
|
|
CERT_CompareCerts(const CERTCertificate *c1, const CERTCertificate *c2)
|
|
{
|
|
SECComparison comp;
|
|
|
|
comp = SECITEM_CompareItem(&c1->derCert, &c2->derCert);
|
|
if (comp == SECEqual) { /* certs are the same */
|
|
return (PR_TRUE);
|
|
} else {
|
|
return (PR_FALSE);
|
|
}
|
|
}
|
|
|
|
static SECStatus
|
|
StringsEqual(char *s1, char *s2)
|
|
{
|
|
if ((s1 == NULL) || (s2 == NULL)) {
|
|
if (s1 != s2) { /* only one is null */
|
|
return (SECFailure);
|
|
}
|
|
return (SECSuccess); /* both are null */
|
|
}
|
|
|
|
if (PORT_Strcmp(s1, s2) != 0) {
|
|
return (SECFailure); /* not equal */
|
|
}
|
|
|
|
return (SECSuccess); /* strings are equal */
|
|
}
|
|
|
|
PRBool
|
|
CERT_CompareCertsForRedirection(CERTCertificate *c1, CERTCertificate *c2)
|
|
{
|
|
SECComparison comp;
|
|
char *c1str, *c2str;
|
|
SECStatus eq;
|
|
|
|
comp = SECITEM_CompareItem(&c1->derCert, &c2->derCert);
|
|
if (comp == SECEqual) { /* certs are the same */
|
|
return (PR_TRUE);
|
|
}
|
|
|
|
/* check if they are issued by the same CA */
|
|
comp = SECITEM_CompareItem(&c1->derIssuer, &c2->derIssuer);
|
|
if (comp != SECEqual) { /* different issuer */
|
|
return (PR_FALSE);
|
|
}
|
|
|
|
/* check country name */
|
|
c1str = CERT_GetCountryName(&c1->subject);
|
|
c2str = CERT_GetCountryName(&c2->subject);
|
|
eq = StringsEqual(c1str, c2str);
|
|
PORT_Free(c1str);
|
|
PORT_Free(c2str);
|
|
if (eq != SECSuccess) {
|
|
return (PR_FALSE);
|
|
}
|
|
|
|
/* check locality name */
|
|
c1str = CERT_GetLocalityName(&c1->subject);
|
|
c2str = CERT_GetLocalityName(&c2->subject);
|
|
eq = StringsEqual(c1str, c2str);
|
|
PORT_Free(c1str);
|
|
PORT_Free(c2str);
|
|
if (eq != SECSuccess) {
|
|
return (PR_FALSE);
|
|
}
|
|
|
|
/* check state name */
|
|
c1str = CERT_GetStateName(&c1->subject);
|
|
c2str = CERT_GetStateName(&c2->subject);
|
|
eq = StringsEqual(c1str, c2str);
|
|
PORT_Free(c1str);
|
|
PORT_Free(c2str);
|
|
if (eq != SECSuccess) {
|
|
return (PR_FALSE);
|
|
}
|
|
|
|
/* check org name */
|
|
c1str = CERT_GetOrgName(&c1->subject);
|
|
c2str = CERT_GetOrgName(&c2->subject);
|
|
eq = StringsEqual(c1str, c2str);
|
|
PORT_Free(c1str);
|
|
PORT_Free(c2str);
|
|
if (eq != SECSuccess) {
|
|
return (PR_FALSE);
|
|
}
|
|
|
|
#ifdef NOTDEF
|
|
/* check orgUnit name */
|
|
/*
|
|
* We need to revisit this and decide which fields should be allowed to be
|
|
* different
|
|
*/
|
|
c1str = CERT_GetOrgUnitName(&c1->subject);
|
|
c2str = CERT_GetOrgUnitName(&c2->subject);
|
|
eq = StringsEqual(c1str, c2str);
|
|
PORT_Free(c1str);
|
|
PORT_Free(c2str);
|
|
if (eq != SECSuccess) {
|
|
return (PR_FALSE);
|
|
}
|
|
#endif
|
|
|
|
return (PR_TRUE); /* all fields but common name are the same */
|
|
}
|
|
|
|
/* CERT_CertChainFromCert and CERT_DestroyCertificateList moved
|
|
to certhigh.c */
|
|
|
|
CERTIssuerAndSN *
|
|
CERT_GetCertIssuerAndSN(PLArenaPool *arena, CERTCertificate *cert)
|
|
{
|
|
CERTIssuerAndSN *result;
|
|
SECStatus rv;
|
|
|
|
if (arena == NULL) {
|
|
arena = cert->arena;
|
|
}
|
|
|
|
result = (CERTIssuerAndSN *)PORT_ArenaZAlloc(arena, sizeof(*result));
|
|
if (result == NULL) {
|
|
PORT_SetError(SEC_ERROR_NO_MEMORY);
|
|
return NULL;
|
|
}
|
|
|
|
rv = SECITEM_CopyItem(arena, &result->derIssuer, &cert->derIssuer);
|
|
if (rv != SECSuccess)
|
|
return NULL;
|
|
|
|
rv = CERT_CopyName(arena, &result->issuer, &cert->issuer);
|
|
if (rv != SECSuccess)
|
|
return NULL;
|
|
|
|
rv = SECITEM_CopyItem(arena, &result->serialNumber, &cert->serialNumber);
|
|
if (rv != SECSuccess)
|
|
return NULL;
|
|
|
|
return result;
|
|
}
|
|
|
|
char *
|
|
CERT_MakeCANickname(CERTCertificate *cert)
|
|
{
|
|
char *firstname = NULL;
|
|
char *org = NULL;
|
|
char *nickname = NULL;
|
|
int count;
|
|
CERTCertificate *dummycert;
|
|
|
|
firstname = CERT_GetCommonName(&cert->subject);
|
|
if (firstname == NULL) {
|
|
firstname = CERT_GetOrgUnitName(&cert->subject);
|
|
}
|
|
|
|
org = CERT_GetOrgName(&cert->issuer);
|
|
if (org == NULL) {
|
|
org = CERT_GetDomainComponentName(&cert->issuer);
|
|
if (org == NULL) {
|
|
if (firstname) {
|
|
org = firstname;
|
|
firstname = NULL;
|
|
} else {
|
|
org = PORT_Strdup("Unknown CA");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* can only fail if PORT_Strdup fails, in which case
|
|
* we're having memory problems. */
|
|
if (org == NULL) {
|
|
goto done;
|
|
}
|
|
|
|
count = 1;
|
|
while (1) {
|
|
|
|
if (firstname) {
|
|
if (count == 1) {
|
|
nickname = PR_smprintf("%s - %s", firstname, org);
|
|
} else {
|
|
nickname = PR_smprintf("%s - %s #%d", firstname, org, count);
|
|
}
|
|
} else {
|
|
if (count == 1) {
|
|
nickname = PR_smprintf("%s", org);
|
|
} else {
|
|
nickname = PR_smprintf("%s #%d", org, count);
|
|
}
|
|
}
|
|
if (nickname == NULL) {
|
|
goto done;
|
|
}
|
|
|
|
/* look up the nickname to make sure it isn't in use already */
|
|
dummycert = CERT_FindCertByNickname(cert->dbhandle, nickname);
|
|
|
|
if (dummycert == NULL) {
|
|
goto done;
|
|
}
|
|
|
|
/* found a cert, destroy it and loop */
|
|
CERT_DestroyCertificate(dummycert);
|
|
|
|
/* free the nickname */
|
|
PORT_Free(nickname);
|
|
|
|
count++;
|
|
}
|
|
|
|
done:
|
|
if (firstname) {
|
|
PORT_Free(firstname);
|
|
}
|
|
if (org) {
|
|
PORT_Free(org);
|
|
}
|
|
|
|
return (nickname);
|
|
}
|
|
|
|
/* CERT_Import_CAChain moved to certhigh.c */
|
|
|
|
void
|
|
CERT_DestroyCrl(CERTSignedCrl *crl)
|
|
{
|
|
SEC_DestroyCrl(crl);
|
|
}
|
|
|
|
static int
|
|
cert_Version(CERTCertificate *cert)
|
|
{
|
|
int version = 0;
|
|
if (cert && cert->version.data && cert->version.len) {
|
|
version = DER_GetInteger(&cert->version);
|
|
if (version < 0)
|
|
version = 0;
|
|
}
|
|
return version;
|
|
}
|
|
|
|
static unsigned int
|
|
cert_ComputeTrustOverrides(CERTCertificate *cert, unsigned int cType)
|
|
{
|
|
CERTCertTrust trust;
|
|
SECStatus rv = SECFailure;
|
|
|
|
rv = CERT_GetCertTrust(cert, &trust);
|
|
|
|
if (rv == SECSuccess &&
|
|
(trust.sslFlags | trust.emailFlags | trust.objectSigningFlags)) {
|
|
|
|
if (trust.sslFlags & (CERTDB_TERMINAL_RECORD | CERTDB_TRUSTED))
|
|
cType |= NS_CERT_TYPE_SSL_SERVER | NS_CERT_TYPE_SSL_CLIENT;
|
|
if (trust.sslFlags & (CERTDB_VALID_CA | CERTDB_TRUSTED_CA))
|
|
cType |= NS_CERT_TYPE_SSL_CA;
|
|
#if defined(CERTDB_NOT_TRUSTED)
|
|
if (trust.sslFlags & CERTDB_NOT_TRUSTED)
|
|
cType &= ~(NS_CERT_TYPE_SSL_SERVER | NS_CERT_TYPE_SSL_CLIENT |
|
|
NS_CERT_TYPE_SSL_CA);
|
|
#endif
|
|
if (trust.emailFlags & (CERTDB_TERMINAL_RECORD | CERTDB_TRUSTED))
|
|
cType |= NS_CERT_TYPE_EMAIL;
|
|
if (trust.emailFlags & (CERTDB_VALID_CA | CERTDB_TRUSTED_CA))
|
|
cType |= NS_CERT_TYPE_EMAIL_CA;
|
|
#if defined(CERTDB_NOT_TRUSTED)
|
|
if (trust.emailFlags & CERTDB_NOT_TRUSTED)
|
|
cType &= ~(NS_CERT_TYPE_EMAIL | NS_CERT_TYPE_EMAIL_CA);
|
|
#endif
|
|
if (trust.objectSigningFlags &
|
|
(CERTDB_TERMINAL_RECORD | CERTDB_TRUSTED))
|
|
cType |= NS_CERT_TYPE_OBJECT_SIGNING;
|
|
if (trust.objectSigningFlags & (CERTDB_VALID_CA | CERTDB_TRUSTED_CA))
|
|
cType |= NS_CERT_TYPE_OBJECT_SIGNING_CA;
|
|
#if defined(CERTDB_NOT_TRUSTED)
|
|
if (trust.objectSigningFlags & CERTDB_NOT_TRUSTED)
|
|
cType &=
|
|
~(NS_CERT_TYPE_OBJECT_SIGNING | NS_CERT_TYPE_OBJECT_SIGNING_CA);
|
|
#endif
|
|
}
|
|
return cType;
|
|
}
|
|
|
|
/*
|
|
* Does a cert belong to a CA? We decide based on perm database trust
|
|
* flags, Netscape Cert Type Extension, and KeyUsage Extension.
|
|
*/
|
|
PRBool
|
|
CERT_IsCACert(CERTCertificate *cert, unsigned int *rettype)
|
|
{
|
|
unsigned int cType = cert->nsCertType;
|
|
PRBool ret = PR_FALSE;
|
|
|
|
/*
|
|
* Check if the constraints are available and it's a CA, OR if it's
|
|
* a X.509 v1 Root CA.
|
|
*/
|
|
CERTBasicConstraints constraints;
|
|
if ((CERT_FindBasicConstraintExten(cert, &constraints) == SECSuccess &&
|
|
constraints.isCA) ||
|
|
(cert->isRoot && cert_Version(cert) < SEC_CERTIFICATE_VERSION_3))
|
|
cType |= (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA);
|
|
|
|
/*
|
|
* Apply trust overrides, if any.
|
|
*/
|
|
cType = cert_ComputeTrustOverrides(cert, cType);
|
|
ret = (cType & (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA |
|
|
NS_CERT_TYPE_OBJECT_SIGNING_CA))
|
|
? PR_TRUE
|
|
: PR_FALSE;
|
|
|
|
if (rettype) {
|
|
*rettype = cType;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
PRBool
|
|
CERT_IsCADERCert(SECItem *derCert, unsigned int *type)
|
|
{
|
|
CERTCertificate *cert;
|
|
PRBool isCA;
|
|
|
|
/* This is okay -- only looks at extensions */
|
|
cert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL);
|
|
if (cert == NULL)
|
|
return PR_FALSE;
|
|
|
|
isCA = CERT_IsCACert(cert, type);
|
|
CERT_DestroyCertificate(cert);
|
|
return isCA;
|
|
}
|
|
|
|
PRBool
|
|
CERT_IsRootDERCert(SECItem *derCert)
|
|
{
|
|
CERTCertificate *cert;
|
|
PRBool isRoot;
|
|
|
|
/* This is okay -- only looks at extensions */
|
|
cert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL);
|
|
if (cert == NULL)
|
|
return PR_FALSE;
|
|
|
|
isRoot = cert->isRoot;
|
|
CERT_DestroyCertificate(cert);
|
|
return isRoot;
|
|
}
|
|
|
|
CERTCompareValidityStatus
|
|
CERT_CompareValidityTimes(CERTValidity *val_a, CERTValidity *val_b)
|
|
{
|
|
PRTime notBeforeA, notBeforeB, notAfterA, notAfterB;
|
|
|
|
if (!val_a || !val_b) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return certValidityUndetermined;
|
|
}
|
|
|
|
if (SECSuccess != DER_DecodeTimeChoice(¬BeforeA, &val_a->notBefore) ||
|
|
SECSuccess != DER_DecodeTimeChoice(¬BeforeB, &val_b->notBefore) ||
|
|
SECSuccess != DER_DecodeTimeChoice(¬AfterA, &val_a->notAfter) ||
|
|
SECSuccess != DER_DecodeTimeChoice(¬AfterB, &val_b->notAfter)) {
|
|
return certValidityUndetermined;
|
|
}
|
|
|
|
/* sanity check */
|
|
if (LL_CMP(notBeforeA, >, notAfterA) || LL_CMP(notBeforeB, >, notAfterB)) {
|
|
PORT_SetError(SEC_ERROR_INVALID_TIME);
|
|
return certValidityUndetermined;
|
|
}
|
|
|
|
if (LL_CMP(notAfterA, !=, notAfterB)) {
|
|
/* one cert validity goes farther into the future, select it */
|
|
return LL_CMP(notAfterA, <, notAfterB) ? certValidityChooseB
|
|
: certValidityChooseA;
|
|
}
|
|
/* the two certs have the same expiration date */
|
|
PORT_Assert(LL_CMP(notAfterA, ==, notAfterB));
|
|
/* do they also have the same start date ? */
|
|
if (LL_CMP(notBeforeA, ==, notBeforeB)) {
|
|
return certValidityEqual;
|
|
}
|
|
/* choose cert with the later start date */
|
|
return LL_CMP(notBeforeA, <, notBeforeB) ? certValidityChooseB
|
|
: certValidityChooseA;
|
|
}
|
|
|
|
/*
|
|
* is certa newer than certb? If one is expired, pick the other one.
|
|
*/
|
|
PRBool
|
|
CERT_IsNewer(CERTCertificate *certa, CERTCertificate *certb)
|
|
{
|
|
PRTime notBeforeA, notAfterA, notBeforeB, notAfterB, now;
|
|
SECStatus rv;
|
|
PRBool newerbefore, newerafter;
|
|
|
|
rv = CERT_GetCertTimes(certa, ¬BeforeA, ¬AfterA);
|
|
if (rv != SECSuccess) {
|
|
return (PR_FALSE);
|
|
}
|
|
|
|
rv = CERT_GetCertTimes(certb, ¬BeforeB, ¬AfterB);
|
|
if (rv != SECSuccess) {
|
|
return (PR_TRUE);
|
|
}
|
|
|
|
newerbefore = PR_FALSE;
|
|
if (LL_CMP(notBeforeA, >, notBeforeB)) {
|
|
newerbefore = PR_TRUE;
|
|
}
|
|
|
|
newerafter = PR_FALSE;
|
|
if (LL_CMP(notAfterA, >, notAfterB)) {
|
|
newerafter = PR_TRUE;
|
|
}
|
|
|
|
if (newerbefore && newerafter) {
|
|
return (PR_TRUE);
|
|
}
|
|
|
|
if ((!newerbefore) && (!newerafter)) {
|
|
return (PR_FALSE);
|
|
}
|
|
|
|
/* get current time */
|
|
now = PR_Now();
|
|
|
|
if (newerbefore) {
|
|
/* cert A was issued after cert B, but expires sooner */
|
|
/* if A is expired, then pick B */
|
|
if (LL_CMP(notAfterA, <, now)) {
|
|
return (PR_FALSE);
|
|
}
|
|
return (PR_TRUE);
|
|
} else {
|
|
/* cert B was issued after cert A, but expires sooner */
|
|
/* if B is expired, then pick A */
|
|
if (LL_CMP(notAfterB, <, now)) {
|
|
return (PR_TRUE);
|
|
}
|
|
return (PR_FALSE);
|
|
}
|
|
}
|
|
|
|
void
|
|
CERT_DestroyCertArray(CERTCertificate **certs, unsigned int ncerts)
|
|
{
|
|
unsigned int i;
|
|
|
|
if (certs) {
|
|
for (i = 0; i < ncerts; i++) {
|
|
if (certs[i]) {
|
|
CERT_DestroyCertificate(certs[i]);
|
|
}
|
|
}
|
|
|
|
PORT_Free(certs);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
char *
|
|
CERT_FixupEmailAddr(const char *emailAddr)
|
|
{
|
|
char *retaddr;
|
|
char *str;
|
|
|
|
if (emailAddr == NULL) {
|
|
return (NULL);
|
|
}
|
|
|
|
/* copy the string */
|
|
str = retaddr = PORT_Strdup(emailAddr);
|
|
if (str == NULL) {
|
|
return (NULL);
|
|
}
|
|
|
|
/* make it lower case */
|
|
while (*str) {
|
|
*str = tolower(*str);
|
|
str++;
|
|
}
|
|
|
|
return (retaddr);
|
|
}
|
|
|
|
/*
|
|
* NOTE - don't allow encode of govt-approved or invisible bits
|
|
*/
|
|
SECStatus
|
|
CERT_DecodeTrustString(CERTCertTrust *trust, const char *trusts)
|
|
{
|
|
unsigned int i;
|
|
unsigned int *pflags;
|
|
|
|
if (!trust) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return SECFailure;
|
|
}
|
|
trust->sslFlags = 0;
|
|
trust->emailFlags = 0;
|
|
trust->objectSigningFlags = 0;
|
|
if (!trusts) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return SECFailure;
|
|
}
|
|
|
|
pflags = &trust->sslFlags;
|
|
|
|
for (i = 0; i < PORT_Strlen(trusts); i++) {
|
|
switch (trusts[i]) {
|
|
case 'p':
|
|
*pflags = *pflags | CERTDB_TERMINAL_RECORD;
|
|
break;
|
|
|
|
case 'P':
|
|
*pflags = *pflags | CERTDB_TRUSTED | CERTDB_TERMINAL_RECORD;
|
|
break;
|
|
|
|
case 'w':
|
|
*pflags = *pflags | CERTDB_SEND_WARN;
|
|
break;
|
|
|
|
case 'c':
|
|
*pflags = *pflags | CERTDB_VALID_CA;
|
|
break;
|
|
|
|
case 'T':
|
|
*pflags = *pflags | CERTDB_TRUSTED_CLIENT_CA | CERTDB_VALID_CA;
|
|
break;
|
|
|
|
case 'C':
|
|
*pflags = *pflags | CERTDB_TRUSTED_CA | CERTDB_VALID_CA;
|
|
break;
|
|
|
|
case 'u':
|
|
*pflags = *pflags | CERTDB_USER;
|
|
break;
|
|
|
|
case 'i':
|
|
*pflags = *pflags | CERTDB_INVISIBLE_CA;
|
|
break;
|
|
case 'g':
|
|
*pflags = *pflags | CERTDB_GOVT_APPROVED_CA;
|
|
break;
|
|
|
|
case ',':
|
|
if (pflags == &trust->sslFlags) {
|
|
pflags = &trust->emailFlags;
|
|
} else {
|
|
pflags = &trust->objectSigningFlags;
|
|
}
|
|
break;
|
|
default:
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return SECFailure;
|
|
}
|
|
}
|
|
|
|
return SECSuccess;
|
|
}
|
|
|
|
static void
|
|
EncodeFlags(char *trusts, unsigned int flags)
|
|
{
|
|
if (flags & CERTDB_VALID_CA)
|
|
if (!(flags & CERTDB_TRUSTED_CA) && !(flags & CERTDB_TRUSTED_CLIENT_CA))
|
|
PORT_Strcat(trusts, "c");
|
|
if (flags & CERTDB_TERMINAL_RECORD)
|
|
if (!(flags & CERTDB_TRUSTED))
|
|
PORT_Strcat(trusts, "p");
|
|
if (flags & CERTDB_TRUSTED_CA)
|
|
PORT_Strcat(trusts, "C");
|
|
if (flags & CERTDB_TRUSTED_CLIENT_CA)
|
|
PORT_Strcat(trusts, "T");
|
|
if (flags & CERTDB_TRUSTED)
|
|
PORT_Strcat(trusts, "P");
|
|
if (flags & CERTDB_USER)
|
|
PORT_Strcat(trusts, "u");
|
|
if (flags & CERTDB_SEND_WARN)
|
|
PORT_Strcat(trusts, "w");
|
|
if (flags & CERTDB_INVISIBLE_CA)
|
|
PORT_Strcat(trusts, "I");
|
|
if (flags & CERTDB_GOVT_APPROVED_CA)
|
|
PORT_Strcat(trusts, "G");
|
|
return;
|
|
}
|
|
|
|
char *
|
|
CERT_EncodeTrustString(CERTCertTrust *trust)
|
|
{
|
|
char tmpTrustSSL[32];
|
|
char tmpTrustEmail[32];
|
|
char tmpTrustSigning[32];
|
|
char *retstr = NULL;
|
|
|
|
if (trust) {
|
|
tmpTrustSSL[0] = '\0';
|
|
tmpTrustEmail[0] = '\0';
|
|
tmpTrustSigning[0] = '\0';
|
|
|
|
EncodeFlags(tmpTrustSSL, trust->sslFlags);
|
|
EncodeFlags(tmpTrustEmail, trust->emailFlags);
|
|
EncodeFlags(tmpTrustSigning, trust->objectSigningFlags);
|
|
|
|
retstr = PR_smprintf("%s,%s,%s", tmpTrustSSL, tmpTrustEmail,
|
|
tmpTrustSigning);
|
|
}
|
|
|
|
return (retstr);
|
|
}
|
|
|
|
SECStatus
|
|
CERT_ImportCerts(CERTCertDBHandle *certdb, SECCertUsage usage,
|
|
unsigned int ncerts, SECItem **derCerts,
|
|
CERTCertificate ***retCerts, PRBool keepCerts, PRBool caOnly,
|
|
char *nickname)
|
|
{
|
|
unsigned int i;
|
|
CERTCertificate **certs = NULL;
|
|
unsigned int fcerts = 0;
|
|
|
|
if (ncerts) {
|
|
certs = PORT_ZNewArray(CERTCertificate *, ncerts);
|
|
if (certs == NULL) {
|
|
return (SECFailure);
|
|
}
|
|
|
|
/* decode all of the certs into the temporary DB */
|
|
for (i = 0, fcerts = 0; i < ncerts; i++) {
|
|
certs[fcerts] = CERT_NewTempCertificate(certdb, derCerts[i], NULL,
|
|
PR_FALSE, PR_TRUE);
|
|
if (certs[fcerts]) {
|
|
SECItem subjKeyID = { siBuffer, NULL, 0 };
|
|
if (CERT_FindSubjectKeyIDExtension(certs[fcerts], &subjKeyID) ==
|
|
SECSuccess) {
|
|
if (subjKeyID.data) {
|
|
cert_AddSubjectKeyIDMapping(&subjKeyID, certs[fcerts]);
|
|
}
|
|
SECITEM_FreeItem(&subjKeyID, PR_FALSE);
|
|
}
|
|
fcerts++;
|
|
}
|
|
}
|
|
|
|
if (keepCerts) {
|
|
for (i = 0; i < fcerts; i++) {
|
|
char *canickname = NULL;
|
|
PRBool isCA;
|
|
|
|
SECKEY_UpdateCertPQG(certs[i]);
|
|
|
|
isCA = CERT_IsCACert(certs[i], NULL);
|
|
if (isCA) {
|
|
canickname = CERT_MakeCANickname(certs[i]);
|
|
}
|
|
|
|
if (isCA && (fcerts > 1)) {
|
|
/* if we are importing only a single cert and specifying
|
|
* a nickname, we want to use that nickname if it a CA,
|
|
* otherwise if there are more than one cert, we don't
|
|
* know which cert it belongs to. But we still may try
|
|
* the individual canickname from the cert itself.
|
|
*/
|
|
/* Bug 1192442 - propagate errors from these calls. */
|
|
(void)CERT_AddTempCertToPerm(certs[i], canickname, NULL);
|
|
} else {
|
|
(void)CERT_AddTempCertToPerm(
|
|
certs[i], nickname ? nickname : canickname, NULL);
|
|
}
|
|
|
|
PORT_Free(canickname);
|
|
/* don't care if it fails - keep going */
|
|
}
|
|
}
|
|
}
|
|
|
|
if (retCerts) {
|
|
*retCerts = certs;
|
|
} else {
|
|
if (certs) {
|
|
CERT_DestroyCertArray(certs, fcerts);
|
|
}
|
|
}
|
|
|
|
return (fcerts || !ncerts) ? SECSuccess : SECFailure;
|
|
}
|
|
|
|
/*
|
|
* a real list of certificates - need to convert CERTCertificateList
|
|
* stuff and ASN 1 encoder/decoder over to using this...
|
|
*/
|
|
CERTCertList *
|
|
CERT_NewCertList(void)
|
|
{
|
|
PLArenaPool *arena = NULL;
|
|
CERTCertList *ret = NULL;
|
|
|
|
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
if (arena == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
ret = (CERTCertList *)PORT_ArenaZAlloc(arena, sizeof(CERTCertList));
|
|
if (ret == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
ret->arena = arena;
|
|
|
|
PR_INIT_CLIST(&ret->list);
|
|
|
|
return (ret);
|
|
|
|
loser:
|
|
if (arena != NULL) {
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
void
|
|
CERT_DestroyCertList(CERTCertList *certs)
|
|
{
|
|
PRCList *node;
|
|
|
|
while (!PR_CLIST_IS_EMPTY(&certs->list)) {
|
|
node = PR_LIST_HEAD(&certs->list);
|
|
CERT_DestroyCertificate(((CERTCertListNode *)node)->cert);
|
|
PR_REMOVE_LINK(node);
|
|
}
|
|
|
|
PORT_FreeArena(certs->arena, PR_FALSE);
|
|
|
|
return;
|
|
}
|
|
|
|
void
|
|
CERT_RemoveCertListNode(CERTCertListNode *node)
|
|
{
|
|
CERT_DestroyCertificate(node->cert);
|
|
PR_REMOVE_LINK(&node->links);
|
|
return;
|
|
}
|
|
|
|
SECStatus
|
|
CERT_AddCertToListTailWithData(CERTCertList *certs, CERTCertificate *cert,
|
|
void *appData)
|
|
{
|
|
CERTCertListNode *node;
|
|
|
|
node = (CERTCertListNode *)PORT_ArenaZAlloc(certs->arena,
|
|
sizeof(CERTCertListNode));
|
|
if (node == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
PR_INSERT_BEFORE(&node->links, &certs->list);
|
|
/* certs->count++; */
|
|
node->cert = cert;
|
|
node->appData = appData;
|
|
return (SECSuccess);
|
|
|
|
loser:
|
|
return (SECFailure);
|
|
}
|
|
|
|
SECStatus
|
|
CERT_AddCertToListTail(CERTCertList *certs, CERTCertificate *cert)
|
|
{
|
|
return CERT_AddCertToListTailWithData(certs, cert, NULL);
|
|
}
|
|
|
|
SECStatus
|
|
CERT_AddCertToListHeadWithData(CERTCertList *certs, CERTCertificate *cert,
|
|
void *appData)
|
|
{
|
|
CERTCertListNode *node;
|
|
CERTCertListNode *head;
|
|
|
|
head = CERT_LIST_HEAD(certs);
|
|
if (head == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
node = (CERTCertListNode *)PORT_ArenaZAlloc(certs->arena,
|
|
sizeof(CERTCertListNode));
|
|
if (node == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
PR_INSERT_BEFORE(&node->links, &head->links);
|
|
/* certs->count++; */
|
|
node->cert = cert;
|
|
node->appData = appData;
|
|
return (SECSuccess);
|
|
|
|
loser:
|
|
return (SECFailure);
|
|
}
|
|
|
|
SECStatus
|
|
CERT_AddCertToListHead(CERTCertList *certs, CERTCertificate *cert)
|
|
{
|
|
return CERT_AddCertToListHeadWithData(certs, cert, NULL);
|
|
}
|
|
|
|
/*
|
|
* Sort callback function to determine if cert a is newer than cert b.
|
|
* Not valid certs are considered older than valid certs.
|
|
*/
|
|
PRBool
|
|
CERT_SortCBValidity(CERTCertificate *certa, CERTCertificate *certb, void *arg)
|
|
{
|
|
PRTime sorttime;
|
|
PRTime notBeforeA, notAfterA, notBeforeB, notAfterB;
|
|
SECStatus rv;
|
|
PRBool newerbefore, newerafter;
|
|
PRBool aNotValid = PR_FALSE, bNotValid = PR_FALSE;
|
|
|
|
sorttime = *(PRTime *)arg;
|
|
|
|
rv = CERT_GetCertTimes(certa, ¬BeforeA, ¬AfterA);
|
|
if (rv != SECSuccess) {
|
|
return (PR_FALSE);
|
|
}
|
|
|
|
rv = CERT_GetCertTimes(certb, ¬BeforeB, ¬AfterB);
|
|
if (rv != SECSuccess) {
|
|
return (PR_TRUE);
|
|
}
|
|
newerbefore = PR_FALSE;
|
|
if (LL_CMP(notBeforeA, >, notBeforeB)) {
|
|
newerbefore = PR_TRUE;
|
|
}
|
|
newerafter = PR_FALSE;
|
|
if (LL_CMP(notAfterA, >, notAfterB)) {
|
|
newerafter = PR_TRUE;
|
|
}
|
|
|
|
/* check if A is valid at sorttime */
|
|
if (CERT_CheckCertValidTimes(certa, sorttime, PR_FALSE) !=
|
|
secCertTimeValid) {
|
|
aNotValid = PR_TRUE;
|
|
}
|
|
|
|
/* check if B is valid at sorttime */
|
|
if (CERT_CheckCertValidTimes(certb, sorttime, PR_FALSE) !=
|
|
secCertTimeValid) {
|
|
bNotValid = PR_TRUE;
|
|
}
|
|
|
|
/* a is valid, b is not */
|
|
if (bNotValid && (!aNotValid)) {
|
|
return (PR_TRUE);
|
|
}
|
|
|
|
/* b is valid, a is not */
|
|
if (aNotValid && (!bNotValid)) {
|
|
return (PR_FALSE);
|
|
}
|
|
|
|
/* a and b are either valid or not valid */
|
|
if (newerbefore && newerafter) {
|
|
return (PR_TRUE);
|
|
}
|
|
|
|
if ((!newerbefore) && (!newerafter)) {
|
|
return (PR_FALSE);
|
|
}
|
|
|
|
if (newerbefore) {
|
|
/* cert A was issued after cert B, but expires sooner */
|
|
return (PR_TRUE);
|
|
} else {
|
|
/* cert B was issued after cert A, but expires sooner */
|
|
return (PR_FALSE);
|
|
}
|
|
}
|
|
|
|
SECStatus
|
|
CERT_AddCertToListSorted(CERTCertList *certs, CERTCertificate *cert,
|
|
CERTSortCallback f, void *arg)
|
|
{
|
|
CERTCertListNode *node;
|
|
CERTCertListNode *head;
|
|
PRBool ret;
|
|
|
|
node = (CERTCertListNode *)PORT_ArenaZAlloc(certs->arena,
|
|
sizeof(CERTCertListNode));
|
|
if (node == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
head = CERT_LIST_HEAD(certs);
|
|
|
|
while (!CERT_LIST_END(head, certs)) {
|
|
|
|
/* if cert is already in the list, then don't add it again */
|
|
if (cert == head->cert) {
|
|
/*XXX*/
|
|
/* don't keep a reference */
|
|
CERT_DestroyCertificate(cert);
|
|
goto done;
|
|
}
|
|
|
|
ret = (*f)(cert, head->cert, arg);
|
|
/* if sort function succeeds, then insert before current node */
|
|
if (ret) {
|
|
PR_INSERT_BEFORE(&node->links, &head->links);
|
|
goto done;
|
|
}
|
|
|
|
head = CERT_LIST_NEXT(head);
|
|
}
|
|
/* if we get to the end, then just insert it at the tail */
|
|
PR_INSERT_BEFORE(&node->links, &certs->list);
|
|
|
|
done:
|
|
/* certs->count++; */
|
|
node->cert = cert;
|
|
return (SECSuccess);
|
|
|
|
loser:
|
|
return (SECFailure);
|
|
}
|
|
|
|
/* This routine is here because pcertdb.c still has a call to it.
|
|
* The SMIME profile code in pcertdb.c should be split into high (find
|
|
* the email cert) and low (store the profile) code. At that point, we
|
|
* can move this to certhigh.c where it belongs.
|
|
*
|
|
* remove certs from a list that don't have keyUsage and certType
|
|
* that match the given usage.
|
|
*/
|
|
SECStatus
|
|
CERT_FilterCertListByUsage(CERTCertList *certList, SECCertUsage usage,
|
|
PRBool ca)
|
|
{
|
|
unsigned int requiredKeyUsage;
|
|
unsigned int requiredCertType;
|
|
CERTCertListNode *node, *savenode;
|
|
SECStatus rv;
|
|
|
|
if (certList == NULL)
|
|
goto loser;
|
|
|
|
rv = CERT_KeyUsageAndTypeForCertUsage(usage, ca, &requiredKeyUsage,
|
|
&requiredCertType);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
node = CERT_LIST_HEAD(certList);
|
|
|
|
while (!CERT_LIST_END(node, certList)) {
|
|
|
|
PRBool bad = (PRBool)(!node->cert);
|
|
|
|
/* bad key usage ? */
|
|
if (!bad &&
|
|
CERT_CheckKeyUsage(node->cert, requiredKeyUsage) != SECSuccess) {
|
|
bad = PR_TRUE;
|
|
}
|
|
/* bad cert type ? */
|
|
if (!bad) {
|
|
unsigned int certType = 0;
|
|
if (ca) {
|
|
/* This function returns a more comprehensive cert type that
|
|
* takes trust flags into consideration. Should probably
|
|
* fix the cert decoding code to do this.
|
|
*/
|
|
(void)CERT_IsCACert(node->cert, &certType);
|
|
} else {
|
|
certType = node->cert->nsCertType;
|
|
}
|
|
if (!(certType & requiredCertType)) {
|
|
bad = PR_TRUE;
|
|
}
|
|
}
|
|
|
|
if (bad) {
|
|
/* remove the node if it is bad */
|
|
savenode = CERT_LIST_NEXT(node);
|
|
CERT_RemoveCertListNode(node);
|
|
node = savenode;
|
|
} else {
|
|
node = CERT_LIST_NEXT(node);
|
|
}
|
|
}
|
|
return (SECSuccess);
|
|
|
|
loser:
|
|
return (SECFailure);
|
|
}
|
|
|
|
PRBool
|
|
CERT_IsUserCert(CERTCertificate *cert)
|
|
{
|
|
CERTCertTrust trust;
|
|
SECStatus rv = SECFailure;
|
|
|
|
rv = CERT_GetCertTrust(cert, &trust);
|
|
if (rv == SECSuccess &&
|
|
((trust.sslFlags & CERTDB_USER) || (trust.emailFlags & CERTDB_USER) ||
|
|
(trust.objectSigningFlags & CERTDB_USER))) {
|
|
return PR_TRUE;
|
|
} else {
|
|
return PR_FALSE;
|
|
}
|
|
}
|
|
|
|
SECStatus
|
|
CERT_FilterCertListForUserCerts(CERTCertList *certList)
|
|
{
|
|
CERTCertListNode *node, *freenode;
|
|
CERTCertificate *cert;
|
|
|
|
if (!certList) {
|
|
return SECFailure;
|
|
}
|
|
|
|
node = CERT_LIST_HEAD(certList);
|
|
|
|
while (!CERT_LIST_END(node, certList)) {
|
|
cert = node->cert;
|
|
if (PR_TRUE != CERT_IsUserCert(cert)) {
|
|
/* Not a User Cert, so remove this cert from the list */
|
|
freenode = node;
|
|
node = CERT_LIST_NEXT(node);
|
|
CERT_RemoveCertListNode(freenode);
|
|
} else {
|
|
/* Is a User cert, so leave it in the list */
|
|
node = CERT_LIST_NEXT(node);
|
|
}
|
|
}
|
|
|
|
return (SECSuccess);
|
|
}
|
|
|
|
static PZLock *certRefCountLock = NULL;
|
|
|
|
/*
|
|
* Acquire the cert reference count lock
|
|
* There is currently one global lock for all certs, but I'm putting a cert
|
|
* arg here so that it will be easy to make it per-cert in the future if
|
|
* that turns out to be necessary.
|
|
*/
|
|
void
|
|
CERT_LockCertRefCount(CERTCertificate *cert)
|
|
{
|
|
PORT_Assert(certRefCountLock != NULL);
|
|
PZ_Lock(certRefCountLock);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Free the cert reference count lock
|
|
*/
|
|
void
|
|
CERT_UnlockCertRefCount(CERTCertificate *cert)
|
|
{
|
|
PORT_Assert(certRefCountLock != NULL);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
PRStatus prstat = PZ_Unlock(certRefCountLock);
|
|
PORT_Assert(prstat == PR_SUCCESS);
|
|
}
|
|
#else
|
|
PZ_Unlock(certRefCountLock);
|
|
#endif
|
|
}
|
|
|
|
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 *certTempPermLock = NULL;
|
|
|
|
/*
|
|
* Acquire the cert temp/perm lock
|
|
*/
|
|
void
|
|
CERT_LockCertTempPerm(const CERTCertificate *cert)
|
|
{
|
|
PORT_Assert(certTempPermLock != NULL);
|
|
PZ_Lock(certTempPermLock);
|
|
}
|
|
|
|
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 (certTempPermLock == NULL) {
|
|
certTempPermLock = PZ_NewLock(nssILockCertDB);
|
|
PORT_Assert(certTempPermLock != NULL);
|
|
if (!certTempPermLock) {
|
|
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(certTempPermLock != NULL);
|
|
if (certTempPermLock) {
|
|
PZ_DestroyLock(certTempPermLock);
|
|
certTempPermLock = NULL;
|
|
} else {
|
|
rv = SECFailure;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* Free the cert trust lock
|
|
*/
|
|
void
|
|
CERT_UnlockCertTrust(const CERTCertificate *cert)
|
|
{
|
|
PORT_Assert(certTrustLock != NULL);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
PRStatus prstat = PZ_Unlock(certTrustLock);
|
|
PORT_Assert(prstat == PR_SUCCESS);
|
|
}
|
|
#else
|
|
PZ_Unlock(certTrustLock);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Free the temp/perm lock
|
|
*/
|
|
void
|
|
CERT_UnlockCertTempPerm(const CERTCertificate *cert)
|
|
{
|
|
PORT_Assert(certTempPermLock != NULL);
|
|
#ifdef DEBUG
|
|
{
|
|
PRStatus prstat = PZ_Unlock(certTempPermLock);
|
|
PORT_Assert(prstat == PR_SUCCESS);
|
|
}
|
|
#else
|
|
(void)PZ_Unlock(certTempPermLock);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|