Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* Certificate handling code
*/
#include "nssilock.h"
#include "prmon.h"
#include "prtime.h"
#include "cert.h"
#include "certi.h"
#include "secder.h"
#include "secoid.h"
#include "secasn1.h"
#include "genname.h"
#include "keyhi.h"
#include "secitem.h"
#include "certdb.h"
#include "prprf.h"
#include "sechash.h"
#include "prlong.h"
#include "certxutl.h"
#include "portreg.h"
#include "secerr.h"
#include "sslerr.h"
#include "pk11func.h"
#include "xconst.h" /* for CERT_DecodeAltNameExtension */
#include "pki.h"
#include "pki3hack.h"
SEC_ASN1_MKSUB(CERT_TimeChoiceTemplate)
SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)
SEC_ASN1_MKSUB(SEC_BitStringTemplate)
SEC_ASN1_MKSUB(SEC_IntegerTemplate)
SEC_ASN1_MKSUB(SEC_SkipTemplate)
/*
* Certificate database handling code
*/
const SEC_ASN1Template CERT_CertExtensionTemplate[] = {
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertExtension) },
{ SEC_ASN1_OBJECT_ID, offsetof(CERTCertExtension, id) },
{ SEC_ASN1_OPTIONAL | SEC_ASN1_BOOLEAN, /* XXX DER_DEFAULT */
offsetof(CERTCertExtension, critical) },
{ SEC_ASN1_OCTET_STRING, offsetof(CERTCertExtension, value) },
{ 0 }
};
const SEC_ASN1Template CERT_SequenceOfCertExtensionTemplate[] = {
{ SEC_ASN1_SEQUENCE_OF, 0, CERT_CertExtensionTemplate }
};
const SEC_ASN1Template CERT_TimeChoiceTemplate[] = {
{ SEC_ASN1_CHOICE, offsetof(SECItem, type), 0, sizeof(SECItem) },
{ SEC_ASN1_UTC_TIME, 0, 0, siUTCTime },
{ SEC_ASN1_GENERALIZED_TIME, 0, 0, siGeneralizedTime },
{ 0 }
};
const SEC_ASN1Template CERT_ValidityTemplate[] = {
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTValidity) },
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTValidity, notBefore),
SEC_ASN1_SUB(CERT_TimeChoiceTemplate), 0 },
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTValidity, notAfter),
SEC_ASN1_SUB(CERT_TimeChoiceTemplate), 0 },
{ 0 }
};
const SEC_ASN1Template CERT_CertificateTemplate[] = {
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertificate) },
{ SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED |
SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, /* XXX DER_DEFAULT */
offsetof(CERTCertificate, version),
SEC_ASN1_SUB(SEC_IntegerTemplate) },
{ SEC_ASN1_INTEGER, offsetof(CERTCertificate, serialNumber) },
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCertificate, signature),
SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
{ SEC_ASN1_SAVE, offsetof(CERTCertificate, derIssuer) },
{ SEC_ASN1_INLINE, offsetof(CERTCertificate, issuer), CERT_NameTemplate },
{ SEC_ASN1_INLINE, offsetof(CERTCertificate, validity),
CERT_ValidityTemplate },
{ SEC_ASN1_SAVE, offsetof(CERTCertificate, derSubject) },
{ SEC_ASN1_INLINE, offsetof(CERTCertificate, subject), CERT_NameTemplate },
{ SEC_ASN1_SAVE, offsetof(CERTCertificate, derPublicKey) },
{ SEC_ASN1_INLINE, offsetof(CERTCertificate, subjectPublicKeyInfo),
CERT_SubjectPublicKeyInfoTemplate },
{ SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 1,
offsetof(CERTCertificate, issuerID),
SEC_ASN1_SUB(SEC_BitStringTemplate) },
{ SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 2,
offsetof(CERTCertificate, subjectID),
SEC_ASN1_SUB(SEC_BitStringTemplate) },
{ SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED |
SEC_ASN1_CONTEXT_SPECIFIC | 3,
offsetof(CERTCertificate, extensions),
CERT_SequenceOfCertExtensionTemplate },
{ 0 }
};
const SEC_ASN1Template SEC_SignedCertificateTemplate[] = {
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertificate) },
{ SEC_ASN1_SAVE, offsetof(CERTCertificate, signatureWrap.data) },
{ SEC_ASN1_INLINE, 0, CERT_CertificateTemplate },
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN,
offsetof(CERTCertificate, signatureWrap.signatureAlgorithm),
SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
{ SEC_ASN1_BIT_STRING, offsetof(CERTCertificate, signatureWrap.signature) },
{ 0 }
};
/*
* Find the subjectName in a DER encoded certificate
*/
const SEC_ASN1Template SEC_CertSubjectTemplate[] = {
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECItem) },
{ SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED |
SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
0, SEC_ASN1_SUB(SEC_SkipTemplate) }, /* version */
{ SEC_ASN1_SKIP }, /* serial number */
{ SEC_ASN1_SKIP }, /* signature algorithm */
{ SEC_ASN1_SKIP }, /* issuer */
{ SEC_ASN1_SKIP }, /* validity */
{ SEC_ASN1_ANY, 0, NULL }, /* subject */
{ SEC_ASN1_SKIP_REST },
{ 0 }
};
/*
* Find the issuerName in a DER encoded certificate
*/
const SEC_ASN1Template SEC_CertIssuerTemplate[] = {
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECItem) },
{ SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED |
SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
0, SEC_ASN1_SUB(SEC_SkipTemplate) }, /* version */
{ SEC_ASN1_SKIP }, /* serial number */
{ SEC_ASN1_SKIP }, /* signature algorithm */
{ SEC_ASN1_ANY, 0, NULL }, /* issuer */
{ SEC_ASN1_SKIP_REST },
{ 0 }
};
/*
* Find the subjectName in a DER encoded certificate
*/
const SEC_ASN1Template SEC_CertSerialNumberTemplate[] = {
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECItem) },
{ SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED |
SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
0, SEC_ASN1_SUB(SEC_SkipTemplate) }, /* version */
{ SEC_ASN1_ANY, 0, NULL }, /* serial number */
{ SEC_ASN1_SKIP_REST },
{ 0 }
};
/*
* Find the issuer and serialNumber in a DER encoded certificate.
* This data is used as the database lookup key since its the unique
* identifier of a certificate.
*/
const SEC_ASN1Template CERT_CertKeyTemplate[] = {
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertKey) },
{ SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED |
SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
0, SEC_ASN1_SUB(SEC_SkipTemplate) }, /* version */
{ SEC_ASN1_INTEGER, offsetof(CERTCertKey, serialNumber) },
{ SEC_ASN1_SKIP }, /* signature algorithm */
{ SEC_ASN1_ANY, offsetof(CERTCertKey, derIssuer) },
{ SEC_ASN1_SKIP_REST },
{ 0 }
};
SEC_ASN1_CHOOSER_IMPLEMENT(CERT_TimeChoiceTemplate)
SEC_ASN1_CHOOSER_IMPLEMENT(CERT_CertificateTemplate)
SEC_ASN1_CHOOSER_IMPLEMENT(SEC_SignedCertificateTemplate)
SEC_ASN1_CHOOSER_IMPLEMENT(CERT_SequenceOfCertExtensionTemplate)
SECStatus
CERT_KeyFromIssuerAndSN(PLArenaPool *arena, SECItem *issuer, SECItem *sn,
SECItem *key)
{
key->len = sn->len + issuer->len;
if ((sn->data == NULL) || (issuer->data == NULL)) {
goto loser;
}
key->data = (unsigned char *)PORT_ArenaAlloc(arena, key->len);
if (!key->data) {
goto loser;
}
/* copy the serialNumber */
PORT_Memcpy(key->data, sn->data, sn->len);
/* copy the issuer */
PORT_Memcpy(&key->data[sn->len], issuer->data, issuer->len);
return (SECSuccess);
loser:
return (SECFailure);
}
/*
* Extract the subject name from a DER certificate
*/
SECStatus
CERT_NameFromDERCert(SECItem *derCert, SECItem *derName)
{
int rv;
PLArenaPool *arena;
CERTSignedData sd;
void *tmpptr;
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if (!arena) {
return (SECFailure);
}
PORT_Memset(&sd, 0, sizeof(CERTSignedData));
rv = SEC_QuickDERDecodeItem(arena, &sd, CERT_SignedDataTemplate, derCert);
if (rv) {
goto loser;
}
PORT_Memset(derName, 0, sizeof(SECItem));
rv = SEC_QuickDERDecodeItem(arena, derName, SEC_CertSubjectTemplate,
&sd.data);
if (rv) {
goto loser;
}
tmpptr = derName->data;
derName->data = (unsigned char *)PORT_Alloc(derName->len);
if (derName->data == NULL) {
goto loser;
}
PORT_Memcpy(derName->data, tmpptr, derName->len);
PORT_FreeArena(arena, PR_FALSE);
return (SECSuccess);
loser:
PORT_FreeArena(arena, PR_FALSE);
return (SECFailure);
}
SECStatus
CERT_IssuerNameFromDERCert(SECItem *derCert, SECItem *derName)
{
int rv;
PORTCheapArenaPool tmpArena;
CERTSignedData sd;
void *tmpptr;
PORT_InitCheapArena(&tmpArena, DER_DEFAULT_CHUNKSIZE);
PORT_Memset(&sd, 0, sizeof(CERTSignedData));
rv = SEC_QuickDERDecodeItem(&tmpArena.arena, &sd, CERT_SignedDataTemplate,
derCert);
if (rv) {
goto loser;
}
PORT_Memset(derName, 0, sizeof(SECItem));
rv = SEC_QuickDERDecodeItem(&tmpArena.arena, derName,
SEC_CertIssuerTemplate, &sd.data);
if (rv) {
goto loser;
}
tmpptr = derName->data;
derName->data = (unsigned char *)PORT_Alloc(derName->len);
if (derName->data == NULL) {
goto loser;
}
PORT_Memcpy(derName->data, tmpptr, derName->len);
PORT_DestroyCheapArena(&tmpArena);
return (SECSuccess);
loser:
PORT_DestroyCheapArena(&tmpArena);
return (SECFailure);
}
SECStatus
CERT_SerialNumberFromDERCert(SECItem *derCert, SECItem *derName)
{
int rv;
PORTCheapArenaPool tmpArena;
CERTSignedData sd;
void *tmpptr;
PORT_InitCheapArena(&tmpArena, DER_DEFAULT_CHUNKSIZE);
PORT_Memset(&sd, 0, sizeof(CERTSignedData));
rv = SEC_QuickDERDecodeItem(&tmpArena.arena, &sd, CERT_SignedDataTemplate,
derCert);
if (rv) {
goto loser;
}
PORT_Memset(derName, 0, sizeof(SECItem));
rv = SEC_QuickDERDecodeItem(&tmpArena.arena, derName,
SEC_CertSerialNumberTemplate, &sd.data);
if (rv) {
goto loser;
}
tmpptr = derName->data;
derName->data = (unsigned char *)PORT_Alloc(derName->len);
if (derName->data == NULL) {
goto loser;
}
PORT_Memcpy(derName->data, tmpptr, derName->len);
PORT_DestroyCheapArena(&tmpArena);
return (SECSuccess);
loser:
PORT_DestroyCheapArena(&tmpArena);
return (SECFailure);
}
/*
* Generate a database key, based on serial number and issuer, from a
* DER certificate.
*/
SECStatus
CERT_KeyFromDERCert(PLArenaPool *reqArena, SECItem *derCert, SECItem *key)
{
int rv;
CERTSignedData sd;
CERTCertKey certkey;
if (!reqArena) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
PORT_Memset(&sd, 0, sizeof(CERTSignedData));
rv =
SEC_QuickDERDecodeItem(reqArena, &sd, CERT_SignedDataTemplate, derCert);
if (rv) {
goto loser;
}
PORT_Memset(&certkey, 0, sizeof(CERTCertKey));
rv = SEC_QuickDERDecodeItem(reqArena, &certkey, CERT_CertKeyTemplate,
&sd.data);
if (rv) {
goto loser;
}
return (CERT_KeyFromIssuerAndSN(reqArena, &certkey.derIssuer,
&certkey.serialNumber, key));
loser:
return (SECFailure);
}
/*
* fill in keyUsage field of the cert based on the cert extension
* if the extension is not critical, then we allow all uses
*/
static SECStatus
GetKeyUsage(CERTCertificate *cert)
{
SECStatus rv;
SECItem tmpitem;
rv = CERT_FindKeyUsageExtension(cert, &tmpitem);
if (rv == SECSuccess) {
/* remember the actual value of the extension */
cert->rawKeyUsage = tmpitem.len ? tmpitem.data[0] : 0;
cert->keyUsagePresent = PR_TRUE;
cert->keyUsage = cert->rawKeyUsage;
PORT_Free(tmpitem.data);
tmpitem.data = NULL;
} else {
/* if the extension is not present, then we allow all uses */
cert->keyUsage = KU_ALL;
cert->rawKeyUsage = KU_ALL;
cert->keyUsagePresent = PR_FALSE;
}
if (CERT_GovtApprovedBitSet(cert)) {
cert->keyUsage |= KU_NS_GOVT_APPROVED;
cert->rawKeyUsage |= KU_NS_GOVT_APPROVED;
}
return (SECSuccess);
}
static SECStatus
findOIDinOIDSeqByTagNum(CERTOidSequence *seq, SECOidTag tagnum)
{
SECItem **oids;
SECItem *oid;
SECStatus rv = SECFailure;
if (seq != NULL) {
oids = seq->oids;
while (oids != NULL && *oids != NULL) {
oid = *oids;
if (SECOID_FindOIDTag(oid) == tagnum) {
rv = SECSuccess;
break;
}
oids++;
}
}
return rv;
}
/*
* fill in nsCertType field of the cert based on the cert extension
*/
SECStatus
cert_GetCertType(CERTCertificate *cert)
{
PRUint32 nsCertType;
if (cert->nsCertType) {
/* once set, no need to recalculate */
return SECSuccess;
}
nsCertType = cert_ComputeCertType(cert);
/* Assert that it is safe to cast &cert->nsCertType to "PRInt32 *" */
PORT_Assert(sizeof(cert->nsCertType) == sizeof(PRInt32));
PR_ATOMIC_SET((PRInt32 *)&cert->nsCertType, nsCertType);
return SECSuccess;
}
PRBool
cert_IsIPsecOID(CERTOidSequence *extKeyUsage)
{
if (findOIDinOIDSeqByTagNum(
extKeyUsage, SEC_OID_EXT_KEY_USAGE_IPSEC_IKE) == SECSuccess) {
return PR_TRUE;
}
if (findOIDinOIDSeqByTagNum(
extKeyUsage, SEC_OID_IPSEC_IKE_END) == SECSuccess) {
return PR_TRUE;
}
if (findOIDinOIDSeqByTagNum(
extKeyUsage, SEC_OID_IPSEC_IKE_INTERMEDIATE) == SECSuccess) {
return PR_TRUE;
}
/* these are now deprecated, but may show up. Treat them the same as IKE */
if (findOIDinOIDSeqByTagNum(
extKeyUsage, SEC_OID_EXT_KEY_USAGE_IPSEC_END) == SECSuccess) {
return PR_TRUE;
}
if (findOIDinOIDSeqByTagNum(
extKeyUsage, SEC_OID_EXT_KEY_USAGE_IPSEC_TUNNEL) == SECSuccess) {
return PR_TRUE;
}
if (findOIDinOIDSeqByTagNum(
extKeyUsage, SEC_OID_EXT_KEY_USAGE_IPSEC_USER) == SECSuccess) {
return PR_TRUE;
}
/* this one should probably be in cert_ComputeCertType and set all usages? */
if (findOIDinOIDSeqByTagNum(
extKeyUsage, SEC_OID_X509_ANY_EXT_KEY_USAGE) == SECSuccess) {
return PR_TRUE;
}
return PR_FALSE;
}
PRUint32
cert_ComputeCertType(CERTCertificate *cert)
{
SECStatus rv;
SECItem tmpitem;
SECItem encodedExtKeyUsage;
CERTOidSequence *extKeyUsage = NULL;
CERTBasicConstraints basicConstraint;
PRUint32 nsCertType = 0;
PRBool isCA = PR_FALSE;
tmpitem.data = NULL;
CERT_FindNSCertTypeExtension(cert, &tmpitem);
encodedExtKeyUsage.data = NULL;
rv = CERT_FindCertExtension(cert, SEC_OID_X509_EXT_KEY_USAGE,
&encodedExtKeyUsage);
if (rv == SECSuccess) {
extKeyUsage = CERT_DecodeOidSequence(&encodedExtKeyUsage);
}
rv = CERT_FindBasicConstraintExten(cert, &basicConstraint);
if (rv == SECSuccess) {
isCA = basicConstraint.isCA;
}
if (tmpitem.data != NULL || extKeyUsage != NULL) {
if (tmpitem.data == NULL || tmpitem.len == 0) {
nsCertType = 0;
} else {
nsCertType = tmpitem.data[0];
}
/* free tmpitem data pointer to avoid memory leak */
PORT_Free(tmpitem.data);
tmpitem.data = NULL;
/*
* for this release, we will allow SSL certs with an email address
* to be used for email
*/
if ((nsCertType & NS_CERT_TYPE_SSL_CLIENT) && cert->emailAddr &&
cert->emailAddr[0]) {
nsCertType |= NS_CERT_TYPE_EMAIL;
}
/*
* for this release, we will allow SSL intermediate CAs to be
* email intermediate CAs too.
*/
if (nsCertType & NS_CERT_TYPE_SSL_CA) {
nsCertType |= NS_CERT_TYPE_EMAIL_CA;
}
/*
* allow a cert with the extended key usage of EMail Protect
* to be used for email or as an email CA, if basic constraints
* indicates that it is a CA.
*/
if (findOIDinOIDSeqByTagNum(extKeyUsage,
SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT) ==
SECSuccess) {
nsCertType |= isCA ? NS_CERT_TYPE_EMAIL_CA : NS_CERT_TYPE_EMAIL;
}
if (findOIDinOIDSeqByTagNum(
extKeyUsage, SEC_OID_EXT_KEY_USAGE_SERVER_AUTH) == SECSuccess) {
nsCertType |= isCA ? NS_CERT_TYPE_SSL_CA : NS_CERT_TYPE_SSL_SERVER;
}
/*
* Treat certs with step-up OID as also having SSL server type.
* COMODO needs this behaviour until June 2020. See Bug 737802.
*/
if (findOIDinOIDSeqByTagNum(extKeyUsage,
SEC_OID_NS_KEY_USAGE_GOVT_APPROVED) ==
SECSuccess) {
nsCertType |= isCA ? NS_CERT_TYPE_SSL_CA : NS_CERT_TYPE_SSL_SERVER;
}
if (findOIDinOIDSeqByTagNum(
extKeyUsage, SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH) == SECSuccess) {
nsCertType |= isCA ? NS_CERT_TYPE_SSL_CA : NS_CERT_TYPE_SSL_CLIENT;
}
if (cert_IsIPsecOID(extKeyUsage)) {
nsCertType |= isCA ? NS_CERT_TYPE_IPSEC_CA : NS_CERT_TYPE_IPSEC;
}
if (findOIDinOIDSeqByTagNum(
extKeyUsage, SEC_OID_EXT_KEY_USAGE_CODE_SIGN) == SECSuccess) {
nsCertType |= isCA ? NS_CERT_TYPE_OBJECT_SIGNING_CA : NS_CERT_TYPE_OBJECT_SIGNING;
}
if (findOIDinOIDSeqByTagNum(
extKeyUsage, SEC_OID_EXT_KEY_USAGE_TIME_STAMP) == SECSuccess) {
nsCertType |= EXT_KEY_USAGE_TIME_STAMP;
}
if (findOIDinOIDSeqByTagNum(extKeyUsage, SEC_OID_OCSP_RESPONDER) ==
SECSuccess) {
nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER;
}
} else {
/* If no NS Cert Type extension and no EKU extension, then */
nsCertType = 0;
if (CERT_IsCACert(cert, &nsCertType))
nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER;
/* if the basic constraint extension says the cert is a CA, then
allow SSL CA and EMAIL CA and Status Responder */
if (isCA) {
nsCertType |= (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA |
EXT_KEY_USAGE_STATUS_RESPONDER);
}
/* allow any ssl or email (no ca or object signing. */
nsCertType |= NS_CERT_TYPE_SSL_CLIENT | NS_CERT_TYPE_SSL_SERVER |
NS_CERT_TYPE_EMAIL;
}
/* IPSEC is allowed to use SSL client and server certs as well as email certs */
if (nsCertType & (NS_CERT_TYPE_SSL_CLIENT | NS_CERT_TYPE_SSL_SERVER | NS_CERT_TYPE_EMAIL)) {
nsCertType |= NS_CERT_TYPE_IPSEC;
}
if (nsCertType & (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA)) {
nsCertType |= NS_CERT_TYPE_IPSEC_CA;
}
if (encodedExtKeyUsage.data != NULL) {
PORT_Free(encodedExtKeyUsage.data);
}
if (extKeyUsage != NULL) {
CERT_DestroyOidSequence(extKeyUsage);
}
return nsCertType;
}
/*
* cert_GetKeyID() - extract or generate the subjectKeyID from a certificate
*/
SECStatus
cert_GetKeyID(CERTCertificate *cert)
{
SECItem tmpitem;
SECStatus rv;
cert->subjectKeyID.len = 0;
/* see of the cert has a key identifier extension */
rv = CERT_FindSubjectKeyIDExtension(cert, &tmpitem);
if (rv == SECSuccess) {
cert->subjectKeyID.data =
(unsigned char *)PORT_ArenaAlloc(cert->arena, tmpitem.len);
if (cert->subjectKeyID.data != NULL) {
PORT_Memcpy(cert->subjectKeyID.data, tmpitem.data, tmpitem.len);
cert->subjectKeyID.len = tmpitem.len;
cert->keyIDGenerated = PR_FALSE;
}
PORT_Free(tmpitem.data);
}
/* if the cert doesn't have a key identifier extension, then generate one*/
if (cert->subjectKeyID.len == 0) {
/*
* pkix says that if the subjectKeyID is not present, then we should
* use the SHA-1 hash of the DER-encoded publicKeyInfo from the cert
*/
cert->subjectKeyID.data =
(unsigned char *)PORT_ArenaAlloc(cert->arena, SHA1_LENGTH);
if (cert->subjectKeyID.data != NULL) {
rv = PK11_HashBuf(SEC_OID_SHA1, cert->subjectKeyID.data,
cert->derPublicKey.data, cert->derPublicKey.len);
if (rv == SECSuccess) {
cert->subjectKeyID.len = SHA1_LENGTH;
}
}
}
if (cert->subjectKeyID.len == 0) {
return (SECFailure);
}
return (SECSuccess);
}
static PRBool
cert_IsRootCert(CERTCertificate *cert)
{
SECStatus rv;
SECItem tmpitem;
/* cache the authKeyID extension, if present */
cert->authKeyID = CERT_FindAuthKeyIDExten(cert->arena, cert);
/* it MUST be self-issued to be a root */
if (cert->derIssuer.len == 0 ||
!SECITEM_ItemsAreEqual(&cert->derIssuer, &cert->derSubject)) {
return PR_FALSE;
}
/* check the authKeyID extension */
if (cert->authKeyID) {
/* authority key identifier is present */
if (cert->authKeyID->keyID.len > 0) {
/* the keyIdentifier field is set, look for subjectKeyID */
rv = CERT_FindSubjectKeyIDExtension(cert, &tmpitem);
if (rv == SECSuccess) {
PRBool match;
/* also present, they MUST match for it to be a root */
match =
SECITEM_ItemsAreEqual(&cert->authKeyID->keyID, &tmpitem);
PORT_Free(tmpitem.data);
if (!match)
return PR_FALSE; /* else fall through */
} else {
/* the subject key ID is required when AKI is present */
return PR_FALSE;
}
}
if (cert->authKeyID->authCertIssuer) {
SECItem *caName;
caName = (SECItem *)CERT_GetGeneralNameByType(
cert->authKeyID->authCertIssuer, certDirectoryName, PR_TRUE);
if (caName) {
if (!SECITEM_ItemsAreEqual(&cert->derIssuer, caName)) {
return PR_FALSE;
} /* else fall through */
} /* else ??? could not get general name as directory name? */
}
if (cert->authKeyID->authCertSerialNumber.len > 0) {
if (!SECITEM_ItemsAreEqual(
&cert->serialNumber,
&cert->authKeyID->authCertSerialNumber)) {
return PR_FALSE;
} /* else fall through */
}
/* all of the AKI fields that were present passed the test */
return PR_TRUE;
}
/* else the AKI was not present, so this is a root */
return PR_TRUE;
}
/*
* take a DER certificate and decode it into a certificate structure
*/
CERTCertificate *
CERT_DecodeDERCertificate(SECItem *derSignedCert, PRBool copyDER,
char *nickname)
{
CERTCertificate *cert;
PLArenaPool *arena;
void *data;
int rv;
int len;
char *tmpname;
/* make a new arena */
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if (!arena) {
return 0;
}
/* allocate the certificate structure */
cert = (CERTCertificate *)PORT_ArenaZAlloc(arena, sizeof(CERTCertificate));
if (!cert) {
goto loser;
}
cert->arena = arena;
if (copyDER) {
/* copy the DER data for the cert into this arena */
data = (void *)PORT_ArenaAlloc(arena, derSignedCert->len);
if (!data) {
goto loser;
}
cert->derCert.data = (unsigned char *)data;
cert->derCert.len = derSignedCert->len;
PORT_Memcpy(data, derSignedCert->data, derSignedCert->len);
} else {
/* point to passed in DER data */
cert->derCert = *derSignedCert;
}
/* decode the certificate info */
rv = SEC_QuickDERDecodeItem(arena, cert, SEC_SignedCertificateTemplate,
&cert->derCert);
if (rv) {
goto loser;
}
if (cert_HasUnknownCriticalExten(cert->extensions) == PR_TRUE) {
cert->options.bits.hasUnsupportedCriticalExt = PR_TRUE;
}
/* generate and save the database key for the cert */
rv = CERT_KeyFromIssuerAndSN(arena, &cert->derIssuer, &cert->serialNumber,
&cert->certKey);
if (rv) {
goto loser;
}
/* set the nickname */
if (nickname == NULL) {
cert->nickname = NULL;
} else {
/* copy and install the nickname */
len = PORT_Strlen(nickname) + 1;
cert->nickname = (char *)PORT_ArenaAlloc(arena, len);
if (cert->nickname == NULL) {
goto loser;
}
PORT_Memcpy(cert->nickname, nickname, len);
}
/* set the email address */
cert->emailAddr = cert_GetCertificateEmailAddresses(cert);
/* initialize the subjectKeyID */
rv = cert_GetKeyID(cert);
if (rv != SECSuccess) {
goto loser;
}
/* initialize keyUsage */
rv = GetKeyUsage(cert);
if (rv != SECSuccess) {
goto loser;
}
/* determine if this is a root cert */
cert->isRoot = cert_IsRootCert(cert);
/* initialize the certType */
rv = cert_GetCertType(cert);
if (rv != SECSuccess) {
goto loser;
}
tmpname = CERT_NameToAscii(&cert->subject);
if (tmpname != NULL) {
cert->subjectName = PORT_ArenaStrdup(cert->arena, tmpname);
PORT_Free(tmpname);
}
tmpname = CERT_NameToAscii(&cert->issuer);
if (tmpname != NULL) {
cert->issuerName = PORT_ArenaStrdup(cert->arena, tmpname);
PORT_Free(tmpname);
}
cert->referenceCount = 1;
cert->slot = NULL;
cert->pkcs11ID = CK_INVALID_HANDLE;
cert->dbnickname = NULL;
return (cert);
loser:
if (arena) {
PORT_FreeArena(arena, PR_FALSE);
}
return (0);
}
CERTCertificate *
__CERT_DecodeDERCertificate(SECItem *derSignedCert, PRBool copyDER,
char *nickname)
{
return CERT_DecodeDERCertificate(derSignedCert, copyDER, nickname);
}
CERTValidity *
CERT_CreateValidity(PRTime notBefore, PRTime notAfter)
{
CERTValidity *v;
int rv;
PLArenaPool *arena;
if (notBefore > notAfter) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return NULL;
}
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if (!arena) {
return (0);
}
v = (CERTValidity *)PORT_ArenaZAlloc(arena, sizeof(CERTValidity));
if (v) {
v->arena = arena;
rv = DER_EncodeTimeChoice(arena, &v->notBefore, notBefore);
if (rv)
goto loser;
rv = DER_EncodeTimeChoice(arena, &v->notAfter, notAfter);
if (rv)
goto loser;
}
return v;
loser:
CERT_DestroyValidity(v);
return 0;
}
SECStatus
CERT_CopyValidity(PLArenaPool *arena, CERTValidity *to, CERTValidity *from)
{
SECStatus rv;
CERT_DestroyValidity(to);
to->arena = arena;
rv = SECITEM_CopyItem(arena, &to->notBefore, &from->notBefore);
if (rv)
return rv;
rv = SECITEM_CopyItem(arena, &to->notAfter, &from->notAfter);
return rv;
}
void
CERT_DestroyValidity(CERTValidity *v)
{
if (v && v->arena) {
PORT_FreeArena(v->arena, PR_FALSE);
}
return;
}
/*
** Amount of time that a certifiate is allowed good before it is actually
** good. This is used for pending certificates, ones that are about to be
** valid. The slop is designed to allow for some variance in the clocks
** of the machine checking the certificate.
*/
#define PENDING_SLOP (24L * 60L * 60L) /* seconds per day */
static PRInt32 pendingSlop = PENDING_SLOP; /* seconds */
PRInt32
CERT_GetSlopTime(void)
{
return pendingSlop; /* seconds */
}
SECStatus
CERT_SetSlopTime(PRInt32 slop) /* seconds */
{
if (slop < 0)
return SECFailure;
pendingSlop = slop;
return SECSuccess;
}
SECStatus
CERT_GetCertTimes(const CERTCertificate *c, PRTime *notBefore, PRTime *notAfter)
{
SECStatus rv;
if (!c || !notBefore || !notAfter) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
/* convert DER not-before time */
rv = DER_DecodeTimeChoice(notBefore, &c->validity.notBefore);
if (rv) {
return (SECFailure);
}
/* convert DER not-after time */
rv = DER_DecodeTimeChoice(notAfter, &c->validity.notAfter);
if (rv) {
return (SECFailure);
}
return (SECSuccess);
}
/*
* Check the validity times of a certificate
*/
SECCertTimeValidity
CERT_CheckCertValidTimes(const CERTCertificate *c, PRTime t,
PRBool allowOverride)
{
PRTime notBefore, notAfter, llPendingSlop, tmp1;
SECStatus rv;
if (!c) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return (secCertTimeUndetermined);
}
/* if cert is already marked OK, then don't bother to check */
if (allowOverride && c->timeOK) {
return (secCertTimeValid);
}
rv = CERT_GetCertTimes(c, &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;
if (!crl) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return (secCertTimeUndetermined);
}
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)) {
PORT_SetError(SEC_ERROR_CRL_EXPIRED);
return (secCertTimeNotValidYet);
}
/* If next update is omitted and the test for notBefore passes, then
we assume that the crl is up to date.
*/
if (LL_IS_ZERO(notAfter)) {
return (secCertTimeValid);
}
if (LL_CMP(t, >, notAfter)) {
PORT_SetError(SEC_ERROR_CRL_EXPIRED);
return (secCertTimeExpired);
}
return (secCertTimeValid);
}
PRBool
SEC_CrlIsNewer(CERTCrl *inNew, CERTCrl *old)
{
PRTime newNotBefore, newNotAfter;
PRTime oldNotBefore, oldNotAfter;
SECStatus rv;
/* problems with the new CRL? reject it */
rv = SEC_GetCrlTimes(inNew, &newNotBefore, &newNotAfter);
if (rv)
return PR_FALSE;
/* problems with the old CRL? replace it */
rv = SEC_GetCrlTimes(old, &oldNotBefore, &oldNotAfter);
if (rv)
return PR_TRUE;
/* Question: what about the notAfter's? */
return ((PRBool)LL_CMP(oldNotBefore, <, newNotBefore));
}
/*
* return required key usage and cert type based on cert usage
*/
SECStatus
CERT_KeyUsageAndTypeForCertUsage(SECCertUsage usage, PRBool ca,
unsigned int *retKeyUsage,
unsigned int *retCertType)
{
unsigned int requiredKeyUsage = 0;
unsigned int requiredCertType = 0;
if (ca) {
switch (usage) {
case certUsageSSLServerWithStepUp:
requiredKeyUsage = KU_NS_GOVT_APPROVED | KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_SSL_CA;
break;
case certUsageSSLClient:
requiredKeyUsage = KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_SSL_CA;
break;
case certUsageSSLServer:
requiredKeyUsage = KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_SSL_CA;
break;
case certUsageIPsec:
requiredKeyUsage = KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_IPSEC_CA;
break;
case certUsageSSLCA:
requiredKeyUsage = KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_SSL_CA;
break;
case certUsageEmailSigner:
requiredKeyUsage = KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_EMAIL_CA;
break;
case certUsageEmailRecipient:
requiredKeyUsage = KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_EMAIL_CA;
break;
case certUsageObjectSigner:
requiredKeyUsage = KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_OBJECT_SIGNING_CA;
break;
case certUsageAnyCA:
case certUsageVerifyCA:
case certUsageStatusResponder:
requiredKeyUsage = KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_OBJECT_SIGNING_CA |
NS_CERT_TYPE_EMAIL_CA | NS_CERT_TYPE_SSL_CA;
break;
default:
PORT_Assert(0);
goto loser;
}
} else {
switch (usage) {
case certUsageSSLClient:
/*
* RFC 5280 lists digitalSignature and keyAgreement for
* id-kp-clientAuth. NSS does not support the *_fixed_dh and
* *_fixed_ecdh client certificate types.
*/
requiredKeyUsage = KU_DIGITAL_SIGNATURE;
requiredCertType = NS_CERT_TYPE_SSL_CLIENT;
break;
case certUsageSSLServer:
requiredKeyUsage = KU_KEY_AGREEMENT_OR_ENCIPHERMENT;
requiredCertType = NS_CERT_TYPE_SSL_SERVER;
break;
case certUsageIPsec:
/* RFC 4945 Section 5.1.3.2 */
requiredKeyUsage = KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION;
requiredCertType = NS_CERT_TYPE_IPSEC;
break;
case certUsageSSLServerWithStepUp:
requiredKeyUsage =
KU_KEY_AGREEMENT_OR_ENCIPHERMENT | KU_NS_GOVT_APPROVED;
requiredCertType = NS_CERT_TYPE_SSL_SERVER;
break;
case certUsageSSLCA:
requiredKeyUsage = KU_KEY_CERT_SIGN;
requiredCertType = NS_CERT_TYPE_SSL_CA;
break;
case certUsageEmailSigner:
requiredKeyUsage = KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION;
requiredCertType = NS_CERT_TYPE_EMAIL;
break;
case certUsageEmailRecipient:
requiredKeyUsage = KU_KEY_AGREEMENT_OR_ENCIPHERMENT;
requiredCertType = NS_CERT_TYPE_EMAIL;
break;
case certUsageObjectSigner:
/* RFC 5280 lists only digitalSignature for id-kp-codeSigning.
*/
requiredKeyUsage = KU_DIGITAL_SIGNATURE;
requiredCertType = NS_CERT_TYPE_OBJECT_SIGNING;
break;
case certUsageStatusResponder:
requiredKeyUsage = KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION;
requiredCertType = EXT_KEY_USAGE_STATUS_RESPONDER;
break;
default:
PORT_Assert(0);
goto loser;
}
}
if (retKeyUsage != NULL) {
*retKeyUsage = requiredKeyUsage;
}
if (retCertType != NULL) {
*retCertType = requiredCertType;
}
return (SECSuccess);
loser:
return (SECFailure);
}
/*
* check the key usage of a cert against a set of required values
*/
SECStatus
CERT_CheckKeyUsage(CERTCertificate *cert, unsigned int requiredUsage)
{
if (!cert) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
/* choose between key agreement or key encipherment based on key
* type in cert
*/
if (requiredUsage & KU_KEY_AGREEMENT_OR_ENCIPHERMENT) {
KeyType keyType = CERT_GetCertKeyType(&cert->subjectPublicKeyInfo);
/* turn off the special bit */
requiredUsage &= (~KU_KEY_AGREEMENT_OR_ENCIPHERMENT);
switch (keyType) {
case rsaKey:
requiredUsage |= KU_KEY_ENCIPHERMENT;
break;
case rsaPssKey:
case dsaKey:
requiredUsage |= KU_DIGITAL_SIGNATURE;
break;
case dhKey:
requiredUsage |= KU_KEY_AGREEMENT;
break;
case ecKey:
/* Accept either signature or agreement. */
if (!(cert->keyUsage &
(KU_DIGITAL_SIGNATURE | KU_KEY_AGREEMENT)))
goto loser;
break;
default:
goto loser;
}
}
/* Allow either digital signature or non-repudiation */
if (requiredUsage & KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION) {
/* turn off the special bit */
requiredUsage &= (~KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION);
if (!(cert->keyUsage & (KU_DIGITAL_SIGNATURE | KU_NON_REPUDIATION)))
goto loser;
}
if ((cert->keyUsage & requiredUsage) == requiredUsage)
return SECSuccess;
loser:
PORT_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE);
return SECFailure;
}
CERTCertificate *
CERT_DupCertificate(CERTCertificate *c)
{
if (c) {
NSSCertificate *tmp = STAN_GetNSSCertificate(c);
nssCertificate_AddRef(tmp);
}
return c;
}
SECStatus
CERT_GetCertificateDer(const CERTCertificate *c, SECItem *der)
{
if (!c || !der) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
*der = c->derCert;
return SECSuccess;
}
/*
* Allow use of default cert database, so that apps(such as mozilla) don't
* have to pass the handle all over the place.
*/
static CERTCertDBHandle *default_cert_db_handle = 0;
void
CERT_SetDefaultCertDB(CERTCertDBHandle *handle)
{
default_cert_db_handle = handle;
return;
}
CERTCertDBHandle *
CERT_GetDefaultCertDB(void)
{
return (default_cert_db_handle);
}
/* XXX this would probably be okay/better as an xp routine? */
static void
sec_lower_string(char *s)
{
if (s == NULL) {
return;
}
while (*s) {
*s = PORT_Tolower(*s);
s++;
}
return;
}
static PRBool
cert_IsIPAddr(const char *hn)
{
PRBool isIPaddr = PR_FALSE;
PRNetAddr netAddr;
isIPaddr = (PR_SUCCESS == PR_StringToNetAddr(hn, &netAddr));
return isIPaddr;
}
/*
** Add a domain name to the list of names that the user has explicitly
** allowed (despite cert name mismatches) for use with a server cert.
*/
SECStatus
CERT_AddOKDomainName(CERTCertificate *cert, const char *hn)
{
CERTOKDomainName *domainOK;
int newNameLen;
if (!hn || !(newNameLen = strlen(hn))) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
domainOK = (CERTOKDomainName *)PORT_ArenaZAlloc(cert->arena, sizeof(*domainOK));
if (!domainOK) {
return SECFailure; /* error code is already set. */
}
domainOK->name = (char *)PORT_ArenaZAlloc(cert->arena, newNameLen + 1);
if (!domainOK->name) {
return SECFailure; /* error code is already set. */
}
PORT_Strncpy(domainOK->name, hn, newNameLen + 1);
sec_lower_string(domainOK->name);
/* put at head of list. */
domainOK->next = cert->domainOK;
cert->domainOK = domainOK;
return SECSuccess;
}
/* returns SECSuccess if hn matches pattern cn,
** returns SECFailure with SSL_ERROR_BAD_CERT_DOMAIN if no match,
** returns SECFailure with some other error code if another error occurs.
**
** This function may modify string cn, so caller must pass a modifiable copy.
*/
static SECStatus
cert_TestHostName(char *cn, const char *hn)
{
static int useShellExp = -1;
if (useShellExp < 0) {
useShellExp = (NULL != PR_GetEnvSecure("NSS_USE_SHEXP_IN_CERT_NAME"));
}
if (useShellExp) {
/* Backward compatible code, uses Shell Expressions (SHEXP). */
int regvalid = PORT_RegExpValid(cn);
if (regvalid != NON_SXP) {
SECStatus rv;
/* cn is a regular expression, try to match the shexp */
int match = PORT_RegExpCaseSearch(hn, cn);
if (match == 0) {
rv = SECSuccess;
} else {
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
rv = SECFailure;
}
return rv;
}
} else {
/* New approach conforms to RFC 6125. */
char *wildcard = PORT_Strchr(cn, '*');
char *firstcndot = PORT_Strchr(cn, '.');
char *secondcndot =
firstcndot ? PORT_Strchr(firstcndot + 1, '.') : NULL;
char *firsthndot = PORT_Strchr(hn, '.');
/* For a cn pattern to be considered valid, the wildcard character...
* - may occur only in a DNS name with at least 3 components, and
* - may occur only as last character in the first component, and
* - may be preceded by additional characters, and
* - must not be preceded by an IDNA ACE prefix (xn--)
*/
if (wildcard && secondcndot && secondcndot[1] && firsthndot &&
firstcndot - wildcard == 1 /* wildcard is last char in first component */
&& secondcndot - firstcndot > 1 /* second component is non-empty */
&& PORT_Strrchr(cn, '*') == wildcard /* only one wildcard in cn */
&& !PORT_Strncasecmp(cn, hn, wildcard - cn) &&
!PORT_Strcasecmp(firstcndot, firsthndot)
/* If hn starts with xn--, then cn must start with wildcard */
&& (PORT_Strncasecmp(hn, "xn--", 4) || wildcard == cn)) {
/* valid wildcard pattern match */
return SECSuccess;
}
}
/* String cn has no wildcard or shell expression.
* Compare entire string hn with cert name.
*/
if (PORT_Strcasecmp(hn, cn) == 0) {
return SECSuccess;
}
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
return SECFailure;
}
SECStatus
cert_VerifySubjectAltName(const CERTCertificate *cert, const char *hn)
{
PLArenaPool *arena = NULL;
CERTGeneralName *nameList = NULL;
CERTGeneralName *current;
char *cn;
int cnBufLen;
int DNSextCount = 0;
int IPextCount = 0;
PRBool isIPaddr = PR_FALSE;
SECStatus rv = SECFailure;
SECItem subAltName;
PRNetAddr netAddr;
char cnbuf[128];
subAltName.data = NULL;
cn = cnbuf;
cnBufLen = sizeof cnbuf;
rv = CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME,
&subAltName);
if (rv != SECSuccess) {
goto fail;
}
isIPaddr = (PR_SUCCESS == PR_StringToNetAddr(hn, &netAddr));
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if (!arena)
goto fail;
nameList = current = CERT_DecodeAltNameExtension(arena, &subAltName);
if (!current)
goto fail;
do {
switch (current->type) {
case certDNSName:
if (!isIPaddr) {
/* DNS name current->name.other.data is not null terminated.
** so must copy it.
*/
int cnLen = current->name.other.len;
rv = CERT_RFC1485_EscapeAndQuote(
cn, cnBufLen, (char *)current->name.other.data, cnLen);
if (rv != SECSuccess &&
PORT_GetError() == SEC_ERROR_OUTPUT_LEN) {
cnBufLen =
cnLen * 3 + 3; /* big enough for worst case */
cn = (char *)PORT_ArenaAlloc(arena, cnBufLen);
if (!cn)
goto fail;
rv = CERT_RFC1485_EscapeAndQuote(
cn, cnBufLen, (char *)current->name.other.data,
cnLen);
}
if (rv == SECSuccess)
rv = cert_TestHostName(cn, hn);
if (rv == SECSuccess)
goto finish;
}
DNSextCount++;
break;
case certIPAddress:
if (isIPaddr) {
int match = 0;
PRIPv6Addr v6Addr;
if (current->name.other.len == 4 && /* IP v4 address */
netAddr.inet.family == PR_AF_INET) {
match = !memcmp(&netAddr.inet.ip,
current->name.other.data, 4);
} else if (current->name.other.len ==
16 && /* IP v6 address */
netAddr.ipv6.family == PR_AF_INET6) {
match = !memcmp(&netAddr.ipv6.ip,
current->name.other.data, 16);
} else if (current->name.other.len ==
16 && /* IP v6 address */
netAddr.inet.family == PR_AF_INET) {
/* convert netAddr to ipv6, then compare. */
/* ipv4 must be in Network Byte Order on input. */
PR_ConvertIPv4AddrToIPv6(netAddr.inet.ip, &v6Addr);
match = !memcmp(&v6Addr, current->name.other.data, 16);
} else if (current->name.other.len == 4 && /* IP v4 address */
netAddr.inet.family == PR_AF_INET6) {
/* convert netAddr to ipv6, then compare. */
PRUint32 ipv4 = (current->name.other.data[0] << 24) |
(current->name.other.data[1] << 16) |
(current->name.other.data[2] << 8) |
current->name.other.data[3];
/* ipv4 must be in Network Byte Order on input. */
PR_ConvertIPv4AddrToIPv6(PR_htonl(ipv4), &v6Addr);
match = !memcmp(&netAddr.ipv6.ip, &v6Addr, 16);
}
if (match) {
rv = SECSuccess;
goto finish;
}
}
IPextCount++;
break;
default:
break;
}
current = CERT_GetNextGeneralName(current);
} while (current != nameList);
fail:
if (!(isIPaddr ? IPextCount : DNSextCount)) {
/* no relevant value in the extension was found. */
PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND);
} else {
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
}
rv = SECFailure;
finish:
/* Don't free nameList, it's part of the arena. */
if (arena) {
PORT_FreeArena(arena, PR_FALSE);
}
if (subAltName.data) {
SECITEM_FreeItem(&subAltName, PR_FALSE);
}
return rv;
}
/*
* If found:
* - subAltName contains the extension (caller must free)
* - return value is the decoded namelist (allocated off arena)
* if not found, or if failure to decode:
* - return value is NULL
*/
CERTGeneralName *
cert_GetSubjectAltNameList(const CERTCertificate *cert, PLArenaPool *arena)
{
CERTGeneralName *nameList = NULL;
SECStatus rv = SECFailure;
SECItem subAltName;
if (!cert || !arena)
return NULL;
subAltName.data = NULL;
rv = CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME,
&subAltName);
if (rv != SECSuccess)
return NULL;
nameList = CERT_DecodeAltNameExtension(arena, &subAltName);
SECITEM_FreeItem(&subAltName, PR_FALSE);
return nameList;
}
PRUint32
cert_CountDNSPatterns(CERTGeneralName *firstName)
{
CERTGeneralName *current;
PRUint32 count = 0;
if (!firstName)
return 0;
current = firstName;
do {
switch (current->type) {
case certDNSName:
case certIPAddress:
++count;
break;
default:
break;
}
current = CERT_GetNextGeneralName(current);
} while (current != firstName);
return count;
}
#ifndef INET6_ADDRSTRLEN
#define INET6_ADDRSTRLEN 46
#endif
/* will fill nickNames,
* will allocate all data from nickNames->arena,
* numberOfGeneralNames should have been obtained from cert_CountDNSPatterns,
* will ensure the numberOfGeneralNames matches the number of output entries.
*/
SECStatus
cert_GetDNSPatternsFromGeneralNames(CERTGeneralName *firstName,
PRUint32 numberOfGeneralNames,
CERTCertNicknames *nickNames)
{
CERTGeneralName *currentInput;
char **currentOutput;
if (!firstName || !nickNames || !numberOfGeneralNames)
return SECFailure;
nickNames->numnicknames = numberOfGeneralNames;
nickNames->nicknames = PORT_ArenaAlloc(