2008-06-06 16:40:11 +04:00
|
|
|
/* ***** 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>
|
2009-04-07 05:36:45 +04:00
|
|
|
* Kaspar Brand <mozbugzilla@velox.ch>
|
2008-06-06 16:40:11 +04:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*
|
2012-04-01 03:41:42 +04:00
|
|
|
* $Id: certdb.c,v 1.121 2012/03/23 03:25:57 wtc%google.com Exp $
|
2008-06-06 16:40:11 +04:00
|
|
|
*/
|
|
|
|
|
|
|
|
#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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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));
|
2010-07-19 09:45:52 +04:00
|
|
|
PR_ATOMIC_SET((PRInt32 *)&cert->nsCertType, nsCertType);
|
2008-06-06 16:40:11 +04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2012-04-01 03:41:42 +04:00
|
|
|
/*
|
|
|
|
* Treat certs with step-up OID as also having SSL server type.
|
|
|
|
* COMODO needs this behaviour until June 2020. See Bug 737802.
|
|
|
|
*/
|
|
|
|
if (findOIDinOIDSeqByTagNum(extKeyUsage,
|
|
|
|
SEC_OID_NS_KEY_USAGE_GOVT_APPROVED) ==
|
|
|
|
SECSuccess){
|
|
|
|
if (basicConstraintPresent == PR_TRUE &&
|
|
|
|
(basicConstraint.isCA)) {
|
|
|
|
nsCertType |= NS_CERT_TYPE_SSL_CA;
|
|
|
|
} else {
|
|
|
|
nsCertType |= NS_CERT_TYPE_SSL_SERVER;
|
|
|
|
}
|
|
|
|
}
|
2008-06-06 16:40:11 +04:00
|
|
|
if (findOIDinOIDSeqByTagNum(extKeyUsage,
|
|
|
|
SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH) ==
|
|
|
|
SECSuccess){
|
|
|
|
if (basicConstraintPresent == PR_TRUE &&
|
|
|
|
(basicConstraint.isCA)) {
|
|
|
|
nsCertType |= NS_CERT_TYPE_SSL_CA;
|
|
|
|
} else {
|
|
|
|
nsCertType |= NS_CERT_TYPE_SSL_CLIENT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (findOIDinOIDSeqByTagNum(extKeyUsage,
|
|
|
|
SEC_OID_EXT_KEY_USAGE_CODE_SIGN) ==
|
|
|
|
SECSuccess) {
|
|
|
|
if (basicConstraintPresent == PR_TRUE &&
|
|
|
|
(basicConstraint.isCA)) {
|
|
|
|
nsCertType |= NS_CERT_TYPE_OBJECT_SIGNING_CA;
|
|
|
|
} else {
|
|
|
|
nsCertType |= NS_CERT_TYPE_OBJECT_SIGNING;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (findOIDinOIDSeqByTagNum(extKeyUsage,
|
|
|
|
SEC_OID_EXT_KEY_USAGE_TIME_STAMP) ==
|
|
|
|
SECSuccess) {
|
|
|
|
nsCertType |= EXT_KEY_USAGE_TIME_STAMP;
|
|
|
|
}
|
|
|
|
if (findOIDinOIDSeqByTagNum(extKeyUsage,
|
|
|
|
SEC_OID_OCSP_RESPONDER) ==
|
|
|
|
SECSuccess) {
|
|
|
|
nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* If no NS Cert Type extension and no EKU extension, then */
|
|
|
|
nsCertType = 0;
|
|
|
|
if (CERT_IsCACert(cert, &nsCertType))
|
|
|
|
nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER;
|
|
|
|
/* if the basic constraint extension says the cert is a CA, then
|
|
|
|
allow SSL CA and EMAIL CA and Status Responder */
|
|
|
|
if (basicConstraintPresent && basicConstraint.isCA ) {
|
|
|
|
nsCertType |= (NS_CERT_TYPE_SSL_CA |
|
|
|
|
NS_CERT_TYPE_EMAIL_CA |
|
|
|
|
EXT_KEY_USAGE_STATUS_RESPONDER);
|
|
|
|
}
|
|
|
|
/* allow any ssl or email (no ca or object signing. */
|
|
|
|
nsCertType |= NS_CERT_TYPE_SSL_CLIENT | NS_CERT_TYPE_SSL_SERVER |
|
|
|
|
NS_CERT_TYPE_EMAIL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (encodedExtKeyUsage.data != NULL) {
|
|
|
|
PORT_Free(encodedExtKeyUsage.data);
|
|
|
|
}
|
|
|
|
if (extKeyUsage != NULL) {
|
|
|
|
CERT_DestroyOidSequence(extKeyUsage);
|
|
|
|
}
|
|
|
|
return nsCertType;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* cert_GetKeyID() - extract or generate the subjectKeyID from a certificate
|
|
|
|
*/
|
|
|
|
SECStatus
|
|
|
|
cert_GetKeyID(CERTCertificate *cert)
|
|
|
|
{
|
|
|
|
SECItem tmpitem;
|
|
|
|
SECStatus rv;
|
|
|
|
|
|
|
|
cert->subjectKeyID.len = 0;
|
|
|
|
|
|
|
|
/* see of the cert has a key identifier extension */
|
|
|
|
rv = CERT_FindSubjectKeyIDExtension(cert, &tmpitem);
|
|
|
|
if ( rv == SECSuccess ) {
|
|
|
|
cert->subjectKeyID.data = (unsigned char*) PORT_ArenaAlloc(cert->arena, tmpitem.len);
|
|
|
|
if ( cert->subjectKeyID.data != NULL ) {
|
|
|
|
PORT_Memcpy(cert->subjectKeyID.data, tmpitem.data, tmpitem.len);
|
|
|
|
cert->subjectKeyID.len = tmpitem.len;
|
|
|
|
cert->keyIDGenerated = PR_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
PORT_Free(tmpitem.data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if the cert doesn't have a key identifier extension, then generate one*/
|
|
|
|
if ( cert->subjectKeyID.len == 0 ) {
|
|
|
|
/*
|
|
|
|
* pkix says that if the subjectKeyID is not present, then we should
|
|
|
|
* use the SHA-1 hash of the DER-encoded publicKeyInfo from the cert
|
|
|
|
*/
|
|
|
|
cert->subjectKeyID.data = (unsigned char *)PORT_ArenaAlloc(cert->arena, SHA1_LENGTH);
|
|
|
|
if ( cert->subjectKeyID.data != NULL ) {
|
|
|
|
rv = PK11_HashBuf(SEC_OID_SHA1,cert->subjectKeyID.data,
|
|
|
|
cert->derPublicKey.data,
|
|
|
|
cert->derPublicKey.len);
|
|
|
|
if ( rv == SECSuccess ) {
|
|
|
|
cert->subjectKeyID.len = SHA1_LENGTH;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( cert->subjectKeyID.len == 0 ) {
|
|
|
|
return(SECFailure);
|
|
|
|
}
|
|
|
|
return(SECSuccess);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static PRBool
|
|
|
|
cert_IsRootCert(CERTCertificate *cert)
|
|
|
|
{
|
|
|
|
SECStatus rv;
|
|
|
|
SECItem tmpitem;
|
|
|
|
|
|
|
|
/* cache the authKeyID extension, if present */
|
|
|
|
cert->authKeyID = CERT_FindAuthKeyIDExten(cert->arena, cert);
|
|
|
|
|
|
|
|
/* it MUST be self-issued to be a root */
|
|
|
|
if (cert->derIssuer.len == 0 ||
|
|
|
|
!SECITEM_ItemsAreEqual(&cert->derIssuer, &cert->derSubject))
|
|
|
|
{
|
|
|
|
return PR_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check the authKeyID extension */
|
|
|
|
if (cert->authKeyID) {
|
|
|
|
/* authority key identifier is present */
|
|
|
|
if (cert->authKeyID->keyID.len > 0) {
|
|
|
|
/* the keyIdentifier field is set, look for subjectKeyID */
|
|
|
|
rv = CERT_FindSubjectKeyIDExtension(cert, &tmpitem);
|
|
|
|
if (rv == SECSuccess) {
|
|
|
|
PRBool match;
|
|
|
|
/* also present, they MUST match for it to be a root */
|
|
|
|
match = SECITEM_ItemsAreEqual(&cert->authKeyID->keyID,
|
|
|
|
&tmpitem);
|
|
|
|
PORT_Free(tmpitem.data);
|
|
|
|
if (!match) return PR_FALSE; /* else fall through */
|
|
|
|
} else {
|
|
|
|
/* the subject key ID is required when AKI is present */
|
|
|
|
return PR_FALSE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (cert->authKeyID->authCertIssuer) {
|
|
|
|
SECItem *caName;
|
|
|
|
caName = (SECItem *)CERT_GetGeneralNameByType(
|
|
|
|
cert->authKeyID->authCertIssuer,
|
|
|
|
certDirectoryName, PR_TRUE);
|
|
|
|
if (caName) {
|
|
|
|
if (!SECITEM_ItemsAreEqual(&cert->derIssuer, caName)) {
|
|
|
|
return PR_FALSE;
|
|
|
|
} /* else fall through */
|
|
|
|
} /* else ??? could not get general name as directory name? */
|
|
|
|
}
|
|
|
|
if (cert->authKeyID->authCertSerialNumber.len > 0) {
|
|
|
|
if (!SECITEM_ItemsAreEqual(&cert->serialNumber,
|
|
|
|
&cert->authKeyID->authCertSerialNumber)) {
|
|
|
|
return PR_FALSE;
|
|
|
|
} /* else fall through */
|
|
|
|
}
|
|
|
|
/* all of the AKI fields that were present passed the test */
|
|
|
|
return PR_TRUE;
|
|
|
|
}
|
|
|
|
/* else the AKI was not present, so this is a root */
|
|
|
|
return PR_TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* take a DER certificate and decode it into a certificate structure
|
|
|
|
*/
|
|
|
|
CERTCertificate *
|
|
|
|
CERT_DecodeDERCertificate(SECItem *derSignedCert, PRBool copyDER,
|
|
|
|
char *nickname)
|
|
|
|
{
|
|
|
|
CERTCertificate *cert;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2009-02-10 20:18:32 +03:00
|
|
|
/* determine if this is a root cert */
|
|
|
|
cert->isRoot = cert_IsRootCert(cert);
|
|
|
|
|
2008-06-06 16:40:11 +04:00
|
|
|
/* 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, ¬Before, ¬After);
|
|
|
|
|
|
|
|
if (rv) {
|
|
|
|
return(secCertTimeExpired); /*XXX is this the right thing to do here?*/
|
|
|
|
}
|
|
|
|
|
|
|
|
LL_I2L(llPendingSlop, pendingSlop);
|
|
|
|
/* convert to micro seconds */
|
|
|
|
LL_UI2L(tmp1, PR_USEC_PER_SEC);
|
|
|
|
LL_MUL(llPendingSlop, llPendingSlop, tmp1);
|
|
|
|
LL_SUB(notBefore, notBefore, llPendingSlop);
|
|
|
|
if ( LL_CMP( t, <, notBefore ) ) {
|
|
|
|
PORT_SetError(SEC_ERROR_EXPIRED_CERTIFICATE);
|
|
|
|
return(secCertTimeNotValidYet);
|
|
|
|
}
|
|
|
|
if ( LL_CMP( t, >, notAfter) ) {
|
|
|
|
PORT_SetError(SEC_ERROR_EXPIRED_CERTIFICATE);
|
|
|
|
return(secCertTimeExpired);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(secCertTimeValid);
|
|
|
|
}
|
|
|
|
|
|
|
|
SECStatus
|
|
|
|
SEC_GetCrlTimes(CERTCrl *date, PRTime *notBefore, PRTime *notAfter)
|
|
|
|
{
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
/* convert DER not-before time */
|
|
|
|
rv = DER_DecodeTimeChoice(notBefore, &date->lastUpdate);
|
|
|
|
if (rv) {
|
|
|
|
return(SECFailure);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* convert DER not-after time */
|
|
|
|
if (date->nextUpdate.data) {
|
|
|
|
rv = DER_DecodeTimeChoice(notAfter, &date->nextUpdate);
|
|
|
|
if (rv) {
|
|
|
|
return(SECFailure);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
LL_I2L(*notAfter, 0L);
|
|
|
|
}
|
|
|
|
return(SECSuccess);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* These routines should probably be combined with the cert
|
|
|
|
* routines using an common extraction routine.
|
|
|
|
*/
|
|
|
|
SECCertTimeValidity
|
|
|
|
SEC_CheckCrlTimes(CERTCrl *crl, PRTime t) {
|
|
|
|
PRTime notBefore, notAfter, llPendingSlop, tmp1;
|
|
|
|
SECStatus rv;
|
|
|
|
|
|
|
|
rv = SEC_GetCrlTimes(crl, ¬Before, ¬After);
|
|
|
|
|
|
|
|
if (rv) {
|
|
|
|
return(secCertTimeExpired);
|
|
|
|
}
|
|
|
|
|
|
|
|
LL_I2L(llPendingSlop, pendingSlop);
|
|
|
|
/* convert to micro seconds */
|
|
|
|
LL_I2L(tmp1, PR_USEC_PER_SEC);
|
|
|
|
LL_MUL(llPendingSlop, llPendingSlop, tmp1);
|
|
|
|
LL_SUB(notBefore, notBefore, llPendingSlop);
|
|
|
|
if ( LL_CMP( t, <, notBefore ) ) {
|
|
|
|
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:
|
2011-08-05 04:13:14 +04:00
|
|
|
/*
|
2011-08-05 19:50:59 +04:00
|
|
|
* RFC 5280 lists digitalSignature and keyAgreement for
|
|
|
|
* id-kp-clientAuth. NSS does not support the *_fixed_dh and
|
|
|
|
* *_fixed_ecdh client certificate types.
|
2011-08-05 04:13:14 +04:00
|
|
|
*/
|
2008-06-06 16:40:11 +04:00
|
|
|
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:
|
2011-08-05 04:13:14 +04:00
|
|
|
requiredKeyUsage = KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION;
|
2008-06-06 16:40:11 +04:00
|
|
|
requiredCertType = NS_CERT_TYPE_EMAIL;
|
|
|
|
break;
|
|
|
|
case certUsageEmailRecipient:
|
|
|
|
requiredKeyUsage = KU_KEY_AGREEMENT_OR_ENCIPHERMENT;
|
|
|
|
requiredCertType = NS_CERT_TYPE_EMAIL;
|
|
|
|
break;
|
|
|
|
case certUsageObjectSigner:
|
2011-08-05 19:50:59 +04:00
|
|
|
/* RFC 5280 lists only digitalSignature for id-kp-codeSigning. */
|
|
|
|
requiredKeyUsage = KU_DIGITAL_SIGNATURE;
|
2008-06-06 16:40:11 +04:00
|
|
|
requiredCertType = NS_CERT_TYPE_OBJECT_SIGNING;
|
|
|
|
break;
|
|
|
|
case certUsageStatusResponder:
|
2011-08-05 04:13:14 +04:00
|
|
|
requiredKeyUsage = KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION;
|
2008-06-06 16:40:11 +04:00
|
|
|
requiredCertType = EXT_KEY_USAGE_STATUS_RESPONDER;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
PORT_Assert(0);
|
|
|
|
goto loser;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( retKeyUsage != NULL ) {
|
|
|
|
*retKeyUsage = requiredKeyUsage;
|
|
|
|
}
|
|
|
|
if ( retCertType != NULL ) {
|
|
|
|
*retCertType = requiredCertType;
|
|
|
|
}
|
|
|
|
|
|
|
|
return(SECSuccess);
|
|
|
|
loser:
|
|
|
|
return(SECFailure);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* check the key usage of a cert against a set of required values
|
|
|
|
*/
|
|
|
|
SECStatus
|
|
|
|
CERT_CheckKeyUsage(CERTCertificate *cert, unsigned int requiredUsage)
|
|
|
|
{
|
|
|
|
if (!cert) {
|
|
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
|
|
return SECFailure;
|
|
|
|
}
|
|
|
|
/* choose between key agreement or key encipherment based on key
|
|
|
|
* type in cert
|
|
|
|
*/
|
|
|
|
if ( requiredUsage & KU_KEY_AGREEMENT_OR_ENCIPHERMENT ) {
|
|
|
|
KeyType keyType = CERT_GetCertKeyType(&cert->subjectPublicKeyInfo);
|
|
|
|
/* turn off the special bit */
|
|
|
|
requiredUsage &= (~KU_KEY_AGREEMENT_OR_ENCIPHERMENT);
|
|
|
|
|
|
|
|
switch (keyType) {
|
|
|
|
case rsaKey:
|
|
|
|
requiredUsage |= KU_KEY_ENCIPHERMENT;
|
|
|
|
break;
|
|
|
|
case dsaKey:
|
|
|
|
requiredUsage |= KU_DIGITAL_SIGNATURE;
|
|
|
|
break;
|
|
|
|
case dhKey:
|
|
|
|
requiredUsage |= KU_KEY_AGREEMENT;
|
|
|
|
break;
|
|
|
|
case ecKey:
|
|
|
|
/* Accept either signature or agreement. */
|
|
|
|
if (!(cert->keyUsage & (KU_DIGITAL_SIGNATURE | KU_KEY_AGREEMENT)))
|
|
|
|
goto loser;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
goto loser;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-08-05 04:13:14 +04:00
|
|
|
/* 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 )
|
2008-06-06 16:40:11 +04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2010-09-18 17:34:25 +04:00
|
|
|
static PRBool
|
|
|
|
cert_IsIPAddr(const char *hn)
|
|
|
|
{
|
|
|
|
PRBool isIPaddr = PR_FALSE;
|
|
|
|
PRNetAddr netAddr;
|
|
|
|
isIPaddr = (PR_SUCCESS == PR_StringToNetAddr(hn, &netAddr));
|
|
|
|
return isIPaddr;
|
|
|
|
}
|
|
|
|
|
2008-06-06 16:40:11 +04:00
|
|
|
/*
|
|
|
|
** 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.
|
|
|
|
**
|
2009-04-07 05:36:45 +04:00
|
|
|
** This function may modify string cn, so caller must pass a modifiable copy.
|
2008-06-06 16:40:11 +04:00
|
|
|
*/
|
|
|
|
static SECStatus
|
|
|
|
cert_TestHostName(char * cn, const char * hn)
|
|
|
|
{
|
2009-04-07 05:36:45 +04:00
|
|
|
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);
|
2008-06-06 16:40:11 +04:00
|
|
|
|
2009-04-07 05:36:45 +04:00
|
|
|
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.
|
|
|
|
*/
|
2008-06-06 16:40:11 +04:00
|
|
|
if (PORT_Strcasecmp(hn, cn) == 0) {
|
|
|
|
return SECSuccess;
|
|
|
|
}
|
2009-04-07 05:36:45 +04:00
|
|
|
|
2008-06-06 16:40:11 +04:00
|
|
|
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;
|
2008-08-15 08:12:54 +04:00
|
|
|
PRBool isIPaddr = PR_FALSE;
|
2008-06-06 16:40:11 +04:00
|
|
|
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) {
|
2008-08-15 08:12:54 +04:00
|
|
|
goto fail;
|
2008-06-06 16:40:11 +04:00
|
|
|
}
|
|
|
|
isIPaddr = (PR_SUCCESS == PR_StringToNetAddr(hn, &netAddr));
|
|
|
|
rv = SECFailure;
|
|
|
|
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
|
|
if (!arena)
|
2008-08-15 08:12:54 +04:00
|
|
|
goto fail;
|
2008-06-06 16:40:11 +04:00
|
|
|
|
|
|
|
nameList = current = CERT_DecodeAltNameExtension(arena, &subAltName);
|
|
|
|
if (!current)
|
2008-08-15 08:12:54 +04:00
|
|
|
goto fail;
|
2008-06-06 16:40:11 +04:00
|
|
|
|
|
|
|
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;
|
2009-04-07 05:36:45 +04:00
|
|
|
rv = CERT_RFC1485_EscapeAndQuote(cn, cnBufLen,
|
2010-02-12 11:47:51 +03:00
|
|
|
(char *)current->name.other.data,
|
|
|
|
cnLen);
|
2009-04-07 05:36:45 +04:00
|
|
|
if (rv != SECSuccess && PORT_GetError() == SEC_ERROR_OUTPUT_LEN) {
|
|
|
|
cnBufLen = cnLen * 3 + 3; /* big enough for worst case */
|
2008-06-06 16:40:11 +04:00
|
|
|
cn = (char *)PORT_ArenaAlloc(arena, cnBufLen);
|
|
|
|
if (!cn)
|
2008-08-15 08:12:54 +04:00
|
|
|
goto fail;
|
2009-04-07 05:36:45 +04:00
|
|
|
rv = CERT_RFC1485_EscapeAndQuote(cn, cnBufLen,
|
2010-02-12 11:47:51 +03:00
|
|
|
(char *)current->name.other.data,
|
|
|
|
cnLen);
|
2008-06-06 16:40:11 +04:00
|
|
|
}
|
2009-04-07 05:36:45 +04:00
|
|
|
if (rv == SECSuccess)
|
|
|
|
rv = cert_TestHostName(cn ,hn);
|
2008-06-06 16:40:11 +04:00
|
|
|
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);
|
|
|
|
|
2008-08-15 08:12:54 +04:00
|
|
|
fail:
|
|
|
|
|
|
|
|
if (!(isIPaddr ? IPextCount : DNSextCount)) {
|
2008-06-06 16:40:11 +04:00
|
|
|
/* 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 *
|
2011-08-19 19:27:10 +04:00
|
|
|
cert_GetSubjectAltNameList(CERTCertificate *cert, PRArenaPool *arena)
|
2008-06-06 16:40:11 +04:00
|
|
|
{
|
|
|
|
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);
|
2009-07-29 04:01:39 +04:00
|
|
|
cn[currentInput->name.other.len] = 0;
|
2008-06-06 16:40:11 +04:00
|
|
|
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);
|
|
|
|
}
|
2009-07-29 04:01:39 +04:00
|
|
|
if (PR_NetAddrToString(&addr, ipbuf, sizeof(ipbuf)) == PR_FAILURE)
|
2008-06-06 16:40:11 +04:00
|
|
|
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 */
|
2010-07-19 09:45:52 +04:00
|
|
|
singleName = CERT_GetCommonName(&cert->subject);
|
2008-06-06 16:40:11 +04:00
|
|
|
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;
|
|
|
|
|
2010-07-19 09:45:52 +04:00
|
|
|
cn = CERT_GetCommonName(&cert->subject);
|
2008-06-06 16:40:11 +04:00
|
|
|
if ( cn ) {
|
2010-09-18 17:34:25 +04:00
|
|
|
PRBool isIPaddr = cert_IsIPAddr(hn);
|
|
|
|
if (isIPaddr) {
|
|
|
|
if (PORT_Strcasecmp(hn, cn) == 0) {
|
|
|
|
rv = SECSuccess;
|
|
|
|
} else {
|
|
|
|
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
|
|
|
|
rv = SECFailure;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
rv = cert_TestHostName(cn, hn);
|
|
|
|
}
|
2008-06-06 16:40:11 +04:00
|
|
|
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;
|
|
|
|
|
2011-08-19 19:27:10 +04:00
|
|
|
firstname = CERT_GetCommonName(&cert->subject);
|
|
|
|
if ( firstname == NULL ) {
|
|
|
|
firstname = CERT_GetOrgUnitName(&cert->subject);
|
|
|
|
}
|
2008-06-06 16:40:11 +04:00
|
|
|
|
2011-08-19 19:27:10 +04:00
|
|
|
org = CERT_GetOrgName(&cert->issuer);
|
|
|
|
if (org == NULL) {
|
|
|
|
org = CERT_GetDomainComponentName(&cert->issuer);
|
2008-06-06 16:40:11 +04:00
|
|
|
if (org == NULL) {
|
2011-08-19 19:27:10 +04:00
|
|
|
if (firstname) {
|
|
|
|
org = firstname;
|
|
|
|
firstname = NULL;
|
|
|
|
} else {
|
|
|
|
org = PORT_Strdup("Unknown CA");
|
2008-06-06 16:40:11 +04:00
|
|
|
}
|
|
|
|
}
|
2011-08-19 19:27:10 +04:00
|
|
|
}
|
2008-06-06 16:40:11 +04:00
|
|
|
|
2011-08-19 19:27:10 +04:00
|
|
|
/* can only fail if PORT_Strdup fails, in which case
|
|
|
|
* we're having memory problems. */
|
|
|
|
if (org == NULL) {
|
|
|
|
goto done;
|
|
|
|
}
|
2008-06-06 16:40:11 +04:00
|
|
|
|
|
|
|
|
2011-08-19 19:27:10 +04:00
|
|
|
count = 1;
|
|
|
|
while ( 1 ) {
|
2008-06-06 16:40:11 +04:00
|
|
|
|
2011-08-19 19:27:10 +04:00
|
|
|
if ( firstname ) {
|
|
|
|
if ( count == 1 ) {
|
|
|
|
nickname = PR_smprintf("%s - %s", firstname, org);
|
2008-06-06 16:40:11 +04:00
|
|
|
} else {
|
2011-08-19 19:27:10 +04:00
|
|
|
nickname = PR_smprintf("%s - %s #%d", firstname, org, count);
|
2008-06-06 16:40:11 +04:00
|
|
|
}
|
2011-08-19 19:27:10 +04:00
|
|
|
} else {
|
|
|
|
if ( count == 1 ) {
|
|
|
|
nickname = PR_smprintf("%s", org);
|
|
|
|
} else {
|
|
|
|
nickname = PR_smprintf("%s #%d", org, count);
|
2008-06-06 16:40:11 +04:00
|
|
|
}
|
2011-08-19 19:27:10 +04:00
|
|
|
}
|
|
|
|
if ( nickname == NULL ) {
|
|
|
|
goto done;
|
|
|
|
}
|
2008-06-06 16:40:11 +04:00
|
|
|
|
2011-08-19 19:27:10 +04:00
|
|
|
/* look up the nickname to make sure it isn't in use already */
|
|
|
|
dummycert = CERT_FindCertByNickname(cert->dbhandle, nickname);
|
2008-06-06 16:40:11 +04:00
|
|
|
|
2011-08-19 19:27:10 +04:00
|
|
|
if ( dummycert == NULL ) {
|
|
|
|
goto done;
|
|
|
|
}
|
2008-06-06 16:40:11 +04:00
|
|
|
|
2011-08-19 19:27:10 +04:00
|
|
|
/* found a cert, destroy it and loop */
|
|
|
|
CERT_DestroyCertificate(dummycert);
|
2008-06-06 16:40:11 +04:00
|
|
|
|
2011-08-19 19:27:10 +04:00
|
|
|
/* free the nickname */
|
2008-06-06 16:40:11 +04:00
|
|
|
PORT_Free(nickname);
|
2011-08-19 19:27:10 +04:00
|
|
|
|
|
|
|
count++;
|
2008-06-06 16:40:11 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2009-01-21 06:43:31 +03:00
|
|
|
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;
|
|
|
|
}
|
2008-06-06 16:40:11 +04:00
|
|
|
|
2009-01-21 06:43:31 +03:00
|
|
|
static unsigned int
|
|
|
|
cert_ComputeTrustOverrides(CERTCertificate *cert, unsigned int cType)
|
|
|
|
{
|
|
|
|
CERTCertTrust *trust = cert->trust;
|
|
|
|
|
|
|
|
if (trust && (trust->sslFlags |
|
|
|
|
trust->emailFlags |
|
|
|
|
trust->objectSigningFlags)) {
|
|
|
|
|
2011-08-19 19:27:10 +04:00
|
|
|
if (trust->sslFlags & (CERTDB_TERMINAL_RECORD|CERTDB_TRUSTED))
|
2009-01-21 06:43:31 +03:00
|
|
|
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
|
2011-08-19 19:27:10 +04:00
|
|
|
if (trust->emailFlags & (CERTDB_TERMINAL_RECORD|CERTDB_TRUSTED))
|
2009-01-21 06:43:31 +03:00
|
|
|
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
|
2011-08-19 19:27:10 +04:00
|
|
|
if (trust->objectSigningFlags & (CERTDB_TERMINAL_RECORD|CERTDB_TRUSTED))
|
2009-01-21 06:43:31 +03:00
|
|
|
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;
|
|
|
|
}
|
2008-06-06 16:40:11 +04:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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)
|
|
|
|
{
|
2009-01-21 06:43:31 +03:00
|
|
|
unsigned int cType = cert->nsCertType;
|
|
|
|
PRBool ret = PR_FALSE;
|
2009-01-16 22:01:34 +03:00
|
|
|
|
2009-01-21 06:43:31 +03:00
|
|
|
if (cType & (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA |
|
|
|
|
NS_CERT_TYPE_OBJECT_SIGNING_CA)) {
|
|
|
|
ret = PR_TRUE;
|
2009-01-16 22:15:28 +03:00
|
|
|
} else {
|
2009-01-21 06:43:31 +03:00
|
|
|
SECStatus rv;
|
|
|
|
CERTBasicConstraints constraints;
|
|
|
|
|
|
|
|
rv = CERT_FindBasicConstraintExten(cert, &constraints);
|
|
|
|
if (rv == SECSuccess && constraints.isCA) {
|
2008-06-06 16:40:11 +04:00
|
|
|
ret = PR_TRUE;
|
2009-01-21 06:43:31 +03:00
|
|
|
cType |= (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA);
|
2008-06-06 16:40:11 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-08-19 19:27:10 +04:00
|
|
|
/* finally check if it's an X.509 v1 root CA */
|
2009-01-21 06:43:31 +03:00
|
|
|
if (!ret &&
|
2011-08-19 19:27:10 +04:00
|
|
|
(cert->isRoot && cert_Version(cert) < SEC_CERTIFICATE_VERSION_3)) {
|
2008-06-06 16:40:11 +04:00
|
|
|
ret = PR_TRUE;
|
2009-01-21 06:43:31 +03:00
|
|
|
cType |= (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA);
|
2008-06-06 16:40:11 +04:00
|
|
|
}
|
2009-01-21 06:43:31 +03:00
|
|
|
/* 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;
|
2008-06-06 16:40:11 +04:00
|
|
|
|
2009-01-21 06:43:31 +03:00
|
|
|
if (rettype != NULL) {
|
|
|
|
*rettype = cType;
|
2008-06-06 16:40:11 +04:00
|
|
|
}
|
2009-01-21 06:43:31 +03:00
|
|
|
return ret;
|
2008-06-06 16:40:11 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
|
|
|
CERT_IsCADERCert(SECItem *derCert, unsigned int *type) {
|
|
|
|
CERTCertificate *cert;
|
|
|
|
PRBool isCA;
|
|
|
|
|
|
|
|
/* This is okay -- only looks at extensions */
|
|
|
|
cert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL);
|
|
|
|
if (cert == NULL) return PR_FALSE;
|
|
|
|
|
|
|
|
isCA = CERT_IsCACert(cert,type);
|
|
|
|
CERT_DestroyCertificate (cert);
|
|
|
|
return isCA;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
|
|
|
CERT_IsRootDERCert(SECItem *derCert)
|
|
|
|
{
|
|
|
|
CERTCertificate *cert;
|
|
|
|
PRBool isRoot;
|
|
|
|
|
|
|
|
/* This is okay -- only looks at extensions */
|
|
|
|
cert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL);
|
|
|
|
if (cert == NULL) return PR_FALSE;
|
|
|
|
|
|
|
|
isRoot = cert->isRoot;
|
|
|
|
CERT_DestroyCertificate (cert);
|
|
|
|
return isRoot;
|
|
|
|
}
|
|
|
|
|
|
|
|
CERTCompareValidityStatus
|
|
|
|
CERT_CompareValidityTimes(CERTValidity* val_a, CERTValidity* val_b)
|
|
|
|
{
|
|
|
|
PRTime notBeforeA, notBeforeB, notAfterA, notAfterB;
|
|
|
|
|
|
|
|
if (!val_a || !val_b)
|
|
|
|
{
|
|
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
|
|
return certValidityUndetermined;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( SECSuccess != DER_DecodeTimeChoice(¬BeforeA, &val_a->notBefore) ||
|
|
|
|
SECSuccess != DER_DecodeTimeChoice(¬BeforeB, &val_b->notBefore) ||
|
|
|
|
SECSuccess != DER_DecodeTimeChoice(¬AfterA, &val_a->notAfter) ||
|
|
|
|
SECSuccess != DER_DecodeTimeChoice(¬AfterB, &val_b->notAfter) ) {
|
|
|
|
return certValidityUndetermined;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* sanity check */
|
|
|
|
if (LL_CMP(notBeforeA,>,notAfterA) || LL_CMP(notBeforeB,>,notAfterB)) {
|
|
|
|
PORT_SetError(SEC_ERROR_INVALID_TIME);
|
|
|
|
return certValidityUndetermined;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (LL_CMP(notAfterA,!=,notAfterB)) {
|
|
|
|
/* one cert validity goes farther into the future, select it */
|
|
|
|
return LL_CMP(notAfterA,<,notAfterB) ?
|
|
|
|
certValidityChooseB : certValidityChooseA;
|
|
|
|
}
|
|
|
|
/* the two certs have the same expiration date */
|
|
|
|
PORT_Assert(LL_CMP(notAfterA, == , notAfterB));
|
|
|
|
/* do they also have the same start date ? */
|
|
|
|
if (LL_CMP(notBeforeA,==,notBeforeB)) {
|
|
|
|
return certValidityEqual;
|
|
|
|
}
|
|
|
|
/* choose cert with the later start date */
|
|
|
|
return LL_CMP(notBeforeA,<,notBeforeB) ?
|
|
|
|
certValidityChooseB : certValidityChooseA;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* is certa newer than certb? If one is expired, pick the other one.
|
|
|
|
*/
|
|
|
|
PRBool
|
|
|
|
CERT_IsNewer(CERTCertificate *certa, CERTCertificate *certb)
|
|
|
|
{
|
|
|
|
PRTime notBeforeA, notAfterA, notBeforeB, notAfterB, now;
|
|
|
|
SECStatus rv;
|
|
|
|
PRBool newerbefore, newerafter;
|
|
|
|
|
|
|
|
rv = CERT_GetCertTimes(certa, ¬BeforeA, ¬AfterA);
|
|
|
|
if ( rv != SECSuccess ) {
|
|
|
|
return(PR_FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
rv = CERT_GetCertTimes(certb, ¬BeforeB, ¬AfterB);
|
|
|
|
if ( rv != SECSuccess ) {
|
|
|
|
return(PR_TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
newerbefore = PR_FALSE;
|
|
|
|
if ( LL_CMP(notBeforeA, >, notBeforeB) ) {
|
|
|
|
newerbefore = PR_TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
newerafter = PR_FALSE;
|
|
|
|
if ( LL_CMP(notAfterA, >, notAfterB) ) {
|
|
|
|
newerafter = PR_TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( newerbefore && newerafter ) {
|
|
|
|
return(PR_TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ( !newerbefore ) && ( !newerafter ) ) {
|
|
|
|
return(PR_FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* get current time */
|
|
|
|
now = PR_Now();
|
|
|
|
|
|
|
|
if ( newerbefore ) {
|
|
|
|
/* cert A was issued after cert B, but expires sooner */
|
|
|
|
/* if A is expired, then pick B */
|
|
|
|
if ( LL_CMP(notAfterA, <, now ) ) {
|
|
|
|
return(PR_FALSE);
|
|
|
|
}
|
|
|
|
return(PR_TRUE);
|
|
|
|
} else {
|
|
|
|
/* cert B was issued after cert A, but expires sooner */
|
|
|
|
/* if B is expired, then pick A */
|
|
|
|
if ( LL_CMP(notAfterB, <, now ) ) {
|
|
|
|
return(PR_TRUE);
|
|
|
|
}
|
|
|
|
return(PR_FALSE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
CERT_DestroyCertArray(CERTCertificate **certs, unsigned int ncerts)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
if ( certs ) {
|
|
|
|
for ( i = 0; i < ncerts; i++ ) {
|
|
|
|
if ( certs[i] ) {
|
|
|
|
CERT_DestroyCertificate(certs[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
PORT_Free(certs);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *
|
|
|
|
CERT_FixupEmailAddr(const char *emailAddr)
|
|
|
|
{
|
|
|
|
char *retaddr;
|
|
|
|
char *str;
|
|
|
|
|
|
|
|
if ( emailAddr == NULL ) {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* copy the string */
|
|
|
|
str = retaddr = PORT_Strdup(emailAddr);
|
|
|
|
if ( str == NULL ) {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* make it lower case */
|
|
|
|
while ( *str ) {
|
|
|
|
*str = tolower( *str );
|
|
|
|
str++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return(retaddr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* NOTE - don't allow encode of govt-approved or invisible bits
|
|
|
|
*/
|
|
|
|
SECStatus
|
2009-01-21 06:43:31 +03:00
|
|
|
CERT_DecodeTrustString(CERTCertTrust *trust, const char *trusts)
|
2008-06-06 16:40:11 +04:00
|
|
|
{
|
|
|
|
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':
|
2011-08-19 19:27:10 +04:00
|
|
|
*pflags = *pflags | CERTDB_TERMINAL_RECORD;
|
2008-06-06 16:40:11 +04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'P':
|
2011-08-19 19:27:10 +04:00
|
|
|
*pflags = *pflags | CERTDB_TRUSTED | CERTDB_TERMINAL_RECORD;
|
2008-06-06 16:40:11 +04:00
|
|
|
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");
|
2011-08-19 19:27:10 +04:00
|
|
|
if (flags & CERTDB_TERMINAL_RECORD)
|
2008-06-06 16:40:11 +04:00
|
|
|
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);
|
Bug 673382: Update NSS to NSS_3_12_11_BETA1, which includes the fixes for
Bug 661609, Bug 650276 (code not used by Mozilla), Bug 602509, Bug 655411,
Bug 655850, Bug 671711, Bug 617565, Bug 668001, Bug 346583, Bug 661061.
2011-07-27 20:07:32 +04:00
|
|
|
if (certs[fcerts]) {
|
|
|
|
SECItem subjKeyID = {siBuffer, NULL, 0};
|
|
|
|
if (CERT_FindSubjectKeyIDExtension(certs[fcerts],
|
|
|
|
&subjKeyID) == SECSuccess) {
|
|
|
|
if (subjKeyID.data) {
|
|
|
|
cert_AddSubjectKeyIDMapping(&subjKeyID, certs[fcerts]);
|
|
|
|
}
|
|
|
|
SECITEM_FreeItem(&subjKeyID, PR_FALSE);
|
|
|
|
}
|
|
|
|
fcerts++;
|
|
|
|
}
|
2008-06-06 16:40:11 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( keepCerts ) {
|
|
|
|
for ( i = 0; i < fcerts; i++ ) {
|
|
|
|
char* canickname = NULL;
|
2011-08-19 19:27:10 +04:00
|
|
|
PRBool isCA;
|
2008-06-06 16:40:11 +04:00
|
|
|
|
|
|
|
SECKEY_UpdateCertPQG(certs[i]);
|
|
|
|
|
2011-08-19 19:27:10 +04:00
|
|
|
isCA = CERT_IsCACert(certs[i], NULL);
|
|
|
|
if ( isCA ) {
|
2008-06-06 16:40:11 +04:00
|
|
|
canickname = CERT_MakeCANickname(certs[i]);
|
|
|
|
}
|
|
|
|
|
2011-08-19 19:27:10 +04:00
|
|
|
if(isCA && (fcerts > 1)) {
|
2008-06-06 16:40:11 +04:00
|
|
|
/* 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);
|
|
|
|
}
|
|
|
|
|
2011-08-19 19:27:10 +04:00
|
|
|
PORT_Free(canickname);
|
2008-06-06 16:40:11 +04:00
|
|
|
/* 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, ¬BeforeA, ¬AfterA);
|
|
|
|
if ( rv != SECSuccess ) {
|
|
|
|
return(PR_FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
rv = CERT_GetCertTimes(certb, ¬BeforeB, ¬AfterB);
|
|
|
|
if ( rv != SECSuccess ) {
|
|
|
|
return(PR_TRUE);
|
|
|
|
}
|
|
|
|
newerbefore = PR_FALSE;
|
|
|
|
if ( LL_CMP(notBeforeA, >, notBeforeB) ) {
|
|
|
|
newerbefore = PR_TRUE;
|
|
|
|
}
|
|
|
|
newerafter = PR_FALSE;
|
|
|
|
if ( LL_CMP(notAfterA, >, notAfterB) ) {
|
|
|
|
newerafter = PR_TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check if A is valid at sorttime */
|
|
|
|
if ( CERT_CheckCertValidTimes(certa, sorttime, PR_FALSE)
|
|
|
|
!= secCertTimeValid ) {
|
|
|
|
aNotValid = PR_TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check if B is valid at sorttime */
|
|
|
|
if ( CERT_CheckCertValidTimes(certb, sorttime, PR_FALSE)
|
|
|
|
!= secCertTimeValid ) {
|
|
|
|
bNotValid = PR_TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* a is valid, b is not */
|
|
|
|
if ( bNotValid && ( ! aNotValid ) ) {
|
|
|
|
return(PR_TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* b is valid, a is not */
|
|
|
|
if ( aNotValid && ( ! bNotValid ) ) {
|
|
|
|
return(PR_FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* a and b are either valid or not valid */
|
|
|
|
if ( newerbefore && newerafter ) {
|
|
|
|
return(PR_TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ( !newerbefore ) && ( !newerafter ) ) {
|
|
|
|
return(PR_FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( newerbefore ) {
|
|
|
|
/* cert A was issued after cert B, but expires sooner */
|
|
|
|
return(PR_TRUE);
|
|
|
|
} else {
|
|
|
|
/* cert B was issued after cert A, but expires sooner */
|
|
|
|
return(PR_FALSE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SECStatus
|
|
|
|
CERT_AddCertToListSorted(CERTCertList *certs,
|
|
|
|
CERTCertificate *cert,
|
|
|
|
CERTSortCallback f,
|
|
|
|
void *arg)
|
|
|
|
{
|
|
|
|
CERTCertListNode *node;
|
|
|
|
CERTCertListNode *head;
|
|
|
|
PRBool ret;
|
|
|
|
|
|
|
|
node = (CERTCertListNode *)PORT_ArenaZAlloc(certs->arena,
|
|
|
|
sizeof(CERTCertListNode));
|
|
|
|
if ( node == NULL ) {
|
|
|
|
goto loser;
|
|
|
|
}
|
|
|
|
|
|
|
|
head = CERT_LIST_HEAD(certs);
|
|
|
|
|
|
|
|
while ( !CERT_LIST_END(head, certs) ) {
|
|
|
|
|
|
|
|
/* if cert is already in the list, then don't add it again */
|
|
|
|
if ( cert == head->cert ) {
|
|
|
|
/*XXX*/
|
|
|
|
/* don't keep a reference */
|
|
|
|
CERT_DestroyCertificate(cert);
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = (* f)(cert, head->cert, arg);
|
|
|
|
/* if sort function succeeds, then insert before current node */
|
|
|
|
if ( ret ) {
|
|
|
|
PR_INSERT_BEFORE(&node->links, &head->links);
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
head = CERT_LIST_NEXT(head);
|
|
|
|
}
|
|
|
|
/* if we get to the end, then just insert it at the tail */
|
|
|
|
PR_INSERT_BEFORE(&node->links, &certs->list);
|
|
|
|
|
|
|
|
done:
|
|
|
|
/* certs->count++; */
|
|
|
|
node->cert = cert;
|
|
|
|
return(SECSuccess);
|
|
|
|
|
|
|
|
loser:
|
|
|
|
return(SECFailure);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This routine is here because pcertdb.c still has a call to it.
|
|
|
|
* The SMIME profile code in pcertdb.c should be split into high (find
|
|
|
|
* the email cert) and low (store the profile) code. At that point, we
|
|
|
|
* can move this to certhigh.c where it belongs.
|
|
|
|
*
|
|
|
|
* remove certs from a list that don't have keyUsage and certType
|
|
|
|
* that match the given usage.
|
|
|
|
*/
|
|
|
|
SECStatus
|
|
|
|
CERT_FilterCertListByUsage(CERTCertList *certList, SECCertUsage usage,
|
|
|
|
PRBool ca)
|
|
|
|
{
|
|
|
|
unsigned int requiredKeyUsage;
|
|
|
|
unsigned int requiredCertType;
|
|
|
|
CERTCertListNode *node, *savenode;
|
|
|
|
SECStatus rv;
|
|
|
|
|
|
|
|
if (certList == NULL) goto loser;
|
|
|
|
|
|
|
|
rv = CERT_KeyUsageAndTypeForCertUsage(usage, ca, &requiredKeyUsage,
|
|
|
|
&requiredCertType);
|
|
|
|
if ( rv != SECSuccess ) {
|
|
|
|
goto loser;
|
|
|
|
}
|
|
|
|
|
|
|
|
node = CERT_LIST_HEAD(certList);
|
|
|
|
|
|
|
|
while ( !CERT_LIST_END(node, certList) ) {
|
|
|
|
|
|
|
|
PRBool bad = (PRBool)(!node->cert);
|
|
|
|
|
|
|
|
/* bad key usage ? */
|
|
|
|
if ( !bad &&
|
|
|
|
CERT_CheckKeyUsage(node->cert, requiredKeyUsage) != SECSuccess ) {
|
|
|
|
bad = PR_TRUE;
|
|
|
|
}
|
|
|
|
/* bad cert type ? */
|
|
|
|
if ( !bad ) {
|
|
|
|
unsigned int certType = 0;
|
|
|
|
if ( ca ) {
|
|
|
|
/* This function returns a more comprehensive cert type that
|
|
|
|
* takes trust flags into consideration. Should probably
|
|
|
|
* fix the cert decoding code to do this.
|
|
|
|
*/
|
|
|
|
(void)CERT_IsCACert(node->cert, &certType);
|
|
|
|
} else {
|
|
|
|
certType = node->cert->nsCertType;
|
|
|
|
}
|
|
|
|
if ( !( certType & requiredCertType ) ) {
|
|
|
|
bad = PR_TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( bad ) {
|
|
|
|
/* remove the node if it is bad */
|
|
|
|
savenode = CERT_LIST_NEXT(node);
|
|
|
|
CERT_RemoveCertListNode(node);
|
|
|
|
node = savenode;
|
|
|
|
} else {
|
|
|
|
node = CERT_LIST_NEXT(node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return(SECSuccess);
|
|
|
|
|
|
|
|
loser:
|
|
|
|
return(SECFailure);
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool CERT_IsUserCert(CERTCertificate* cert)
|
|
|
|
{
|
|
|
|
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;
|
2011-08-19 19:27:10 +04:00
|
|
|
static PLHashTable *gSubjKeyIDSlotCheckHash = NULL;
|
|
|
|
static PRLock *gSubjKeyIDSlotCheckLock = NULL;
|
2008-06-06 16:40:11 +04:00
|
|
|
|
|
|
|
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
|
|
|
|
};
|
|
|
|
|
2011-08-19 19:27:10 +04:00
|
|
|
SECStatus
|
|
|
|
cert_CreateSubjectKeyIDSlotCheckHash(void)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* This hash is used to remember the series of a slot
|
|
|
|
* when we last checked for user certs
|
|
|
|
*/
|
|
|
|
gSubjKeyIDSlotCheckHash = PL_NewHashTable(0, SECITEM_Hash,
|
|
|
|
SECITEM_HashCompare,
|
|
|
|
SECITEM_HashCompare,
|
|
|
|
&cert_AllocOps, NULL);
|
|
|
|
if (!gSubjKeyIDSlotCheckHash) {
|
|
|
|
PORT_SetError(SEC_ERROR_NO_MEMORY);
|
|
|
|
return SECFailure;
|
|
|
|
}
|
|
|
|
gSubjKeyIDSlotCheckLock = PR_NewLock();
|
|
|
|
if (!gSubjKeyIDSlotCheckLock) {
|
|
|
|
PL_HashTableDestroy(gSubjKeyIDSlotCheckHash);
|
|
|
|
gSubjKeyIDSlotCheckHash = NULL;
|
|
|
|
PORT_SetError(SEC_ERROR_NO_MEMORY);
|
|
|
|
return SECFailure;
|
|
|
|
}
|
|
|
|
return SECSuccess;
|
|
|
|
}
|
|
|
|
|
2008-06-06 16:40:11 +04:00
|
|
|
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;
|
|
|
|
}
|
2011-08-19 19:27:10 +04:00
|
|
|
/* initialize the companion hash (for remembering slot series) */
|
|
|
|
if (cert_CreateSubjectKeyIDSlotCheckHash() != SECSuccess) {
|
|
|
|
cert_DestroySubjectKeyIDHashTable();
|
|
|
|
return SECFailure;
|
|
|
|
}
|
2008-06-06 16:40:11 +04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-08-19 19:27:10 +04:00
|
|
|
SECStatus
|
|
|
|
cert_UpdateSubjectKeyIDSlotCheck(SECItem *slotid, int series)
|
|
|
|
{
|
|
|
|
SECItem *oldSeries, *newSlotid, *newSeries;
|
|
|
|
SECStatus rv = SECFailure;
|
|
|
|
|
|
|
|
if (!gSubjKeyIDSlotCheckLock) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
newSlotid = SECITEM_DupItem(slotid);
|
|
|
|
newSeries = SECITEM_AllocItem(NULL, NULL, sizeof(int));
|
|
|
|
if (!newSlotid || !newSeries ) {
|
|
|
|
PORT_SetError(SEC_ERROR_NO_MEMORY);
|
|
|
|
goto loser;
|
|
|
|
}
|
|
|
|
PORT_Memcpy(newSeries->data, &series, sizeof(int));
|
|
|
|
|
|
|
|
PR_Lock(gSubjKeyIDSlotCheckLock);
|
|
|
|
oldSeries = (SECItem *)PL_HashTableLookup(gSubjKeyIDSlotCheckHash, slotid);
|
|
|
|
if (oldSeries) {
|
|
|
|
/*
|
|
|
|
* make sure we don't leak the key of an existing entry
|
|
|
|
* (similar to cert_AddSubjectKeyIDMapping, see comment there)
|
|
|
|
*/
|
|
|
|
PL_HashTableRemove(gSubjKeyIDSlotCheckHash, slotid);
|
|
|
|
}
|
|
|
|
rv = (PL_HashTableAdd(gSubjKeyIDSlotCheckHash, newSlotid, newSeries)) ?
|
|
|
|
SECSuccess : SECFailure;
|
|
|
|
PR_Unlock(gSubjKeyIDSlotCheckLock);
|
|
|
|
if (rv == SECSuccess) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
loser:
|
|
|
|
if (newSlotid) {
|
|
|
|
SECITEM_FreeItem(newSlotid, PR_TRUE);
|
|
|
|
}
|
|
|
|
if (newSeries) {
|
|
|
|
SECITEM_FreeItem(newSeries, PR_TRUE);
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
cert_SubjectKeyIDSlotCheckSeries(SECItem *slotid)
|
|
|
|
{
|
|
|
|
SECItem *seriesItem = NULL;
|
|
|
|
int series;
|
|
|
|
|
|
|
|
if (!gSubjKeyIDSlotCheckLock) {
|
|
|
|
PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
PR_Lock(gSubjKeyIDSlotCheckLock);
|
|
|
|
seriesItem = (SECItem *)PL_HashTableLookup(gSubjKeyIDSlotCheckHash, slotid);
|
|
|
|
PR_Unlock(gSubjKeyIDSlotCheckLock);
|
|
|
|
/* getting a null series just means we haven't registered one yet,
|
|
|
|
* just return 0 */
|
|
|
|
if (seriesItem == NULL) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* if we got a series back, assert if it's not the proper length. */
|
|
|
|
PORT_Assert(seriesItem->len == sizeof(int));
|
|
|
|
if (seriesItem->len != sizeof(int)) {
|
|
|
|
PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
PORT_Memcpy(&series, seriesItem->data, sizeof(int));
|
|
|
|
return series;
|
|
|
|
}
|
|
|
|
|
|
|
|
SECStatus
|
|
|
|
cert_DestroySubjectKeyIDSlotCheckHash(void)
|
|
|
|
{
|
|
|
|
if (gSubjKeyIDSlotCheckHash) {
|
|
|
|
PR_Lock(gSubjKeyIDSlotCheckLock);
|
|
|
|
PL_HashTableDestroy(gSubjKeyIDSlotCheckHash);
|
|
|
|
gSubjKeyIDSlotCheckHash = NULL;
|
|
|
|
PR_Unlock(gSubjKeyIDSlotCheckLock);
|
|
|
|
PR_DestroyLock(gSubjKeyIDSlotCheckLock);
|
|
|
|
gSubjKeyIDSlotCheckLock = NULL;
|
|
|
|
}
|
|
|
|
return SECSuccess;
|
|
|
|
}
|
|
|
|
|
2008-06-06 16:40:11 +04:00
|
|
|
SECStatus
|
|
|
|
cert_DestroySubjectKeyIDHashTable(void)
|
|
|
|
{
|
|
|
|
if (gSubjKeyIDHash) {
|
|
|
|
PR_Lock(gSubjKeyIDLock);
|
|
|
|
PL_HashTableDestroy(gSubjKeyIDHash);
|
|
|
|
gSubjKeyIDHash = NULL;
|
|
|
|
PR_Unlock(gSubjKeyIDLock);
|
|
|
|
PR_DestroyLock(gSubjKeyIDLock);
|
|
|
|
gSubjKeyIDLock = NULL;
|
|
|
|
}
|
2011-08-19 19:27:10 +04:00
|
|
|
cert_DestroySubjectKeyIDSlotCheckHash();
|
2008-06-06 16:40:11 +04:00
|
|
|
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;
|
|
|
|
}
|