blob: 481b64bd71e5d45aba5b8f26c87ced6d19e72345 [file] [log] [blame]
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Netscape security libraries.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1994-2000
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Aaron Spangler <aaron@spangler.ods.org>
* Kaspar Brand <mozbugzilla@velox.ch>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/*
* Certificate handling code
*
* $Id: certdb.c,v 1.101 2009/05/18 21:33:25 nelson%bolyard.com Exp $
*/
#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(PRArenaPool *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;
PRArenaPool *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;
PRArenaPool *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;
PRArenaPool *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(PRArenaPool *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);
}
/*
* determine if a fortezza V1 Cert is a CA or not.
*/
static PRBool
fortezzaIsCA( CERTCertificate *cert) {
PRBool isCA = PR_FALSE;
CERTSubjectPublicKeyInfo *spki = &cert->subjectPublicKeyInfo;
int tag;
tag = SECOID_GetAlgorithmTag(&spki->algorithm);
if ((tag == SEC_OID_MISSI_KEA_DSS_OLD) ||
(tag == SEC_OID_MISSI_KEA_DSS) ||
(tag == SEC_OID_MISSI_DSS_OLD) ||
(tag == SEC_OID_MISSI_DSS) ) {
SECItem rawkey;
unsigned char *rawptr;
unsigned char *end;
int len;
rawkey = spki->subjectPublicKey;
DER_ConvertBitString(&rawkey);
rawptr = rawkey.data;
end = rawkey.data + rawkey.len;
/* version */
rawptr += sizeof(((SECKEYPublicKey*)0)->u.fortezza.KMID)+2;
/* clearance (the string up to the first byte with the hi-bit on */
while ((rawptr < end) && (*rawptr++ & 0x80));
if (rawptr >= end) { return PR_FALSE; }
/* KEAPrivilege (the string up to the first byte with the hi-bit on */
while ((rawptr < end) && (*rawptr++ & 0x80));
if (rawptr >= end) { return PR_FALSE; }
/* skip the key */
len = (*rawptr << 8) | rawptr[1];
rawptr += 2 + len;
/* shared key */
if (rawptr >= end) { return PR_FALSE; }
/* DSS Version is next */
rawptr += 2;
/* DSSPrivilege (the string up to the first byte with the hi-bit on */
if (*rawptr & 0x30) isCA = PR_TRUE;
}
return isCA;
}
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_AtomicSet((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. */
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 the cert is a fortezza CA cert, then allow SSL CA and EMAIL CA */
if (fortezzaIsCA(cert)) {
nsCertType |= NS_CERT_TYPE_SSL_CA;
nsCertType |= NS_CERT_TYPE_EMAIL_CA;
}
}
if (encodedExtKeyUsage.data != NULL) {
PORT_Free(encodedExtKeyUsage.data);
}
if (extKeyUsage != NULL) {
CERT_DestroyOidSequence(extKeyUsage);
}
return nsCertType;
}
/*
* cert_GetKeyID() - extract or generate the subjectKeyID from a certificate
*/
SECStatus
cert_GetKeyID(CERTCertificate *cert)
{
SECItem tmpitem;
SECStatus rv;
SECKEYPublicKey *key;
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 and the cert is
* a V1 fortezza certificate, use the cert's 8 byte KMID as the
* key identifier. */
key = CERT_KMIDPublicKey(cert);
if (key != NULL) {
if (key->keyType == fortezzaKey) {
cert->subjectKeyID.data = (unsigned char *)PORT_ArenaAlloc(cert->arena, 8);
if ( cert->subjectKeyID.data != NULL ) {
PORT_Memcpy(cert->subjectKeyID.data, key->u.fortezza.KMID, 8);
cert->subjectKeyID.len = 8;
cert->keyIDGenerated = PR_FALSE;
}
}
SECKEY_DestroyPublicKey(key);
}
/* 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;
PRArenaPool *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(int64 notBefore, int64 notAfter)
{
CERTValidity *v;
int rv;
PRArenaPool *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(PRArenaPool *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(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(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, &notBefore, &notAfter);
if (rv) {
return(secCertTimeExpired); /*XXX is this the right thing to do here?*/
}
LL_I2L(llPendingSlop, pendingSlop);
/* convert to micro seconds */
LL_UI2L(tmp1, PR_USEC_PER_SEC);
LL_MUL(llPendingSlop, llPendingSlop, tmp1);
LL_SUB(notBefore, notBefore, llPendingSlop);
if ( LL_CMP( t, <, notBefore ) ) {
PORT_SetError(SEC_ERROR_EXPIRED_CERTIFICATE);
return(secCertTimeNotValidYet);
}
if ( LL_CMP( t, >, notAfter) ) {
PORT_SetError(SEC_ERROR_EXPIRED_CERTIFICATE);
return(secCertTimeExpired);
}
return(secCertTimeValid);
}
SECStatus
SEC_GetCrlTimes(CERTCrl *date, PRTime *notBefore, PRTime *notAfter)
{
int rv;
/* convert DER not-before time */
rv = DER_DecodeTimeChoice(notBefore, &date->lastUpdate);
if (rv) {
return(SECFailure);
}
/* convert DER not-after time */
if (date->nextUpdate.data) {
rv = DER_DecodeTimeChoice(notAfter, &date->nextUpdate);
if (rv) {
return(SECFailure);
}
}
else {
LL_I2L(*notAfter, 0L);
}
return(SECSuccess);
}
/* These routines should probably be combined with the cert
* routines using an common extraction routine.
*/
SECCertTimeValidity
SEC_CheckCrlTimes(CERTCrl *crl, PRTime t) {
PRTime notBefore, notAfter, llPendingSlop, tmp1;
SECStatus rv;
rv = SEC_GetCrlTimes(crl, &notBefore, &notAfter);
if (rv) {
return(secCertTimeExpired);
}
LL_I2L(llPendingSlop, pendingSlop);
/* convert to micro seconds */
LL_I2L(tmp1, PR_USEC_PER_SEC);
LL_MUL(llPendingSlop, llPendingSlop, tmp1);
LL_SUB(notBefore, notBefore, llPendingSlop);
if ( LL_CMP( t, <, notBefore ) ) {
return(secCertTimeNotValidYet);
}
/* If next update is omitted and the test for notBefore passes, then
we assume that the crl is up to date.
*/
if ( LL_IS_ZERO(notAfter) ) {
return(secCertTimeValid);
}
if ( LL_CMP( t, >, notAfter) ) {
return(secCertTimeExpired);
}
return(secCertTimeValid);
}
PRBool
SEC_CrlIsNewer(CERTCrl *inNew, CERTCrl *old) {
PRTime newNotBefore, newNotAfter;
PRTime oldNotBefore, oldNotAfter;
SECStatus rv;
/* problems with the new CRL? reject it */
rv = SEC_GetCrlTimes(inNew, &newNotBefore, &newNotAfter);
if (rv) return PR_FALSE;
/* problems with the old CRL? replace it */
rv = SEC_GetCrlTimes(old, &oldNotBefore, &oldNotAfter);
if (rv) return PR_TRUE;
/* Question: what about the notAfter's? */
return ((PRBool)LL_CMP(oldNotBefore, <, newNotBefore));
}
/*
* return required key usage and cert type based on cert usage
*/
SECStatus
CERT_KeyUsageAndTypeForCertUsage(SECCertUsage usage,
PRBool ca,
unsigned int *retKeyUsage,
unsigned int *retCertType)
{
unsigned int requiredKeyUsage = 0;
unsigned int requiredCertType = 0;
if ( ca ) {
switch ( usage ) {
case certUsageSSLServerWithStepUp:
requiredKeyUsage = KU_NS_GOVT_APPROVED | KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_SSL_CA;
break;
case certUsageSSLClient:
requiredKeyUsage = KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_SSL_CA;
break;
case certUsageSSLServer:
requiredKeyUsage = KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_SSL_CA;
break;
case certUsageSSLCA:
requiredKeyUsage = KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_SSL_CA;
break;
case certUsageEmailSigner:
requiredKeyUsage = KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_EMAIL_CA;
break;
case certUsageEmailRecipient:
requiredKeyUsage = KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_EMAIL_CA;
break;
case certUsageObjectSigner:
requiredKeyUsage = KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_OBJECT_SIGNING_CA;
break;
case certUsageAnyCA:
case certUsageVerifyCA:
case certUsageStatusResponder:
requiredKeyUsage = KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_OBJECT_SIGNING_CA |
NS_CERT_TYPE_EMAIL_CA |
NS_CERT_TYPE_SSL_CA;
break;
default:
PORT_Assert(0);
goto loser;
}
} else {
switch ( usage ) {
case certUsageSSLClient:
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;
requiredCertType = NS_CERT_TYPE_EMAIL;
break;
case certUsageEmailRecipient:
requiredKeyUsage = KU_KEY_AGREEMENT_OR_ENCIPHERMENT;
requiredCertType = NS_CERT_TYPE_EMAIL;
break;
case certUsageObjectSigner:
requiredKeyUsage = KU_DIGITAL_SIGNATURE;
requiredCertType = NS_CERT_TYPE_OBJECT_SIGNING;
break;
case certUsageStatusResponder:
requiredKeyUsage = KU_DIGITAL_SIGNATURE;
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)
{
unsigned int certKeyUsage;
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 fortezzaKey:
case keaKey:
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;
}
}
certKeyUsage = cert->keyUsage;
if (certKeyUsage & KU_NON_REPUDIATION)
certKeyUsage |= KU_DIGITAL_SIGNATURE;
if ( (certKeyUsage & 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;
}
/*
** 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 2818. */
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
*/
if (wildcard && secondcndot && secondcndot[1] && firsthndot
&& firstcndot - wildcard == 1
&& secondcndot - firstcndot > 1
&& PORT_Strrchr(cn, '*') == wildcard
&& !PORT_Strncasecmp(cn, hn, wildcard - cn)
&& !PORT_Strcasecmp(firstcndot, firsthndot)) {
/* 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(CERTCertificate *cert, const char *hn)
{
PRArenaPool * arena = NULL;
CERTGeneralName * nameList = NULL;
CERTGeneralName * current;
char * cn;
int cnBufLen;
unsigned int hnLen;
int DNSextCount = 0;
int IPextCount = 0;
PRBool isIPaddr = PR_FALSE;
SECStatus rv = SECFailure;
SECItem subAltName;
PRNetAddr netAddr;
char cnbuf[128];
subAltName.data = NULL;
hnLen = strlen(hn);
cn = cnbuf;
cnBufLen = sizeof cnbuf;
rv = CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME,
&subAltName);
if (rv != SECSuccess) {
goto fail;
}
isIPaddr = (PR_SUCCESS == PR_StringToNetAddr(hn, &netAddr));
rv = SECFailure;
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if (!arena)
goto fail;
nameList = current = CERT_DecodeAltNameExtension(arena, &subAltName);
if (!current)
goto fail;
do {
switch (current->type) {
case certDNSName:
if (!isIPaddr) {
/* DNS name current->name.other.data is not null terminated.
** so must copy it.
*/
int cnLen = current->name.other.len;
rv = CERT_RFC1485_EscapeAndQuote(cn, cnBufLen,
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,
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(CERTCertificate *cert,
PRArenaPool *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;
PRArenaPool *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 */
/* now try the NS cert name extension first, then the common name */
singleName =
CERT_FindNSStringExtension(cert, SEC_OID_NS_CERT_EXT_SSL_SERVER_NAME);
if (!singleName) {
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(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;
/* try the cert extension first, then the common name */
cn = CERT_FindNSStringExtension(cert, SEC_OID_NS_CERT_EXT_SSL_SERVER_NAME);
if ( !cn ) {
cn = CERT_GetCommonName(&cert->subject);
}
if ( cn ) {
rv = cert_TestHostName(cn, hn);
PORT_Free(cn);
} else
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
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 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(PRArenaPool *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;
CERTCertDBHandle *handle;
handle = cert->dbhandle;
nickname = CERT_GetNickName(cert, handle, cert->arena);
if (nickname == NULL) {
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 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);
}
/* 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 = cert->trust;
if (trust && (trust->sslFlags |
trust->emailFlags |
trust->objectSigningFlags)) {
if (trust->sslFlags & (CERTDB_VALID_PEER|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_VALID_PEER|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_VALID_PEER|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 or FORTEZZA V1 CA */
if (!ret &&
((cert->isRoot && cert_Version(cert) < SEC_CERTIFICATE_VERSION_3) ||
fortezzaIsCA(cert) )) {
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(&notBeforeA, &val_a->notBefore) ||
SECSuccess != DER_DecodeTimeChoice(&notBeforeB, &val_b->notBefore) ||
SECSuccess != DER_DecodeTimeChoice(&notAfterA, &val_a->notAfter) ||
SECSuccess != DER_DecodeTimeChoice(&notAfterB, &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, &notBeforeA, &notAfterA);
if ( rv != SECSuccess ) {
return(PR_FALSE);
}
rv = CERT_GetCertTimes(certb, &notBeforeB, &notAfterB);
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_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 'i':
*pflags = *pflags | CERTDB_INVISIBLE_CA;
break;
case 'g':
*pflags = *pflags | CERTDB_GOVT_APPROVED_CA;
break;
case ',':
if ( pflags == &trust->sslFlags ) {
pflags = &trust->emailFlags;
} else {
pflags = &trust->objectSigningFlags;
}
break;
default:
return SECFailure;
}
}
return SECSuccess;
}
static void
EncodeFlags(char *trusts, unsigned int flags)
{
if (flags & CERTDB_VALID_CA)
if (!(flags & CERTDB_TRUSTED_CA) &&
!(flags & CERTDB_TRUSTED_CLIENT_CA))
PORT_Strcat(trusts, "c");
if (flags & CERTDB_VALID_PEER)
if (!(flags & CERTDB_TRUSTED))
PORT_Strcat(trusts, "p");
if (flags & CERTDB_TRUSTED_CA)
PORT_Strcat(trusts, "C");
if (flags & CERTDB_TRUSTED_CLIENT_CA)
PORT_Strcat(trusts, "T");
if (flags & CERTDB_TRUSTED)
PORT_Strcat(trusts, "P");
if (flags & CERTDB_USER)
PORT_Strcat(trusts, "u");
if (flags & CERTDB_SEND_WARN)
PORT_Strcat(trusts, "w");
if (flags & CERTDB_INVISIBLE_CA)
PORT_Strcat(trusts, "I");
if (flags & CERTDB_GOVT_APPROVED_CA)
PORT_Strcat(trusts, "G");
return;
}
char *
CERT_EncodeTrustString(CERTCertTrust *trust)
{
char tmpTrustSSL[32];
char tmpTrustEmail[32];
char tmpTrustSigning[32];
char *retstr = NULL;
if ( trust ) {
tmpTrustSSL[0] = '\0';
tmpTrustEmail[0] = '\0';
tmpTrustSigning[0] = '\0';
EncodeFlags(tmpTrustSSL, trust->sslFlags);
EncodeFlags(tmpTrustEmail, trust->emailFlags);
EncodeFlags(tmpTrustSigning, trust->objectSigningFlags);
retstr = PR_smprintf("%s,%s,%s", tmpTrustSSL, tmpTrustEmail,
tmpTrustSigning);
}
return(retstr);
}
SECStatus
CERT_ImportCerts(CERTCertDBHandle *certdb, SECCertUsage usage,
unsigned int ncerts, SECItem **derCerts,
CERTCertificate ***retCerts, PRBool keepCerts,
PRBool caOnly, char *nickname)
{
unsigned int i;
CERTCertificate **certs = NULL;
SECStatus rv;
unsigned int fcerts = 0;
if ( ncerts ) {
certs = PORT_ZNewArray(CERTCertificate*, ncerts);
if ( certs == NULL ) {
return(SECFailure);
}
/* decode all of the certs into the temporary DB */
for ( i = 0, fcerts= 0; i < ncerts; i++) {
certs[fcerts] = CERT_NewTempCertificate(certdb,
derCerts[i],
NULL,
PR_FALSE,
PR_TRUE);
if (certs[fcerts]) fcerts++;
}
if ( keepCerts ) {
for ( i = 0; i < fcerts; i++ ) {
char* canickname = NULL;
PRBool freeNickname = PR_FALSE;
SECKEY_UpdateCertPQG(certs[i]);
if ( CERT_IsCACert(certs[i], NULL) ) {
canickname = CERT_MakeCANickname(certs[i]);
if ( canickname != NULL ) {
freeNickname = PR_TRUE;
}
}
if(CERT_IsCACert(certs[i], NULL) && (fcerts > 1)) {
/* if we are importing only a single cert and specifying
* a nickname, we want to use that nickname if it a CA,
* otherwise if there are more than one cert, we don't
* know which cert it belongs to. But we still may try
* the individual canickname from the cert itself.
*/
rv = CERT_AddTempCertToPerm(certs[i], canickname, NULL);
} else {
rv = CERT_AddTempCertToPerm(certs[i],
nickname?nickname:canickname, NULL);
}
if (PR_TRUE == freeNickname) {
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)
{
PRArenaPool *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, &notBeforeA, &notAfterA);
if ( rv != SECSuccess ) {
return(PR_FALSE);
}
rv = CERT_GetCertTimes(certb, &notBeforeB, &notAfterB);
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)
{
if ( cert->trust &&
((cert->trust->sslFlags & CERTDB_USER ) ||
(cert->trust->emailFlags & CERTDB_USER ) ||
(cert->trust->objectSigningFlags & CERTDB_USER )) ) {
return PR_TRUE;
} else {
return PR_FALSE;
}
}
SECStatus
CERT_FilterCertListForUserCerts(CERTCertList *certList)
{
CERTCertListNode *node, *freenode;
CERTCertificate *cert;
if (!certList) {
return SECFailure;
}
node = CERT_LIST_HEAD(certList);
while ( ! CERT_LIST_END(node, certList) ) {
cert = node->cert;
if ( PR_TRUE != CERT_IsUserCert(cert) ) {
/* Not a User Cert, so remove this cert from the list */
freenode = node;
node = CERT_LIST_NEXT(node);
CERT_RemoveCertListNode(freenode);
} else {
/* Is a User cert, so leave it in the list */
node = CERT_LIST_NEXT(node);
}
}
return(SECSuccess);
}
static PZLock *certRefCountLock = NULL;
/*
* Acquire the cert reference count lock
* There is currently one global lock for all certs, but I'm putting a cert
* arg here so that it will be easy to make it per-cert in the future if
* that turns out to be necessary.
*/
void
CERT_LockCertRefCount(CERTCertificate *cert)
{
PORT_Assert(certRefCountLock != NULL);
PZ_Lock(certRefCountLock);
return;
}
/*
* Free the cert reference count lock
*/
void
CERT_UnlockCertRefCount(CERTCertificate *cert)
{
PRStatus prstat;
PORT_Assert(certRefCountLock != NULL);
prstat = PZ_Unlock(certRefCountLock);
PORT_Assert(prstat == PR_SUCCESS);
return;
}
static PZLock *certTrustLock = NULL;
/*
* Acquire the cert trust lock
* There is currently one global lock for all certs, but I'm putting a cert
* arg here so that it will be easy to make it per-cert in the future if
* that turns out to be necessary.
*/
void
CERT_LockCertTrust(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);
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(CERTCertificate *cert)
{
PRStatus prstat;
PORT_Assert(certTrustLock != NULL);
prstat = PZ_Unlock(certTrustLock);
PORT_Assert(prstat == PR_SUCCESS);
return;
}
/*
* Get the StatusConfig data for this handle
*/
CERTStatusConfig *
CERT_GetStatusConfig(CERTCertDBHandle *handle)
{
return handle->statusConfig;
}
/*
* Set the StatusConfig data for this handle. There
* should not be another configuration set.
*/
void
CERT_SetStatusConfig(CERTCertDBHandle *handle, CERTStatusConfig *statusConfig)
{
PORT_Assert(handle->statusConfig == NULL);
handle->statusConfig = statusConfig;
}
/*
* Code for dealing with subjKeyID to cert mappings.
*/
static PLHashTable *gSubjKeyIDHash = NULL;
static PRLock *gSubjKeyIDLock = NULL;
static 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_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;
}
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_DestroySubjectKeyIDHashTable(void)
{
if (gSubjKeyIDHash) {
PR_Lock(gSubjKeyIDLock);
PL_HashTableDestroy(gSubjKeyIDHash);
gSubjKeyIDHash = NULL;
PR_Unlock(gSubjKeyIDLock);
PR_DestroyLock(gSubjKeyIDLock);
gSubjKeyIDLock = NULL;
}
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;
}