netscape-revival
security/lib/cert/certdb.c
/*
* Certificate handling code
*
* Copyright © 1995 Netscape Communications Corporation, all rights reserved.
*
*/
#include "prtime.h"
#include "xp_reg.h"
#include "cert.h"
#include "secder.h"
#include "crypto.h"
#include "key.h"
#include "secitem.h"
#include "mcom_db.h"
#include "certdb.h"
#include "prprf.h"
#include "sechash.h"
#include "prlong.h"
#include "certxutl.h"
#if defined(SERVER_BUILD)
#define dbopen NS_dbopen
#endif
extern int SEC_ERROR_BAD_DATABASE;
extern int SEC_ERROR_BAD_DER;
extern int SEC_ERROR_BAD_SIGNATURE;
extern int SEC_ERROR_CA_CERT_INVALID;
extern int SEC_ERROR_CERT_NOT_VALID;
extern int SEC_ERROR_CERT_USAGES_INVALID;
extern int SEC_ERROR_CRL_BAD_SIGNATURE;
extern int SEC_ERROR_CRL_EXPIRED;
extern int SEC_ERROR_EXTENSION_NOT_FOUND;
extern int SEC_ERROR_EXPIRED_CERTIFICATE;
extern int SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE;
extern int SEC_ERROR_KRL_BAD_SIGNATURE;
extern int SEC_ERROR_KRL_EXPIRED;
extern int SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID;
extern int SEC_ERROR_REVOKED_CERTIFICATE;
extern int SEC_ERROR_REVOKED_KEY;
extern int SEC_ERROR_NO_KRL;
extern int SEC_ERROR_NO_MEMORY;
extern int SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION;
extern int SEC_ERROR_UNKNOWN_ISSUER;
extern int SEC_ERROR_UNTRUSTED_CERT;
extern int SEC_ERROR_UNTRUSTED_ISSUER;
extern int SSL_ERROR_BAD_CERT_DOMAIN;
extern int SSL_ERROR_NO_CERTIFICATE;
extern int SEC_ERROR_INADEQUATE_KEY_USAGE;
extern int SEC_ERROR_INADEQUATE_CERT_TYPE;
extern int SEC_ERROR_CA_CERT_INVALID;
/*
* Certificate database handling code
*/
DERTemplate CERTCertExtensionTemplate[] = {
{ DER_SEQUENCE,
0, NULL, sizeof(CERTCertExtension) },
{ DER_OBJECT_ID,
offsetof(CERTCertExtension,id) },
{ DER_OPTIONAL | DER_BOOLEAN, /* XXX DER_DEFAULT */
offsetof(CERTCertExtension,critical), },
{ DER_OCTET_STRING,
offsetof(CERTCertExtension,value) },
{ 0, }
};
DERTemplate CERTCertificateTemplate[] = {
{ DER_SEQUENCE,
0, NULL, sizeof(CERTCertificate) },
{ DER_EXPLICIT | DER_OPTIONAL | DER_CONSTRUCTED | DER_CONTEXT_SPECIFIC | 0,
/* XXX DER_DEFAULT */
offsetof(CERTCertificate,version),
NULL, DER_INTEGER },
{ DER_INTEGER,
offsetof(CERTCertificate,serialNumber), },
{ DER_INLINE,
offsetof(CERTCertificate,signature),
SECAlgorithmIDTemplate, },
{ DER_DERPTR | DER_OUTER,
offsetof(CERTCertificate,derIssuer), },
{ DER_INLINE,
offsetof(CERTCertificate,issuer),
CERTNameTemplate, },
{ DER_INLINE,
offsetof(CERTCertificate,validity),
CERTValidityTemplate, },
{ DER_DERPTR | DER_OUTER,
offsetof(CERTCertificate,derSubject), },
{ DER_INLINE,
offsetof(CERTCertificate,subject),
CERTNameTemplate, },
{ DER_DERPTR | DER_OUTER,
offsetof(CERTCertificate,derPublicKey), },
{ DER_INLINE,
offsetof(CERTCertificate,subjectPublicKeyInfo),
CERTSubjectPublicKeyInfoTemplate, },
{ DER_OPTIONAL | DER_CONSTRUCTED | DER_CONTEXT_SPECIFIC | 1,
offsetof(CERTCertificate,issuerID),
NULL, DER_OBJECT_ID },
{ DER_OPTIONAL | DER_CONSTRUCTED | DER_CONTEXT_SPECIFIC | 2,
offsetof(CERTCertificate,subjectID),
NULL, DER_OBJECT_ID },
{ DER_EXPLICIT | DER_OPTIONAL | DER_CONSTRUCTED | DER_CONTEXT_SPECIFIC | 3,
offsetof(CERTCertificate,extensions),
CERTCertExtensionTemplate, DER_INDEFINITE | DER_SEQUENCE },
{ 0, }
};
DERTemplate SECSignedCertificateTemplate[] =
{
{ DER_SEQUENCE,
0, NULL, sizeof(CERTCertificate) },
{ DER_DERPTR | DER_OUTER,
offsetof(CERTCertificate,signatureWrap.data), },
{ DER_INLINE,
0, CERTCertificateTemplate },
{ DER_INLINE,
offsetof(CERTCertificate,signatureWrap.signatureAlgorithm),
SECAlgorithmIDTemplate, },
{ DER_BIT_STRING,
offsetof(CERTCertificate,signatureWrap.signature), },
{ 0, }
};
/*
* Find the subjectName in a DER encoded certificate
*/
DERTemplate SECCertSubjectTemplate[] = {
{ DER_SEQUENCE,
0, NULL, sizeof(SECItem) },
{ DER_EXPLICIT | DER_OPTIONAL | DER_CONSTRUCTED | DER_CONTEXT_SPECIFIC | 0,
0, NULL, DER_SKIP }, /* version */
{ DER_SKIP, }, /* serial number */
{ DER_SKIP, }, /* signature algorithm */
{ DER_SKIP, }, /* issuer */
{ DER_SKIP, }, /* validity */
{ DER_DERPTR | DER_OUTER, 0, }, /* subject */
{ 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.
*/
DERTemplate CERTCertKeyTemplate[] = {
{ DER_SEQUENCE,
0, NULL, sizeof(CERTCertKey) },
{ DER_EXPLICIT | DER_OPTIONAL | DER_CONSTRUCTED | DER_CONTEXT_SPECIFIC | 0,
0, NULL, DER_SKIP }, /* version */
{ DER_INTEGER,
offsetof(CERTCertKey,serialNumber), },
{ DER_SKIP, }, /* signature algorithm */
{ DER_DERPTR | DER_OUTER,
offsetof(CERTCertKey,derIssuer) },
{ 0, }
};
/*
* Extract the subject name from a DER certificate
*/
SECStatus
CERT_NameFromDERCert(SECItem *derCert, SECItem *derName)
{
int rv;
PRArenaPool *arena;
CERTSignedData sd;
void *tmpptr;
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if ( ! arena ) {
return(SECFailure);
}
rv = DER_Decode(arena, &sd, CERTSignedDataTemplate, derCert);
if ( rv ) {
goto loser;
}
rv = DER_Decode(arena, derName, SECCertSubjectTemplate, &sd.data);
if ( rv ) {
goto loser;
}
tmpptr = derName->data;
derName->data = 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_KeyFromIssuerAndSN(PRArenaPool *arena, SECItem *issuer, SECItem *sn,
SECItem *key)
{
key->len = sn->len + issuer->len;
key->data = 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);
}
/*
* Generate a database key, based on serial number and issuer, from a
* DER certificate.
*/
SECStatus
CERT_KeyFromDERCert(PRArenaPool *arena, SECItem *derCert, SECItem *key)
{
int rv;
CERTSignedData sd;
CERTCertKey certkey;
rv = DER_Decode(arena, &sd, CERTSignedDataTemplate, derCert);
if ( rv ) {
goto loser;
}
rv = DER_Decode(arena, &certkey, CERTCertKeyTemplate, &sd.data);
if ( rv ) {
goto loser;
}
return(CERT_KeyFromIssuerAndSN(arena, &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
*/
SECStatus
GetKeyUsage(CERTCertificate *cert)
{
SECStatus rv;
SECItem tmpitem;
PRBool critical;
rv = CERT_FindKeyUsageExtension(cert, &tmpitem);
if ( rv == SECSuccess ) {
rv = CERT_GetExtenCriticality(cert->extensions, SEC_OID_X509_KEY_USAGE,
&critical);
if ( rv != SECSuccess ) {
cert->keyUsage = 0;
cert->rawKeyUsage = 0;
cert->keyUsagePresent = PR_FALSE;
return(SECFailure);
}
/* remember the actual value of the extension */
cert->rawKeyUsage = tmpitem.data[0];
cert->keyUsagePresent = PR_TRUE;
if ( critical ) {
cert->keyUsage = tmpitem.data[0];
} else {
/* if it is not critical, then we allow all uses. */
cert->keyUsage = KU_ALL;
}
} 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);
}
/*
* fill in nsCertType field of the cert based on the cert extension
*/
SECStatus
GetCertType(CERTCertificate *cert)
{
SECStatus rv;
SECItem tmpitem;
PRBool critical;
rv = CERT_FindNSCertTypeExtension(cert, &tmpitem);
if ( rv == SECSuccess ) {
cert->nsCertType = tmpitem.data[0];
/*
* for this release, we will allow SSL certs with an email address
* to be used for email
*/
if ( ( cert->nsCertType & NS_CERT_TYPE_SSL_CLIENT ) &&
cert->emailAddr ) {
cert->nsCertType |= NS_CERT_TYPE_EMAIL;
}
/*
* for this release, we will allow SSL intermediate CAs to be
* email intermediate CAs too.
*/
if ( cert->nsCertType & NS_CERT_TYPE_SSL_CA ) {
cert->nsCertType |= NS_CERT_TYPE_EMAIL_CA;
}
} else {
/* if no extension, then allow any application, but no CA */
cert->nsCertType = NS_CERT_TYPE_APP;
}
return(SECSuccess);
}
/*
* GetKeyID() - extract or generate the subjectKeyID from a certificate
*/
SECStatus
GetKeyID(CERTCertificate *cert)
{
SECItem tmpitem;
SECStatus rv;
cert->subjectKeyID.len = 0;
/* see of the cert has a key identifier extension */
rv = CERT_FindSubjectKeyIDExten(cert, &tmpitem);
if ( rv == SECSuccess ) {
cert->subjectKeyID.data = 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 ) {
unsigned char hashbuf[SHA1_LENGTH];
/*
* 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 = PORT_ArenaAlloc(cert->arena, SHA1_LENGTH);
if ( cert->subjectKeyID.data != NULL ) {
rv = SHA1_HashBuf(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);
}
/*
* take a DER certificate and decode it into a certificate structure
*/
CERTCertificate *
CERT_DecodeDERCertificate(SECItem *derSignedCert, PRBool copyDER,
char *nickname)
{
CERTCertificate *cert;
PRArenaPool *arena;
PRBool freenn = PR_FALSE;
void *data;
int rv;
int len;
char *tmpname;
/* make a new arena */
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if ( !arena ) {
PORT_SetError(SEC_ERROR_NO_MEMORY);
return 0;
}
/* allocate the certificate structure */
cert = (CERTCertificate *)PORT_ArenaZAlloc(arena, sizeof(CERTCertificate));
if ( !cert ) {
PORT_SetError(SEC_ERROR_NO_MEMORY);
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 ) {
PORT_SetError(SEC_ERROR_NO_MEMORY);
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 = DER_Decode(arena, cert, SECSignedCertificateTemplate,
&cert->derCert);
if ( rv ) {
goto loser;
}
if (cert_HasUnknownCriticalExten (cert->extensions) == PR_TRUE) {
PORT_SetError(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION);
goto loser;
}
/* generate and save the database key for the cert */
rv = CERT_KeyFromDERCert(arena, &cert->derCert, &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 = PORT_ArenaAlloc(arena, len);
if ( cert->nickname == NULL ) {
goto loser;
}
PORT_Memcpy(cert->nickname, nickname, len);
}
/* set the email address */
cert->emailAddr = CERT_GetCertEmailAddress(&cert->subject);
/* initialize the subjectKeyID */
rv = GetKeyID(cert);
if ( rv != SECSuccess ) {
goto loser;
}
/* initialize keyUsage */
rv = GetKeyUsage(cert);
if ( rv != SECSuccess ) {
goto loser;
}
/* initialize the certType */
rv = 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_KEY;
cert->dbnickname = NULL;
return(cert);
loser:
if ( arena ) {
PORT_FreeArena(arena, PR_FALSE);
}
return(0);
}
/*
** 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)
SECStatus
CERT_GetCertTimes(CERTCertificate *c, int64 *notBefore, int64 *notAfter)
{
int rv;
/* convert DER not-before time */
rv = DER_UTCTimeToTime(notBefore, &c->validity.notBefore);
if (rv) {
return(SECFailure);
}
/* convert DER not-after time */
rv = DER_UTCTimeToTime(notAfter, &c->validity.notAfter);
if (rv) {
return(SECFailure);
}
return(SECSuccess);
}
/*
* Check the validity times of a certificate
*/
SECCertTimeValidity
CERT_CheckCertValidTimes(CERTCertificate *c, int64 t)
{
int64 notBefore, notAfter, pendingSlop;
SECStatus rv;
#ifdef NOTDEF /* do we really want to do this here? */
/* if cert is already marked OK, then don't bother to check */
if ( c->timeOK ) {
return(SECSuccess);
}
#endif
rv = CERT_GetCertTimes(c, ¬Before, ¬After);
if (rv) {
return(secCertTimeExpired); /*XXX is this the right thing to do here?*/
}
LL_I2L(pendingSlop, PENDING_SLOP);
LL_SUB(notBefore, notBefore, pendingSlop);
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, int64 *notBefore, int64 *notAfter)
{
int rv;
/* convert DER not-before time */
rv = DER_UTCTimeToTime(notBefore, &date->lastUpdate);
if (rv) {
return(SECFailure);
}
/* convert DER not-after time */
if (date->nextUpdate.data) {
rv = DER_UTCTimeToTime(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, int64 t) {
int64 notBefore, notAfter, pendingSlop;
SECStatus rv;
rv = SEC_GetCrlTimes(crl, ¬Before, ¬After);
if (rv) {
return(secCertTimeExpired);
}
LL_I2L(pendingSlop, PENDING_SLOP);
LL_SUB(notBefore, notBefore, pendingSlop);
if ( LL_CMP( t, <, notBefore ) ) {
return(secCertTimeNotValidYet);
}
/* If next update is omitted and the test for notBefore passes, then
we assume that the crl is up to date.
*/
if ( LL_IS_ZERO(notAfter) ) {
return(secCertTimeValid);
}
if ( LL_CMP( t, >, notAfter) ) {
return(secCertTimeExpired);
}
return(secCertTimeValid);
}
PRBool
SEC_CrlIsNewer(CERTCrl *new, CERTCrl *old) {
int64 newNotBefore, newNotAfter;
int64 oldNotBefore, oldNotAfter;
SECStatus rv;
/* problems with the new CRL? reject it */
rv = SEC_GetCrlTimes(new, &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 (LL_CMP(oldNotBefore, <, newNotBefore));
}
/*
* WARNING - this function is depricated, and will either go away or have
* a new API in the near future.
*
* Check the validity times of a certificate
*/
SECStatus
CERT_CertTimesValid(CERTCertificate *c)
{
int64 now, notBefore, notAfter, pendingSlop;
SECStatus rv;
/* if cert is already marked OK, then don't bother to check */
if ( c->timeOK ) {
return(SECSuccess);
}
/* get current UTC time */
now = PR_Now();
rv = CERT_GetCertTimes(c, ¬Before, ¬After);
if (rv) {
return(SECFailure);
}
LL_I2L(pendingSlop, PENDING_SLOP);
LL_SUB(notBefore, notBefore, pendingSlop);
if (LL_CMP(now, <, notBefore) || LL_CMP(now, >, notAfter)) {
PORT_SetError(SEC_ERROR_EXPIRED_CERTIFICATE);
return(SECFailure);
}
return(SECSuccess);
}
/*
* verify the signature of a signed data object with the given certificate
*/
SECStatus
CERT_VerifySignedData(CERTSignedData *sd, CERTCertificate *cert,
int64 t, void *wincx)
{
SECItem sig;
SECKEYPublicKey *pubKey = 0;
SECStatus rv;
SECCertTimeValidity validity;
SECOidTag algid;
/* check the certificate's validity */
validity = CERT_CheckCertValidTimes(cert, t);
if ( validity != secCertTimeValid ) {
return(SECFailure);
}
/* get cert's public key */
pubKey = CERT_ExtractPublicKey(&cert->subjectPublicKeyInfo);
if ( !pubKey ) {
return(SECFailure);
}
/* check the signature */
sig = sd->signature;
DER_ConvertBitString(&sig);
algid = SECOID_GetAlgorithmTag(&sd->signatureAlgorithm);
/* ANSI X9.57 specifies DSA signatures as DER encoded data */
if (algid == SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST) {
rv = DSAU_VerifyDerSignature(sd->data.data, sd->data.len, pubKey,
&sig, wincx);
} else {
rv = VFY_VerifyData(sd->data.data, sd->data.len, pubKey, &sig, wincx);
}
SECKEY_DestroyPublicKey(pubKey);
if ( rv ) {
return(SECFailure);
}
return(SECSuccess);
}
#define SEC_MAX_CERT_CHAIN 20
/*
* This must only be called on a cert that is known to have an issuer
* with an invalid time
*/
CERTCertificate *
CERT_FindExpiredIssuer(CERTCertDBHandle *handle, CERTCertificate *cert)
{
SECStatus rv;
CERTCertificate *issuerCert = NULL;
CERTCertificate *subjectCert;
subjectCert = CERT_DupCertificate(cert);
if ( subjectCert == NULL ) {
goto loser;
}
while ( 1 ) {
/* find the certificate of the issuer */
issuerCert = CERT_FindCertByName(handle, &subjectCert->derIssuer);
if ( ! issuerCert ) {
goto loser;
}
rv = CERT_CertTimesValid(issuerCert);
if ( rv == SECFailure ) {
/* this is the invalid issuer */
CERT_DestroyCertificate(subjectCert);
return(issuerCert);
}
CERT_DestroyCertificate(subjectCert);
subjectCert = issuerCert;
}
loser:
if ( issuerCert ) {
CERT_DestroyCertificate(issuerCert);
}
if ( subjectCert ) {
CERT_DestroyCertificate(subjectCert);
}
return(NULL);
}
SECStatus
SEC_CheckKRL(CERTCertDBHandle *handle,SECKEYPublicKey *key,
CERTCertificate *rootCert, int64 t, void * wincx)
{
CERTSignedCrl *crl = NULL;
SECStatus rv = SECFailure;
SECStatus rv2;
CERTCrlEntry **crlEntry;
SECCertTimeValidity validity;
CERTCertificate *issuerCert = NULL;
/* first look up the KRL */
crl = SEC_FindCrlByName(handle,&rootCert->derSubject, SEC_KRL_TYPE);
if (crl == NULL) {
PORT_SetError(SEC_ERROR_NO_KRL);
goto done;
}
/* get the issuing certificate */
issuerCert = CERT_FindCertByName(handle, &crl->crl.derName);
if (issuerCert == NULL) {
PORT_SetError(SEC_ERROR_KRL_BAD_SIGNATURE);
goto done;
}
/* now verify the KRL signature */
rv2 = CERT_VerifySignedData(&crl->signatureWrap, issuerCert, t, wincx);
if (rv2 != SECSuccess) {
PORT_SetError(SEC_ERROR_KRL_BAD_SIGNATURE);
goto done;
}
/* Verify the date validity of the KRL */
validity = SEC_CheckCrlTimes(&crl->crl, t);
if (validity == secCertTimeExpired) {
PORT_SetError(SEC_ERROR_KRL_EXPIRED);
goto done;
}
/* now make sure the key in this cert is still valid */
if (key->keyType != fortezzaKey) {
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
goto done; /* This should be an assert? */
}
/* now make sure the key is not on the revocation list */
for (crlEntry = crl->crl.entries; crlEntry && *crlEntry; crlEntry++) {
if (PORT_Memcmp((*crlEntry)->serialNumber.data,
key->u.fortezza.KMID,
(*crlEntry)->serialNumber.len)) {
PORT_SetError(SEC_ERROR_REVOKED_KEY);
goto done;
}
}
rv = SECSuccess;
done:
if (issuerCert) CERT_DestroyCertificate(issuerCert);
if (crl) SEC_DestroyCrl(crl);
return rv;
}
SECStatus
SEC_CheckCRL(CERTCertDBHandle *handle,CERTCertificate *cert,
CERTCertificate *caCert, int64 t, void * wincx)
{
CERTSignedCrl *crl = NULL;
SECStatus rv = SECSuccess;
CERTCrlEntry **crlEntry;
SECCertTimeValidity validity;
/* first look up the CRL */
crl = SEC_FindCrlByName(handle,&caCert->derSubject, SEC_CRL_TYPE);
if (crl == NULL) {
/* XXX for now no CRL is ok */
goto done;
}
/* now verify the CRL signature */
rv = CERT_VerifySignedData(&crl->signatureWrap, caCert, t, wincx);
if (rv != SECSuccess) {
PORT_SetError(SEC_ERROR_CRL_BAD_SIGNATURE);
rv = SECWouldBlock; /* Soft error, ask the user */
goto done;
}
/* Verify the date validity of the KRL */
validity = SEC_CheckCrlTimes(&crl->crl,t);
if (validity == secCertTimeExpired) {
PORT_SetError(SEC_ERROR_CRL_EXPIRED);
rv = SECWouldBlock; /* Soft error, ask the user */
}
/* now make sure the key is not on the revocation list */
for (crlEntry = crl->crl.entries; crlEntry && *crlEntry; crlEntry++) {
if (SECITEM_CompareItem(&(*crlEntry)->serialNumber,&cert->serialNumber) == SECEqual) {
PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE);
rv = SECFailure; /* cert is revoked */
goto done;
}
}
done:
if (crl) SEC_DestroyCrl(crl);
return rv;
}
/*
* Find the issuer of a cert. Use the authorityKeyID if it exists.
*/
CERTCertificate *
CERT_FindCertIssuer(CERTCertificate *cert)
{
CERTAuthKeyID *authorityKeyID = NULL;
CERTCertificate *issuerCert = NULL;
SECItem *caName;
void *subjectMark = NULL;
SECItem issuerCertKey;
SECStatus rv;
subjectMark = PORT_ArenaMark(cert->arena);
authorityKeyID = CERT_FindAuthKeyIDExten(cert);
if ( authorityKeyID != NULL ) {
/* has the authority key ID extension */
if ( authorityKeyID->keyID.data != NULL ) {
/* extension contains a key ID, so lookup based on it */
issuerCert = CERT_FindCertByKeyID(cert->dbhandle, &cert->derIssuer,
&authorityKeyID->keyID);
if ( issuerCert == NULL ) {
PORT_SetError (SEC_ERROR_UNKNOWN_ISSUER);
goto loser;
}
} else if ( authorityKeyID->authCertIssuer != NULL ) {
/* no key ID, so try issuer and serial number */
caName = CERT_GetGeneralNameByType(authorityKeyID->authCertIssuer,
certDirectoryName, PR_TRUE);
/*
* caName is NULL when the authCertIssuer field is not
* being used, or other name form is used instead.
* If the directoryName format and serialNumber fields are
* used, we use them to find the CA cert.
* Note:
* By the time it gets here, we known for sure that if the
* authCertIssuer exists, then the authCertSerialNumber
* must also exists (CERT_DecodeAuthKeyID() ensures this).
* We don't need to check again.
*/
if (caName != NULL) {
rv = CERT_KeyFromIssuerAndSN(cert->arena, caName,
&authorityKeyID->authCertSerialNumber,
&issuerCertKey);
if ( rv == SECSuccess ) {
issuerCert = CERT_FindCertByKey(cert->dbhandle,
&issuerCertKey);
}
if ( issuerCert == NULL ) {
PORT_SetError (SEC_ERROR_UNKNOWN_ISSUER);
goto loser;
}
}
}
}
if ( issuerCert == NULL ) {
/* if there is not authorityKeyID, then just user the issuer */
issuerCert = CERT_FindCertByName(cert->dbhandle, &cert->derIssuer);
}
loser:
if (subjectMark != NULL) {
PORT_ArenaRelease(cert->arena, subjectMark);
subjectMark = NULL;
}
return(issuerCert);
}
static void
AddToVerifyLog(CERTVerifyLog *log, CERTCertificate *cert, unsigned long error,
unsigned int depth, void *arg)
{
CERTVerifyLogNode *node, *tnode;
PORT_Assert(log != NULL);
node = (CERTVerifyLogNode *)PORT_ArenaAlloc(log->arena,
sizeof(CERTVerifyLogNode));
if ( node != NULL ) {
node->cert = CERT_DupCertificate(cert);
node->error = error;
node->depth = depth;
node->arg = arg;
if ( log->tail == NULL ) {
/* empty list */
log->head = log->tail = node;
node->prev = NULL;
node->next = NULL;
} else if ( depth >= log->tail->depth ) {
/* add to tail */
node->prev = log->tail;
log->tail->next = node;
log->tail = node;
node->next = NULL;
} else if ( depth < log->head->depth ) {
/* add at head */
node->prev = NULL;
node->next = log->head;
log->head->prev = node;
log->head = node;
} else {
/* add in middle */
tnode = log->tail;
while ( tnode != NULL ) {
if ( depth >= tnode->depth ) {
/* insert after tnode */
node->prev = tnode;
node->next = tnode->next;
tnode->next->prev = node;
tnode->next = node;
break;
}
tnode = tnode->prev;
}
}
log->count++;
}
return;
}
#define EXIT_IF_NOT_LOGGING(log) \
if ( log == NULL ) { \
goto loser; \
}
#define LOG_ERROR_OR_EXIT(log,cert,depth,arg) \
if ( log != NULL ) { \
AddToVerifyLog(log, cert, PORT_GetError(), depth, (void *)arg); \
} else { \
goto loser; \
}
#define LOG_ERROR(log,cert,depth,arg) \
if ( log != NULL ) { \
AddToVerifyLog(log, cert, PORT_GetError(), depth, (void *)arg); \
}
SECStatus
CERT_VerifyCertChain(CERTCertDBHandle *handle, CERTCertificate *cert,
PRBool checkSig, SECCertUsage certUsage, int64 t,
void *wincx, CERTVerifyLog *log)
{
SECTrustType trustType;
CERTBasicConstraints basicConstraint;
CERTCertificate *issuerCert = NULL;
CERTCertificate *subjectCert;
PRBool isca;
PRBool isFortezza = PR_FALSE;
SECStatus rv;
SECComparison rvCompare;
SECStatus rvFinal = SECSuccess;
int count;
int currentPathLen = -1;
int flags;
unsigned int caCertType;
unsigned int requiredCAKeyUsage;
unsigned int requiredFlags;
enum { cbd_None, cbd_User, cbd_CA } last_type = cbd_None;
SECKEYPublicKey *key;
if ( certUsage == certUsageSSLServerWithStepUp ) {
requiredCAKeyUsage = ( KU_KEY_CERT_SIGN | KU_NS_GOVT_APPROVED );
} else {
requiredCAKeyUsage = KU_KEY_CERT_SIGN;
}
switch ( certUsage ) {
case certUsageSSLClient:
requiredFlags = CERTDB_TRUSTED_CLIENT_CA;
trustType = trustSSL;
caCertType = NS_CERT_TYPE_SSL_CA;
break;
case certUsageSSLServer:
case certUsageSSLCA:
requiredFlags = CERTDB_TRUSTED_CA;
trustType = trustSSL;
caCertType = NS_CERT_TYPE_SSL_CA;
break;
case certUsageSSLServerWithStepUp:
requiredFlags = CERTDB_TRUSTED_CA | CERTDB_GOVT_APPROVED_CA;
trustType = trustSSL;
caCertType = NS_CERT_TYPE_SSL_CA;
break;
case certUsageEmailSigner:
case certUsageEmailRecipient:
requiredFlags = CERTDB_TRUSTED_CA;
trustType = trustEmail;
caCertType = NS_CERT_TYPE_EMAIL_CA;
break;
case certUsageObjectSigner:
requiredFlags = CERTDB_TRUSTED_CA;
trustType = trustObjectSigning;
caCertType = NS_CERT_TYPE_OBJECT_SIGNING_CA;
break;
case certUsageVerifyCA:
requiredFlags = CERTDB_TRUSTED_CA;
trustType = 0;
caCertType = NS_CERT_TYPE_CA;
break;
default:
PORT_Assert(0);
EXIT_IF_NOT_LOGGING(log);
requiredFlags = 0;
trustType = 0;
caCertType = 0;
}
subjectCert = CERT_DupCertificate(cert);
if ( subjectCert == NULL ) {
goto loser;
}
/* determine if the cert is fortezza. Getting the key is an easy
* way to determine it, especially since we need to get the privillege
* from the key anyway.
*/
key = CERT_ExtractPublicKey(&cert->subjectPublicKeyInfo);
if (key != NULL) {
isFortezza = (key->keyType == fortezzaKey);
/* find out what type of cert we are starting with */
if (isFortezza) {
unsigned char priv = 0;;
rv = SEC_CheckKRL(handle, key, NULL, t, wincx);
if (rv == SECFailure) {
/* PORT_SetError is already set by SEC_Check_KRL */
SECKEY_DestroyPublicKey(key);
/* Bob - should we log and continue when logging? */
goto loser;
}
if (key->u.fortezza.DSSpriviledge.len > 0) {
priv = key->u.fortezza.DSSpriviledge.data[0];
}
last_type = (priv & 0x60) ? cbd_CA : cbd_User;
}
SECKEY_DestroyPublicKey(key);
}
for ( count = 0; count < SEC_MAX_CERT_CHAIN; count++ ) {
/* find the certificate of the issuer */
issuerCert = CERT_FindCertIssuer(subjectCert);
if ( ! issuerCert ) {
PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER);
LOG_ERROR(log,subjectCert,count,0);
goto loser;
}
/* verify the signature on the cert */
if ( checkSig ) {
rv = CERT_VerifySignedData(&subjectCert->signatureWrap,
issuerCert, t, wincx);
if ( rv != SECSuccess ) {
if ( PORT_GetError() == SEC_ERROR_EXPIRED_CERTIFICATE ) {
PORT_SetError(SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE);
LOG_ERROR_OR_EXIT(log,issuerCert,count+1,0);
} else {
PORT_SetError(SEC_ERROR_BAD_SIGNATURE);
LOG_ERROR_OR_EXIT(log,subjectCert,count,0);
}
}
}
/*
* XXX - fortezza may need error logging stuff added
*/
if (isFortezza) {
unsigned char priv = 0;
/* read the key */
key = CERT_ExtractPublicKey(&issuerCert->subjectPublicKeyInfo);
/* Cant' get Key? fail. */
if (key == NULL) {
PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER);
goto loser;
}
/* if the issuer is not a fortezza cert, we bail */
if (key->keyType != fortezzaKey) {
SECKEY_DestroyPublicKey(key);
PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER);
goto loser;
}
/* get the privilege mask */
if (key->u.fortezza.DSSpriviledge.len > 0) {
priv = key->u.fortezza.DSSpriviledge.data[0];
}
/*
* make sure the CA's keys are OK
*/
rv = SEC_CheckKRL(handle, key, NULL, t, wincx);
if (rv != SECSuccess) {
SECKEY_DestroyPublicKey(key);
goto loser;
}
SECKEY_DestroyPublicKey(key);
switch (last_type) {
case cbd_User:
/* first check for subordination */
/*rv = FortezzaSubordinateCheck(cert,issuerCert);*/
rv = SECSuccess;
/* now check for issuer privilege */
if ((rv != SECSuccess) || ((priv & 0x40) == 0)) {
/* bail */
PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER);
goto loser;
}
break;
case cbd_CA:
if ((priv & 0x20) == 0) {
/* bail */
PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER);
goto loser;
}
break;
default:
/* bail */ /* shouldn't ever happen */
PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER);
goto loser;
}
last_type = cbd_CA;
}
/* XXX - the error logging may need to go down into CRL stuff at some
* point
*/
/* check revoked list (issuer) */
rv = SEC_CheckCRL(handle, subjectCert, issuerCert, t, wincx);
if (rv == SECFailure) {
LOG_ERROR_OR_EXIT(log,subjectCert,count,0);
} else if (rv == SECWouldBlock) {
/* We found something fishy, so we intend to issue an
* error to the user, but the user may wish to continue
* processing, in which case we better make sure nothing
* worse has happened... so keep cranking the loop */
rvFinal = SECFailure;
LOG_ERROR(log,subjectCert,count,0);
}
if ( issuerCert->isperm ) {
/*
* check the trust parms of the issuer
*/
if ( certUsage == certUsageVerifyCA ) {
if ( subjectCert->nsCertType & NS_CERT_TYPE_EMAIL_CA ) {
trustType = trustEmail;
} else if ( subjectCert->nsCertType & NS_CERT_TYPE_SSL_CA ) {
trustType = trustSSL;
} else {
trustType = trustObjectSigning;
}
}
flags = SEC_GET_TRUST_FLAGS(issuerCert->trust, trustType);
if ( flags & CERTDB_VALID_CA ) {
if ( ( flags & requiredFlags ) == requiredFlags ) {
/* we found a trusted one, so return */
rv = rvFinal;
goto done;
}
}
}
/* If the basicConstraint extension is included in an immediate CA
* certificate, make sure that the isCA flag is on. If the
* pathLenConstraint component exists, it must be greater than the
* number of CA certificates we have seen so far. If the extension
* is omitted, we will assume that this is a CA certificate with
* an unlimited pathLenConstraint (since it already passes the
* netscape-cert-type extension checking).
*/
rv = CERT_FindBasicConstraintExten(issuerCert, &basicConstraint);
if ( rv != SECSuccess ) {
if (PORT_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND) {
LOG_ERROR_OR_EXIT(log,issuerCert,count+1,0);
} else {
currentPathLen = CERT_UNLIMITED_PATH_CONSTRAINT;
}
isca = PR_FALSE;
} else {
if ( basicConstraint.isCA == PR_FALSE ) {
PORT_SetError (SEC_ERROR_CA_CERT_INVALID);
LOG_ERROR_OR_EXIT(log,issuerCert,count+1,0);
}
/* make sure that the path len constraint is properly set.
*/
if ( basicConstraint.pathLenConstraint ==
CERT_UNLIMITED_PATH_CONSTRAINT ) {
currentPathLen = CERT_UNLIMITED_PATH_CONSTRAINT;
} else if ( currentPathLen == CERT_UNLIMITED_PATH_CONSTRAINT ) {
/* error if the previous CA's path length constraint is
* unlimited but its CA's path is not.
*/
PORT_SetError (SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID);
LOG_ERROR_OR_EXIT(log,issuerCert,count+1,basicConstraint.pathLenConstraint);
} else if (basicConstraint.pathLenConstraint > currentPathLen) {
currentPathLen = basicConstraint.pathLenConstraint;
} else {
PORT_SetError (SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID);
LOG_ERROR_OR_EXIT(log,issuerCert,count+1,basicConstraint.pathLenConstraint);
}
isca = PR_TRUE;
}
/*
* Make sure that if this is an intermediate CA in the chain that
* it was given permission by its signer to be a CA.
*/
if ( isca ) {
/*
* if basicConstraints says it is a ca, then we check the
* nsCertType. If the nsCertType has any CA bits set, then
* it must have the right one.
*/
if ( issuerCert->nsCertType & NS_CERT_TYPE_CA ) {
if ( issuerCert->nsCertType & caCertType ) {
isca = PR_TRUE;
} else {
isca = PR_FALSE;
}
}
} else {
if ( issuerCert->nsCertType & caCertType ) {
isca = PR_TRUE;
} else {
isca = PR_FALSE;
}
}
if ( ( !isFortezza ) && ( !isca ) ) {
PORT_SetError(SEC_ERROR_CA_CERT_INVALID);
LOG_ERROR_OR_EXIT(log,issuerCert,count+1,0);
}
/* make sure key usage allows cert signing */
if ( ( issuerCert->keyUsage & requiredCAKeyUsage ) !=
requiredCAKeyUsage) {
PORT_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE);
LOG_ERROR_OR_EXIT(log,issuerCert,count+1,requiredCAKeyUsage);
}
/* make sure that the issuer is not self signed. If it is, then
* stop here to prevent looping.
*/
rvCompare = SECITEM_CompareItem(&issuerCert->derSubject,
&issuerCert->derIssuer);
if (rvCompare == SECEqual) {
PORT_SetError(SEC_ERROR_UNTRUSTED_ISSUER);
LOG_ERROR(log,issuerCert,count+1,0);
goto loser;
}
CERT_DestroyCertificate(subjectCert);
subjectCert = issuerCert;
}
subjectCert = NULL;
PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER);
LOG_ERROR(log,issuerCert,count,0);
loser:
rv = SECFailure;
done:
if ( issuerCert ) {
CERT_DestroyCertificate(issuerCert);
}
if ( subjectCert ) {
CERT_DestroyCertificate(subjectCert);
}
return rv;
}
/*
* verify a certificate by checking if its valid and that we
* trust the issuer.
* Note that this routine does not verify the signature of the certificate.
*/
SECStatus
CERT_VerifyCert(CERTCertDBHandle *handle, CERTCertificate *cert,
PRBool checkSig, SECCertUsage certUsage, int64 t,
void *wincx, CERTVerifyLog *log)
{
SECStatus rv;
unsigned int requiredKeyUsage;
unsigned int requiredCertType;
unsigned int flags;
SECCertTimeValidity validity;
unsigned int certType;
/* make sure that the cert is valid at time t */
validity = CERT_CheckCertValidTimes (cert, t);
if ( validity != secCertTimeValid ) {
LOG_ERROR_OR_EXIT(log,cert,0,validity);
}
/* check key usage and netscape cert type */
certType = cert->nsCertType;
switch ( certUsage ) {
case certUsageSSLClient:
requiredKeyUsage = KU_DIGITAL_SIGNATURE;
requiredCertType = NS_CERT_TYPE_SSL_CLIENT;
break;
case certUsageSSLServer:
requiredKeyUsage = KU_KEY_ENCIPHERMENT;
requiredCertType = NS_CERT_TYPE_SSL_SERVER;
break;
case certUsageSSLServerWithStepUp:
requiredKeyUsage = KU_KEY_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;
requiredCertType = NS_CERT_TYPE_EMAIL;
break;
case certUsageEmailRecipient:
requiredKeyUsage = KU_KEY_ENCIPHERMENT;
requiredCertType = NS_CERT_TYPE_EMAIL;
break;
case certUsageObjectSigner:
requiredKeyUsage = KU_DIGITAL_SIGNATURE;
requiredCertType = NS_CERT_TYPE_OBJECT_SIGNING;
break;
case certUsageVerifyCA:
requiredKeyUsage = KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_CA;
if ( ! ( certType & NS_CERT_TYPE_CA ) ) {
certType |= NS_CERT_TYPE_CA;
}
break;
default:
PORT_Assert(0);
EXIT_IF_NOT_LOGGING(log);
requiredKeyUsage = 0;
requiredCertType = 0;
}
if ( ( cert->keyUsage & requiredKeyUsage ) != requiredKeyUsage ) {
PORT_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE);
LOG_ERROR_OR_EXIT(log,cert,0,requiredKeyUsage);
}
if ( ! ( certType & requiredCertType ) ) {
PORT_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE);
LOG_ERROR_OR_EXIT(log,cert,0,requiredCertType);
}
/* check trust flags to see if this cert is directly trusted */
if ( cert->trust ) { /* the cert is in the DB */
switch ( certUsage ) {
case certUsageSSLClient:
case certUsageSSLServer:
flags = cert->trust->sslFlags;
/* is the cert directly trusted or not trusted ? */
if ( flags & CERTDB_VALID_PEER ) {/*the trust record is valid*/
if ( flags & CERTDB_TRUSTED ) { /* trust this cert */
return(SECSuccess);
} else { /* don't trust this cert */
PORT_SetError(SEC_ERROR_UNTRUSTED_CERT);
LOG_ERROR_OR_EXIT(log,cert,0,flags);
}
}
break;
case certUsageSSLServerWithStepUp:
/* XXX - step up certs can't be directly trusted */
break;
case certUsageSSLCA:
break;
case certUsageEmailSigner:
case certUsageEmailRecipient:
flags = cert->trust->emailFlags;
/* is the cert directly trusted or not trusted ? */
if ( ( flags & ( CERTDB_VALID_PEER | CERTDB_TRUSTED ) ) ==
( CERTDB_VALID_PEER | CERTDB_TRUSTED ) ) {
return(SECSuccess);
}
break;
case certUsageObjectSigner:
flags = cert->trust->objectSigningFlags;
/* is the cert directly trusted or not trusted ? */
if ( ( flags & ( CERTDB_VALID_PEER | CERTDB_TRUSTED ) ) ==
( CERTDB_VALID_PEER | CERTDB_TRUSTED ) ) {
return(SECSuccess);
}
break;
case certUsageVerifyCA:
flags = cert->trust->sslFlags;
/* is the cert directly trusted or not trusted ? */
if ( ( flags & ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) ==
( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) {
return(SECSuccess);
}
flags = cert->trust->emailFlags;
/* is the cert directly trusted or not trusted ? */
if ( ( flags & ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) ==
( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) {
return(SECSuccess);
}
flags = cert->trust->objectSigningFlags;
/* is the cert directly trusted or not trusted ? */
if ( ( flags & ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) ==
( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) {
return(SECSuccess);
}
break;
}
}
rv = CERT_VerifyCertChain(handle, cert, checkSig, certUsage,
t, wincx, log);
return (rv);
loser:
rv = SECFailure;
return(rv);
}
/*
* verify a certificate by checking if its valid and that we
* trust the issuer. Verify time against now.
*/
SECStatus
CERT_VerifyCertNow(CERTCertDBHandle *handle, CERTCertificate *cert,
PRBool checkSig, SECCertUsage certUsage, void *wincx)
{
return(CERT_VerifyCert(handle, cert, checkSig,
certUsage, PR_Now(), wincx, NULL));
}
CERTCertificate *
CERT_DupCertificate(CERTCertificate *c)
{
if (c) {
++c->referenceCount;
}
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);
}
/*
* Open volatile certificate database and index databases. This is a
* fallback if the real databases can't be opened or created. It is only
* resident in memory, so it will not be persistent. We do this so that
* we don't crash if the databases can't be created.
*/
SECStatus
CERT_OpenVolatileCertDB(CERTCertDBHandle *handle)
{
/*
* Open the memory resident perm cert database.
*/
handle->permCertDB = dbopen( 0, O_RDWR | O_CREAT, 0600, DB_HASH, 0 );
if ( !handle->permCertDB ) {
goto loser;
}
/*
* Open the memory resident decoded cert database.
*/
handle->tempCertDB = dbopen( 0, O_RDWR | O_CREAT, 0600, DB_HASH, 0 );
if ( !handle->tempCertDB ) {
goto loser;
}
/* initialize the cert database */
if ( CERT_InitCertDB(handle) ) {
goto loser;
}
return (SECSuccess);
loser:
PORT_SetError(SEC_ERROR_BAD_DATABASE);
if ( handle->permCertDB ) {
(* handle->permCertDB->close)(handle->permCertDB);
handle->permCertDB = 0;
}
if ( handle->tempCertDB ) {
(* handle->tempCertDB->close)(handle->tempCertDB);
handle->tempCertDB = 0;
}
return(SECFailure);
}
/* 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;
}
/* 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(CERTCertificate *cert, char *hn)
{
char *cn;
int match;
char *domain;
char *hndomain;
int regvalid;
char *hostname;
SECStatus rv;
if ( cert->domainOK ) { /* user already said OK */
return(SECSuccess);
}
hostname = PORT_Strdup(hn);
if ( hostname == NULL ) {
return(SECFailure);
}
sec_lower_string(hostname);
/* try the cert extension first, then the common name */
cn = CERT_FindNSStringExtension(cert, SEC_OID_NS_CERT_EXT_SSL_SERVER_NAME);
if ( cn == NULL ) {
cn = CERT_GetCommonName(&cert->subject);
}
sec_lower_string(cn);
if ( cn ) {
if ( ( hndomain = PORT_Strchr(hostname, '.') ) == NULL ) {
/* No domain in server name */
if ( ( domain = PORT_Strchr(cn, '.') ) != NULL ) {
/* there is a domain in the cn string, so chop it off */
*domain = '\0';
}
}
regvalid = XP_RegExpValid(cn);
if ( regvalid == NON_SXP ) {
/* compare entire hostname with cert name */
if ( PORT_Strcmp(hostname, cn) == 0 ) {
rv = SECSuccess;
goto done;
}
if ( hndomain ) {
/* compare just domain name with cert name */
if ( PORT_Strcmp(hndomain+1, cn) == 0 ) {
rv = SECSuccess;
goto done;
}
}
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
rv = SECFailure;
goto done;
} else {
/* try to match the shexp */
match = XP_RegExpCaseSearch(hostname, cn);
if ( match == 0 ) {
rv = SECSuccess;
} else {
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
rv = SECFailure;
}
goto done;
}
}
PORT_SetError(SEC_ERROR_NO_MEMORY);
rv = SECFailure;
done:
/* free the common name */
if ( cn ) {
PORT_Free(cn);
}
if ( hostname ) {
PORT_Free(hostname);
}
return(rv);
}
PRBool
CERT_CompareCerts(CERTCertificate *c1, 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 are taking this out because the new certs for merchant.netscape.com
* and transact.netscape.com have different orgUnit values. 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
**
** Construct a CERTCertificateList consisting of the given certificate and all
** of the issuer certs until we either get to a self-signed cert or can't find
** an issuer. Since we don't know how many certs are in the chain we have to
** build a linked list first as we count them.
*/
typedef struct certNode {
struct certNode *next;
CERTCertificate *cert;
} certNode;
CERTCertificateList *
CERT_CertChainFromCert(CERTCertDBHandle *handle, CERTCertificate *cert)
{
CERTCertificateList *chain = NULL;
CERTCertificate *c;
SECItem *p;
int rv, len = 0;
PRArenaPool *tmpArena, *arena;
certNode *head, *tail, *node;
/*
* Initialize stuff so we can goto loser.
*/
head = NULL;
arena = NULL;
/* arena for linked list */
tmpArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if (tmpArena == NULL) goto no_memory;
/* arena for SecCertificateList */
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if (arena == NULL) goto no_memory;
head = tail = PORT_ArenaZAlloc(tmpArena, sizeof(certNode));
if (head == NULL) goto no_memory;
/* put primary cert first in the linked list */
head->cert = c = CERT_DupCertificate(cert);
if (head->cert == NULL) goto loser;
len++;
/* add certs until we come to a self-signed one */
while(SECITEM_CompareItem(&c->derIssuer, &c->derSubject) != SECEqual) {
c = CERT_FindCertByName(handle, &tail->cert->derIssuer);
if (c == NULL) break;
tail->next = PORT_ArenaZAlloc(tmpArena, sizeof(certNode));
tail = tail->next;
if (tail == NULL) goto no_memory;
tail->cert = c;
len++;
}
/* now build the CERTCertificateList */
chain = PORT_ArenaAlloc(arena, sizeof(CERTCertificateList));
if (chain == NULL) goto no_memory;
chain->certs = PORT_ArenaAlloc(arena, len * sizeof(SECItem));
if (chain->certs == NULL) goto no_memory;
for(node = head, p = chain->certs; node; node = node->next, p++) {
rv = SECITEM_CopyItem(arena, p, &node->cert->derCert);
CERT_DestroyCertificate(node->cert);
node->cert = NULL;
if (rv < 0) goto loser;
}
chain->len = len;
chain->arena = arena;
PORT_FreeArena(tmpArena, PR_FALSE);
return chain;
no_memory:
PORT_SetError(SEC_ERROR_NO_MEMORY);
loser:
if (head != NULL) {
for (node = head; node; node = node->next) {
if (node->cert != NULL)
CERT_DestroyCertificate(node->cert);
}
}
if (arena != NULL) {
PORT_FreeArena(arena, PR_FALSE);
}
if (tmpArena != NULL) {
PORT_FreeArena(tmpArena, PR_FALSE);
}
return NULL;
}
void
CERT_DestroyCertificateList(CERTCertificateList *list)
{
PORT_FreeArena(list->arena, PR_FALSE);
}
CERTIssuerAndSN *
CERT_GetCertIssuerAndSN(PRArenaPool *arena, CERTCertificate *cert)
{
CERTIssuerAndSN *result;
SECStatus rv;
if ( arena == NULL ) {
arena = cert->arena;
}
result = 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;
CERTCertDBHandle *handle;
handle = cert->dbhandle;
firstname = CERT_GetCommonName(&cert->subject);
if ( firstname == NULL ) {
firstname = CERT_GetOrgUnitName(&cert->subject);
}
org = CERT_GetOrgName(&cert->issuer);
if ( org == NULL ) {
goto loser;
}
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 loser;
}
/* look up the nickname to make sure it isn't in use already */
dummycert = CERT_FindCertByNickname(handle, nickname);
if ( dummycert == NULL ) {
goto done;
}
/* found a cert, destroy it and loop */
CERT_DestroyCertificate(dummycert);
/* free the nickname */
PORT_Free(nickname);
count++;
}
loser:
if ( nickname ) {
PORT_Free(nickname);
}
nickname = "";
done:
if ( firstname ) {
PORT_Free(firstname);
}
if ( org ) {
PORT_Free(org);
}
return(nickname);
}
SECStatus
CERT_ImportCAChain(SECItem *certs, int numcerts, SECCertUsage certUsage)
{
SECStatus rv;
SECItem *derCert;
SECItem certKey;
PRArenaPool *arena;
CERTCertificate *cert = NULL;
CERTCertificate *newcert = NULL;
CERTCertDBHandle *handle;
int64 newtime, oldtime;
CERTCertTrust trust;
PRBool notca;
char *nickname;
handle = CERT_GetDefaultCertDB();
arena = NULL;
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if ( ! arena ) {
goto loser;
}
while (numcerts--) {
derCert = certs;
certs++;
/* get the key (issuer+cn) from the cert */
rv = CERT_KeyFromDERCert(arena, derCert, &certKey);
if ( rv != SECSuccess ) {
goto loser;
}
/* same cert already exists in the database, don't need to do
* anything more with it
*/
cert = CERT_FindCertByKey(handle, &certKey);
if ( cert ) {
CERT_DestroyCertificate(cert);
cert = NULL;
continue;
}
/* decode my certificate */
newcert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL);
if ( newcert == NULL ) {
goto loser;
}
/* make sure that cert is valid */
rv = CERT_CertTimesValid(newcert);
if ( rv == SECFailure ) {
goto endloop;
}
/* does it have the CA extension */
/*
* Make sure that if this is an intermediate CA in the chain that
* it was given permission by its signer to be a CA.
*/
if ( certUsage == certUsageSSLCA ) {
/* cert is being used for SSL */
if ( newcert->nsCertType & NS_CERT_TYPE_SSL_CA ) {
notca = PR_FALSE;
} else {
notca = PR_TRUE;
}
} else {
/* only deal with SSL usage for now */
notca = PR_TRUE;
}
/* if it doesn't have the right CA extension, then dump it */
if ( notca ) {
goto endloop;
}
/* cert of the same name already exists */
cert = CERT_FindCertByName(handle, &newcert->derSubject);
if ( cert ) { /* different cert with same name - yuck!! */
/* get the expiration time of the new cert being downloaded */
rv = DER_UTCTimeToTime(&newtime, &newcert->validity.notAfter);
if ( rv ) {
goto loser;
}
/* get the expiration time of the old cert with the same name */
rv = DER_UTCTimeToTime(&oldtime, &cert->validity.notAfter);
if ( rv ) {
goto loser;
}
if ( LL_CMP(newtime, >, oldtime) ) {
/* newly downloaded cert is newer */
/* XXX - need to implement this */
}
CERT_DestroyCertificate(cert);
cert = NULL;
goto endloop;
}
/* it passed all of the tests, so lets add it to the database */
/* mark it as a CA */
PORT_Memset((void *)&trust, 0, sizeof(trust));
trust.sslFlags = CERTDB_VALID_CA;
cert = CERT_NewTempCertificate(handle, derCert, NULL, PR_FALSE, PR_TRUE);
if ( cert == NULL ) {
goto loser;
}
/* get a default nickname for it */
nickname = CERT_MakeCANickname(cert);
rv = CERT_AddTempCertToPerm(cert, nickname, &trust);
/* free the nickname */
if ( nickname ) {
PORT_Free(nickname);
}
CERT_DestroyCertificate(cert);
cert = NULL;
if ( rv != SECSuccess ) {
goto loser;
}
endloop:
if ( newcert ) {
CERT_DestroyCertificate(newcert);
newcert = NULL;
}
}
rv = SECSuccess;
goto done;
loser:
rv = SECFailure;
done:
if ( newcert ) {
CERT_DestroyCertificate(newcert);
newcert = NULL;
}
if ( cert ) {
CERT_DestroyCertificate(cert);
cert = NULL;
}
if ( arena ) {
PORT_FreeArena(arena, PR_FALSE);
}
return(rv);
}
void
CERT_DestroyCrl (CERTSignedCrl *crl)
{
SEC_DestroyCrl (crl);
}
/*
* 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)
{
CERTCertTrust *trust;
SECStatus rv;
if ( cert->isperm ) {
trust = cert->trust;
if ( ( ( trust->sslFlags & CERTDB_VALID_CA ) == CERTDB_VALID_CA ) ||
( ( trust->emailFlags & CERTDB_VALID_CA ) == CERTDB_VALID_CA ) ||
( ( trust->objectSigningFlags & CERTDB_VALID_CA ) ==
CERTDB_VALID_CA ) ){
return(PR_TRUE);
}
} else {
if ( cert->nsCertType &
( NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA |
NS_CERT_TYPE_OBJECT_SIGNING_CA ) ) {
return(PR_TRUE);
} else {
CERTBasicConstraints constraints;
rv = CERT_FindBasicConstraintExten(cert, &constraints);
if ( rv == SECSuccess ) {
if ( constraints.isCA ) {
return(PR_TRUE);
}
}
}
}
return(PR_FALSE);
}
/*
* is certa newer than certb? If one is expired, pick the other one.
*/
PRBool
CERT_IsNewer(CERTCertificate *certa, CERTCertificate *certb)
{
int64 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 UTC 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(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, char *trusts)
{
int i;
unsigned int *pflags;
trust->sslFlags = 0;
trust->emailFlags = 0;
trust->objectSigningFlags = 0;
pflags = &trust->sslFlags;
for (i=0; i < PORT_Strlen(trusts); i++) {
switch (trusts[i]) {
case 'p':
*pflags = *pflags | CERTDB_VALID_PEER;
break;
case 'P':
*pflags = *pflags | CERTDB_TRUSTED | CERTDB_VALID_PEER;
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 ',':
if ( pflags == &trust->sslFlags ) {
pflags = &trust->emailFlags;
} else {
pflags = &trust->objectSigningFlags;
}
break;
default:
return SECFailure;
}
}
return SECSuccess;
}
static void
EncodeFlags(char *trusts, unsigned int flags)
{
if (flags & CERTDB_VALID_CA)
if (!(flags & CERTDB_TRUSTED_CA) &&
!(flags & CERTDB_TRUSTED_CLIENT_CA))
XP_STRCAT(trusts, "c");
if (flags & CERTDB_VALID_PEER)
if (!(flags & CERTDB_TRUSTED))
XP_STRCAT(trusts, "p");
if (flags & CERTDB_TRUSTED_CA)
XP_STRCAT(trusts, "C");
if (flags & CERTDB_TRUSTED_CLIENT_CA)
XP_STRCAT(trusts, "T");
if (flags & CERTDB_TRUSTED)
XP_STRCAT(trusts, "P");
if (flags & CERTDB_USER)
XP_STRCAT(trusts, "u");
if (flags & CERTDB_SEND_WARN)
XP_STRCAT(trusts, "w");
if (flags & CERTDB_INVISIBLE_CA)
XP_STRCAT(trusts, "I");
if (flags & CERTDB_GOVT_APPROVED_CA)
XP_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)
{
int i;
CERTCertificate **certs = NULL, *cert = NULL;
SECStatus rv;
if ( ncerts ) {
certs = PORT_Alloc(sizeof(CERTCertificate *) * ncerts );
if ( certs == NULL ) {
return(SECFailure);
}
/* decode all of the certs into the temporary DB */
for ( i = 0; i < ncerts; i++ ) {
certs[i] = CERT_NewTempCertificate(certdb, derCerts[i], NULL,
PR_FALSE, PR_TRUE);
}
if ( keepCerts ) {
for ( i = 0; i < ncerts; i++ ) {
rv = CERT_SaveImportedCert(certs[i], usage, caOnly, nickname);
/* don't care if it fails - keep going */
}
}
}
if ( retCerts ) {
*retCerts = certs;
} else {
CERT_DestroyCertArray(certs, ncerts);
}
return(SECSuccess);
loser:
if ( retCerts ) {
*retCerts = NULL;
}
if ( certs ) {
CERT_DestroyCertArray(certs, ncerts);
}
return(SECFailure);
}