mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-11-11 02:10:17 +01:00
44b7f056d9
bug1001332, 56b691c003ad, bug1086145, bug1054069, bug1155922, bug991783, bug1125025, bug1162521, bug1162644, bug1132941, bug1164364, bug1166205, bug1166163, bug1166515, bug1138554, bug1167046, bug1167043, bug1169451, bug1172128, bug1170322, bug102794, bug1128184, bug557830, bug1174648, bug1180244, bug1177784, bug1173413, bug1169174, bug1084669, bug951455, bug1183395, bug1177430, bug1183827, bug1160139, bug1154106, bug1142209, bug1185033, bug1193467, bug1182667(with sha512 changes backed out, which breaks VC6 compilation), bug1158489, bug337796
3284 lines
84 KiB
C
3284 lines
84 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;
|
|
PLArenaPool *arena;
|
|
CERTSignedData sd;
|
|
void *tmpptr;
|
|
|
|
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
|
|
if ( ! arena ) {
|
|
return(SECFailure);
|
|
}
|
|
|
|
PORT_Memset(&sd, 0, sizeof(CERTSignedData));
|
|
rv = SEC_QuickDERDecodeItem(arena, &sd, CERT_SignedDataTemplate, derCert);
|
|
|
|
if ( rv ) {
|
|
goto loser;
|
|
}
|
|
|
|
PORT_Memset(derName, 0, sizeof(SECItem));
|
|
rv = SEC_QuickDERDecodeItem(arena, derName, SEC_CertIssuerTemplate, &sd.data);
|
|
|
|
if ( rv ) {
|
|
goto loser;
|
|
}
|
|
|
|
tmpptr = derName->data;
|
|
derName->data = (unsigned char*)PORT_Alloc(derName->len);
|
|
if ( derName->data == NULL ) {
|
|
goto loser;
|
|
}
|
|
|
|
PORT_Memcpy(derName->data, tmpptr, derName->len);
|
|
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
return(SECSuccess);
|
|
|
|
loser:
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
return(SECFailure);
|
|
}
|
|
|
|
SECStatus
|
|
CERT_SerialNumberFromDERCert(SECItem *derCert, SECItem *derName)
|
|
{
|
|
int rv;
|
|
PLArenaPool *arena;
|
|
CERTSignedData sd;
|
|
void *tmpptr;
|
|
|
|
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
|
|
if ( ! arena ) {
|
|
return(SECFailure);
|
|
}
|
|
|
|
PORT_Memset(&sd, 0, sizeof(CERTSignedData));
|
|
rv = SEC_QuickDERDecodeItem(arena, &sd, CERT_SignedDataTemplate, derCert);
|
|
|
|
if ( rv ) {
|
|
goto loser;
|
|
}
|
|
|
|
PORT_Memset(derName, 0, sizeof(SECItem));
|
|
rv = SEC_QuickDERDecodeItem(arena, derName, SEC_CertSerialNumberTemplate, &sd.data);
|
|
|
|
if ( rv ) {
|
|
goto loser;
|
|
}
|
|
|
|
tmpptr = derName->data;
|
|
derName->data = (unsigned char*)PORT_Alloc(derName->len);
|
|
if ( derName->data == NULL ) {
|
|
goto loser;
|
|
}
|
|
|
|
PORT_Memcpy(derName->data, tmpptr, derName->len);
|
|
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
return(SECSuccess);
|
|
|
|
loser:
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
return(SECFailure);
|
|
}
|
|
|
|
/*
|
|
* Generate a database key, based on serial number and issuer, from a
|
|
* DER certificate.
|
|
*/
|
|
SECStatus
|
|
CERT_KeyFromDERCert(PLArenaPool *reqArena, SECItem *derCert, SECItem *key)
|
|
{
|
|
int rv;
|
|
CERTSignedData sd;
|
|
CERTCertKey certkey;
|
|
|
|
if (!reqArena) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return SECFailure;
|
|
}
|
|
|
|
PORT_Memset(&sd, 0, sizeof(CERTSignedData));
|
|
rv = SEC_QuickDERDecodeItem(reqArena, &sd, CERT_SignedDataTemplate,
|
|
derCert);
|
|
|
|
if ( rv ) {
|
|
goto loser;
|
|
}
|
|
|
|
PORT_Memset(&certkey, 0, sizeof(CERTCertKey));
|
|
rv = SEC_QuickDERDecodeItem(reqArena, &certkey, CERT_CertKeyTemplate,
|
|
&sd.data);
|
|
|
|
if ( rv ) {
|
|
goto loser;
|
|
}
|
|
|
|
return(CERT_KeyFromIssuerAndSN(reqArena, &certkey.derIssuer,
|
|
&certkey.serialNumber, key));
|
|
loser:
|
|
return(SECFailure);
|
|
}
|
|
|
|
/*
|
|
* fill in keyUsage field of the cert based on the cert extension
|
|
* if the extension is not critical, then we allow all uses
|
|
*/
|
|
static SECStatus
|
|
GetKeyUsage(CERTCertificate *cert)
|
|
{
|
|
SECStatus rv;
|
|
SECItem tmpitem;
|
|
|
|
rv = CERT_FindKeyUsageExtension(cert, &tmpitem);
|
|
if ( rv == SECSuccess ) {
|
|
/* remember the actual value of the extension */
|
|
cert->rawKeyUsage = tmpitem.data[0];
|
|
cert->keyUsagePresent = PR_TRUE;
|
|
cert->keyUsage = tmpitem.data[0];
|
|
|
|
PORT_Free(tmpitem.data);
|
|
tmpitem.data = NULL;
|
|
|
|
} else {
|
|
/* if the extension is not present, then we allow all uses */
|
|
cert->keyUsage = KU_ALL;
|
|
cert->rawKeyUsage = KU_ALL;
|
|
cert->keyUsagePresent = PR_FALSE;
|
|
}
|
|
|
|
if ( CERT_GovtApprovedBitSet(cert) ) {
|
|
cert->keyUsage |= KU_NS_GOVT_APPROVED;
|
|
cert->rawKeyUsage |= KU_NS_GOVT_APPROVED;
|
|
}
|
|
|
|
return(SECSuccess);
|
|
}
|
|
|
|
|
|
static SECStatus
|
|
findOIDinOIDSeqByTagNum(CERTOidSequence *seq, SECOidTag tagnum)
|
|
{
|
|
SECItem **oids;
|
|
SECItem *oid;
|
|
SECStatus rv = SECFailure;
|
|
|
|
if (seq != NULL) {
|
|
oids = seq->oids;
|
|
while (oids != NULL && *oids != NULL) {
|
|
oid = *oids;
|
|
if (SECOID_FindOIDTag(oid) == tagnum) {
|
|
rv = SECSuccess;
|
|
break;
|
|
}
|
|
oids++;
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* fill in nsCertType field of the cert based on the cert extension
|
|
*/
|
|
SECStatus
|
|
cert_GetCertType(CERTCertificate *cert)
|
|
{
|
|
PRUint32 nsCertType;
|
|
|
|
if (cert->nsCertType) {
|
|
/* once set, no need to recalculate */
|
|
return SECSuccess;
|
|
}
|
|
nsCertType = cert_ComputeCertType(cert);
|
|
|
|
/* Assert that it is safe to cast &cert->nsCertType to "PRInt32 *" */
|
|
PORT_Assert(sizeof(cert->nsCertType) == sizeof(PRInt32));
|
|
PR_ATOMIC_SET((PRInt32 *)&cert->nsCertType, nsCertType);
|
|
return SECSuccess;
|
|
}
|
|
|
|
PRUint32
|
|
cert_ComputeCertType(CERTCertificate *cert)
|
|
{
|
|
SECStatus rv;
|
|
SECItem tmpitem;
|
|
SECItem encodedExtKeyUsage;
|
|
CERTOidSequence *extKeyUsage = NULL;
|
|
PRBool basicConstraintPresent = PR_FALSE;
|
|
CERTBasicConstraints basicConstraint;
|
|
PRUint32 nsCertType = 0;
|
|
|
|
tmpitem.data = NULL;
|
|
CERT_FindNSCertTypeExtension(cert, &tmpitem);
|
|
encodedExtKeyUsage.data = NULL;
|
|
rv = CERT_FindCertExtension(cert, SEC_OID_X509_EXT_KEY_USAGE,
|
|
&encodedExtKeyUsage);
|
|
if (rv == SECSuccess) {
|
|
extKeyUsage = CERT_DecodeOidSequence(&encodedExtKeyUsage);
|
|
}
|
|
rv = CERT_FindBasicConstraintExten(cert, &basicConstraint);
|
|
if (rv == SECSuccess) {
|
|
basicConstraintPresent = PR_TRUE;
|
|
}
|
|
if (tmpitem.data != NULL || extKeyUsage != NULL) {
|
|
if (tmpitem.data == NULL) {
|
|
nsCertType = 0;
|
|
} else {
|
|
nsCertType = tmpitem.data[0];
|
|
}
|
|
|
|
/* free tmpitem data pointer to avoid memory leak */
|
|
PORT_Free(tmpitem.data);
|
|
tmpitem.data = NULL;
|
|
|
|
/*
|
|
* for this release, we will allow SSL certs with an email address
|
|
* to be used for email
|
|
*/
|
|
if ( ( nsCertType & NS_CERT_TYPE_SSL_CLIENT ) &&
|
|
cert->emailAddr && cert->emailAddr[0]) {
|
|
nsCertType |= NS_CERT_TYPE_EMAIL;
|
|
}
|
|
/*
|
|
* for this release, we will allow SSL intermediate CAs to be
|
|
* email intermediate CAs too.
|
|
*/
|
|
if ( nsCertType & NS_CERT_TYPE_SSL_CA ) {
|
|
nsCertType |= NS_CERT_TYPE_EMAIL_CA;
|
|
}
|
|
/*
|
|
* allow a cert with the extended key usage of EMail Protect
|
|
* to be used for email or as an email CA, if basic constraints
|
|
* indicates that it is a CA.
|
|
*/
|
|
if (findOIDinOIDSeqByTagNum(extKeyUsage,
|
|
SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT) ==
|
|
SECSuccess) {
|
|
if (basicConstraintPresent == PR_TRUE &&
|
|
(basicConstraint.isCA)) {
|
|
nsCertType |= NS_CERT_TYPE_EMAIL_CA;
|
|
} else {
|
|
nsCertType |= NS_CERT_TYPE_EMAIL;
|
|
}
|
|
}
|
|
if (findOIDinOIDSeqByTagNum(extKeyUsage,
|
|
SEC_OID_EXT_KEY_USAGE_SERVER_AUTH) ==
|
|
SECSuccess){
|
|
if (basicConstraintPresent == PR_TRUE &&
|
|
(basicConstraint.isCA)) {
|
|
nsCertType |= NS_CERT_TYPE_SSL_CA;
|
|
} else {
|
|
nsCertType |= NS_CERT_TYPE_SSL_SERVER;
|
|
}
|
|
}
|
|
/*
|
|
* Treat certs with step-up OID as also having SSL server type.
|
|
* COMODO needs this behaviour until June 2020. See Bug 737802.
|
|
*/
|
|
if (findOIDinOIDSeqByTagNum(extKeyUsage,
|
|
SEC_OID_NS_KEY_USAGE_GOVT_APPROVED) ==
|
|
SECSuccess){
|
|
if (basicConstraintPresent == PR_TRUE &&
|
|
(basicConstraint.isCA)) {
|
|
nsCertType |= NS_CERT_TYPE_SSL_CA;
|
|
} else {
|
|
nsCertType |= NS_CERT_TYPE_SSL_SERVER;
|
|
}
|
|
}
|
|
if (findOIDinOIDSeqByTagNum(extKeyUsage,
|
|
SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH) ==
|
|
SECSuccess){
|
|
if (basicConstraintPresent == PR_TRUE &&
|
|
(basicConstraint.isCA)) {
|
|
nsCertType |= NS_CERT_TYPE_SSL_CA;
|
|
} else {
|
|
nsCertType |= NS_CERT_TYPE_SSL_CLIENT;
|
|
}
|
|
}
|
|
if (findOIDinOIDSeqByTagNum(extKeyUsage,
|
|
SEC_OID_EXT_KEY_USAGE_CODE_SIGN) ==
|
|
SECSuccess) {
|
|
if (basicConstraintPresent == PR_TRUE &&
|
|
(basicConstraint.isCA)) {
|
|
nsCertType |= NS_CERT_TYPE_OBJECT_SIGNING_CA;
|
|
} else {
|
|
nsCertType |= NS_CERT_TYPE_OBJECT_SIGNING;
|
|
}
|
|
}
|
|
if (findOIDinOIDSeqByTagNum(extKeyUsage,
|
|
SEC_OID_EXT_KEY_USAGE_TIME_STAMP) ==
|
|
SECSuccess) {
|
|
nsCertType |= EXT_KEY_USAGE_TIME_STAMP;
|
|
}
|
|
if (findOIDinOIDSeqByTagNum(extKeyUsage,
|
|
SEC_OID_OCSP_RESPONDER) ==
|
|
SECSuccess) {
|
|
nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER;
|
|
}
|
|
} else {
|
|
/* If no NS Cert Type extension and no EKU extension, then */
|
|
nsCertType = 0;
|
|
if (CERT_IsCACert(cert, &nsCertType))
|
|
nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER;
|
|
/* if the basic constraint extension says the cert is a CA, then
|
|
allow SSL CA and EMAIL CA and Status Responder */
|
|
if (basicConstraintPresent && basicConstraint.isCA ) {
|
|
nsCertType |= (NS_CERT_TYPE_SSL_CA |
|
|
NS_CERT_TYPE_EMAIL_CA |
|
|
EXT_KEY_USAGE_STATUS_RESPONDER);
|
|
}
|
|
/* allow any ssl or email (no ca or object signing. */
|
|
nsCertType |= NS_CERT_TYPE_SSL_CLIENT | NS_CERT_TYPE_SSL_SERVER |
|
|
NS_CERT_TYPE_EMAIL;
|
|
}
|
|
|
|
if (encodedExtKeyUsage.data != NULL) {
|
|
PORT_Free(encodedExtKeyUsage.data);
|
|
}
|
|
if (extKeyUsage != NULL) {
|
|
CERT_DestroyOidSequence(extKeyUsage);
|
|
}
|
|
return nsCertType;
|
|
}
|
|
|
|
/*
|
|
* cert_GetKeyID() - extract or generate the subjectKeyID from a certificate
|
|
*/
|
|
SECStatus
|
|
cert_GetKeyID(CERTCertificate *cert)
|
|
{
|
|
SECItem tmpitem;
|
|
SECStatus rv;
|
|
|
|
cert->subjectKeyID.len = 0;
|
|
|
|
/* see of the cert has a key identifier extension */
|
|
rv = CERT_FindSubjectKeyIDExtension(cert, &tmpitem);
|
|
if ( rv == SECSuccess ) {
|
|
cert->subjectKeyID.data = (unsigned char*) PORT_ArenaAlloc(cert->arena, tmpitem.len);
|
|
if ( cert->subjectKeyID.data != NULL ) {
|
|
PORT_Memcpy(cert->subjectKeyID.data, tmpitem.data, tmpitem.len);
|
|
cert->subjectKeyID.len = tmpitem.len;
|
|
cert->keyIDGenerated = PR_FALSE;
|
|
}
|
|
|
|
PORT_Free(tmpitem.data);
|
|
}
|
|
|
|
/* if the cert doesn't have a key identifier extension, then generate one*/
|
|
if ( cert->subjectKeyID.len == 0 ) {
|
|
/*
|
|
* pkix says that if the subjectKeyID is not present, then we should
|
|
* use the SHA-1 hash of the DER-encoded publicKeyInfo from the cert
|
|
*/
|
|
cert->subjectKeyID.data = (unsigned char *)PORT_ArenaAlloc(cert->arena, SHA1_LENGTH);
|
|
if ( cert->subjectKeyID.data != NULL ) {
|
|
rv = PK11_HashBuf(SEC_OID_SHA1,cert->subjectKeyID.data,
|
|
cert->derPublicKey.data,
|
|
cert->derPublicKey.len);
|
|
if ( rv == SECSuccess ) {
|
|
cert->subjectKeyID.len = SHA1_LENGTH;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( cert->subjectKeyID.len == 0 ) {
|
|
return(SECFailure);
|
|
}
|
|
return(SECSuccess);
|
|
|
|
}
|
|
|
|
static PRBool
|
|
cert_IsRootCert(CERTCertificate *cert)
|
|
{
|
|
SECStatus rv;
|
|
SECItem tmpitem;
|
|
|
|
/* cache the authKeyID extension, if present */
|
|
cert->authKeyID = CERT_FindAuthKeyIDExten(cert->arena, cert);
|
|
|
|
/* it MUST be self-issued to be a root */
|
|
if (cert->derIssuer.len == 0 ||
|
|
!SECITEM_ItemsAreEqual(&cert->derIssuer, &cert->derSubject))
|
|
{
|
|
return PR_FALSE;
|
|
}
|
|
|
|
/* check the authKeyID extension */
|
|
if (cert->authKeyID) {
|
|
/* authority key identifier is present */
|
|
if (cert->authKeyID->keyID.len > 0) {
|
|
/* the keyIdentifier field is set, look for subjectKeyID */
|
|
rv = CERT_FindSubjectKeyIDExtension(cert, &tmpitem);
|
|
if (rv == SECSuccess) {
|
|
PRBool match;
|
|
/* also present, they MUST match for it to be a root */
|
|
match = SECITEM_ItemsAreEqual(&cert->authKeyID->keyID,
|
|
&tmpitem);
|
|
PORT_Free(tmpitem.data);
|
|
if (!match) return PR_FALSE; /* else fall through */
|
|
} else {
|
|
/* the subject key ID is required when AKI is present */
|
|
return PR_FALSE;
|
|
}
|
|
}
|
|
if (cert->authKeyID->authCertIssuer) {
|
|
SECItem *caName;
|
|
caName = (SECItem *)CERT_GetGeneralNameByType(
|
|
cert->authKeyID->authCertIssuer,
|
|
certDirectoryName, PR_TRUE);
|
|
if (caName) {
|
|
if (!SECITEM_ItemsAreEqual(&cert->derIssuer, caName)) {
|
|
return PR_FALSE;
|
|
} /* else fall through */
|
|
} /* else ??? could not get general name as directory name? */
|
|
}
|
|
if (cert->authKeyID->authCertSerialNumber.len > 0) {
|
|
if (!SECITEM_ItemsAreEqual(&cert->serialNumber,
|
|
&cert->authKeyID->authCertSerialNumber)) {
|
|
return PR_FALSE;
|
|
} /* else fall through */
|
|
}
|
|
/* all of the AKI fields that were present passed the test */
|
|
return PR_TRUE;
|
|
}
|
|
/* else the AKI was not present, so this is a root */
|
|
return PR_TRUE;
|
|
}
|
|
|
|
/*
|
|
* take a DER certificate and decode it into a certificate structure
|
|
*/
|
|
CERTCertificate *
|
|
CERT_DecodeDERCertificate(SECItem *derSignedCert, PRBool copyDER,
|
|
char *nickname)
|
|
{
|
|
CERTCertificate *cert;
|
|
PLArenaPool *arena;
|
|
void *data;
|
|
int rv;
|
|
int len;
|
|
char *tmpname;
|
|
|
|
/* make a new arena */
|
|
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
|
|
if ( !arena ) {
|
|
return 0;
|
|
}
|
|
|
|
/* allocate the certificate structure */
|
|
cert = (CERTCertificate *)PORT_ArenaZAlloc(arena, sizeof(CERTCertificate));
|
|
|
|
if ( !cert ) {
|
|
goto loser;
|
|
}
|
|
|
|
cert->arena = arena;
|
|
|
|
if ( copyDER ) {
|
|
/* copy the DER data for the cert into this arena */
|
|
data = (void *)PORT_ArenaAlloc(arena, derSignedCert->len);
|
|
if ( !data ) {
|
|
goto loser;
|
|
}
|
|
cert->derCert.data = (unsigned char *)data;
|
|
cert->derCert.len = derSignedCert->len;
|
|
PORT_Memcpy(data, derSignedCert->data, derSignedCert->len);
|
|
} else {
|
|
/* point to passed in DER data */
|
|
cert->derCert = *derSignedCert;
|
|
}
|
|
|
|
/* decode the certificate info */
|
|
rv = SEC_QuickDERDecodeItem(arena, cert, SEC_SignedCertificateTemplate,
|
|
&cert->derCert);
|
|
|
|
if ( rv ) {
|
|
goto loser;
|
|
}
|
|
|
|
if (cert_HasUnknownCriticalExten (cert->extensions) == PR_TRUE) {
|
|
cert->options.bits.hasUnsupportedCriticalExt = PR_TRUE;
|
|
}
|
|
|
|
/* generate and save the database key for the cert */
|
|
rv = CERT_KeyFromIssuerAndSN(arena, &cert->derIssuer, &cert->serialNumber,
|
|
&cert->certKey);
|
|
if ( rv ) {
|
|
goto loser;
|
|
}
|
|
|
|
/* set the nickname */
|
|
if ( nickname == NULL ) {
|
|
cert->nickname = NULL;
|
|
} else {
|
|
/* copy and install the nickname */
|
|
len = PORT_Strlen(nickname) + 1;
|
|
cert->nickname = (char*)PORT_ArenaAlloc(arena, len);
|
|
if ( cert->nickname == NULL ) {
|
|
goto loser;
|
|
}
|
|
|
|
PORT_Memcpy(cert->nickname, nickname, len);
|
|
}
|
|
|
|
/* set the email address */
|
|
cert->emailAddr = cert_GetCertificateEmailAddresses(cert);
|
|
|
|
/* initialize the subjectKeyID */
|
|
rv = cert_GetKeyID(cert);
|
|
if ( rv != SECSuccess ) {
|
|
goto loser;
|
|
}
|
|
|
|
/* initialize keyUsage */
|
|
rv = GetKeyUsage(cert);
|
|
if ( rv != SECSuccess ) {
|
|
goto loser;
|
|
}
|
|
|
|
/* determine if this is a root cert */
|
|
cert->isRoot = cert_IsRootCert(cert);
|
|
|
|
/* initialize the certType */
|
|
rv = cert_GetCertType(cert);
|
|
if ( rv != SECSuccess ) {
|
|
goto loser;
|
|
}
|
|
|
|
tmpname = CERT_NameToAscii(&cert->subject);
|
|
if ( tmpname != NULL ) {
|
|
cert->subjectName = PORT_ArenaStrdup(cert->arena, tmpname);
|
|
PORT_Free(tmpname);
|
|
}
|
|
|
|
tmpname = CERT_NameToAscii(&cert->issuer);
|
|
if ( tmpname != NULL ) {
|
|
cert->issuerName = PORT_ArenaStrdup(cert->arena, tmpname);
|
|
PORT_Free(tmpname);
|
|
}
|
|
|
|
cert->referenceCount = 1;
|
|
cert->slot = NULL;
|
|
cert->pkcs11ID = CK_INVALID_HANDLE;
|
|
cert->dbnickname = NULL;
|
|
|
|
return(cert);
|
|
|
|
loser:
|
|
|
|
if ( arena ) {
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
CERTCertificate *
|
|
__CERT_DecodeDERCertificate(SECItem *derSignedCert, PRBool copyDER,
|
|
char *nickname)
|
|
{
|
|
return CERT_DecodeDERCertificate(derSignedCert, copyDER, nickname);
|
|
}
|
|
|
|
|
|
CERTValidity *
|
|
CERT_CreateValidity(PRTime notBefore, PRTime notAfter)
|
|
{
|
|
CERTValidity *v;
|
|
int rv;
|
|
PLArenaPool *arena;
|
|
|
|
if (notBefore > notAfter) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return NULL;
|
|
}
|
|
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
|
|
if ( !arena ) {
|
|
return(0);
|
|
}
|
|
|
|
v = (CERTValidity*) PORT_ArenaZAlloc(arena, sizeof(CERTValidity));
|
|
if (v) {
|
|
v->arena = arena;
|
|
rv = DER_EncodeTimeChoice(arena, &v->notBefore, notBefore);
|
|
if (rv) goto loser;
|
|
rv = DER_EncodeTimeChoice(arena, &v->notAfter, notAfter);
|
|
if (rv) goto loser;
|
|
}
|
|
return v;
|
|
|
|
loser:
|
|
CERT_DestroyValidity(v);
|
|
return 0;
|
|
}
|
|
|
|
SECStatus
|
|
CERT_CopyValidity(PLArenaPool *arena, CERTValidity *to, CERTValidity *from)
|
|
{
|
|
SECStatus rv;
|
|
|
|
CERT_DestroyValidity(to);
|
|
to->arena = arena;
|
|
|
|
rv = SECITEM_CopyItem(arena, &to->notBefore, &from->notBefore);
|
|
if (rv) return rv;
|
|
rv = SECITEM_CopyItem(arena, &to->notAfter, &from->notAfter);
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
CERT_DestroyValidity(CERTValidity *v)
|
|
{
|
|
if (v && v->arena) {
|
|
PORT_FreeArena(v->arena, PR_FALSE);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
** Amount of time that a certifiate is allowed good before it is actually
|
|
** good. This is used for pending certificates, ones that are about to be
|
|
** valid. The slop is designed to allow for some variance in the clocks
|
|
** of the machine checking the certificate.
|
|
*/
|
|
#define PENDING_SLOP (24L*60L*60L) /* seconds per day */
|
|
static PRInt32 pendingSlop = PENDING_SLOP; /* seconds */
|
|
|
|
PRInt32
|
|
CERT_GetSlopTime(void)
|
|
{
|
|
return pendingSlop; /* seconds */
|
|
}
|
|
|
|
SECStatus
|
|
CERT_SetSlopTime(PRInt32 slop) /* seconds */
|
|
{
|
|
if (slop < 0)
|
|
return SECFailure;
|
|
pendingSlop = slop;
|
|
return SECSuccess;
|
|
}
|
|
|
|
SECStatus
|
|
CERT_GetCertTimes(const CERTCertificate *c, PRTime *notBefore, PRTime *notAfter)
|
|
{
|
|
SECStatus rv;
|
|
|
|
if (!c || !notBefore || !notAfter) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return SECFailure;
|
|
}
|
|
|
|
/* convert DER not-before time */
|
|
rv = DER_DecodeTimeChoice(notBefore, &c->validity.notBefore);
|
|
if (rv) {
|
|
return(SECFailure);
|
|
}
|
|
|
|
/* convert DER not-after time */
|
|
rv = DER_DecodeTimeChoice(notAfter, &c->validity.notAfter);
|
|
if (rv) {
|
|
return(SECFailure);
|
|
}
|
|
|
|
return(SECSuccess);
|
|
}
|
|
|
|
/*
|
|
* Check the validity times of a certificate
|
|
*/
|
|
SECCertTimeValidity
|
|
CERT_CheckCertValidTimes(const CERTCertificate *c, PRTime t,
|
|
PRBool allowOverride)
|
|
{
|
|
PRTime notBefore, notAfter, llPendingSlop, tmp1;
|
|
SECStatus rv;
|
|
|
|
if (!c) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return(secCertTimeUndetermined);
|
|
}
|
|
/* if cert is already marked OK, then don't bother to check */
|
|
if ( allowOverride && c->timeOK ) {
|
|
return(secCertTimeValid);
|
|
}
|
|
|
|
rv = CERT_GetCertTimes(c, ¬Before, ¬After);
|
|
|
|
if (rv) {
|
|
return(secCertTimeExpired); /*XXX is this the right thing to do here?*/
|
|
}
|
|
|
|
LL_I2L(llPendingSlop, pendingSlop);
|
|
/* convert to micro seconds */
|
|
LL_UI2L(tmp1, PR_USEC_PER_SEC);
|
|
LL_MUL(llPendingSlop, llPendingSlop, tmp1);
|
|
LL_SUB(notBefore, notBefore, llPendingSlop);
|
|
if ( LL_CMP( t, <, notBefore ) ) {
|
|
PORT_SetError(SEC_ERROR_EXPIRED_CERTIFICATE);
|
|
return(secCertTimeNotValidYet);
|
|
}
|
|
if ( LL_CMP( t, >, notAfter) ) {
|
|
PORT_SetError(SEC_ERROR_EXPIRED_CERTIFICATE);
|
|
return(secCertTimeExpired);
|
|
}
|
|
|
|
return(secCertTimeValid);
|
|
}
|
|
|
|
SECStatus
|
|
SEC_GetCrlTimes(CERTCrl *date, PRTime *notBefore, PRTime *notAfter)
|
|
{
|
|
int rv;
|
|
|
|
/* convert DER not-before time */
|
|
rv = DER_DecodeTimeChoice(notBefore, &date->lastUpdate);
|
|
if (rv) {
|
|
return(SECFailure);
|
|
}
|
|
|
|
/* convert DER not-after time */
|
|
if (date->nextUpdate.data) {
|
|
rv = DER_DecodeTimeChoice(notAfter, &date->nextUpdate);
|
|
if (rv) {
|
|
return(SECFailure);
|
|
}
|
|
}
|
|
else {
|
|
LL_I2L(*notAfter, 0L);
|
|
}
|
|
return(SECSuccess);
|
|
}
|
|
|
|
/* These routines should probably be combined with the cert
|
|
* routines using an common extraction routine.
|
|
*/
|
|
SECCertTimeValidity
|
|
SEC_CheckCrlTimes(CERTCrl *crl, PRTime t) {
|
|
PRTime notBefore, notAfter, llPendingSlop, tmp1;
|
|
SECStatus rv;
|
|
|
|
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 dsaKey:
|
|
requiredUsage |= KU_DIGITAL_SIGNATURE;
|
|
break;
|
|
case dhKey:
|
|
requiredUsage |= KU_KEY_AGREEMENT;
|
|
break;
|
|
case ecKey:
|
|
/* Accept either signature or agreement. */
|
|
if (!(cert->keyUsage & (KU_DIGITAL_SIGNATURE | KU_KEY_AGREEMENT)))
|
|
goto loser;
|
|
break;
|
|
default:
|
|
goto loser;
|
|
}
|
|
}
|
|
|
|
/* Allow either digital signature or non-repudiation */
|
|
if ( requiredUsage & KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION ) {
|
|
/* turn off the special bit */
|
|
requiredUsage &= (~KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION);
|
|
|
|
if (!(cert->keyUsage & (KU_DIGITAL_SIGNATURE | KU_NON_REPUDIATION)))
|
|
goto loser;
|
|
}
|
|
|
|
if ( (cert->keyUsage & requiredUsage) == requiredUsage )
|
|
return SECSuccess;
|
|
|
|
loser:
|
|
PORT_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE);
|
|
return SECFailure;
|
|
}
|
|
|
|
|
|
CERTCertificate *
|
|
CERT_DupCertificate(CERTCertificate *c)
|
|
{
|
|
if (c) {
|
|
NSSCertificate *tmp = STAN_GetNSSCertificate(c);
|
|
nssCertificate_AddRef(tmp);
|
|
}
|
|
return c;
|
|
}
|
|
|
|
/*
|
|
* Allow use of default cert database, so that apps(such as mozilla) don't
|
|
* have to pass the handle all over the place.
|
|
*/
|
|
static CERTCertDBHandle *default_cert_db_handle = 0;
|
|
|
|
void
|
|
CERT_SetDefaultCertDB(CERTCertDBHandle *handle)
|
|
{
|
|
default_cert_db_handle = handle;
|
|
|
|
return;
|
|
}
|
|
|
|
CERTCertDBHandle *
|
|
CERT_GetDefaultCertDB(void)
|
|
{
|
|
return(default_cert_db_handle);
|
|
}
|
|
|
|
/* XXX this would probably be okay/better as an xp routine? */
|
|
static void
|
|
sec_lower_string(char *s)
|
|
{
|
|
if ( s == NULL ) {
|
|
return;
|
|
}
|
|
|
|
while ( *s ) {
|
|
*s = PORT_Tolower(*s);
|
|
s++;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static PRBool
|
|
cert_IsIPAddr(const char *hn)
|
|
{
|
|
PRBool isIPaddr = PR_FALSE;
|
|
PRNetAddr netAddr;
|
|
isIPaddr = (PR_SUCCESS == PR_StringToNetAddr(hn, &netAddr));
|
|
return isIPaddr;
|
|
}
|
|
|
|
/*
|
|
** Add a domain name to the list of names that the user has explicitly
|
|
** allowed (despite cert name mismatches) for use with a server cert.
|
|
*/
|
|
SECStatus
|
|
CERT_AddOKDomainName(CERTCertificate *cert, const char *hn)
|
|
{
|
|
CERTOKDomainName *domainOK;
|
|
int newNameLen;
|
|
|
|
if (!hn || !(newNameLen = strlen(hn))) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return SECFailure;
|
|
}
|
|
domainOK = (CERTOKDomainName *)PORT_ArenaZAlloc(cert->arena,
|
|
(sizeof *domainOK) + newNameLen);
|
|
if (!domainOK)
|
|
return SECFailure; /* error code is already set. */
|
|
|
|
PORT_Strcpy(domainOK->name, hn);
|
|
sec_lower_string(domainOK->name);
|
|
|
|
/* put at head of list. */
|
|
domainOK->next = cert->domainOK;
|
|
cert->domainOK = domainOK;
|
|
return SECSuccess;
|
|
}
|
|
|
|
/* returns SECSuccess if hn matches pattern cn,
|
|
** returns SECFailure with SSL_ERROR_BAD_CERT_DOMAIN if no match,
|
|
** returns SECFailure with some other error code if another error occurs.
|
|
**
|
|
** This function may modify string cn, so caller must pass a modifiable copy.
|
|
*/
|
|
static SECStatus
|
|
cert_TestHostName(char * cn, const char * hn)
|
|
{
|
|
static int useShellExp = -1;
|
|
|
|
if (useShellExp < 0) {
|
|
useShellExp = (NULL != PR_GetEnv("NSS_USE_SHEXP_IN_CERT_NAME"));
|
|
}
|
|
if (useShellExp) {
|
|
/* Backward compatible code, uses Shell Expressions (SHEXP). */
|
|
int regvalid = PORT_RegExpValid(cn);
|
|
if (regvalid != NON_SXP) {
|
|
SECStatus rv;
|
|
/* cn is a regular expression, try to match the shexp */
|
|
int match = PORT_RegExpCaseSearch(hn, cn);
|
|
|
|
if ( match == 0 ) {
|
|
rv = SECSuccess;
|
|
} else {
|
|
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
|
|
rv = SECFailure;
|
|
}
|
|
return rv;
|
|
}
|
|
} else {
|
|
/* New approach conforms to RFC 6125. */
|
|
char *wildcard = PORT_Strchr(cn, '*');
|
|
char *firstcndot = PORT_Strchr(cn, '.');
|
|
char *secondcndot = firstcndot ? PORT_Strchr(firstcndot+1, '.') : NULL;
|
|
char *firsthndot = PORT_Strchr(hn, '.');
|
|
|
|
/* For a cn pattern to be considered valid, the wildcard character...
|
|
* - may occur only in a DNS name with at least 3 components, and
|
|
* - may occur only as last character in the first component, and
|
|
* - may be preceded by additional characters, and
|
|
* - must not be preceded by an IDNA ACE prefix (xn--)
|
|
*/
|
|
if (wildcard && secondcndot && secondcndot[1] && firsthndot
|
|
&& firstcndot - wildcard == 1 /* wildcard is last char in first component */
|
|
&& secondcndot - firstcndot > 1 /* second component is non-empty */
|
|
&& PORT_Strrchr(cn, '*') == wildcard /* only one wildcard in cn */
|
|
&& !PORT_Strncasecmp(cn, hn, wildcard - cn)
|
|
&& !PORT_Strcasecmp(firstcndot, firsthndot)
|
|
/* If hn starts with xn--, then cn must start with wildcard */
|
|
&& (PORT_Strncasecmp(hn, "xn--", 4) || wildcard == cn)) {
|
|
/* valid wildcard pattern match */
|
|
return SECSuccess;
|
|
}
|
|
}
|
|
/* String cn has no wildcard or shell expression.
|
|
* Compare entire string hn with cert name.
|
|
*/
|
|
if (PORT_Strcasecmp(hn, cn) == 0) {
|
|
return SECSuccess;
|
|
}
|
|
|
|
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
|
|
return SECFailure;
|
|
}
|
|
|
|
|
|
SECStatus
|
|
cert_VerifySubjectAltName(const CERTCertificate *cert, const char *hn)
|
|
{
|
|
PLArenaPool * arena = NULL;
|
|
CERTGeneralName * nameList = NULL;
|
|
CERTGeneralName * current;
|
|
char * cn;
|
|
int cnBufLen;
|
|
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));
|
|
rv = SECFailure;
|
|
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
if (!arena)
|
|
goto fail;
|
|
|
|
nameList = current = CERT_DecodeAltNameExtension(arena, &subAltName);
|
|
if (!current)
|
|
goto fail;
|
|
|
|
do {
|
|
switch (current->type) {
|
|
case certDNSName:
|
|
if (!isIPaddr) {
|
|
/* DNS name current->name.other.data is not null terminated.
|
|
** so must copy it.
|
|
*/
|
|
int cnLen = current->name.other.len;
|
|
rv = CERT_RFC1485_EscapeAndQuote(cn, cnBufLen,
|
|
(char *)current->name.other.data,
|
|
cnLen);
|
|
if (rv != SECSuccess && PORT_GetError() == SEC_ERROR_OUTPUT_LEN) {
|
|
cnBufLen = cnLen * 3 + 3; /* big enough for worst case */
|
|
cn = (char *)PORT_ArenaAlloc(arena, cnBufLen);
|
|
if (!cn)
|
|
goto fail;
|
|
rv = CERT_RFC1485_EscapeAndQuote(cn, cnBufLen,
|
|
(char *)current->name.other.data,
|
|
cnLen);
|
|
}
|
|
if (rv == SECSuccess)
|
|
rv = cert_TestHostName(cn ,hn);
|
|
if (rv == SECSuccess)
|
|
goto finish;
|
|
}
|
|
DNSextCount++;
|
|
break;
|
|
case certIPAddress:
|
|
if (isIPaddr) {
|
|
int match = 0;
|
|
PRIPv6Addr v6Addr;
|
|
if (current->name.other.len == 4 && /* IP v4 address */
|
|
netAddr.inet.family == PR_AF_INET) {
|
|
match = !memcmp(&netAddr.inet.ip,
|
|
current->name.other.data, 4);
|
|
} else if (current->name.other.len == 16 && /* IP v6 address */
|
|
netAddr.ipv6.family == PR_AF_INET6) {
|
|
match = !memcmp(&netAddr.ipv6.ip,
|
|
current->name.other.data, 16);
|
|
} else if (current->name.other.len == 16 && /* IP v6 address */
|
|
netAddr.inet.family == PR_AF_INET) {
|
|
/* convert netAddr to ipv6, then compare. */
|
|
/* ipv4 must be in Network Byte Order on input. */
|
|
PR_ConvertIPv4AddrToIPv6(netAddr.inet.ip, &v6Addr);
|
|
match = !memcmp(&v6Addr, current->name.other.data, 16);
|
|
} else if (current->name.other.len == 4 && /* IP v4 address */
|
|
netAddr.inet.family == PR_AF_INET6) {
|
|
/* convert netAddr to ipv6, then compare. */
|
|
PRUint32 ipv4 = (current->name.other.data[0] << 24) |
|
|
(current->name.other.data[1] << 16) |
|
|
(current->name.other.data[2] << 8) |
|
|
current->name.other.data[3];
|
|
/* ipv4 must be in Network Byte Order on input. */
|
|
PR_ConvertIPv4AddrToIPv6(PR_htonl(ipv4), &v6Addr);
|
|
match = !memcmp(&netAddr.ipv6.ip, &v6Addr, 16);
|
|
}
|
|
if (match) {
|
|
rv = SECSuccess;
|
|
goto finish;
|
|
}
|
|
}
|
|
IPextCount++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
current = CERT_GetNextGeneralName(current);
|
|
} while (current != nameList);
|
|
|
|
fail:
|
|
|
|
if (!(isIPaddr ? IPextCount : DNSextCount)) {
|
|
/* no relevant value in the extension was found. */
|
|
PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND);
|
|
} else {
|
|
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
|
|
}
|
|
rv = SECFailure;
|
|
|
|
finish:
|
|
|
|
/* Don't free nameList, it's part of the arena. */
|
|
if (arena) {
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
}
|
|
|
|
if (subAltName.data) {
|
|
SECITEM_FreeItem(&subAltName, PR_FALSE);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* If found:
|
|
* - subAltName contains the extension (caller must free)
|
|
* - return value is the decoded namelist (allocated off arena)
|
|
* if not found, or if failure to decode:
|
|
* - return value is NULL
|
|
*/
|
|
CERTGeneralName *
|
|
cert_GetSubjectAltNameList(const CERTCertificate *cert, PLArenaPool *arena)
|
|
{
|
|
CERTGeneralName * nameList = NULL;
|
|
SECStatus rv = SECFailure;
|
|
SECItem subAltName;
|
|
|
|
if (!cert || !arena)
|
|
return NULL;
|
|
|
|
subAltName.data = NULL;
|
|
|
|
rv = CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME,
|
|
&subAltName);
|
|
if (rv != SECSuccess)
|
|
return NULL;
|
|
|
|
nameList = CERT_DecodeAltNameExtension(arena, &subAltName);
|
|
SECITEM_FreeItem(&subAltName, PR_FALSE);
|
|
return nameList;
|
|
}
|
|
|
|
PRUint32
|
|
cert_CountDNSPatterns(CERTGeneralName *firstName)
|
|
{
|
|
CERTGeneralName * current;
|
|
PRUint32 count = 0;
|
|
|
|
if (!firstName)
|
|
return 0;
|
|
|
|
current = firstName;
|
|
do {
|
|
switch (current->type) {
|
|
case certDNSName:
|
|
case certIPAddress:
|
|
++count;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
current = CERT_GetNextGeneralName(current);
|
|
} while (current != firstName);
|
|
|
|
return count;
|
|
}
|
|
|
|
#ifndef INET6_ADDRSTRLEN
|
|
#define INET6_ADDRSTRLEN 46
|
|
#endif
|
|
|
|
/* will fill nickNames,
|
|
* will allocate all data from nickNames->arena,
|
|
* numberOfGeneralNames should have been obtained from cert_CountDNSPatterns,
|
|
* will ensure the numberOfGeneralNames matches the number of output entries.
|
|
*/
|
|
SECStatus
|
|
cert_GetDNSPatternsFromGeneralNames(CERTGeneralName *firstName,
|
|
PRUint32 numberOfGeneralNames,
|
|
CERTCertNicknames *nickNames)
|
|
{
|
|
CERTGeneralName *currentInput;
|
|
char **currentOutput;
|
|
|
|
if (!firstName || !nickNames || !numberOfGeneralNames)
|
|
return SECFailure;
|
|
|
|
nickNames->numnicknames = numberOfGeneralNames;
|
|
nickNames->nicknames = PORT_ArenaAlloc(nickNames->arena,
|
|
sizeof(char *) * numberOfGeneralNames);
|
|
if (!nickNames->nicknames)
|
|
return SECFailure;
|
|
|
|
currentInput = firstName;
|
|
currentOutput = nickNames->nicknames;
|
|
do {
|
|
char *cn = NULL;
|
|
char ipbuf[INET6_ADDRSTRLEN];
|
|
PRNetAddr addr;
|
|
|
|
if (numberOfGeneralNames < 1) {
|
|
/* internal consistency error */
|
|
return SECFailure;
|
|
}
|
|
|
|
switch (currentInput->type) {
|
|
case certDNSName:
|
|
/* DNS name currentInput->name.other.data is not null terminated.
|
|
** so must copy it.
|
|
*/
|
|
cn = (char *)PORT_ArenaAlloc(nickNames->arena,
|
|
currentInput->name.other.len + 1);
|
|
if (!cn)
|
|
return SECFailure;
|
|
PORT_Memcpy(cn, currentInput->name.other.data,
|
|
currentInput->name.other.len);
|
|
cn[currentInput->name.other.len] = 0;
|
|
break;
|
|
case certIPAddress:
|
|
if (currentInput->name.other.len == 4) {
|
|
addr.inet.family = PR_AF_INET;
|
|
memcpy(&addr.inet.ip, currentInput->name.other.data,
|
|
currentInput->name.other.len);
|
|
} else if (currentInput->name.other.len == 16) {
|
|
addr.ipv6.family = PR_AF_INET6;
|
|
memcpy(&addr.ipv6.ip, currentInput->name.other.data,
|
|
currentInput->name.other.len);
|
|
}
|
|
if (PR_NetAddrToString(&addr, ipbuf, sizeof(ipbuf)) == PR_FAILURE)
|
|
return SECFailure;
|
|
cn = PORT_ArenaStrdup(nickNames->arena, ipbuf);
|
|
if (!cn)
|
|
return SECFailure;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (cn) {
|
|
*currentOutput = cn;
|
|
nickNames->totallen += PORT_Strlen(cn);
|
|
++currentOutput;
|
|
--numberOfGeneralNames;
|
|
}
|
|
currentInput = CERT_GetNextGeneralName(currentInput);
|
|
} while (currentInput != firstName);
|
|
|
|
return (numberOfGeneralNames == 0) ? SECSuccess : SECFailure;
|
|
}
|
|
|
|
/*
|
|
* Collect all valid DNS names from the given cert.
|
|
* The output arena will reference some temporaray data,
|
|
* but this saves us from dealing with two arenas.
|
|
* The caller may free all data by freeing CERTCertNicknames->arena.
|
|
*/
|
|
CERTCertNicknames *
|
|
CERT_GetValidDNSPatternsFromCert(CERTCertificate *cert)
|
|
{
|
|
CERTGeneralName *generalNames;
|
|
CERTCertNicknames *nickNames;
|
|
PLArenaPool *arena;
|
|
char *singleName;
|
|
|
|
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
if (!arena) {
|
|
return NULL;
|
|
}
|
|
|
|
nickNames = PORT_ArenaAlloc(arena, sizeof(CERTCertNicknames));
|
|
if (!nickNames) {
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
return NULL;
|
|
}
|
|
|
|
/* init the structure */
|
|
nickNames->arena = arena;
|
|
nickNames->head = NULL;
|
|
nickNames->numnicknames = 0;
|
|
nickNames->nicknames = NULL;
|
|
nickNames->totallen = 0;
|
|
|
|
generalNames = cert_GetSubjectAltNameList(cert, arena);
|
|
if (generalNames) {
|
|
SECStatus rv_getnames = SECFailure;
|
|
PRUint32 numNames = cert_CountDNSPatterns(generalNames);
|
|
|
|
if (numNames) {
|
|
rv_getnames = cert_GetDNSPatternsFromGeneralNames(generalNames,
|
|
numNames, nickNames);
|
|
}
|
|
|
|
/* if there were names, we'll exit now, either with success or failure */
|
|
if (numNames) {
|
|
if (rv_getnames == SECSuccess) {
|
|
return nickNames;
|
|
}
|
|
|
|
/* failure to produce output */
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* no SAN extension or no names found in extension */
|
|
singleName = CERT_GetCommonName(&cert->subject);
|
|
if (singleName) {
|
|
nickNames->numnicknames = 1;
|
|
nickNames->nicknames = PORT_ArenaAlloc(arena, sizeof(char *));
|
|
if (nickNames->nicknames) {
|
|
*nickNames->nicknames = PORT_ArenaStrdup(arena, singleName);
|
|
}
|
|
PORT_Free(singleName);
|
|
|
|
/* Did we allocate both the buffer of pointers and the string? */
|
|
if (nickNames->nicknames && *nickNames->nicknames) {
|
|
return nickNames;
|
|
}
|
|
}
|
|
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
return NULL;
|
|
}
|
|
|
|
/* Make sure that the name of the host we are connecting to matches the
|
|
* name that is incoded in the common-name component of the certificate
|
|
* that they are using.
|
|
*/
|
|
SECStatus
|
|
CERT_VerifyCertName(const CERTCertificate *cert, const char *hn)
|
|
{
|
|
char * cn;
|
|
SECStatus rv;
|
|
CERTOKDomainName *domainOK;
|
|
|
|
if (!hn || !strlen(hn)) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return SECFailure;
|
|
}
|
|
|
|
/* if the name is one that the user has already approved, it's OK. */
|
|
for (domainOK = cert->domainOK; domainOK; domainOK = domainOK->next) {
|
|
if (0 == PORT_Strcasecmp(hn, domainOK->name)) {
|
|
return SECSuccess;
|
|
}
|
|
}
|
|
|
|
/* Per RFC 2818, if the SubjectAltName extension is present, it must
|
|
** be used as the cert's identity.
|
|
*/
|
|
rv = cert_VerifySubjectAltName(cert, hn);
|
|
if (rv == SECSuccess || PORT_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND)
|
|
return rv;
|
|
|
|
cn = CERT_GetCommonName(&cert->subject);
|
|
if ( cn ) {
|
|
PRBool isIPaddr = cert_IsIPAddr(hn);
|
|
if (isIPaddr) {
|
|
if (PORT_Strcasecmp(hn, cn) == 0) {
|
|
rv = SECSuccess;
|
|
} else {
|
|
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
|
|
rv = SECFailure;
|
|
}
|
|
} else {
|
|
rv = cert_TestHostName(cn, hn);
|
|
}
|
|
PORT_Free(cn);
|
|
} else
|
|
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
|
|
return rv;
|
|
}
|
|
|
|
PRBool
|
|
CERT_CompareCerts(const CERTCertificate *c1, const CERTCertificate *c2)
|
|
{
|
|
SECComparison comp;
|
|
|
|
comp = SECITEM_CompareItem(&c1->derCert, &c2->derCert);
|
|
if ( comp == SECEqual ) { /* certs are the same */
|
|
return(PR_TRUE);
|
|
} else {
|
|
return(PR_FALSE);
|
|
}
|
|
}
|
|
|
|
static SECStatus
|
|
StringsEqual(char *s1, char *s2) {
|
|
if ( ( s1 == NULL ) || ( s2 == NULL ) ) {
|
|
if ( s1 != s2 ) { /* only one is null */
|
|
return(SECFailure);
|
|
}
|
|
return(SECSuccess); /* both are null */
|
|
}
|
|
|
|
if ( PORT_Strcmp( s1, s2 ) != 0 ) {
|
|
return(SECFailure); /* not equal */
|
|
}
|
|
|
|
return(SECSuccess); /* strings are equal */
|
|
}
|
|
|
|
|
|
PRBool
|
|
CERT_CompareCertsForRedirection(CERTCertificate *c1, CERTCertificate *c2)
|
|
{
|
|
SECComparison comp;
|
|
char *c1str, *c2str;
|
|
SECStatus eq;
|
|
|
|
comp = SECITEM_CompareItem(&c1->derCert, &c2->derCert);
|
|
if ( comp == SECEqual ) { /* certs are the same */
|
|
return(PR_TRUE);
|
|
}
|
|
|
|
/* check if they are issued by the same CA */
|
|
comp = SECITEM_CompareItem(&c1->derIssuer, &c2->derIssuer);
|
|
if ( comp != SECEqual ) { /* different issuer */
|
|
return(PR_FALSE);
|
|
}
|
|
|
|
/* check country name */
|
|
c1str = CERT_GetCountryName(&c1->subject);
|
|
c2str = CERT_GetCountryName(&c2->subject);
|
|
eq = StringsEqual(c1str, c2str);
|
|
PORT_Free(c1str);
|
|
PORT_Free(c2str);
|
|
if ( eq != SECSuccess ) {
|
|
return(PR_FALSE);
|
|
}
|
|
|
|
/* check locality name */
|
|
c1str = CERT_GetLocalityName(&c1->subject);
|
|
c2str = CERT_GetLocalityName(&c2->subject);
|
|
eq = StringsEqual(c1str, c2str);
|
|
PORT_Free(c1str);
|
|
PORT_Free(c2str);
|
|
if ( eq != SECSuccess ) {
|
|
return(PR_FALSE);
|
|
}
|
|
|
|
/* check state name */
|
|
c1str = CERT_GetStateName(&c1->subject);
|
|
c2str = CERT_GetStateName(&c2->subject);
|
|
eq = StringsEqual(c1str, c2str);
|
|
PORT_Free(c1str);
|
|
PORT_Free(c2str);
|
|
if ( eq != SECSuccess ) {
|
|
return(PR_FALSE);
|
|
}
|
|
|
|
/* check org name */
|
|
c1str = CERT_GetOrgName(&c1->subject);
|
|
c2str = CERT_GetOrgName(&c2->subject);
|
|
eq = StringsEqual(c1str, c2str);
|
|
PORT_Free(c1str);
|
|
PORT_Free(c2str);
|
|
if ( eq != SECSuccess ) {
|
|
return(PR_FALSE);
|
|
}
|
|
|
|
#ifdef NOTDEF
|
|
/* check orgUnit name */
|
|
/*
|
|
* We need to revisit this and decide which fields should be allowed to be
|
|
* different
|
|
*/
|
|
c1str = CERT_GetOrgUnitName(&c1->subject);
|
|
c2str = CERT_GetOrgUnitName(&c2->subject);
|
|
eq = StringsEqual(c1str, c2str);
|
|
PORT_Free(c1str);
|
|
PORT_Free(c2str);
|
|
if ( eq != SECSuccess ) {
|
|
return(PR_FALSE);
|
|
}
|
|
#endif
|
|
|
|
return(PR_TRUE); /* all fields but common name are the same */
|
|
}
|
|
|
|
|
|
/* CERT_CertChainFromCert and CERT_DestroyCertificateList moved
|
|
to certhigh.c */
|
|
|
|
|
|
CERTIssuerAndSN *
|
|
CERT_GetCertIssuerAndSN(PLArenaPool *arena, CERTCertificate *cert)
|
|
{
|
|
CERTIssuerAndSN *result;
|
|
SECStatus rv;
|
|
|
|
if ( arena == NULL ) {
|
|
arena = cert->arena;
|
|
}
|
|
|
|
result = (CERTIssuerAndSN*)PORT_ArenaZAlloc(arena, sizeof(*result));
|
|
if (result == NULL) {
|
|
PORT_SetError (SEC_ERROR_NO_MEMORY);
|
|
return NULL;
|
|
}
|
|
|
|
rv = SECITEM_CopyItem(arena, &result->derIssuer, &cert->derIssuer);
|
|
if (rv != SECSuccess)
|
|
return NULL;
|
|
|
|
rv = CERT_CopyName(arena, &result->issuer, &cert->issuer);
|
|
if (rv != SECSuccess)
|
|
return NULL;
|
|
|
|
rv = SECITEM_CopyItem(arena, &result->serialNumber, &cert->serialNumber);
|
|
if (rv != SECSuccess)
|
|
return NULL;
|
|
|
|
return result;
|
|
}
|
|
|
|
char *
|
|
CERT_MakeCANickname(CERTCertificate *cert)
|
|
{
|
|
char *firstname = NULL;
|
|
char *org = NULL;
|
|
char *nickname = NULL;
|
|
int count;
|
|
CERTCertificate *dummycert;
|
|
|
|
firstname = CERT_GetCommonName(&cert->subject);
|
|
if ( firstname == NULL ) {
|
|
firstname = CERT_GetOrgUnitName(&cert->subject);
|
|
}
|
|
|
|
org = CERT_GetOrgName(&cert->issuer);
|
|
if (org == NULL) {
|
|
org = CERT_GetDomainComponentName(&cert->issuer);
|
|
if (org == NULL) {
|
|
if (firstname) {
|
|
org = firstname;
|
|
firstname = NULL;
|
|
} else {
|
|
org = PORT_Strdup("Unknown CA");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* can only fail if PORT_Strdup fails, in which case
|
|
* we're having memory problems. */
|
|
if (org == NULL) {
|
|
goto done;
|
|
}
|
|
|
|
|
|
count = 1;
|
|
while ( 1 ) {
|
|
|
|
if ( firstname ) {
|
|
if ( count == 1 ) {
|
|
nickname = PR_smprintf("%s - %s", firstname, org);
|
|
} else {
|
|
nickname = PR_smprintf("%s - %s #%d", firstname, org, count);
|
|
}
|
|
} else {
|
|
if ( count == 1 ) {
|
|
nickname = PR_smprintf("%s", org);
|
|
} else {
|
|
nickname = PR_smprintf("%s #%d", org, count);
|
|
}
|
|
}
|
|
if ( nickname == NULL ) {
|
|
goto done;
|
|
}
|
|
|
|
/* look up the nickname to make sure it isn't in use already */
|
|
dummycert = CERT_FindCertByNickname(cert->dbhandle, nickname);
|
|
|
|
if ( dummycert == NULL ) {
|
|
goto done;
|
|
}
|
|
|
|
/* found a cert, destroy it and loop */
|
|
CERT_DestroyCertificate(dummycert);
|
|
|
|
/* free the nickname */
|
|
PORT_Free(nickname);
|
|
|
|
count++;
|
|
}
|
|
|
|
done:
|
|
if ( firstname ) {
|
|
PORT_Free(firstname);
|
|
}
|
|
if ( org ) {
|
|
PORT_Free(org);
|
|
}
|
|
|
|
return(nickname);
|
|
}
|
|
|
|
/* CERT_Import_CAChain moved to certhigh.c */
|
|
|
|
void
|
|
CERT_DestroyCrl (CERTSignedCrl *crl)
|
|
{
|
|
SEC_DestroyCrl (crl);
|
|
}
|
|
|
|
static int
|
|
cert_Version(CERTCertificate *cert)
|
|
{
|
|
int version = 0;
|
|
if (cert && cert->version.data && cert->version.len) {
|
|
version = DER_GetInteger(&cert->version);
|
|
if (version < 0)
|
|
version = 0;
|
|
}
|
|
return version;
|
|
}
|
|
|
|
static unsigned int
|
|
cert_ComputeTrustOverrides(CERTCertificate *cert, unsigned int cType)
|
|
{
|
|
CERTCertTrust trust;
|
|
SECStatus rv = SECFailure;
|
|
|
|
rv = CERT_GetCertTrust(cert, &trust);
|
|
|
|
if (rv == SECSuccess && (trust.sslFlags |
|
|
trust.emailFlags |
|
|
trust.objectSigningFlags)) {
|
|
|
|
if (trust.sslFlags & (CERTDB_TERMINAL_RECORD|CERTDB_TRUSTED))
|
|
cType |= NS_CERT_TYPE_SSL_SERVER|NS_CERT_TYPE_SSL_CLIENT;
|
|
if (trust.sslFlags & (CERTDB_VALID_CA|CERTDB_TRUSTED_CA))
|
|
cType |= NS_CERT_TYPE_SSL_CA;
|
|
#if defined(CERTDB_NOT_TRUSTED)
|
|
if (trust.sslFlags & CERTDB_NOT_TRUSTED)
|
|
cType &= ~(NS_CERT_TYPE_SSL_SERVER|NS_CERT_TYPE_SSL_CLIENT|
|
|
NS_CERT_TYPE_SSL_CA);
|
|
#endif
|
|
if (trust.emailFlags & (CERTDB_TERMINAL_RECORD|CERTDB_TRUSTED))
|
|
cType |= NS_CERT_TYPE_EMAIL;
|
|
if (trust.emailFlags & (CERTDB_VALID_CA|CERTDB_TRUSTED_CA))
|
|
cType |= NS_CERT_TYPE_EMAIL_CA;
|
|
#if defined(CERTDB_NOT_TRUSTED)
|
|
if (trust.emailFlags & CERTDB_NOT_TRUSTED)
|
|
cType &= ~(NS_CERT_TYPE_EMAIL|NS_CERT_TYPE_EMAIL_CA);
|
|
#endif
|
|
if (trust.objectSigningFlags & (CERTDB_TERMINAL_RECORD|CERTDB_TRUSTED))
|
|
cType |= NS_CERT_TYPE_OBJECT_SIGNING;
|
|
if (trust.objectSigningFlags & (CERTDB_VALID_CA|CERTDB_TRUSTED_CA))
|
|
cType |= NS_CERT_TYPE_OBJECT_SIGNING_CA;
|
|
#if defined(CERTDB_NOT_TRUSTED)
|
|
if (trust.objectSigningFlags & CERTDB_NOT_TRUSTED)
|
|
cType &= ~(NS_CERT_TYPE_OBJECT_SIGNING|
|
|
NS_CERT_TYPE_OBJECT_SIGNING_CA);
|
|
#endif
|
|
}
|
|
return cType;
|
|
}
|
|
|
|
/*
|
|
* Does a cert belong to a CA? We decide based on perm database trust
|
|
* flags, Netscape Cert Type Extension, and KeyUsage Extension.
|
|
*/
|
|
PRBool
|
|
CERT_IsCACert(CERTCertificate *cert, unsigned int *rettype)
|
|
{
|
|
unsigned int cType = cert->nsCertType;
|
|
PRBool ret = PR_FALSE;
|
|
|
|
if (cType & (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA |
|
|
NS_CERT_TYPE_OBJECT_SIGNING_CA)) {
|
|
ret = PR_TRUE;
|
|
} else {
|
|
SECStatus rv;
|
|
CERTBasicConstraints constraints;
|
|
|
|
rv = CERT_FindBasicConstraintExten(cert, &constraints);
|
|
if (rv == SECSuccess && constraints.isCA) {
|
|
ret = PR_TRUE;
|
|
cType |= (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA);
|
|
}
|
|
}
|
|
|
|
/* finally check if it's an X.509 v1 root CA */
|
|
if (!ret &&
|
|
(cert->isRoot && cert_Version(cert) < SEC_CERTIFICATE_VERSION_3)) {
|
|
ret = PR_TRUE;
|
|
cType |= (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA);
|
|
}
|
|
/* Now apply trust overrides, if any */
|
|
cType = cert_ComputeTrustOverrides(cert, cType);
|
|
ret = (cType & (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA |
|
|
NS_CERT_TYPE_OBJECT_SIGNING_CA)) ? PR_TRUE : PR_FALSE;
|
|
|
|
if (rettype != NULL) {
|
|
*rettype = cType;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
PRBool
|
|
CERT_IsCADERCert(SECItem *derCert, unsigned int *type) {
|
|
CERTCertificate *cert;
|
|
PRBool isCA;
|
|
|
|
/* This is okay -- only looks at extensions */
|
|
cert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL);
|
|
if (cert == NULL) return PR_FALSE;
|
|
|
|
isCA = CERT_IsCACert(cert,type);
|
|
CERT_DestroyCertificate (cert);
|
|
return isCA;
|
|
}
|
|
|
|
PRBool
|
|
CERT_IsRootDERCert(SECItem *derCert)
|
|
{
|
|
CERTCertificate *cert;
|
|
PRBool isRoot;
|
|
|
|
/* This is okay -- only looks at extensions */
|
|
cert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL);
|
|
if (cert == NULL) return PR_FALSE;
|
|
|
|
isRoot = cert->isRoot;
|
|
CERT_DestroyCertificate (cert);
|
|
return isRoot;
|
|
}
|
|
|
|
CERTCompareValidityStatus
|
|
CERT_CompareValidityTimes(CERTValidity* val_a, CERTValidity* val_b)
|
|
{
|
|
PRTime notBeforeA, notBeforeB, notAfterA, notAfterB;
|
|
|
|
if (!val_a || !val_b)
|
|
{
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return certValidityUndetermined;
|
|
}
|
|
|
|
if ( SECSuccess != DER_DecodeTimeChoice(¬BeforeA, &val_a->notBefore) ||
|
|
SECSuccess != DER_DecodeTimeChoice(¬BeforeB, &val_b->notBefore) ||
|
|
SECSuccess != DER_DecodeTimeChoice(¬AfterA, &val_a->notAfter) ||
|
|
SECSuccess != DER_DecodeTimeChoice(¬AfterB, &val_b->notAfter) ) {
|
|
return certValidityUndetermined;
|
|
}
|
|
|
|
/* sanity check */
|
|
if (LL_CMP(notBeforeA,>,notAfterA) || LL_CMP(notBeforeB,>,notAfterB)) {
|
|
PORT_SetError(SEC_ERROR_INVALID_TIME);
|
|
return certValidityUndetermined;
|
|
}
|
|
|
|
if (LL_CMP(notAfterA,!=,notAfterB)) {
|
|
/* one cert validity goes farther into the future, select it */
|
|
return LL_CMP(notAfterA,<,notAfterB) ?
|
|
certValidityChooseB : certValidityChooseA;
|
|
}
|
|
/* the two certs have the same expiration date */
|
|
PORT_Assert(LL_CMP(notAfterA, == , notAfterB));
|
|
/* do they also have the same start date ? */
|
|
if (LL_CMP(notBeforeA,==,notBeforeB)) {
|
|
return certValidityEqual;
|
|
}
|
|
/* choose cert with the later start date */
|
|
return LL_CMP(notBeforeA,<,notBeforeB) ?
|
|
certValidityChooseB : certValidityChooseA;
|
|
}
|
|
|
|
/*
|
|
* is certa newer than certb? If one is expired, pick the other one.
|
|
*/
|
|
PRBool
|
|
CERT_IsNewer(CERTCertificate *certa, CERTCertificate *certb)
|
|
{
|
|
PRTime notBeforeA, notAfterA, notBeforeB, notAfterB, now;
|
|
SECStatus rv;
|
|
PRBool newerbefore, newerafter;
|
|
|
|
rv = CERT_GetCertTimes(certa, ¬BeforeA, ¬AfterA);
|
|
if ( rv != SECSuccess ) {
|
|
return(PR_FALSE);
|
|
}
|
|
|
|
rv = CERT_GetCertTimes(certb, ¬BeforeB, ¬AfterB);
|
|
if ( rv != SECSuccess ) {
|
|
return(PR_TRUE);
|
|
}
|
|
|
|
newerbefore = PR_FALSE;
|
|
if ( LL_CMP(notBeforeA, >, notBeforeB) ) {
|
|
newerbefore = PR_TRUE;
|
|
}
|
|
|
|
newerafter = PR_FALSE;
|
|
if ( LL_CMP(notAfterA, >, notAfterB) ) {
|
|
newerafter = PR_TRUE;
|
|
}
|
|
|
|
if ( newerbefore && newerafter ) {
|
|
return(PR_TRUE);
|
|
}
|
|
|
|
if ( ( !newerbefore ) && ( !newerafter ) ) {
|
|
return(PR_FALSE);
|
|
}
|
|
|
|
/* get current time */
|
|
now = PR_Now();
|
|
|
|
if ( newerbefore ) {
|
|
/* cert A was issued after cert B, but expires sooner */
|
|
/* if A is expired, then pick B */
|
|
if ( LL_CMP(notAfterA, <, now ) ) {
|
|
return(PR_FALSE);
|
|
}
|
|
return(PR_TRUE);
|
|
} else {
|
|
/* cert B was issued after cert A, but expires sooner */
|
|
/* if B is expired, then pick A */
|
|
if ( LL_CMP(notAfterB, <, now ) ) {
|
|
return(PR_TRUE);
|
|
}
|
|
return(PR_FALSE);
|
|
}
|
|
}
|
|
|
|
void
|
|
CERT_DestroyCertArray(CERTCertificate **certs, unsigned int ncerts)
|
|
{
|
|
unsigned int i;
|
|
|
|
if ( certs ) {
|
|
for ( i = 0; i < ncerts; i++ ) {
|
|
if ( certs[i] ) {
|
|
CERT_DestroyCertificate(certs[i]);
|
|
}
|
|
}
|
|
|
|
PORT_Free(certs);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
char *
|
|
CERT_FixupEmailAddr(const char *emailAddr)
|
|
{
|
|
char *retaddr;
|
|
char *str;
|
|
|
|
if ( emailAddr == NULL ) {
|
|
return(NULL);
|
|
}
|
|
|
|
/* copy the string */
|
|
str = retaddr = PORT_Strdup(emailAddr);
|
|
if ( str == NULL ) {
|
|
return(NULL);
|
|
}
|
|
|
|
/* make it lower case */
|
|
while ( *str ) {
|
|
*str = tolower( *str );
|
|
str++;
|
|
}
|
|
|
|
return(retaddr);
|
|
}
|
|
|
|
/*
|
|
* NOTE - don't allow encode of govt-approved or invisible bits
|
|
*/
|
|
SECStatus
|
|
CERT_DecodeTrustString(CERTCertTrust *trust, const char *trusts)
|
|
{
|
|
unsigned int i;
|
|
unsigned int *pflags;
|
|
|
|
if (!trust) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return SECFailure;
|
|
}
|
|
trust->sslFlags = 0;
|
|
trust->emailFlags = 0;
|
|
trust->objectSigningFlags = 0;
|
|
if (!trusts) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return SECFailure;
|
|
}
|
|
|
|
pflags = &trust->sslFlags;
|
|
|
|
for (i=0; i < PORT_Strlen(trusts); i++) {
|
|
switch (trusts[i]) {
|
|
case 'p':
|
|
*pflags = *pflags | CERTDB_TERMINAL_RECORD;
|
|
break;
|
|
|
|
case 'P':
|
|
*pflags = *pflags | CERTDB_TRUSTED | CERTDB_TERMINAL_RECORD;
|
|
break;
|
|
|
|
case 'w':
|
|
*pflags = *pflags | CERTDB_SEND_WARN;
|
|
break;
|
|
|
|
case 'c':
|
|
*pflags = *pflags | CERTDB_VALID_CA;
|
|
break;
|
|
|
|
case 'T':
|
|
*pflags = *pflags | CERTDB_TRUSTED_CLIENT_CA | CERTDB_VALID_CA;
|
|
break;
|
|
|
|
case 'C' :
|
|
*pflags = *pflags | CERTDB_TRUSTED_CA | CERTDB_VALID_CA;
|
|
break;
|
|
|
|
case 'u':
|
|
*pflags = *pflags | CERTDB_USER;
|
|
break;
|
|
|
|
case 'i':
|
|
*pflags = *pflags | CERTDB_INVISIBLE_CA;
|
|
break;
|
|
case 'g':
|
|
*pflags = *pflags | CERTDB_GOVT_APPROVED_CA;
|
|
break;
|
|
|
|
case ',':
|
|
if ( pflags == &trust->sslFlags ) {
|
|
pflags = &trust->emailFlags;
|
|
} else {
|
|
pflags = &trust->objectSigningFlags;
|
|
}
|
|
break;
|
|
default:
|
|
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) return CERT_AddCertToListTail(certs,cert);
|
|
|
|
node = (CERTCertListNode *)PORT_ArenaZAlloc(certs->arena,
|
|
sizeof(CERTCertListNode));
|
|
if ( node == NULL ) {
|
|
goto loser;
|
|
}
|
|
|
|
PR_INSERT_BEFORE(&node->links, &head->links);
|
|
/* certs->count++; */
|
|
node->cert = cert;
|
|
node->appData = appData;
|
|
return(SECSuccess);
|
|
|
|
loser:
|
|
return(SECFailure);
|
|
}
|
|
|
|
SECStatus
|
|
CERT_AddCertToListHead(CERTCertList *certs, CERTCertificate *cert)
|
|
{
|
|
return CERT_AddCertToListHeadWithData(certs, cert, NULL);
|
|
}
|
|
|
|
/*
|
|
* Sort callback function to determine if cert a is newer than cert b.
|
|
* Not valid certs are considered older than valid certs.
|
|
*/
|
|
PRBool
|
|
CERT_SortCBValidity(CERTCertificate *certa,
|
|
CERTCertificate *certb,
|
|
void *arg)
|
|
{
|
|
PRTime sorttime;
|
|
PRTime notBeforeA, notAfterA, notBeforeB, notAfterB;
|
|
SECStatus rv;
|
|
PRBool newerbefore, newerafter;
|
|
PRBool aNotValid = PR_FALSE, bNotValid = PR_FALSE;
|
|
|
|
sorttime = *(PRTime *)arg;
|
|
|
|
rv = CERT_GetCertTimes(certa, ¬BeforeA, ¬AfterA);
|
|
if ( rv != SECSuccess ) {
|
|
return(PR_FALSE);
|
|
}
|
|
|
|
rv = CERT_GetCertTimes(certb, ¬BeforeB, ¬AfterB);
|
|
if ( rv != SECSuccess ) {
|
|
return(PR_TRUE);
|
|
}
|
|
newerbefore = PR_FALSE;
|
|
if ( LL_CMP(notBeforeA, >, notBeforeB) ) {
|
|
newerbefore = PR_TRUE;
|
|
}
|
|
newerafter = PR_FALSE;
|
|
if ( LL_CMP(notAfterA, >, notAfterB) ) {
|
|
newerafter = PR_TRUE;
|
|
}
|
|
|
|
/* check if A is valid at sorttime */
|
|
if ( CERT_CheckCertValidTimes(certa, sorttime, PR_FALSE)
|
|
!= secCertTimeValid ) {
|
|
aNotValid = PR_TRUE;
|
|
}
|
|
|
|
/* check if B is valid at sorttime */
|
|
if ( CERT_CheckCertValidTimes(certb, sorttime, PR_FALSE)
|
|
!= secCertTimeValid ) {
|
|
bNotValid = PR_TRUE;
|
|
}
|
|
|
|
/* a is valid, b is not */
|
|
if ( bNotValid && ( ! aNotValid ) ) {
|
|
return(PR_TRUE);
|
|
}
|
|
|
|
/* b is valid, a is not */
|
|
if ( aNotValid && ( ! bNotValid ) ) {
|
|
return(PR_FALSE);
|
|
}
|
|
|
|
/* a and b are either valid or not valid */
|
|
if ( newerbefore && newerafter ) {
|
|
return(PR_TRUE);
|
|
}
|
|
|
|
if ( ( !newerbefore ) && ( !newerafter ) ) {
|
|
return(PR_FALSE);
|
|
}
|
|
|
|
if ( newerbefore ) {
|
|
/* cert A was issued after cert B, but expires sooner */
|
|
return(PR_TRUE);
|
|
} else {
|
|
/* cert B was issued after cert A, but expires sooner */
|
|
return(PR_FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
SECStatus
|
|
CERT_AddCertToListSorted(CERTCertList *certs,
|
|
CERTCertificate *cert,
|
|
CERTSortCallback f,
|
|
void *arg)
|
|
{
|
|
CERTCertListNode *node;
|
|
CERTCertListNode *head;
|
|
PRBool ret;
|
|
|
|
node = (CERTCertListNode *)PORT_ArenaZAlloc(certs->arena,
|
|
sizeof(CERTCertListNode));
|
|
if ( node == NULL ) {
|
|
goto loser;
|
|
}
|
|
|
|
head = CERT_LIST_HEAD(certs);
|
|
|
|
while ( !CERT_LIST_END(head, certs) ) {
|
|
|
|
/* if cert is already in the list, then don't add it again */
|
|
if ( cert == head->cert ) {
|
|
/*XXX*/
|
|
/* don't keep a reference */
|
|
CERT_DestroyCertificate(cert);
|
|
goto done;
|
|
}
|
|
|
|
ret = (* f)(cert, head->cert, arg);
|
|
/* if sort function succeeds, then insert before current node */
|
|
if ( ret ) {
|
|
PR_INSERT_BEFORE(&node->links, &head->links);
|
|
goto done;
|
|
}
|
|
|
|
head = CERT_LIST_NEXT(head);
|
|
}
|
|
/* if we get to the end, then just insert it at the tail */
|
|
PR_INSERT_BEFORE(&node->links, &certs->list);
|
|
|
|
done:
|
|
/* certs->count++; */
|
|
node->cert = cert;
|
|
return(SECSuccess);
|
|
|
|
loser:
|
|
return(SECFailure);
|
|
}
|
|
|
|
/* This routine is here because pcertdb.c still has a call to it.
|
|
* The SMIME profile code in pcertdb.c should be split into high (find
|
|
* the email cert) and low (store the profile) code. At that point, we
|
|
* can move this to certhigh.c where it belongs.
|
|
*
|
|
* remove certs from a list that don't have keyUsage and certType
|
|
* that match the given usage.
|
|
*/
|
|
SECStatus
|
|
CERT_FilterCertListByUsage(CERTCertList *certList, SECCertUsage usage,
|
|
PRBool ca)
|
|
{
|
|
unsigned int requiredKeyUsage;
|
|
unsigned int requiredCertType;
|
|
CERTCertListNode *node, *savenode;
|
|
SECStatus rv;
|
|
|
|
if (certList == NULL) goto loser;
|
|
|
|
rv = CERT_KeyUsageAndTypeForCertUsage(usage, ca, &requiredKeyUsage,
|
|
&requiredCertType);
|
|
if ( rv != SECSuccess ) {
|
|
goto loser;
|
|
}
|
|
|
|
node = CERT_LIST_HEAD(certList);
|
|
|
|
while ( !CERT_LIST_END(node, certList) ) {
|
|
|
|
PRBool bad = (PRBool)(!node->cert);
|
|
|
|
/* bad key usage ? */
|
|
if ( !bad &&
|
|
CERT_CheckKeyUsage(node->cert, requiredKeyUsage) != SECSuccess ) {
|
|
bad = PR_TRUE;
|
|
}
|
|
/* bad cert type ? */
|
|
if ( !bad ) {
|
|
unsigned int certType = 0;
|
|
if ( ca ) {
|
|
/* This function returns a more comprehensive cert type that
|
|
* takes trust flags into consideration. Should probably
|
|
* fix the cert decoding code to do this.
|
|
*/
|
|
(void)CERT_IsCACert(node->cert, &certType);
|
|
} else {
|
|
certType = node->cert->nsCertType;
|
|
}
|
|
if ( !( certType & requiredCertType ) ) {
|
|
bad = PR_TRUE;
|
|
}
|
|
}
|
|
|
|
if ( bad ) {
|
|
/* remove the node if it is bad */
|
|
savenode = CERT_LIST_NEXT(node);
|
|
CERT_RemoveCertListNode(node);
|
|
node = savenode;
|
|
} else {
|
|
node = CERT_LIST_NEXT(node);
|
|
}
|
|
}
|
|
return(SECSuccess);
|
|
|
|
loser:
|
|
return(SECFailure);
|
|
}
|
|
|
|
PRBool CERT_IsUserCert(CERTCertificate* cert)
|
|
{
|
|
CERTCertTrust trust;
|
|
SECStatus rv = SECFailure;
|
|
|
|
rv = CERT_GetCertTrust(cert, &trust);
|
|
if (rv == SECSuccess &&
|
|
((trust.sslFlags & CERTDB_USER ) ||
|
|
(trust.emailFlags & CERTDB_USER ) ||
|
|
(trust.objectSigningFlags & CERTDB_USER )) ) {
|
|
return PR_TRUE;
|
|
} else {
|
|
return PR_FALSE;
|
|
}
|
|
}
|
|
|
|
SECStatus
|
|
CERT_FilterCertListForUserCerts(CERTCertList *certList)
|
|
{
|
|
CERTCertListNode *node, *freenode;
|
|
CERTCertificate *cert;
|
|
|
|
if (!certList) {
|
|
return SECFailure;
|
|
}
|
|
|
|
node = CERT_LIST_HEAD(certList);
|
|
|
|
while ( ! CERT_LIST_END(node, certList) ) {
|
|
cert = node->cert;
|
|
if ( PR_TRUE != CERT_IsUserCert(cert) ) {
|
|
/* Not a User Cert, so remove this cert from the list */
|
|
freenode = node;
|
|
node = CERT_LIST_NEXT(node);
|
|
CERT_RemoveCertListNode(freenode);
|
|
} else {
|
|
/* Is a User cert, so leave it in the list */
|
|
node = CERT_LIST_NEXT(node);
|
|
}
|
|
}
|
|
|
|
return(SECSuccess);
|
|
}
|
|
|
|
static PZLock *certRefCountLock = NULL;
|
|
|
|
/*
|
|
* Acquire the cert reference count lock
|
|
* There is currently one global lock for all certs, but I'm putting a cert
|
|
* arg here so that it will be easy to make it per-cert in the future if
|
|
* that turns out to be necessary.
|
|
*/
|
|
void
|
|
CERT_LockCertRefCount(CERTCertificate *cert)
|
|
{
|
|
PORT_Assert(certRefCountLock != NULL);
|
|
PZ_Lock(certRefCountLock);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Free the cert reference count lock
|
|
*/
|
|
void
|
|
CERT_UnlockCertRefCount(CERTCertificate *cert)
|
|
{
|
|
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);
|
|
return;
|
|
}
|
|
|
|
SECStatus
|
|
cert_InitLocks(void)
|
|
{
|
|
if ( certRefCountLock == NULL ) {
|
|
certRefCountLock = PZ_NewLock(nssILockRefLock);
|
|
PORT_Assert(certRefCountLock != NULL);
|
|
if (!certRefCountLock) {
|
|
return SECFailure;
|
|
}
|
|
}
|
|
|
|
if ( certTrustLock == NULL ) {
|
|
certTrustLock = PZ_NewLock(nssILockCertDB);
|
|
PORT_Assert(certTrustLock != NULL);
|
|
if (!certTrustLock) {
|
|
PZ_DestroyLock(certRefCountLock);
|
|
certRefCountLock = NULL;
|
|
return SECFailure;
|
|
}
|
|
}
|
|
|
|
return SECSuccess;
|
|
}
|
|
|
|
SECStatus
|
|
cert_DestroyLocks(void)
|
|
{
|
|
SECStatus rv = SECSuccess;
|
|
|
|
PORT_Assert(certRefCountLock != NULL);
|
|
if (certRefCountLock) {
|
|
PZ_DestroyLock(certRefCountLock);
|
|
certRefCountLock = NULL;
|
|
} else {
|
|
rv = SECFailure;
|
|
}
|
|
|
|
PORT_Assert(certTrustLock != NULL);
|
|
if (certTrustLock) {
|
|
PZ_DestroyLock(certTrustLock);
|
|
certTrustLock = NULL;
|
|
} else {
|
|
rv = SECFailure;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* Free the cert trust lock
|
|
*/
|
|
void
|
|
CERT_UnlockCertTrust(const CERTCertificate *cert)
|
|
{
|
|
PORT_Assert(certTrustLock != NULL);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
PRStatus prstat = PZ_Unlock(certTrustLock);
|
|
PORT_Assert(prstat == PR_SUCCESS);
|
|
}
|
|
#else
|
|
PZ_Unlock(certTrustLock);
|
|
#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;
|
|
}
|