4421 lines
128 KiB
C
4421 lines
128 KiB
C
/**
|
|
* @file x509.c
|
|
* @version $Format:%h%d$
|
|
*
|
|
* X.509 Parser.
|
|
*/
|
|
/*
|
|
* Copyright (c) 2013-2016 INSIDE Secure Corporation
|
|
* Copyright (c) PeerSec Networks, 2002-2011
|
|
* All Rights Reserved
|
|
*
|
|
* The latest version of this code is available at http://www.matrixssl.org
|
|
*
|
|
* This software is open source; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This General Public License does NOT permit incorporating this software
|
|
* into proprietary programs. If you are unable to comply with the GPL, a
|
|
* commercial license for this software may be purchased from INSIDE at
|
|
* http://www.insidesecure.com/
|
|
*
|
|
* This program is distributed in WITHOUT ANY WARRANTY; without even the
|
|
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
* See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*/
|
|
/******************************************************************************/
|
|
|
|
#include "../cryptoApi.h"
|
|
|
|
#ifdef USE_X509
|
|
|
|
/******************************************************************************/
|
|
|
|
#ifdef POSIX
|
|
#include <time.h>
|
|
#endif
|
|
|
|
/******************************************************************************/
|
|
|
|
#define MAX_CERTS_PER_FILE 16
|
|
|
|
#ifdef USE_CERT_PARSE
|
|
/*
|
|
Certificate extensions
|
|
*/
|
|
#define IMPLICIT_ISSUER_ID 1
|
|
#define IMPLICIT_SUBJECT_ID 2
|
|
#define EXPLICIT_EXTENSION 3
|
|
|
|
/*
|
|
Distinguished Name attributes
|
|
*/
|
|
#define ATTRIB_COUNTRY_NAME 6
|
|
#define ATTRIB_LOCALITY 7
|
|
#define ATTRIB_ORGANIZATION 10
|
|
#define ATTRIB_ORG_UNIT 11
|
|
#define ATTRIB_DN_QUALIFIER 46
|
|
#define ATTRIB_STATE_PROVINCE 8
|
|
#define ATTRIB_COMMON_NAME 3
|
|
|
|
/** Enumerate X.509 milestones for issuedBefore() api */
|
|
typedef enum {
|
|
RFC_6818, /* January 2013 X.509 Updates Below */
|
|
RFC_5280, /* May 2008 X.509 Obsoletes Below */
|
|
RFC_3280, /* April 2002 X.509 Obsoletes Below */
|
|
RFC_2459, /* January 1999 X.509 First RFC */
|
|
X509_V3, /* 1996 X.509v3 Pre-RFC */
|
|
X509_V2, /* 1993 X.509v2 Pre-RFC */
|
|
X509_V1, /* 1988 X.509v1 Pre-RFC */
|
|
} rfc_e;
|
|
|
|
#define USE_OID_TRACE
|
|
|
|
#ifdef USE_OID_TRACE
|
|
#define OID_LIST(A, B) { { A, B }, #B, oid_##B }
|
|
#else
|
|
#define OID_LIST(A, B) { { A, B }, oid_##B }
|
|
#endif
|
|
static const struct {
|
|
uint16_t oid[MAX_OID_LEN];
|
|
#ifdef USE_OID_TRACE
|
|
char name[32];
|
|
#endif
|
|
int id;
|
|
} oid_list[] = {
|
|
/* X.509 certificate extensions */
|
|
OID_LIST(id_ce, id_ce_authorityKeyIdentifier),
|
|
OID_LIST(id_ce, id_ce_subjectKeyIdentifier),
|
|
OID_LIST(id_ce, id_ce_keyUsage),
|
|
OID_LIST(id_ce, id_ce_certificatePolicies),
|
|
OID_LIST(id_ce, id_ce_policyMappings),
|
|
OID_LIST(id_ce, id_ce_subjectAltName),
|
|
OID_LIST(id_ce, id_ce_issuerAltName),
|
|
OID_LIST(id_ce, id_ce_subjectDirectoryAttributes),
|
|
OID_LIST(id_ce, id_ce_basicConstraints),
|
|
OID_LIST(id_ce, id_ce_nameConstraints),
|
|
OID_LIST(id_ce, id_ce_policyConstraints),
|
|
OID_LIST(id_ce, id_ce_extKeyUsage),
|
|
OID_LIST(id_ce, id_ce_cRLDistributionPoints),
|
|
OID_LIST(id_ce, id_ce_inhibitAnyPolicy),
|
|
OID_LIST(id_ce, id_ce_freshestCRL),
|
|
OID_LIST(id_pe, id_pe_authorityInfoAccess),
|
|
OID_LIST(id_pe, id_pe_subjectInfoAccess),
|
|
/* Extended Key Usage */
|
|
OID_LIST(id_ce_eku, id_ce_eku_anyExtendedKeyUsage),
|
|
OID_LIST(id_kp, id_kp_serverAuth),
|
|
OID_LIST(id_kp, id_kp_clientAuth),
|
|
OID_LIST(id_kp, id_kp_codeSigning),
|
|
OID_LIST(id_kp, id_kp_emailProtection),
|
|
OID_LIST(id_kp, id_kp_timeStamping),
|
|
OID_LIST(id_kp, id_kp_OCSPSigning),
|
|
|
|
/* List terminator */
|
|
OID_LIST(0, 0),
|
|
};
|
|
|
|
/*
|
|
Hybrid ASN.1/X.509 cert parsing helpers
|
|
*/
|
|
static int32_t getExplicitVersion(const unsigned char **pp, uint16_t len,
|
|
int32_t expVal, int32_t *val);
|
|
static int32_t getTimeValidity(psPool_t *pool, const unsigned char **pp,
|
|
uint16_t len,
|
|
int32_t *notBeforeTimeType, int32_t *notAfterTimeType,
|
|
char **notBefore, char **notAfter);
|
|
static int32_t getImplicitBitString(psPool_t *pool, const unsigned char **pp,
|
|
uint16_t len, int32_t impVal, unsigned char **bitString,
|
|
uint16_t *bitLen);
|
|
static int32_t validateDateRange(psX509Cert_t *cert);
|
|
static int32_t issuedBefore(rfc_e rfc, const psX509Cert_t *cert);
|
|
|
|
#ifdef USE_RSA
|
|
static int32_t x509ConfirmSignature(const unsigned char *sigHash,
|
|
const unsigned char *sigOut, uint16_t sigLen);
|
|
#endif
|
|
|
|
#ifdef USE_CRL
|
|
static void x509FreeRevoked(x509revoked_t **revoked);
|
|
#endif
|
|
|
|
#endif /* USE_CERT_PARSE */
|
|
|
|
/******************************************************************************/
|
|
#ifdef MATRIX_USE_FILE_SYSTEM
|
|
/******************************************************************************/
|
|
|
|
static int32_t pemCertFileBufToX509(psPool_t *pool, const unsigned char *fileBuf,
|
|
uint16_t fileBufLen, psList_t **x509certList);
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Open a PEM X.509 certificate file and parse it
|
|
|
|
Memory info:
|
|
Caller must free outcert with psX509FreeCert on function success
|
|
Caller does not have to free outcert on function failure
|
|
*/
|
|
int32 psX509ParseCertFile(psPool_t *pool, char *fileName,
|
|
psX509Cert_t **outcert, int32 flags)
|
|
{
|
|
int32 fileBufLen, err;
|
|
unsigned char *fileBuf;
|
|
psList_t *fileList, *currentFile, *x509list, *frontX509;
|
|
psX509Cert_t *currentCert, *firstCert, *prevCert;
|
|
|
|
*outcert = NULL;
|
|
/*
|
|
First test to see if there are multiple files being passed in.
|
|
Looking for a semi-colon delimiter
|
|
*/
|
|
if ((err = psParseList(pool, fileName, ';', &fileList)) < 0) {
|
|
return err;
|
|
}
|
|
currentFile = fileList;
|
|
firstCert = prevCert = NULL;
|
|
|
|
/* Recurse each individual file */
|
|
while (currentFile) {
|
|
if ((err = psGetFileBuf(pool, (char*)currentFile->item, &fileBuf,
|
|
&fileBufLen)) < PS_SUCCESS) {
|
|
psFreeList(fileList, pool);
|
|
if (firstCert) psX509FreeCert(firstCert);
|
|
return err;
|
|
}
|
|
|
|
if ((err = pemCertFileBufToX509(pool, fileBuf, fileBufLen, &x509list))
|
|
< PS_SUCCESS) {
|
|
psFreeList(fileList, pool);
|
|
psFree(fileBuf, pool);
|
|
if (firstCert) psX509FreeCert(firstCert);
|
|
return err;
|
|
}
|
|
psFree(fileBuf, pool);
|
|
|
|
frontX509 = x509list;
|
|
/*
|
|
Recurse each individual cert buffer from within the file
|
|
*/
|
|
while (x509list != NULL) {
|
|
if ((err = psX509ParseCert(pool, x509list->item, x509list->len,
|
|
¤tCert, flags)) < PS_SUCCESS) {
|
|
psX509FreeCert(currentCert);
|
|
psFreeList(fileList, pool);
|
|
psFreeList(frontX509, pool);
|
|
if (firstCert) psX509FreeCert(firstCert);
|
|
return err;
|
|
}
|
|
|
|
x509list = x509list->next;
|
|
if (firstCert == NULL) {
|
|
firstCert = currentCert;
|
|
} else {
|
|
prevCert->next = currentCert;
|
|
}
|
|
prevCert = currentCert;
|
|
currentCert = currentCert->next;
|
|
}
|
|
currentFile = currentFile->next;
|
|
psFreeList(frontX509, pool);
|
|
}
|
|
psFreeList(fileList, pool);
|
|
|
|
*outcert = firstCert;
|
|
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
*/
|
|
static int32_t pemCertFileBufToX509(psPool_t *pool, const unsigned char *fileBuf,
|
|
uint16_t fileBufLen, psList_t **x509certList)
|
|
{
|
|
psList_t *front, *prev, *current;
|
|
unsigned char *start, *end, *endTmp;
|
|
const unsigned char *chFileBuf;
|
|
unsigned char l;
|
|
|
|
*x509certList = NULL;
|
|
prev = NULL;
|
|
if (fileBuf == NULL) {
|
|
psTraceCrypto("Bad parameters to pemCertFileBufToX509\n");
|
|
return PS_ARG_FAIL;
|
|
}
|
|
front = current = psMalloc(pool, sizeof(psList_t));
|
|
if (current == NULL) {
|
|
psError("Memory allocation error first pemCertFileBufToX509\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
l = strlen("CERTIFICATE-----");
|
|
memset(current, 0x0, sizeof(psList_t));
|
|
chFileBuf = fileBuf;
|
|
while (fileBufLen > 0) {
|
|
if (
|
|
((start = (unsigned char *)strstr((char *)chFileBuf, "-----BEGIN")) != NULL) &&
|
|
((start = (unsigned char *)strstr((char *)chFileBuf, "CERTIFICATE-----")) != NULL) &&
|
|
((end = (unsigned char *)strstr((char *)start, "-----END")) != NULL) &&
|
|
((endTmp = (unsigned char *)strstr((char *)end,"CERTIFICATE-----")) != NULL)
|
|
) {
|
|
start += l;
|
|
if (current == NULL) {
|
|
current = psMalloc(pool, sizeof(psList_t));
|
|
if (current == NULL) {
|
|
psFreeList(front, pool);
|
|
psError("Memory allocation error: pemCertFileBufToX509\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memset(current, 0x0, sizeof(psList_t));
|
|
prev->next = current;
|
|
}
|
|
current->len = (uint16_t)(end - start);
|
|
end = endTmp + l;
|
|
while (*end == '\x0d' || *end == '\x0a' || *end == '\x09'
|
|
|| *end == ' ') {
|
|
end++;
|
|
}
|
|
} else {
|
|
psFreeList(front, pool);
|
|
psTraceCrypto("File buffer does not look to be X.509 PEM format\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
current->item = psMalloc(pool, current->len);
|
|
if (current->item == NULL) {
|
|
psFreeList(front, pool);
|
|
psError("Memory allocation error: pemCertFileBufToX509\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memset(current->item, '\0', current->len);
|
|
|
|
fileBufLen -= (uint16_t)(end - fileBuf);
|
|
fileBuf = end;
|
|
|
|
if (psBase64decode(start, current->len, current->item, ¤t->len) != 0) {
|
|
psFreeList(front, pool);
|
|
psTraceCrypto("Unable to base64 decode certificate\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
prev = current;
|
|
current = current->next;
|
|
chFileBuf = fileBuf;
|
|
}
|
|
*x509certList = front;
|
|
return PS_SUCCESS;
|
|
}
|
|
#endif /* MATRIX_USE_FILE_SYSTEM */
|
|
/******************************************************************************/
|
|
|
|
|
|
#ifdef USE_PKCS1_PSS
|
|
/*
|
|
RSASSA-PSS-params ::= SEQUENCE {
|
|
hashAlgorithm [0] HashAlgorithm DEFAULT sha1,
|
|
maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1,
|
|
saltLength [2] INTEGER DEFAULT 20,
|
|
trailerField [3] TrailerField DEFAULT trailerFieldBC
|
|
}
|
|
Note, each of these is sequential, but optional.
|
|
*/
|
|
static int32 getRsaPssParams(const unsigned char **pp, int32 size,
|
|
psX509Cert_t *cert, int32 secondPass)
|
|
{
|
|
const unsigned char *p, *end;
|
|
int32 oi, second, asnint;
|
|
uint16_t plen;
|
|
|
|
p = *pp;
|
|
/* SEQUENCE has already been pulled off into size */
|
|
end = p + size;
|
|
|
|
/* The signature algorithm appears twice in an X.509 cert and must be
|
|
identical. If secondPass is set we check for that */
|
|
if ((uint32)(end - p) < 1) {
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (*p == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 0)) {
|
|
p++;
|
|
if (getAsnLength(&p, (uint32)(end - p), &plen) < 0 ||
|
|
(end - p) < plen) {
|
|
psTraceCrypto("Error parsing rsapss hash alg\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/* hashAlgorithm is OID */
|
|
if (getAsnAlgorithmIdentifier(&p, (uint32)(end - p), &oi, &plen) < 0) {
|
|
psTraceCrypto("Error parsing rsapss hash alg 2\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (secondPass) {
|
|
if (oi != cert->pssHash) {
|
|
psTraceCrypto("rsapss hash alg doesn't repeat\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/* Convert to PKCS1_ ID for pssDecode on second pass */
|
|
if (oi == OID_SHA1_ALG) {
|
|
second = PKCS1_SHA1_ID;
|
|
} else if (oi == OID_SHA256_ALG) {
|
|
second = PKCS1_SHA256_ID;
|
|
} else if (oi == OID_MD5_ALG) {
|
|
second = PKCS1_MD5_ID;
|
|
#ifdef USE_SHA384
|
|
} else if (oi == OID_SHA384_ALG) {
|
|
second = PKCS1_SHA384_ID;
|
|
#endif
|
|
#ifdef USE_SHA512
|
|
} else if (oi == OID_SHA512_ALG) {
|
|
second = PKCS1_SHA512_ID;
|
|
#endif
|
|
} else {
|
|
psTraceCrypto("Unsupported rsapss hash alg\n");
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
cert->pssHash = second;
|
|
} else {
|
|
/* first time, save the OID for compare */
|
|
cert->pssHash = oi;
|
|
}
|
|
}
|
|
if ((uint32)(end - p) < 1) {
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (*p == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 1)) {
|
|
/* maskGenAlgorthm is OID */
|
|
p++;
|
|
if (getAsnLength(&p, (uint32)(end - p), &plen) < 0 ||
|
|
(end - p) < plen) {
|
|
psTraceCrypto("Error parsing mask gen alg\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (getAsnAlgorithmIdentifier(&p, (uint32)(end - p), &oi, &plen) < 0) {
|
|
psTraceCrypto("Error parsing mask gen alg 2\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (secondPass) {
|
|
if (oi != cert->maskGen) {
|
|
psTraceCrypto("rsapss mask gen alg doesn't repeat\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
}
|
|
cert->maskGen = oi;
|
|
if (cert->maskGen != OID_ID_MGF1) {
|
|
psTraceCrypto("Unsupported RSASSA-PSS maskGenAlgorithm\n");
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
/* MaskGenAlgorithm ::= AlgorithmIdentifier {
|
|
{PKCS1MGFAlgorithms}
|
|
}
|
|
PKCS1MGFAlgorithms ALGORITHM-IDENTIFIER ::= {
|
|
{ OID id-mgf1 PARAMETERS HashAlgorithm },
|
|
... -- Allows for future expansion --
|
|
}
|
|
|
|
The default mask generation function is MGF1 with SHA-1:
|
|
|
|
mgf1SHA1 MaskGenAlgorithm ::= {
|
|
algorithm id-mgf1,
|
|
parameters HashAlgorithm : sha1
|
|
}
|
|
*/
|
|
if (getAsnAlgorithmIdentifier(&p, (uint32)(end - p), &oi, &plen) < 0) {
|
|
psTraceCrypto("Error parsing mask hash alg\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (secondPass) {
|
|
if (oi != cert->maskHash) {
|
|
psTraceCrypto("rsapss mask hash alg doesn't repeat\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
}
|
|
cert->maskHash = oi;
|
|
}
|
|
if ((uint32)(end - p) < 1) {
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (*p == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 2)) {
|
|
/* saltLen */
|
|
p++;
|
|
if (getAsnLength(&p, (uint32)(end - p), &plen) < 0 ||
|
|
(end - p) < plen) {
|
|
psTraceCrypto("Error parsing salt len\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (getAsnInteger(&p, (uint32)(end - p), &asnint) < 0) {
|
|
psTraceCrypto("Error parsing salt len 2\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (secondPass) {
|
|
if (asnint != cert->saltLen) {
|
|
psTraceCrypto("Error: salt len doesn't repeat\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
}
|
|
cert->saltLen = plen;
|
|
}
|
|
if ((uint32)(end - p) < 1) {
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (*p == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 3)) {
|
|
/* It shall be 1 for this version of the document, which represents
|
|
the trailer field with hexadecimal value 0xBC */
|
|
p++;
|
|
if (getAsnLength(&p, (uint32)(end - p), &plen) < 0 ||
|
|
(end - p) < plen) {
|
|
psTraceCrypto("Error parsing rsapss trailer\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (getAsnInteger(&p, (uint32)(end - p), &asnint) < 0 ||
|
|
plen != 0xBC) {
|
|
psTraceCrypto("Error parsing rsapss trailer 2\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
}
|
|
|
|
if (p != end) {
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
*pp = (unsigned char*)p;
|
|
return PS_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Parse an X509 v3 ASN.1 certificate stream
|
|
http://tools.ietf.org/html/rfc3280
|
|
|
|
flags
|
|
CERT_STORE_UNPARSED_BUFFER
|
|
CERT_STORE_DN_BUFFER
|
|
|
|
Memory info:
|
|
Caller must always free outcert with psX509FreeCert. Even on failure
|
|
*/
|
|
int32 psX509ParseCert(psPool_t *pool, const unsigned char *pp, uint32 size,
|
|
psX509Cert_t **outcert, int32 flags)
|
|
{
|
|
psX509Cert_t *cert;
|
|
const unsigned char *p, *end, *far_end, *certStart;
|
|
uint16_t len;
|
|
uint32_t oneCertLen;
|
|
int32 parsing, rc;
|
|
unsigned char sha1KeyHash[SHA1_HASH_SIZE];
|
|
#ifdef USE_CERT_PARSE
|
|
psDigestContext_t hashCtx;
|
|
const unsigned char *certEnd;
|
|
uint16_t certLen, plen;
|
|
#endif
|
|
|
|
/*
|
|
Allocate the cert structure right away. User MUST always call
|
|
psX509FreeCert regardless of whether this function succeeds.
|
|
memset is important because the test for NULL is what is used
|
|
to determine what to free
|
|
*/
|
|
*outcert = cert = psMalloc(pool, sizeof(psX509Cert_t));
|
|
if (cert == NULL) {
|
|
psError("Memory allocation failure in psX509ParseCert\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memset(cert, 0x0, sizeof(psX509Cert_t));
|
|
cert->pool = pool;
|
|
|
|
p = pp;
|
|
far_end = p + size;
|
|
/*
|
|
Certificate ::= SEQUENCE {
|
|
tbsCertificate TBSCertificate,
|
|
signatureAlgorithm AlgorithmIdentifier,
|
|
signatureValue BIT STRING }
|
|
*/
|
|
parsing = 1;
|
|
while (parsing) {
|
|
certStart = p;
|
|
if ((rc = getAsnSequence32(&p, (uint32_t)(far_end - p), &oneCertLen, 0))
|
|
< 0){
|
|
psTraceCrypto("Initial cert parse error\n");
|
|
return rc;
|
|
}
|
|
/* The whole list of certs could be > 64K bytes, but we still
|
|
restrict individual certs to 64KB */
|
|
if (oneCertLen > 0xFFFF) {
|
|
psAssert(oneCertLen <= 0xFFFF);
|
|
return PS_FAILURE;
|
|
}
|
|
end = p + oneCertLen;
|
|
/*
|
|
If the user has specified to keep the ASN.1 buffer in the X.509
|
|
structure, now is the time to account for it
|
|
*/
|
|
if (flags & CERT_STORE_UNPARSED_BUFFER) {
|
|
cert->binLen = oneCertLen + (int32)(p - certStart);
|
|
cert->unparsedBin = psMalloc(pool, cert->binLen);
|
|
if (cert->unparsedBin == NULL) {
|
|
psError("Memory allocation error in psX509ParseCert\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memcpy(cert->unparsedBin, certStart, cert->binLen);
|
|
}
|
|
|
|
#ifdef ENABLE_CA_CERT_HASH
|
|
/* We use the cert_sha1_hash type for the Trusted CA Indication so
|
|
run a SHA1 has over the entire Certificate DER encoding. */
|
|
psSha1Init(&hashCtx.sha1);
|
|
psSha1Update(&hashCtx.sha1, certStart,
|
|
oneCertLen + (int32)(p - certStart));
|
|
psSha1Final(&hashCtx.sha1, cert->sha1CertHash);
|
|
#endif
|
|
|
|
#ifdef USE_CERT_PARSE
|
|
certStart = p;
|
|
/*
|
|
TBSCertificate ::= SEQUENCE {
|
|
version [0] EXPLICIT Version DEFAULT v1,
|
|
serialNumber CertificateSerialNumber,
|
|
signature AlgorithmIdentifier,
|
|
issuer Name,
|
|
validity Validity,
|
|
subject Name,
|
|
subjectPublicKeyInfo SubjectPublicKeyInfo,
|
|
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
|
|
-- If present, version shall be v2 or v3
|
|
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
|
|
-- If present, version shall be v2 or v3
|
|
extensions [3] EXPLICIT Extensions OPTIONAL
|
|
-- If present, version shall be v3 }
|
|
*/
|
|
if ((rc = getAsnSequence(&p, (uint32)(end - p), &len)) < 0) {
|
|
psTraceCrypto("ASN sequence parse error\n");
|
|
return rc;
|
|
}
|
|
certEnd = p + len;
|
|
certLen = certEnd - certStart;
|
|
|
|
/*
|
|
Version ::= INTEGER { v1(0), v2(1), v3(2) }
|
|
*/
|
|
if ((rc = getExplicitVersion(&p, (uint32)(end - p), 0, &cert->version))
|
|
< 0) {
|
|
psTraceCrypto("ASN version parse error\n");
|
|
return rc;
|
|
}
|
|
if (cert->version != 2) {
|
|
psTraceIntCrypto("ERROR: non-v3 certificate version %d insecure\n",
|
|
cert->version);
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/*
|
|
CertificateSerialNumber ::= INTEGER
|
|
There is a special return code for a missing serial number that
|
|
will get written to the parse warning flag
|
|
*/
|
|
if ((rc = getSerialNum(pool, &p, (uint32)(end - p), &cert->serialNumber,
|
|
&cert->serialNumberLen)) < 0) {
|
|
psTraceCrypto("ASN serial number parse error\n");
|
|
return rc;
|
|
}
|
|
/*
|
|
AlgorithmIdentifier ::= SEQUENCE {
|
|
algorithm OBJECT IDENTIFIER,
|
|
parameters ANY DEFINED BY algorithm OPTIONAL }
|
|
*/
|
|
if ((rc = getAsnAlgorithmIdentifier(&p, (uint32)(end - p),
|
|
&cert->certAlgorithm, &plen)) < 0) {
|
|
psTraceCrypto("Couldn't parse algorithm identifier for certAlgorithm\n");
|
|
return rc;
|
|
}
|
|
if (plen != 0) {
|
|
#ifdef USE_PKCS1_PSS
|
|
if (cert->certAlgorithm == OID_RSASSA_PSS) {
|
|
/* RSASSA-PSS-params ::= SEQUENCE {
|
|
hashAlgorithm [0] HashAlgorithm DEFAULT sha1,
|
|
maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1,
|
|
saltLength [2] INTEGER DEFAULT 20,
|
|
trailerField [3] TrailerField DEFAULT trailerFieldBC
|
|
}
|
|
*/
|
|
if ((rc = getAsnSequence(&p, (uint32)(end - p), &len)) < 0) {
|
|
psTraceCrypto("ASN sequence parse error\n");
|
|
return rc;
|
|
}
|
|
/* Always set the defaults before parsing */
|
|
cert->pssHash = PKCS1_SHA1_ID;
|
|
cert->saltLen = SHA1_HASH_SIZE;
|
|
/* Something other than defaults to parse here? */
|
|
if (len > 0) {
|
|
if ((rc = getRsaPssParams(&p, len, cert, 0)) < 0) {
|
|
return rc;
|
|
}
|
|
}
|
|
} else {
|
|
psTraceCrypto("Unsupported X.509 certAlgorithm\n");
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
#else
|
|
psTraceCrypto("Unsupported X.509 certAlgorithm\n");
|
|
return PS_UNSUPPORTED_FAIL;
|
|
#endif
|
|
}
|
|
/*
|
|
Name ::= CHOICE {
|
|
RDNSequence }
|
|
|
|
RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
|
|
|
|
RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
|
|
|
|
AttributeTypeAndValue ::= SEQUENCE {
|
|
type AttributeType,
|
|
value AttributeValue }
|
|
|
|
AttributeType ::= OBJECT IDENTIFIER
|
|
|
|
AttributeValue ::= ANY DEFINED BY AttributeType
|
|
*/
|
|
if ((rc = psX509GetDNAttributes(pool, &p, (uint32)(end - p),
|
|
&cert->issuer, flags)) < 0) {
|
|
psTraceCrypto("Couldn't parse issuer DN attributes\n");
|
|
return rc;
|
|
}
|
|
/*
|
|
Validity ::= SEQUENCE {
|
|
notBefore Time,
|
|
notAfter Time }
|
|
*/
|
|
if ((rc = getTimeValidity(pool, &p, (uint32)(end - p),
|
|
&cert->notBeforeTimeType, &cert->notAfterTimeType,
|
|
&cert->notBefore, &cert->notAfter)) < 0) {
|
|
psTraceCrypto("Couldn't parse validity\n");
|
|
return rc;
|
|
}
|
|
|
|
/* SECURITY - platforms without a date function will always succeed */
|
|
if ((rc = validateDateRange(cert)) < 0) {
|
|
psTraceCrypto("Validity date check failed\n");
|
|
return rc;
|
|
}
|
|
/*
|
|
Subject DN
|
|
*/
|
|
if ((rc = psX509GetDNAttributes(pool, &p, (uint32)(end - p),
|
|
&cert->subject, flags)) < 0) {
|
|
psTraceCrypto("Couldn't parse subject DN attributes\n");
|
|
return rc;
|
|
}
|
|
/*
|
|
SubjectPublicKeyInfo ::= SEQUENCE {
|
|
algorithm AlgorithmIdentifier,
|
|
subjectPublicKey BIT STRING }
|
|
*/
|
|
if ((rc = getAsnSequence(&p, (uint32)(end - p), &len)) < 0) {
|
|
psTraceCrypto("Couldn't get ASN sequence for pubKeyAlgorithm\n");
|
|
return rc;
|
|
}
|
|
if ((rc = getAsnAlgorithmIdentifier(&p, (uint32)(end - p),
|
|
&cert->pubKeyAlgorithm, &plen)) < 0) {
|
|
psTraceCrypto("Couldn't parse algorithm id for pubKeyAlgorithm\n");
|
|
return rc;
|
|
}
|
|
|
|
/* Populate with correct type based on pubKeyAlgorithm OID */
|
|
switch (cert->pubKeyAlgorithm) {
|
|
#ifdef USE_ECC
|
|
case OID_ECDSA_KEY_ALG:
|
|
if (plen == 0 || plen > (int32)(end - p)) {
|
|
psTraceCrypto("Bad params on EC OID\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
psInitPubKey(pool, &cert->publicKey, PS_ECC);
|
|
if (getEcPubKey(pool, &p, (uint16_t)(end - p),
|
|
&cert->publicKey.key.ecc, sha1KeyHash) < 0) {
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/* keysize will be the size of the public ecc key (2 * privateLen) */
|
|
cert->publicKey.keysize = psEccSize(&cert->publicKey.key.ecc);
|
|
if (cert->publicKey.keysize < (MIN_ECC_BITS / 8)) {
|
|
psTraceIntCrypto("ECC key size < %d\n", MIN_ECC_BITS);
|
|
psClearPubKey(&cert->publicKey);
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
break;
|
|
#endif
|
|
#ifdef USE_RSA
|
|
case OID_RSA_KEY_ALG:
|
|
psAssert(plen == 0); /* No parameters on RSA pub key OID */
|
|
psInitPubKey(pool, &cert->publicKey, PS_RSA);
|
|
if ((rc = psRsaParseAsnPubKey(pool, &p, (uint16_t)(end - p),
|
|
&cert->publicKey.key.rsa, sha1KeyHash)) < 0) {
|
|
psTraceCrypto("Couldn't get RSA pub key from cert\n");
|
|
return rc;
|
|
}
|
|
cert->publicKey.keysize = psRsaSize(&cert->publicKey.key.rsa);
|
|
|
|
if (cert->publicKey.keysize < (MIN_RSA_BITS / 8)) {
|
|
psTraceIntCrypto("RSA key size < %d\n", MIN_RSA_BITS);
|
|
psClearPubKey(&cert->publicKey);
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
|
|
break;
|
|
#endif
|
|
default:
|
|
psTraceIntCrypto("Unsupported public key algorithm in cert parse: %d\n",
|
|
cert->pubKeyAlgorithm);
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
|
|
#ifdef USE_OCSP
|
|
/* A sha1 hash of the public key is useful for OCSP */
|
|
memcpy(cert->sha1KeyHash, sha1KeyHash, SHA1_HASH_SIZE);
|
|
#endif
|
|
|
|
/* As the next three values are optional, we can do a specific test here */
|
|
if (*p != (ASN_SEQUENCE | ASN_CONSTRUCTED)) {
|
|
if (getImplicitBitString(pool, &p, (uint32)(end - p),
|
|
IMPLICIT_ISSUER_ID, &cert->uniqueIssuerId,
|
|
&cert->uniqueIssuerIdLen) < 0 ||
|
|
getImplicitBitString(pool, &p, (uint32)(end - p),
|
|
IMPLICIT_SUBJECT_ID, &cert->uniqueSubjectId,
|
|
&cert->uniqueSubjectIdLen) < 0 ||
|
|
getExplicitExtensions(pool, &p, (uint32)(end - p),
|
|
EXPLICIT_EXTENSION, &cert->extensions, 0) < 0) {
|
|
psTraceCrypto("There was an error parsing a certificate\n");
|
|
psTraceCrypto("extension. This is likely caused by an\n");
|
|
psTraceCrypto("extension format that is not currently\n");
|
|
psTraceCrypto("recognized. Please email Inside support\n");
|
|
psTraceCrypto("to add support for the extension.\n\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
}
|
|
|
|
/* This is the end of the cert. Do a check here to be certain */
|
|
if (certEnd != p) {
|
|
psTraceCrypto("Error. Expecting end of cert\n");
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
|
|
/* Reject any cert without a distinguishedName or subjectAltName */
|
|
if (cert->subject.commonName == NULL &&
|
|
cert->subject.country == NULL &&
|
|
cert->subject.state == NULL &&
|
|
cert->subject.locality == NULL &&
|
|
cert->subject.organization == NULL &&
|
|
cert->subject.orgUnit == NULL &&
|
|
cert->extensions.san == NULL) {
|
|
psTraceCrypto("Error. Cert has no name information\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/* Certificate signature info */
|
|
if ((rc = getAsnAlgorithmIdentifier(&p, (uint32)(end - p),
|
|
&cert->sigAlgorithm, &plen)) < 0) {
|
|
psTraceCrypto("Couldn't get algorithm identifier for sigAlgorithm\n");
|
|
return rc;
|
|
}
|
|
if (plen != 0) {
|
|
#ifdef USE_PKCS1_PSS
|
|
if (cert->sigAlgorithm == OID_RSASSA_PSS) {
|
|
/* RSASSA-PSS-params ::= SEQUENCE {
|
|
hashAlgorithm [0] HashAlgorithm DEFAULT sha1,
|
|
maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1,
|
|
saltLength [2] INTEGER DEFAULT 20,
|
|
trailerField [3] TrailerField DEFAULT trailerFieldBC
|
|
}
|
|
*/
|
|
if ((rc = getAsnSequence(&p, (uint32)(end - p), &len)) < 0) {
|
|
psTraceCrypto("ASN sequence parse error\n");
|
|
return rc;
|
|
}
|
|
/* Something other than defaults to parse here? */
|
|
if (len > 0) {
|
|
if ((rc = getRsaPssParams(&p, len, cert, 1)) < 0) {
|
|
return rc;
|
|
}
|
|
}
|
|
} else {
|
|
psTraceCrypto("Unsupported X.509 sigAlgorithm\n");
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
#else
|
|
psTraceCrypto("Unsupported X.509 sigAlgorithm\n");
|
|
return PS_UNSUPPORTED_FAIL;
|
|
#endif
|
|
}
|
|
/*
|
|
Signature algorithm must match that specified in TBS cert
|
|
*/
|
|
if (cert->certAlgorithm != cert->sigAlgorithm) {
|
|
psTraceCrypto("Parse error: mismatched signature type\n");
|
|
return PS_CERT_AUTH_FAIL;
|
|
}
|
|
|
|
/*
|
|
Compute the hash of the cert here for CA validation
|
|
*/
|
|
switch (cert->certAlgorithm) {
|
|
#ifdef ENABLE_MD5_SIGNED_CERTS
|
|
#ifdef USE_MD2
|
|
case OID_MD2_RSA_SIG:
|
|
psMd2Init(&hashCtx.md2);
|
|
psMd2Update(&hashCtx.md2, certStart, certLen);
|
|
psMd2Final(&hashCtx.md2, cert->sigHash);
|
|
break;
|
|
#endif
|
|
case OID_MD5_RSA_SIG:
|
|
psMd5Init(&hashCtx.md5);
|
|
psMd5Update(&hashCtx.md5, certStart, certLen);
|
|
psMd5Final(&hashCtx.md5, cert->sigHash);
|
|
break;
|
|
#endif
|
|
#ifdef ENABLE_SHA1_SIGNED_CERTS
|
|
case OID_SHA1_RSA_SIG:
|
|
#ifdef USE_ECC
|
|
case OID_SHA1_ECDSA_SIG:
|
|
#endif
|
|
psSha1Init(&hashCtx.sha1);
|
|
psSha1Update(&hashCtx.sha1, certStart, certLen);
|
|
psSha1Final(&hashCtx.sha1, cert->sigHash);
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA256
|
|
case OID_SHA256_RSA_SIG:
|
|
#ifdef USE_ECC
|
|
case OID_SHA256_ECDSA_SIG:
|
|
#endif
|
|
psSha256Init(&hashCtx.sha256);
|
|
psSha256Update(&hashCtx.sha256, certStart, certLen);
|
|
psSha256Final(&hashCtx.sha256, cert->sigHash);
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA384
|
|
case OID_SHA384_RSA_SIG:
|
|
#ifdef USE_ECC
|
|
case OID_SHA384_ECDSA_SIG:
|
|
#endif
|
|
psSha384Init(&hashCtx.sha384);
|
|
psSha384Update(&hashCtx.sha384, certStart, certLen);
|
|
psSha384Final(&hashCtx.sha384, cert->sigHash);
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA512
|
|
case OID_SHA512_RSA_SIG:
|
|
#ifdef USE_ECC
|
|
case OID_SHA512_ECDSA_SIG:
|
|
#endif
|
|
psSha512Init(&hashCtx.sha512);
|
|
psSha512Update(&hashCtx.sha512, certStart, certLen);
|
|
psSha512Final(&hashCtx.sha512, cert->sigHash);
|
|
break;
|
|
#endif
|
|
#ifdef USE_PKCS1_PSS
|
|
case OID_RSASSA_PSS:
|
|
switch (cert->pssHash) {
|
|
#ifdef ENABLE_MD5_SIGNED_CERTS
|
|
case PKCS1_MD5_ID:
|
|
psMd5Init(&hashCtx.md5);
|
|
psMd5Update(&hashCtx.md5, certStart, certLen);
|
|
psMd5Final(&hashCtx.md5, cert->sigHash);
|
|
break;
|
|
#endif
|
|
#ifdef ENABLE_SHA1_SIGNED_CERTS
|
|
case PKCS1_SHA1_ID:
|
|
psSha1Init(&hashCtx.sha1);
|
|
psSha1Update(&hashCtx.sha1, certStart, certLen);
|
|
psSha1Final(&hashCtx.sha1, cert->sigHash);
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA256
|
|
case PKCS1_SHA256_ID:
|
|
psSha256Init(&hashCtx.sha256);
|
|
psSha256Update(&hashCtx.sha256, certStart, certLen);
|
|
psSha256Final(&hashCtx.sha256, cert->sigHash);
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA384
|
|
case PKCS1_SHA384_ID:
|
|
psSha384Init(&hashCtx.sha384);
|
|
psSha384Update(&hashCtx.sha384, certStart, certLen);
|
|
psSha384Final(&hashCtx.sha384, cert->sigHash);
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA512
|
|
case PKCS1_SHA512_ID:
|
|
psSha512Init(&hashCtx.sha512);
|
|
psSha512Update(&hashCtx.sha512, certStart, certLen);
|
|
psSha512Final(&hashCtx.sha512, cert->sigHash);
|
|
break;
|
|
#endif
|
|
default:
|
|
return PS_UNSUPPORTED_FAIL;
|
|
|
|
} /* switch pssHash */
|
|
break;
|
|
#endif /* USE_PKCS1_PSS */
|
|
|
|
default:
|
|
psTraceCrypto("Unsupported cert algorithm\n");
|
|
return PS_UNSUPPORTED_FAIL;
|
|
|
|
} /* switch certAlgorithm */
|
|
|
|
/* 6 empty bytes is plenty enough to know if sigHash didn't calculate */
|
|
if (memcmp(cert->sigHash, "\0\0\0\0\0\0", 6) == 0) {
|
|
psTraceIntCrypto("No library signature alg support for cert: %d\n",
|
|
cert->certAlgorithm);
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
|
|
if ((rc = psX509GetSignature(pool, &p, (uint32)(end - p),
|
|
&cert->signature, &cert->signatureLen)) < 0) {
|
|
psTraceCrypto("Couldn't parse signature\n");
|
|
return rc;
|
|
}
|
|
|
|
#else /* !USE_CERT_PARSE */
|
|
p = certStart + len + (int32)(p - certStart);
|
|
#endif /* USE_CERT_PARSE */
|
|
/*
|
|
The ability to parse additional chained certs is a PKI product
|
|
feature addition. Chaining in MatrixSSL is handled internally.
|
|
*/
|
|
if ((p != far_end) && (p < (far_end + 1))) {
|
|
if (*p == 0x0 && *(p + 1) == 0x0) {
|
|
parsing = 0; /* An indefinite length stream was passed in */
|
|
/* caller will have to deal with skipping these becuase they
|
|
would have read off the TL of this ASN.1 stream */
|
|
} else {
|
|
cert->next = psMalloc(pool, sizeof(psX509Cert_t));
|
|
if (cert->next == NULL) {
|
|
psError("Memory allocation error in psX509ParseCert\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
cert = cert->next;
|
|
memset(cert, 0x0, sizeof(psX509Cert_t));
|
|
cert->pool = pool;
|
|
}
|
|
} else {
|
|
parsing = 0;
|
|
}
|
|
}
|
|
|
|
return (int32)(p - pp);
|
|
}
|
|
|
|
#ifdef USE_CERT_PARSE
|
|
void x509FreeExtensions(x509v3extensions_t *extensions)
|
|
{
|
|
|
|
x509GeneralName_t *active, *inc;
|
|
|
|
if (extensions->san) {
|
|
active = extensions->san;
|
|
while (active != NULL) {
|
|
inc = active->next;
|
|
psFree(active->data, extensions->pool);
|
|
psFree(active, extensions->pool);
|
|
active = inc;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_CRL
|
|
if (extensions->crlDist) {
|
|
active = extensions->crlDist;
|
|
while (active != NULL) {
|
|
inc = active->next;
|
|
psFree(active->data, extensions->pool);
|
|
psFree(active, extensions->pool);
|
|
active = inc;
|
|
}
|
|
}
|
|
#endif /* CRL */
|
|
|
|
#ifdef USE_FULL_CERT_PARSE
|
|
if (extensions->nameConstraints.excluded) {
|
|
active = extensions->nameConstraints.excluded;
|
|
while (active != NULL) {
|
|
inc = active->next;
|
|
psFree(active->data, extensions->pool);
|
|
psFree(active, extensions->pool);
|
|
active = inc;
|
|
}
|
|
}
|
|
if (extensions->nameConstraints.permitted) {
|
|
active = extensions->nameConstraints.permitted;
|
|
while (active != NULL) {
|
|
inc = active->next;
|
|
psFree(active->data, extensions->pool);
|
|
psFree(active, extensions->pool);
|
|
active = inc;
|
|
}
|
|
}
|
|
#endif /* USE_FULL_CERT_PARSE */
|
|
if (extensions->sk.id) psFree(extensions->sk.id, extensions->pool);
|
|
if (extensions->ak.keyId) psFree(extensions->ak.keyId, extensions->pool);
|
|
if (extensions->ak.serialNum) psFree(extensions->ak.serialNum,
|
|
extensions->pool);
|
|
if (extensions->ak.attribs.commonName)
|
|
psFree(extensions->ak.attribs.commonName, extensions->pool);
|
|
if (extensions->ak.attribs.country) psFree(extensions->ak.attribs.country,
|
|
extensions->pool);
|
|
if (extensions->ak.attribs.state) psFree(extensions->ak.attribs.state,
|
|
extensions->pool);
|
|
if (extensions->ak.attribs.locality)
|
|
psFree(extensions->ak.attribs.locality, extensions->pool);
|
|
if (extensions->ak.attribs.organization)
|
|
psFree(extensions->ak.attribs.organization, extensions->pool);
|
|
if (extensions->ak.attribs.orgUnit) psFree(extensions->ak.attribs.orgUnit,
|
|
extensions->pool);
|
|
if (extensions->ak.attribs.dnenc) psFree(extensions->ak.attribs.dnenc,
|
|
extensions->pool);
|
|
}
|
|
#endif /* USE_CERT_PARSE */
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
User must call after all calls to psX509ParseCert
|
|
(we violate the coding standard a bit here for clarity)
|
|
*/
|
|
void psX509FreeCert(psX509Cert_t *cert)
|
|
{
|
|
psX509Cert_t *curr, *next;
|
|
psPool_t *pool;
|
|
|
|
curr = cert;
|
|
while (curr) {
|
|
pool = curr->pool;
|
|
if (curr->unparsedBin) psFree(curr->unparsedBin, pool);
|
|
#ifdef USE_CERT_PARSE
|
|
psX509FreeDNStruct(&curr->issuer, pool);
|
|
psX509FreeDNStruct(&curr->subject, pool);
|
|
if (curr->serialNumber) psFree(curr->serialNumber, pool);
|
|
if (curr->notBefore) psFree(curr->notBefore, pool);
|
|
if (curr->notAfter) psFree(curr->notAfter, pool);
|
|
if (curr->signature) psFree(curr->signature, pool);
|
|
if (curr->uniqueIssuerId) psFree(curr->uniqueIssuerId, pool);
|
|
if (curr->uniqueSubjectId) psFree(curr->uniqueSubjectId, pool);
|
|
|
|
|
|
if (curr->publicKey.type != PS_NONE) {
|
|
switch (curr->pubKeyAlgorithm) {
|
|
#ifdef USE_RSA
|
|
case OID_RSA_KEY_ALG:
|
|
psRsaClearKey(&curr->publicKey.key.rsa);
|
|
break;
|
|
#endif
|
|
|
|
#ifdef USE_ECC
|
|
case OID_ECDSA_KEY_ALG:
|
|
psEccClearKey(&curr->publicKey.key.ecc);
|
|
break;
|
|
#endif
|
|
default:
|
|
psAssert(0);
|
|
break;
|
|
}
|
|
curr->publicKey.type = PS_NONE;
|
|
}
|
|
|
|
x509FreeExtensions(&curr->extensions);
|
|
#ifdef USE_CRL
|
|
x509FreeRevoked(&curr->revoked);
|
|
#endif
|
|
#endif /* USE_CERT_PARSE */
|
|
next = curr->next;
|
|
psFree(curr, pool);
|
|
curr = next;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_CERT_PARSE
|
|
/******************************************************************************/
|
|
/*
|
|
Currently just returning the raw BIT STRING and size in bytes
|
|
*/
|
|
#define MIN_HASH_SIZE 16
|
|
int32_t psX509GetSignature(psPool_t *pool, const unsigned char **pp, uint16_t len,
|
|
unsigned char **sig, uint16_t *sigLen)
|
|
{
|
|
const unsigned char *p = *pp, *end;
|
|
uint16_t llen;
|
|
|
|
end = p + len;
|
|
if (len < 1 || (*(p++) != ASN_BIT_STRING) ||
|
|
getAsnLength(&p, len - 1, &llen) < 0 ||
|
|
(uint32)(end - p) < llen ||
|
|
llen < (1 + MIN_HASH_SIZE)) {
|
|
|
|
psTraceCrypto("Initial parse error in getSignature\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/* We assume this ignore_bits byte is always 0. */
|
|
psAssert(*p == 0);
|
|
p++;
|
|
/* Length was including the ignore_bits byte, subtract it */
|
|
*sigLen = llen - 1;
|
|
*sig = psMalloc(pool, *sigLen);
|
|
if (*sig == NULL) {
|
|
psError("Memory allocation error in getSignature\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memcpy(*sig, p, *sigLen);
|
|
*pp = p + *sigLen;
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Validate the expected name against a subset of the GeneralName rules
|
|
for DNS, Email and IP types.
|
|
We assume the expected name is not maliciously entered. If it is, it may
|
|
match an invalid GeneralName in a remote cert chain.
|
|
Returns 0 on valid format, PS_FAILURE on invalid format of GeneralName
|
|
*/
|
|
int32_t psX509ValidateGeneralName(const char *n)
|
|
{
|
|
const char *c;
|
|
int atfound; /* Ampersand found */
|
|
int notip; /* Not an ip address */
|
|
|
|
if (n == NULL) return 0;
|
|
|
|
/* Must be at least one character */
|
|
if (*n == '\0') return PS_FAILURE;
|
|
|
|
atfound = notip = 0;
|
|
for (c = n; *c != '\0'; c++ ) {
|
|
|
|
/* Negative tests first in the loop */
|
|
/* Can't have any combination of . and - and @ together */
|
|
if (c != n) {
|
|
if (*c == '.' && *(c-1) == '.') return PS_FAILURE;
|
|
if (*c == '.' && *(c-1) == '-') return PS_FAILURE;
|
|
if (*c == '.' && *(c-1) == '@') return PS_FAILURE;
|
|
if (*c == '-' && *(c-1) == '.') return PS_FAILURE;
|
|
if (*c == '-' && *(c-1) == '-') return PS_FAILURE;
|
|
if (*c == '-' && *(c-1) == '@') return PS_FAILURE;
|
|
if (*c == '@' && *(c-1) == '.') return PS_FAILURE;
|
|
if (*c == '@' && *(c-1) == '-') return PS_FAILURE;
|
|
if (*c == '@' && *(c-1) == '@') return PS_FAILURE;
|
|
}
|
|
|
|
/* Note whether we have hit a non numeric name */
|
|
if (*c != '.' && (*c < '0' || *c > '9')) notip++;
|
|
|
|
/* Now positive tests */
|
|
/* Cannot start or end with . or -, but can contain them */
|
|
if (c != n && *(c + 1) != '\0' && (*c == '.' || *c == '-')) continue;
|
|
/* Can contain at most one @ , and not at the start or end */
|
|
if (*c == '@') {
|
|
atfound++;
|
|
if (c != n && *(c + 1) != '\0' && atfound == 1) {
|
|
continue;
|
|
}
|
|
}
|
|
/* Numbers allowed generally */
|
|
if (*c >= '0' && *c <= '9') continue;
|
|
/* Upper and lowercase characters allowed */
|
|
if (*c >= 'A' && *c <= 'Z') continue;
|
|
if (*c >= 'a' && *c <= 'z') continue;
|
|
|
|
/* Everything else is a failure */
|
|
return PS_FAILURE;
|
|
}
|
|
/* RFC 1034 states if it's not an IP, it can't start with a number,
|
|
However, RFC 1123 updates this and does allow a number as the
|
|
first character of a DNS name.
|
|
See the X.509 RFC: http://tools.ietf.org/html/rfc5280#section-4.2.1.6 */
|
|
if (atfound && (*n >= '0' && *n <= '9')) return PS_FAILURE;
|
|
|
|
/* We could at this point store whether it is a DNS, Email or IP */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Parses a sequence of GeneralName types
|
|
TODO: the actual types should be parsed. Just copying data blob
|
|
|
|
GeneralName ::= CHOICE {
|
|
otherName [0] OtherName,
|
|
rfc822Name [1] IA5String,
|
|
dNSName [2] IA5String,
|
|
x400Address [3] ORAddress,
|
|
directoryName [4] Name,
|
|
ediPartyName [5] EDIPartyName,
|
|
uniformResourceIdentifier [6] IA5String,
|
|
iPAddress [7] OCTET STRING,
|
|
registeredID [8] OBJECT IDENTIFIER }
|
|
*/
|
|
static int32_t parseGeneralNames(psPool_t *pool, const unsigned char **buf,
|
|
uint16_t len, const unsigned char *extEnd,
|
|
x509GeneralName_t **name, int16_t limit)
|
|
{
|
|
uint16_t otherNameLen;
|
|
const unsigned char *p, *c, *save, *end;
|
|
x509GeneralName_t *activeName, *firstName, *prevName;
|
|
|
|
if (*name == NULL) {
|
|
firstName = NULL;
|
|
} else {
|
|
firstName = *name;
|
|
}
|
|
p = *buf;
|
|
end = p + len;
|
|
|
|
while (len > 0) {
|
|
if (firstName == NULL) {
|
|
activeName = firstName = psMalloc(pool, sizeof(x509GeneralName_t));
|
|
if (activeName == NULL) {
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memset(firstName, 0x0, sizeof(x509GeneralName_t));
|
|
firstName->pool = pool;
|
|
*name = firstName;
|
|
} else {
|
|
/*
|
|
Find the end
|
|
*/
|
|
prevName = firstName;
|
|
activeName = firstName->next;
|
|
while (activeName != NULL) {
|
|
prevName = activeName;
|
|
activeName = activeName->next;
|
|
}
|
|
prevName->next = psMalloc(pool, sizeof(x509GeneralName_t));
|
|
if (prevName->next == NULL) {
|
|
/* TODO: free the list */
|
|
return PS_MEM_FAIL;
|
|
}
|
|
activeName = prevName->next;
|
|
memset(activeName, 0x0, sizeof(x509GeneralName_t));
|
|
activeName->pool = pool;
|
|
}
|
|
activeName->id = *p & 0xF;
|
|
p++; len--;
|
|
switch (activeName->id) {
|
|
case GN_OTHER:
|
|
strncpy((char *)activeName->name, "other",
|
|
sizeof(activeName->name) - 1);
|
|
/* OtherName ::= SEQUENCE {
|
|
type-id OBJECT IDENTIFIER,
|
|
value [0] EXPLICIT ANY DEFINED BY type-id }
|
|
*/
|
|
save = p;
|
|
if (getAsnLength(&p, (uint32)(extEnd - p), &otherNameLen) < 0 ||
|
|
otherNameLen < 1 ||
|
|
(uint32)(extEnd - p) < otherNameLen) {
|
|
psTraceCrypto("ASN parse error SAN otherName\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (*(p++) != ASN_OID ||
|
|
getAsnLength(&p, (int32)(extEnd - p), &activeName->oidLen) < 0 ||
|
|
(uint32)(extEnd - p) < activeName->oidLen) {
|
|
|
|
psTraceCrypto("ASN parse error SAN otherName oid\n");
|
|
return -1;
|
|
}
|
|
/* Note activeName->oidLen could be zero here */
|
|
memcpy(activeName->oid, p, activeName->oidLen);
|
|
p += activeName->oidLen;
|
|
/* value looks like
|
|
0xA0, <len>, <TYPE>, <dataLen>, <data>
|
|
We're supporting only string-type TYPE so just skipping it
|
|
*/
|
|
if ((uint32)(extEnd - p) < 1 || *p != 0xA0) {
|
|
psTraceCrypto("ASN parse error SAN otherName\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p++; /* Jump over A0 */
|
|
if (getAsnLength(&p, (uint32)(extEnd - p), &otherNameLen) < 0 ||
|
|
otherNameLen < 1 ||
|
|
(uint32)(extEnd - p) < otherNameLen) {
|
|
psTraceCrypto("ASN parse error SAN otherName value\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if ((uint32)(extEnd - p) < 1) {
|
|
psTraceCrypto("ASN parse error SAN otherName len\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/* TODO - validate *p == STRING type? */
|
|
p++; /* Jump over TYPE */
|
|
len -= (p - save);
|
|
break;
|
|
case GN_EMAIL:
|
|
strncpy((char *)activeName->name, "email",
|
|
sizeof(activeName->name) - 1);
|
|
break;
|
|
case GN_DNS:
|
|
strncpy((char *)activeName->name, "DNS",
|
|
sizeof(activeName->name) - 1);
|
|
break;
|
|
case GN_X400:
|
|
strncpy((char *)activeName->name, "x400Address",
|
|
sizeof(activeName->name) - 1);
|
|
break;
|
|
case GN_DIR:
|
|
strncpy((char *)activeName->name, "directoryName",
|
|
sizeof(activeName->name) - 1);
|
|
break;
|
|
case GN_EDI:
|
|
strncpy((char *)activeName->name, "ediPartyName",
|
|
sizeof(activeName->name) - 1);
|
|
break;
|
|
case GN_URI:
|
|
strncpy((char *)activeName->name, "URI",
|
|
sizeof(activeName->name) - 1);
|
|
break;
|
|
case GN_IP:
|
|
strncpy((char *)activeName->name, "iPAddress",
|
|
sizeof(activeName->name) - 1);
|
|
break;
|
|
case GN_REGID:
|
|
strncpy((char *)activeName->name, "registeredID",
|
|
sizeof(activeName->name) - 1);
|
|
break;
|
|
default:
|
|
strncpy((char *)activeName->name, "unknown",
|
|
sizeof(activeName->name) - 1);
|
|
break;
|
|
}
|
|
|
|
save = p;
|
|
if (getAsnLength(&p, (uint32)(extEnd - p), &activeName->dataLen) < 0 ||
|
|
activeName->dataLen < 1 ||
|
|
(uint32)(extEnd - p) < activeName->dataLen) {
|
|
psTraceCrypto("ASN len error in parseGeneralNames\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
len -= (p - save);
|
|
|
|
/* Currently we validate that the IA5String fields are printable
|
|
At a minimum, this prevents attacks with null terminators or
|
|
invisible characters in the certificate.
|
|
Additional validation of name format is done indirectly
|
|
via byte comparison to the expected name in ValidateGeneralName
|
|
or directly by the user in the certificate callback */
|
|
switch (activeName->id) {
|
|
case GN_EMAIL:
|
|
case GN_DNS:
|
|
case GN_URI:
|
|
save = p + activeName->dataLen;
|
|
for (c = p; c < save; c++) {
|
|
if (*c < ' ' || *c > '~') {
|
|
psTraceCrypto("ASN invalid GeneralName character\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
}
|
|
break;
|
|
case GN_IP:
|
|
if (activeName->dataLen < 4) {
|
|
psTraceCrypto("Unknown GN_IP format\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
activeName->data = psMalloc(pool, activeName->dataLen + 1);
|
|
if (activeName->data == NULL) {
|
|
psError("Memory allocation error: activeName->data\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
/* This guarantees data is null terminated, even for non IA5Strings */
|
|
memset(activeName->data, 0x0, activeName->dataLen + 1);
|
|
memcpy(activeName->data, p, activeName->dataLen);
|
|
|
|
p = p + activeName->dataLen;
|
|
len -= activeName->dataLen;
|
|
|
|
if (limit > 0) {
|
|
if (--limit == 0) {
|
|
*buf = end;
|
|
return PS_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
*buf = p;
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/**
|
|
Parse ASN.1 DER encoded OBJECT bytes into an OID array.
|
|
@param[in] der Pointer to start of OBJECT encoding.
|
|
@param[in] derlen Number of bytes pointed to by 'der'.
|
|
@param[out] oid Caller allocated array to receive OID, of
|
|
at least MAX_OID_LEN elements.
|
|
@return Number of OID elements written to 'oid', 0 on error.
|
|
*/
|
|
static uint8_t psParseOid(const unsigned char *der, uint16_t derlen,
|
|
uint32_t oid[MAX_OID_LEN])
|
|
{
|
|
const unsigned char *end;
|
|
uint8_t n, sanity;
|
|
|
|
if (derlen < 1) {
|
|
return 0;
|
|
}
|
|
end = der + derlen;
|
|
/* First two OID elements are single octet, base 40 for some reason */
|
|
oid[0] = *der / 40;
|
|
oid[1] = *der % 40;
|
|
der++;
|
|
/* Zero the remainder of OID and leave n == 2 */
|
|
for (n = MAX_OID_LEN - 1; n > 2; n--) {
|
|
oid[n] = 0;
|
|
}
|
|
while (der < end && n < MAX_OID_LEN) {
|
|
/* If the high bit is 0, it's short form variable length quantity */
|
|
if (!(*der & 0x80)) {
|
|
oid[n++] = *der++;
|
|
} else {
|
|
sanity = 0;
|
|
/* Long form. High bit means another (lower) 7 bits following */
|
|
do {
|
|
oid[n] |= (*der & 0x7F);
|
|
/* A clear high bit ends the byte sequence */
|
|
if (!(*der & 0x80)) {
|
|
break;
|
|
}
|
|
/* Allow a maximum of 4 x 7 bit shifts (28 bits) */
|
|
if (++sanity > 4) {
|
|
return 0;
|
|
}
|
|
/* Make room for the next 7 bits */
|
|
oid[n] <<= 7;
|
|
der++;
|
|
} while (der < end);
|
|
der++;
|
|
n++;
|
|
}
|
|
}
|
|
if (n < MAX_OID_LEN) {
|
|
return n;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Look up an OID in our known oid table.
|
|
@param[in] oid Array of OID segments to look up in table.
|
|
@param[in] oidlen Number of segments in 'oid'
|
|
@return A valid OID enum on success, 0 on failure.
|
|
*/
|
|
static oid_e psFindOid(const uint32_t oid[MAX_OID_LEN], uint8_t oidlen)
|
|
{
|
|
int i, j;
|
|
|
|
psAssert(oidlen <= MAX_OID_LEN);
|
|
for (j = 0; oid_list[j].id != 0; j++) {
|
|
for (i = 0; i < oidlen; i++) {
|
|
if ((uint16_t)(oid[i] & 0xFFFF) != oid_list[j].oid[i]) {
|
|
break;
|
|
}
|
|
if ((i + 1) == oidlen) {
|
|
return oid_list[j].id;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef USE_OID_TRACE
|
|
/**
|
|
Print an OID in dot notation, with it's symbolic name, if found.
|
|
@param[in] oid Array of OID segments print.
|
|
@param[in] oidlen Number of segments in 'oid'
|
|
@return void
|
|
*/
|
|
static void psTraceOid(uint32_t oid[MAX_OID_LEN], uint8_t oidlen)
|
|
{
|
|
int i, j, found;
|
|
|
|
for (i = 0; i < oidlen; i++) {
|
|
if ((i + 1) < oidlen) {
|
|
psTraceIntCrypto("%u.", oid[i]);
|
|
} else {
|
|
psTraceIntCrypto("%u", oid[i]);
|
|
}
|
|
}
|
|
found = 0;
|
|
for (j = 0; oid_list[j].oid[0] != 0 && !found; j++) {
|
|
for (i = 0; i < oidlen; i++) {
|
|
if ((uint8_t)(oid[i] & 0xFF) != oid_list[j].oid[i]) {
|
|
break;
|
|
}
|
|
if ((i + 1) == oidlen) {
|
|
psTraceStrCrypto(" (%s)", oid_list[j].name);
|
|
found++;
|
|
}
|
|
}
|
|
}
|
|
psTraceCrypto("\n");
|
|
}
|
|
#else
|
|
#define psTraceOid(A, B) psTraceCrypto("\n");
|
|
#endif
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
X509v3 extensions
|
|
*/
|
|
|
|
int32_t getExplicitExtensions(psPool_t *pool, const unsigned char **pp,
|
|
uint16_t inlen, int32_t expVal,
|
|
x509v3extensions_t *extensions, uint8_t known)
|
|
{
|
|
const unsigned char *p = *pp, *end;
|
|
const unsigned char *extEnd, *extStart, *save;
|
|
unsigned char critical;
|
|
uint16_t len, fullExtLen;
|
|
uint32_t oid[MAX_OID_LEN];
|
|
uint8_t oidlen;
|
|
oid_e noid;
|
|
#ifdef USE_FULL_CERT_PARSE
|
|
uint16_t subExtLen;
|
|
const unsigned char *subSave;
|
|
int32_t nc = 0;
|
|
#endif
|
|
|
|
end = p + inlen;
|
|
if (inlen < 1) {
|
|
return PS_ARG_FAIL;
|
|
}
|
|
extensions->pool = pool;
|
|
if (known) {
|
|
goto KNOWN_EXT;
|
|
}
|
|
/*
|
|
Not treating this as an error because it is optional.
|
|
*/
|
|
if (*p != (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | expVal)) {
|
|
return 0;
|
|
}
|
|
p++;
|
|
if (getAsnLength(&p, (uint32)(end - p), &len) < 0 ||
|
|
(uint32)(end - p) < len) {
|
|
psTraceCrypto("Initial getAsnLength failure in extension parse\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
KNOWN_EXT:
|
|
/*
|
|
Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
|
|
|
|
Extension ::= SEQUENCE {
|
|
extnID OBJECT IDENTIFIER,
|
|
extnValue OCTET STRING }
|
|
*/
|
|
if (getAsnSequence(&p, (uint32)(end - p), &len) < 0 ||
|
|
(uint32)(end - p) < len) {
|
|
psTraceCrypto("Initial getAsnSequence failure in extension parse\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
extEnd = p + len;
|
|
while ((p != extEnd) && *p == (ASN_SEQUENCE | ASN_CONSTRUCTED)) {
|
|
if (getAsnSequence(&p, (uint32)(extEnd - p), &fullExtLen) < 0) {
|
|
psTraceCrypto("getAsnSequence failure in extension parse\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
extStart = p;
|
|
/*
|
|
Conforming CAs MUST support key identifiers, basic constraints,
|
|
key usage, and certificate policies extensions
|
|
*/
|
|
if (extEnd - p < 1 || *p++ != ASN_OID) {
|
|
psTraceCrypto("Malformed extension header\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (getAsnLength(&p, (uint32)(extEnd - p), &len) < 0 ||
|
|
(uint32)(extEnd - p) < len) {
|
|
psTraceCrypto("Malformed extension length\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if ((oidlen = psParseOid(p, len, oid)) < 1) {
|
|
psTraceCrypto("Malformed extension OID\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
noid = psFindOid(oid, oidlen);
|
|
p += len;
|
|
/*
|
|
Possible boolean value here for 'critical' id. It's a failure if a
|
|
critical extension is found that is not supported
|
|
*/
|
|
critical = 0;
|
|
if (extEnd - p < 1) {
|
|
psTraceCrypto("Malformed extension length\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (*p == ASN_BOOLEAN) {
|
|
p++;
|
|
if (extEnd - p < 2) {
|
|
psTraceCrypto("Error parsing critical id len for cert extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (*p != 1) {
|
|
psTraceCrypto("Error parsing critical id for cert extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p++;
|
|
if (*p > 0) {
|
|
/* Officially DER TRUE must be 0xFF, openssl is more lax */
|
|
if (*p != 0xFF) {
|
|
psTraceCrypto("Warning: DER BOOLEAN TRUE should be 0xFF\n");
|
|
}
|
|
critical = 1;
|
|
}
|
|
p++;
|
|
}
|
|
if (extEnd - p < 1 || (*p++ != ASN_OCTET_STRING) ||
|
|
getAsnLength(&p, (uint32)(extEnd - p), &len) < 0 ||
|
|
(uint32)(extEnd - p) < len) {
|
|
psTraceCrypto("Expecting OCTET STRING in ext parse\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
|
|
/* Set bits 1..9 to indicate criticality of known extensions */
|
|
if (critical) {
|
|
extensions->critFlags |= EXT_CRIT_FLAG(noid);
|
|
}
|
|
|
|
switch (noid) {
|
|
/*
|
|
BasicConstraints ::= SEQUENCE {
|
|
cA BOOLEAN DEFAULT FALSE,
|
|
pathLenConstraint INTEGER (0..MAX) OPTIONAL }
|
|
*/
|
|
case OID_ENUM(id_ce_basicConstraints):
|
|
if (getAsnSequence(&p, (uint32)(extEnd - p), &len) < 0) {
|
|
psTraceCrypto("Error parsing BasicConstraints extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/*
|
|
"This goes against PKIX guidelines but some CAs do it and some
|
|
software requires this to avoid interpreting an end user
|
|
certificate as a CA."
|
|
- OpenSSL certificate configuration doc
|
|
|
|
basicConstraints=CA:FALSE
|
|
*/
|
|
if (len == 0) {
|
|
break;
|
|
}
|
|
/*
|
|
Have seen some certs that don't include a cA bool.
|
|
*/
|
|
if (*p == ASN_BOOLEAN) {
|
|
if (extEnd - p < 3) {
|
|
psTraceCrypto("Error parsing BC extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p++;
|
|
if (*p++ != 1) {
|
|
psTraceCrypto("Error parse BasicConstraints CA bool\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/* Officially DER TRUE must be 0xFF, openssl is more lax */
|
|
if (*p > 0 && *p != 0xFF) {
|
|
psTraceCrypto("Warning: cA TRUE should be 0xFF\n");
|
|
}
|
|
extensions->bc.cA = *p++;
|
|
} else {
|
|
extensions->bc.cA = 0;
|
|
}
|
|
/*
|
|
Now need to check if there is a path constraint. Only makes
|
|
sense if cA is true. If it's missing, there is no limit to
|
|
the cert path
|
|
*/
|
|
if (*p == ASN_INTEGER) {
|
|
if (getAsnInteger(&p, (uint32)(extEnd - p),
|
|
&(extensions->bc.pathLenConstraint)) < 0) {
|
|
psTraceCrypto("Error parsing BasicConstraints pathLen\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
} else {
|
|
extensions->bc.pathLenConstraint = -1;
|
|
}
|
|
break;
|
|
|
|
case OID_ENUM(id_ce_subjectAltName):
|
|
if (getAsnSequence(&p, (uint32)(extEnd - p), &len) < 0) {
|
|
psTraceCrypto("Error parsing altSubjectName extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/* NOTE: The final limit parameter was introduced for this
|
|
case because a well known search engine site sends back
|
|
about 7 KB worth of subject alt names and that has created
|
|
memory problems for a couple users. Set the -1 here to
|
|
something reasonable (5) if you've found yourself here
|
|
for this memory reason */
|
|
if (parseGeneralNames(pool, &p, len, extEnd, &extensions->san,
|
|
-1) < 0) {
|
|
psTraceCrypto("Error parsing altSubjectName names\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
|
|
break;
|
|
|
|
case OID_ENUM(id_ce_keyUsage):
|
|
/*
|
|
KeyUsage ::= BIT STRING {
|
|
digitalSignature (0),
|
|
nonRepudiation (1),
|
|
keyEncipherment (2),
|
|
dataEncipherment (3),
|
|
keyAgreement (4),
|
|
keyCertSign (5),
|
|
cRLSign (6),
|
|
encipherOnly (7),
|
|
decipherOnly (8) }
|
|
*/
|
|
if (*p++ != ASN_BIT_STRING) {
|
|
psTraceCrypto("Error parsing keyUsage extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (getAsnLength(&p, (int32)(extEnd - p), &len) < 0 ||
|
|
(uint32)(extEnd - p) < len) {
|
|
psTraceCrypto("Malformed keyUsage extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (len < 2) {
|
|
psTraceCrypto("Malformed keyUsage extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/*
|
|
If the lenth is <= 3, then there might be a
|
|
KEY_USAGE_DECIPHER_ONLY (or maybe just some empty bytes).
|
|
*/
|
|
if (len >= 3) {
|
|
if (p[2] == (KEY_USAGE_DECIPHER_ONLY >> 8) && p[0] == 7) {
|
|
extensions->keyUsageFlags |= KEY_USAGE_DECIPHER_ONLY;
|
|
}
|
|
}
|
|
extensions->keyUsageFlags |= p[1];
|
|
p = p + len;
|
|
break;
|
|
|
|
case OID_ENUM(id_ce_extKeyUsage):
|
|
if (getAsnSequence(&p, (int32)(extEnd - p), &fullExtLen) < 0) {
|
|
psTraceCrypto("Error parsing authKeyId extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
save = p;
|
|
while (fullExtLen > 0) {
|
|
if (*p++ != ASN_OID) {
|
|
psTraceCrypto("Malformed extension header\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (getAsnLength(&p, fullExtLen, &len) < 0 ||
|
|
fullExtLen < len) {
|
|
psTraceCrypto("Malformed extension length\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if ((oidlen = psParseOid(p, len, oid)) < 1) {
|
|
psTraceCrypto("Malformed extension OID\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
noid = psFindOid(oid, oidlen);
|
|
p += len;
|
|
if (fullExtLen < (uint32)(p - save)) {
|
|
psTraceCrypto("Inner OID parse fail EXTND_KEY_USAGE\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
fullExtLen -= (p - save);
|
|
save = p;
|
|
switch (noid) {
|
|
case OID_ENUM(id_kp_serverAuth):
|
|
extensions->ekuFlags |= EXT_KEY_USAGE_TLS_SERVER_AUTH;
|
|
break;
|
|
case OID_ENUM(id_kp_clientAuth):
|
|
extensions->ekuFlags |= EXT_KEY_USAGE_TLS_CLIENT_AUTH;
|
|
break;
|
|
case OID_ENUM(id_kp_codeSigning):
|
|
extensions->ekuFlags |= EXT_KEY_USAGE_CODE_SIGNING;
|
|
break;
|
|
case OID_ENUM(id_kp_emailProtection):
|
|
extensions->ekuFlags |= EXT_KEY_USAGE_EMAIL_PROTECTION;
|
|
break;
|
|
case OID_ENUM(id_kp_timeStamping):
|
|
extensions->ekuFlags |= EXT_KEY_USAGE_TIME_STAMPING;
|
|
break;
|
|
case OID_ENUM(id_kp_OCSPSigning):
|
|
extensions->ekuFlags |= EXT_KEY_USAGE_OCSP_SIGNING;
|
|
break;
|
|
case OID_ENUM(id_ce_eku_anyExtendedKeyUsage):
|
|
extensions->ekuFlags |= EXT_KEY_USAGE_ANY;
|
|
break;
|
|
default:
|
|
psTraceCrypto("WARNING: Unknown EXT_KEY_USAGE:");
|
|
psTraceOid(oid, oidlen);
|
|
break;
|
|
} /* end switch */
|
|
}
|
|
break;
|
|
|
|
#ifdef USE_FULL_CERT_PARSE
|
|
|
|
case OID_ENUM(id_ce_nameConstraints):
|
|
if (critical) {
|
|
/* We're going to fail if critical since no real
|
|
pattern matching is happening yet */
|
|
psTraceCrypto("ERROR: critical nameConstraints unsupported\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (getAsnSequence(&p, (int32)(extEnd - p), &fullExtLen) < 0) {
|
|
psTraceCrypto("Error parsing authKeyId extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
while (fullExtLen > 0) {
|
|
save = p;
|
|
|
|
if (*p == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 0)) {
|
|
/* permittedSubtrees */
|
|
p++;
|
|
nc = 0;
|
|
}
|
|
if (*p == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 1)) {
|
|
/* excludedSubtrees */
|
|
p++;
|
|
nc = 1;
|
|
}
|
|
subExtLen = 0;
|
|
if (getAsnLength(&p, (uint32)(extEnd - p), &subExtLen) < 0 ||
|
|
subExtLen < 1 || (uint32)(extEnd - p) < subExtLen) {
|
|
psTraceCrypto("ASN get len error in nameConstraint\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (fullExtLen < (subExtLen + (p - save))) {
|
|
psTraceCrypto("fullExtLen parse fail nameConstraint\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
fullExtLen -= subExtLen + (p - save);
|
|
while (subExtLen > 0) {
|
|
subSave = p;
|
|
if (getAsnSequence(&p, (int32)(extEnd - p), &len) < 0) {
|
|
psTraceCrypto("Error parsing nameConst ext\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (subExtLen < (len + (p - subSave))) {
|
|
psTraceCrypto("subExtLen fail nameConstraint\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
subExtLen -= len + (p - subSave);
|
|
if (nc == 0) {
|
|
if (parseGeneralNames(pool, &p, len, extEnd,
|
|
&extensions->nameConstraints.permitted, -1) <0){
|
|
psTraceCrypto("Error parsing nameConstraint\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
} else {
|
|
if (parseGeneralNames(pool, &p, len, extEnd,
|
|
&extensions->nameConstraints.excluded, -1) < 0){
|
|
psTraceCrypto("Error parsing nameConstraint\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
#ifdef USE_CRL
|
|
case OID_ENUM(id_ce_cRLDistributionPoints):
|
|
|
|
if (getAsnSequence(&p, (int32)(extEnd - p), &fullExtLen) < 0) {
|
|
psTraceCrypto("Error parsing authKeyId extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
|
|
while (fullExtLen > 0) {
|
|
save = p;
|
|
if (getAsnSequence(&p, (uint32)(extEnd - p), &len) < 0) {
|
|
psTraceCrypto("getAsnSequence fail in crldist parse\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (fullExtLen < (len + (p - save))) {
|
|
psTraceCrypto("fullExtLen parse fail crldist\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
fullExtLen -= len + (p - save);
|
|
/* All memebers are optional */
|
|
if (*p == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 0)) {
|
|
/* DistributionPointName */
|
|
p++;
|
|
if (getAsnLength(&p, (uint32)(extEnd - p), &len) < 0 ||
|
|
len < 1 || (uint32)(extEnd - p) < len) {
|
|
psTraceCrypto("ASN get len error in CRL extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
|
|
if ((*p & 0xF) == 0) { /* fullName (GeneralNames) */
|
|
p++;
|
|
if (getAsnLength(&p, (uint32)(extEnd - p), &len) < 0
|
|
|| len < 1 || (uint32)(extEnd - p) < len) {
|
|
psTraceCrypto("ASN get len error in CRL extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (parseGeneralNames(pool, &p, len, extEnd,
|
|
&extensions->crlDist, -1) > 0) {
|
|
psTraceCrypto("dist gen name parse fail\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
} else if ((*p & 0xF) == 1) { /* RelativeDistName */
|
|
p++;
|
|
/* RelativeDistName not parsed */
|
|
if (getAsnLength(&p, (uint32)(extEnd - p), &len) < 0
|
|
|| len < 1 || (uint32)(extEnd - p) < len) {
|
|
psTraceCrypto("ASN get len error in CRL extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p += len;
|
|
} else {
|
|
psTraceCrypto("DistributionPointName parse fail\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
}
|
|
if (*p == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 1)) {
|
|
p++;
|
|
/* ReasonFlags not parsed */
|
|
if (getAsnLength(&p, (uint32)(extEnd - p), &len) < 0 ||
|
|
len < 1 || (uint32)(extEnd - p) < len) {
|
|
psTraceCrypto("ASN get len error in CRL extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p += len;
|
|
}
|
|
if (*p == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 2)) {
|
|
p++;
|
|
/* General Names not parsed */
|
|
if (getAsnLength(&p, (uint32)(extEnd - p), &len) < 0 ||
|
|
len < 1 || (uint32)(extEnd - p) < len) {
|
|
psTraceCrypto("ASN get len error in CRL extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p += len;
|
|
}
|
|
}
|
|
break;
|
|
#endif /* USE_CRL */
|
|
#endif /* FULL_CERT_PARSE */
|
|
|
|
case OID_ENUM(id_ce_authorityKeyIdentifier):
|
|
/*
|
|
AuthorityKeyIdentifier ::= SEQUENCE {
|
|
keyIdentifier [0] KeyIdentifier OPTIONAL,
|
|
authorityCertIssuer [1] GeneralNames OPTIONAL,
|
|
authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL }
|
|
|
|
KeyIdentifier ::= OCTET STRING
|
|
*/
|
|
if (getAsnSequence(&p, (int32)(extEnd - p), &len) < 0) {
|
|
psTraceCrypto("Error parsing authKeyId extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/* Have seen a cert that has a zero length ext here. Let it pass. */
|
|
if (len == 0) {
|
|
break;
|
|
}
|
|
/* All members are optional */
|
|
if (*p == (ASN_CONTEXT_SPECIFIC | ASN_PRIMITIVE | 0)) {
|
|
p++;
|
|
if (getAsnLength(&p, (int32)(extEnd - p),
|
|
&extensions->ak.keyLen) < 0 ||
|
|
(uint32)(extEnd - p) < extensions->ak.keyLen) {
|
|
psTraceCrypto("Error keyLen in authKeyId extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
extensions->ak.keyId =psMalloc(pool, extensions->ak.keyLen);
|
|
if (extensions->ak.keyId == NULL) {
|
|
psError("Mem allocation err: extensions->ak.keyId\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memcpy(extensions->ak.keyId, p, extensions->ak.keyLen);
|
|
p = p + extensions->ak.keyLen;
|
|
}
|
|
if (*p == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 1)) {
|
|
p++;
|
|
if (getAsnLength(&p, (int32)(extEnd - p), &len) < 0 ||
|
|
len < 1 || (uint32)(extEnd - p) < len) {
|
|
psTraceCrypto("ASN get len error in authKeyId extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if ((*p ^ ASN_CONTEXT_SPECIFIC ^ ASN_CONSTRUCTED) != 4) {
|
|
/* We are just dealing with DN formats here */
|
|
psTraceIntCrypto("Error auth key-id name type: %d\n",
|
|
*p ^ ASN_CONTEXT_SPECIFIC ^ ASN_CONSTRUCTED);
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p++;
|
|
if (getAsnLength(&p, (int32)(extEnd - p), &len) < 0 ||
|
|
(uint32)(extEnd - p) < len) {
|
|
psTraceCrypto("ASN get len error2 in authKeyId extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (psX509GetDNAttributes(pool, &p, (int32)(extEnd - p),
|
|
&(extensions->ak.attribs), 0) < 0) {
|
|
psTraceCrypto("Error parsing ak.attribs\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
}
|
|
if ((*p == (ASN_CONTEXT_SPECIFIC | ASN_PRIMITIVE | 2)) ||
|
|
(*p == ASN_INTEGER)){
|
|
/*
|
|
Treat as a serial number (not a native INTEGER)
|
|
*/
|
|
if (getSerialNum(pool, &p, (int32)(extEnd - p),
|
|
&(extensions->ak.serialNum), &len) < 0) {
|
|
psTraceCrypto("Error parsing ak.serialNum\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
extensions->ak.serialNumLen = len;
|
|
}
|
|
break;
|
|
|
|
case OID_ENUM(id_ce_subjectKeyIdentifier):
|
|
/*
|
|
The value of the subject key identifier MUST be the value
|
|
placed in the key identifier field of the Auth Key Identifier
|
|
extension of certificates issued by the subject of
|
|
this certificate.
|
|
*/
|
|
if (*p++ != ASN_OCTET_STRING || getAsnLength(&p,
|
|
(int32)(extEnd - p), &(extensions->sk.len)) < 0 ||
|
|
(uint32)(extEnd - p) < extensions->sk.len) {
|
|
psTraceCrypto("Error parsing subjectKeyId extension\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
extensions->sk.id = psMalloc(pool, extensions->sk.len);
|
|
if (extensions->sk.id == NULL) {
|
|
psError("Memory allocation error extensions->sk.id\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memcpy(extensions->sk.id, p, extensions->sk.len);
|
|
p = p + extensions->sk.len;
|
|
break;
|
|
|
|
/* These extensions are known but not handled */
|
|
case OID_ENUM(id_ce_certificatePolicies):
|
|
case OID_ENUM(id_ce_policyMappings):
|
|
case OID_ENUM(id_ce_issuerAltName):
|
|
case OID_ENUM(id_ce_subjectDirectoryAttributes):
|
|
case OID_ENUM(id_ce_policyConstraints):
|
|
case OID_ENUM(id_ce_inhibitAnyPolicy):
|
|
case OID_ENUM(id_ce_freshestCRL):
|
|
case OID_ENUM(id_pe_subjectInfoAccess):
|
|
default:
|
|
/* Unsupported or skipping because USE_FULL_CERT_PARSE undefd */
|
|
if (critical) {
|
|
psTraceCrypto("Unsupported critical ext encountered: ");
|
|
psTraceOid(oid, oidlen);
|
|
#ifndef ALLOW_UNKNOWN_CRITICAL_EXTENSIONS
|
|
_psTrace("An unsupported critical extension was "
|
|
"encountered. X.509 specifications say "
|
|
"connections must be terminated in this case. "
|
|
"Define ALLOW_UNKNOWN_CRITICAL_EXTENSIONS to "
|
|
"bypass this rule if testing and email Inside "
|
|
"support to inquire about this extension.\n\n");
|
|
return PS_PARSE_FAIL;
|
|
#else
|
|
#ifdef WIN32
|
|
#pragma message("IGNORING UNKNOWN CRITICAL EXTENSIONS IS A SECURITY RISK")
|
|
#else
|
|
#warning "IGNORING UNKNOWN CRITICAL EXTENSIONS IS A SECURITY RISK"
|
|
#endif
|
|
#endif
|
|
}
|
|
p++;
|
|
/*
|
|
Skip over based on the length reported from the ASN_SEQUENCE
|
|
surrounding the entire extension. It is not a guarantee that
|
|
the value of the extension itself will contain it's own length.
|
|
*/
|
|
p = p + (fullExtLen - (p - extStart));
|
|
break;
|
|
}
|
|
}
|
|
*pp = p;
|
|
return 0;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Although a certificate serial number is encoded as an integer type, that
|
|
doesn't prevent it from being abused as containing a variable length
|
|
binary value. Get it here.
|
|
*/
|
|
int32_t getSerialNum(psPool_t *pool, const unsigned char **pp, uint16_t len,
|
|
unsigned char **sn, uint16_t *snLen)
|
|
{
|
|
const unsigned char *p = *pp;
|
|
uint16_t vlen;
|
|
|
|
if ((*p != (ASN_CONTEXT_SPECIFIC | ASN_PRIMITIVE | 2)) &&
|
|
(*p != ASN_INTEGER)) {
|
|
psTraceCrypto("X.509 getSerialNum failed on first bytes\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p++;
|
|
|
|
if (len < 1 || getAsnLength(&p, len - 1, &vlen) < 0 || (len - 1) < vlen) {
|
|
psTraceCrypto("ASN getSerialNum failed\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
*snLen = vlen;
|
|
|
|
if (vlen > 0) {
|
|
*sn = psMalloc(pool, vlen);
|
|
if (*sn == NULL) {
|
|
psError("Memory allocation failure in getSerialNum\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memcpy(*sn, p, vlen);
|
|
p += vlen;
|
|
}
|
|
*pp = p;
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/**
|
|
Explicit value encoding has an additional tag layer.
|
|
*/
|
|
static int32_t getExplicitVersion(const unsigned char **pp, uint16_t len,
|
|
int32_t expVal, int32_t *val)
|
|
{
|
|
const unsigned char *p = *pp;
|
|
uint16_t exLen;
|
|
|
|
if (len < 1) {
|
|
psTraceCrypto("Invalid length to getExplicitVersion\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/*
|
|
This is an optional value, so don't error if not present. The default
|
|
value is version 1
|
|
*/
|
|
if (*p != (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | expVal)) {
|
|
*val = 0;
|
|
return PS_SUCCESS;
|
|
}
|
|
p++;
|
|
if (getAsnLength(&p, len - 1, &exLen) < 0 || (len - 1) < exLen) {
|
|
psTraceCrypto("getAsnLength failure in getExplicitVersion\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (getAsnInteger(&p, exLen, val) < 0) {
|
|
psTraceCrypto("getAsnInteger failure in getExplicitVersion\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
*pp = p;
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/**
|
|
Verify a string has nearly valid date range format and length.
|
|
*/
|
|
static unsigned char asciidate(const unsigned char *c, unsigned int utctime)
|
|
{
|
|
if (utctime != ASN_UTCTIME) { /* 4 character year */
|
|
if (*c < '1' && *c > '2') return 0; c++; /* Year 1900 - 2999 */
|
|
if (*c < '0' && *c > '9') return 0; c++;
|
|
}
|
|
if (*c < '0' && *c > '9') return 0; c++;
|
|
if (*c < '0' && *c > '9') return 0; c++;
|
|
if (*c < '0' && *c > '1') return 0; c++; /* Month 00 - 19 */
|
|
if (*c < '0' && *c > '9') return 0; c++;
|
|
if (*c < '0' && *c > '3') return 0; c++; /* Day 00 - 39 */
|
|
if (*c < '0' && *c > '9') return 0;
|
|
return 1;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/**
|
|
Tests if the certificate was issued before the given date.
|
|
Because there is no actual issuance date in the certificate, we use the
|
|
'notBefore' date (the initial date the certificate is valid) as the
|
|
effective issuance date.
|
|
@security This api is used to be more lenient on certificates that are still
|
|
valid, but were created before certain more strict certificate rules
|
|
were specified.
|
|
|
|
@param[in] rfc The RFC to check against.
|
|
@param[in] cert The cert to check the issuing date on.
|
|
@return 1 if yes, 0 if no, -1 on parse error.
|
|
*/
|
|
static int32 issuedBefore(rfc_e rfc, const psX509Cert_t *cert)
|
|
{
|
|
unsigned char *c;
|
|
unsigned int y;
|
|
unsigned short m;
|
|
|
|
/* Validate the 'not before' date */
|
|
if ((c = (unsigned char *)cert->notBefore) == NULL) {
|
|
return PS_FAILURE;
|
|
}
|
|
/* UTCTIME, defined in 1982, has just a 2 digit year */
|
|
/* year as unsigned int handles over/underflows */
|
|
if (cert->notBeforeTimeType == ASN_UTCTIME) {
|
|
if (!asciidate(c, ASN_UTCTIME)) {
|
|
return PS_FAILURE;
|
|
}
|
|
y = 2000 + 10 * (c[0] - '0') + (c[1] - '0'); c += 2;
|
|
/* Years from '96 through '99 are in the 1900's */
|
|
if (y >= 2096) {
|
|
y -= 100;
|
|
}
|
|
} else {
|
|
if (!asciidate(c, 0)) {
|
|
return PS_FAILURE;
|
|
}
|
|
y = 1000 * (c[0] - '0') + 100 * (c[1] - '0') +
|
|
10 * (c[2] - '0') + (c[3] - '0'); c += 4;
|
|
}
|
|
/* month as unsigned short handles over/underflows */
|
|
m = 10 * (c[0] - '0') + (c[1] - '0');
|
|
/* Must have been issued at least when X509v3 was added */
|
|
if (y < 1996 || m < 1 || m > 12) {
|
|
return -1;
|
|
}
|
|
switch (rfc) {
|
|
case RFC_6818:
|
|
if (y < 2013) { /* No month check needed for Jan */
|
|
return 1;
|
|
}
|
|
return 0;
|
|
case RFC_5280:
|
|
if (y < 2008 || (y == 2008 && m < 5)) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
case RFC_3280:
|
|
if (y < 2002 || (y == 2002 && m < 4)) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
case RFC_2459:
|
|
if (y < 1999) { /* No month check needed for Jan */
|
|
return 1;
|
|
}
|
|
return 0;
|
|
default:
|
|
return -1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/**
|
|
Validate the dates in the cert to machine date.
|
|
SECURITY - always succeeds on systems without date support
|
|
Returns
|
|
0 on success
|
|
PS_CERT_AUTH_FAIL_DATE if date is out of range
|
|
PS_FAILURE on parse error
|
|
*/
|
|
static int32 validateDateRange(psX509Cert_t *cert)
|
|
{
|
|
#ifdef POSIX
|
|
struct tm t;
|
|
time_t rawtime;
|
|
unsigned char *c;
|
|
unsigned int y;
|
|
unsigned short m, d;
|
|
|
|
time(&rawtime);
|
|
localtime_r(&rawtime, &t);
|
|
/* Localtime does months from 0-11 and (year-1900)! Normalize it. */
|
|
t.tm_mon++;
|
|
t.tm_year += 1900;
|
|
|
|
/* Validate the 'not before' date */
|
|
if ((c = (unsigned char *)cert->notBefore) == NULL) {
|
|
return PS_FAILURE;
|
|
}
|
|
/* UTCTIME, defined in 1982, has just a 2 digit year */
|
|
/* year as unsigned int handles over/underflows */
|
|
if (cert->notBeforeTimeType == ASN_UTCTIME) {
|
|
if (!asciidate(c, ASN_UTCTIME)) {
|
|
return PS_FAILURE;
|
|
}
|
|
y = 2000 + 10 * (c[0] - '0') + (c[1] - '0'); c += 2;
|
|
/* Years from '96 through '99 are in the 1900's */
|
|
if (y >= 2096) {
|
|
y -= 100;
|
|
}
|
|
} else {
|
|
if (!asciidate(c, 0)) {
|
|
return PS_FAILURE;
|
|
}
|
|
y = 1000 * (c[0] - '0') + 100 * (c[1] - '0') +
|
|
10 * (c[2] - '0') + (c[3] - '0'); c += 4;
|
|
}
|
|
/* month,day as unsigned short handles over/underflows */
|
|
m = 10 * (c[0] - '0') + (c[1] - '0'); c += 2;
|
|
d = 10 * (c[0] - '0') + (c[1] - '0');
|
|
/* Must have been issued at least when X509v3 was added */
|
|
if (y < 1996 || m < 1 || m > 12 || d < 1 || d > 31) {
|
|
return PS_FAILURE;
|
|
}
|
|
if (t.tm_year < (int)y) {
|
|
cert->authFailFlags |= PS_CERT_AUTH_FAIL_DATE_FLAG;
|
|
} else if (t.tm_year == (int)y) {
|
|
if (t.tm_mon < m) {
|
|
cert->authFailFlags |= PS_CERT_AUTH_FAIL_DATE_FLAG;
|
|
} else if (t.tm_mon == m && t.tm_mday < d) {
|
|
cert->authFailFlags |= PS_CERT_AUTH_FAIL_DATE_FLAG;
|
|
}
|
|
}
|
|
|
|
/* Validate the 'not after' date */
|
|
if ((c = (unsigned char *)cert->notAfter) == NULL) {
|
|
return PS_FAILURE;
|
|
}
|
|
/* UTCTIME, defined in 1982, has just a 2 digit year */
|
|
/* year as unsigned int handles over/underflows */
|
|
if (cert->notAfterTimeType == ASN_UTCTIME) {
|
|
if (!asciidate(c, ASN_UTCTIME)) {
|
|
return PS_FAILURE;
|
|
}
|
|
y = 2000 + 10 * (c[0] - '0') + (c[1] - '0'); c += 2;
|
|
/* Years from '96 through '99 are in the 1900's */
|
|
if (y >= 2096) {
|
|
y -= 100;
|
|
}
|
|
} else {
|
|
if (!asciidate(c, 0)) {
|
|
return PS_FAILURE;
|
|
}
|
|
y = 1000 * (c[0] - '0') + 100 * (c[1] - '0') +
|
|
10 * (c[2] - '0') + (c[3] - '0'); c += 4;
|
|
}
|
|
/* month,day as unsigned short handles over/underflows */
|
|
m = 10 * (c[0] - '0') + (c[1] - '0'); c += 2;
|
|
d = 10 * (c[0] - '0') + (c[1] - '0');
|
|
/* Must have been issued at least when X509v3 was added */
|
|
if (y < 1996 || m < 1 || m > 12 || d < 1 || d > 31) {
|
|
return PS_FAILURE;
|
|
}
|
|
if (t.tm_year > (int)y) {
|
|
cert->authFailFlags |= PS_CERT_AUTH_FAIL_DATE_FLAG;
|
|
} else if (t.tm_year == (int)y) {
|
|
if (t.tm_mon > m) {
|
|
cert->authFailFlags |= PS_CERT_AUTH_FAIL_DATE_FLAG;
|
|
} else if (t.tm_mon == m && t.tm_mday > d) {
|
|
cert->authFailFlags |= PS_CERT_AUTH_FAIL_DATE_FLAG;
|
|
}
|
|
}
|
|
return 0;
|
|
#else
|
|
/* Warn if we are skipping the date validation checks. */
|
|
#ifdef WIN32
|
|
#pragma message("CERTIFICATE DATE VALIDITY NOT SUPPORTED ON THIS PLATFORM.")
|
|
#else
|
|
#warning "CERTIFICATE DATE VALIDITY NOT SUPPORTED ON THIS PLATFORM."
|
|
#endif
|
|
cert->authFailFlags |= PS_CERT_AUTH_FAIL_DATE_FLAG;
|
|
return 0;
|
|
#endif /* POSIX */
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Implementation specific date parser. Does not actually verify the date
|
|
*/
|
|
static int32_t getTimeValidity(psPool_t *pool, const unsigned char **pp,
|
|
uint16_t len, int32_t *notBeforeTimeType,
|
|
int32_t *notAfterTimeType,
|
|
char **notBefore, char **notAfter)
|
|
{
|
|
const unsigned char *p = *pp, *end;
|
|
uint16_t seqLen, timeLen;
|
|
|
|
end = p + len;
|
|
if (len < 1 || *(p++) != (ASN_SEQUENCE | ASN_CONSTRUCTED) ||
|
|
getAsnLength(&p, len - 1, &seqLen) < 0 ||
|
|
(uint32)(end - p) < seqLen) {
|
|
psTraceCrypto("getTimeValidity failed on inital parse\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/*
|
|
Have notBefore and notAfter times in UTCTime or GeneralizedTime formats
|
|
*/
|
|
if ((end - p) < 1 || ((*p != ASN_UTCTIME) && (*p != ASN_GENERALIZEDTIME))) {
|
|
psTraceCrypto("Malformed validity\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
*notBeforeTimeType = *p;
|
|
p++;
|
|
/*
|
|
Allocate them as null terminated strings
|
|
*/
|
|
if (getAsnLength(&p, seqLen, &timeLen) < 0 || (uint32)(end - p) < timeLen) {
|
|
psTraceCrypto("Malformed validity 2\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
*notBefore = psMalloc(pool, timeLen + 1);
|
|
if (*notBefore == NULL) {
|
|
psError("Memory allocation error in getTimeValidity for notBefore\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memcpy(*notBefore, p, timeLen);
|
|
(*notBefore)[timeLen] = '\0';
|
|
p = p + timeLen;
|
|
if ((end - p) < 1 || ((*p != ASN_UTCTIME) && (*p != ASN_GENERALIZEDTIME))) {
|
|
psTraceCrypto("Malformed validity 3\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
*notAfterTimeType = *p;
|
|
p++;
|
|
if (getAsnLength(&p, seqLen - timeLen, &timeLen) < 0 ||
|
|
(uint32)(end - p) < timeLen) {
|
|
psTraceCrypto("Malformed validity 4\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
*notAfter = psMalloc(pool, timeLen + 1);
|
|
if (*notAfter == NULL) {
|
|
psError("Memory allocation error in getTimeValidity for notAfter\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memcpy(*notAfter, p, timeLen);
|
|
(*notAfter)[timeLen] = '\0';
|
|
p = p + timeLen;
|
|
|
|
*pp = p;
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Could be optional. If the tag doesn't contain the value from the left
|
|
of the IMPLICIT keyword we don't have a match and we don't incr the pointer.
|
|
*/
|
|
static int32_t getImplicitBitString(psPool_t *pool, const unsigned char **pp,
|
|
uint16_t len, int32_t impVal, unsigned char **bitString,
|
|
uint16_t *bitLen)
|
|
{
|
|
const unsigned char *p = *pp;
|
|
int32_t ignore_bits;
|
|
|
|
if (len < 1) {
|
|
psTraceCrypto("Initial parse error in getImplicitBitString\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/*
|
|
We don't treat this case as an error, because of the optional nature.
|
|
*/
|
|
if (*p != (ASN_CONTEXT_SPECIFIC | ASN_PRIMITIVE | impVal)) {
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
p++;
|
|
if (getAsnLength(&p, len, bitLen) < 0) {
|
|
psTraceCrypto("Malformed implicitBitString\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
ignore_bits = *p++;
|
|
(*bitLen)--;
|
|
psAssert(ignore_bits == 0);
|
|
|
|
*bitString = psMalloc(pool, *bitLen);
|
|
if (*bitString == NULL) {
|
|
psError("Memory allocation error in getImplicitBitString\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memcpy(*bitString, p, *bitLen);
|
|
*pp = p + *bitLen;
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Implementations of this specification MUST be prepared to receive
|
|
the following standard attribute types in issuer names:
|
|
country, organization, organizational-unit, distinguished name qualifier,
|
|
state or province name, and common name
|
|
*/
|
|
int32_t psX509GetDNAttributes(psPool_t *pool, const unsigned char **pp,
|
|
uint16_t len, x509DNattributes_t *attribs, uint32_t flags)
|
|
{
|
|
const unsigned char *p = *pp;
|
|
const unsigned char *dnEnd, *dnStart, *moreInSetPtr;
|
|
int32 id, stringType, checkHiddenNull, moreInSet;
|
|
uint16_t llen, setlen, arcLen;
|
|
char *stringOut;
|
|
#ifdef USE_SHA1
|
|
psSha1_t hash;
|
|
#elif defined(USE_SHA256)
|
|
psSha256_t hash;
|
|
#else
|
|
//TODO can we avoid hash altogether? We do not free/finalize the hash ctx on error return below.
|
|
#error USE_SHA1 or USE_SHA256 must be defined
|
|
#endif
|
|
|
|
dnStart = p;
|
|
if (getAsnSequence(&p, len, &llen) < 0) {
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
dnEnd = p + llen;
|
|
|
|
/*
|
|
The possibility of a CERTIFICATE_REQUEST message. Set aside full DN
|
|
*/
|
|
if (flags & CERT_STORE_DN_BUFFER) {
|
|
attribs->dnencLen = (uint32)(dnEnd - dnStart);
|
|
attribs->dnenc = psMalloc(pool, attribs->dnencLen);
|
|
if (attribs->dnenc == NULL) {
|
|
psError("Memory allocation error in getDNAttributes\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memcpy(attribs->dnenc, dnStart, attribs->dnencLen);
|
|
}
|
|
moreInSet = 0;
|
|
while (p < dnEnd) {
|
|
if (getAsnSet(&p, (uint32)(dnEnd - p), &setlen) < 0) {
|
|
psTraceCrypto("Malformed DN attributes\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/* 99.99% of certs have one attribute per SET but did come across
|
|
one that nested a couple at this level so let's watch out for
|
|
that with the "moreInSet" logic */
|
|
MORE_IN_SET:
|
|
moreInSetPtr = p;
|
|
if (getAsnSequence(&p, (uint32)(dnEnd - p), &llen) < 0) {
|
|
psTraceCrypto("Malformed DN attributes 2\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (moreInSet > 0) {
|
|
moreInSet -= llen + (int32)(p - moreInSetPtr);
|
|
} else {
|
|
if (setlen != llen + (int32)(p - moreInSetPtr)) {
|
|
moreInSet = setlen - (int32)(p - moreInSetPtr) - llen;
|
|
}
|
|
}
|
|
if (dnEnd <= p || (*(p++) != ASN_OID) ||
|
|
getAsnLength(&p, (uint32)(dnEnd - p), &arcLen) < 0 ||
|
|
(uint32)(dnEnd - p) < arcLen) {
|
|
psTraceCrypto("Malformed DN attributes 3\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/*
|
|
id-at OBJECT IDENTIFIER ::= {joint-iso-ccitt(2) ds(5) 4}
|
|
id-at-commonName OBJECT IDENTIFIER ::= {id-at 3}
|
|
id-at-countryName OBJECT IDENTIFIER ::= {id-at 6}
|
|
id-at-localityName OBJECT IDENTIFIER ::= {id-at 7}
|
|
id-at-stateOrProvinceName OBJECT IDENTIFIER ::= {id-at 8}
|
|
id-at-organizationName OBJECT IDENTIFIER ::= {id-at 10}
|
|
id-at-organizationalUnitName OBJECT IDENTIFIER ::= {id-at 11}
|
|
*/
|
|
*pp = p;
|
|
/*
|
|
Currently we are skipping OIDs not of type {joint-iso-ccitt(2) ds(5) 4}
|
|
However, we could be dealing with an OID we MUST support per RFC.
|
|
domainComponent is one such example.
|
|
*/
|
|
if (dnEnd - p < 2) {
|
|
psTraceCrypto("Malformed DN attributes 4\n");
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
/* check id-at */
|
|
if ((*p++ != 85) || (*p++ != 4) ) {
|
|
/* OIDs we are not parsing */
|
|
p = *pp;
|
|
/*
|
|
Move past the OID and string type, get data size, and skip it.
|
|
NOTE: Have had problems parsing older certs in this area.
|
|
*/
|
|
if ((uint32)(dnEnd - p) < arcLen + 1) {
|
|
psTraceCrypto("Malformed DN attributes 5\n");
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
p += arcLen + 1;
|
|
if (getAsnLength(&p, (uint32)(dnEnd - p), &llen) < 0 ||
|
|
(uint32)(dnEnd - p) < llen) {
|
|
psTraceCrypto("Malformed DN attributes 6\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p = p + llen;
|
|
continue;
|
|
}
|
|
/* Next are the id of the attribute type and the ASN string type */
|
|
if (arcLen != 3 || dnEnd - p < 2) {
|
|
psTraceCrypto("Malformed DN attributes 7\n");
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
id = (int32)*p++;
|
|
/* Done with OID parsing */
|
|
stringType = (int32)*p++;
|
|
|
|
if (getAsnLength(&p, (uint32)(dnEnd - p), &llen) < 0 ||
|
|
(uint32)(dnEnd - p) < llen) {
|
|
psTraceCrypto("Malformed DN attributes 8\n");
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
/*
|
|
For the known 8-bit character string types, we flag that we want
|
|
to test for a hidden null in the middle of the string to address the
|
|
issue of www.goodguy.com\0badguy.com. For BMPSTRING, the user will
|
|
have to validate against the xLen member for such abuses.
|
|
*/
|
|
checkHiddenNull = PS_FALSE;
|
|
switch (stringType) {
|
|
case ASN_PRINTABLESTRING:
|
|
case ASN_UTF8STRING:
|
|
case ASN_IA5STRING:
|
|
/* coverity[unterminated_case] */
|
|
checkHiddenNull = PS_TRUE;
|
|
/* fall through */
|
|
case ASN_T61STRING:
|
|
case ASN_BMPSTRING:
|
|
case ASN_BIT_STRING:
|
|
stringOut = psMalloc(pool, llen + 2);
|
|
if (stringOut == NULL) {
|
|
psError("Memory allocation error in getDNAttributes\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memcpy(stringOut, p, llen);
|
|
/*
|
|
Terminate with 2 null chars to support standard string
|
|
manipulations with any potential unicode types.
|
|
*/
|
|
stringOut[llen] = '\0';
|
|
stringOut[llen + 1] = '\0';
|
|
|
|
if (checkHiddenNull) {
|
|
if ((uint32)strlen(stringOut) != llen) {
|
|
psFree(stringOut, pool);
|
|
psTraceCrypto("Malformed DN attributes 9\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
}
|
|
|
|
p = p + llen;
|
|
llen += 2; /* Add the two null bytes for length assignments */
|
|
break;
|
|
default:
|
|
psTraceIntCrypto("Unsupported DN attrib type %d\n", stringType);
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
|
|
switch (id) {
|
|
case ATTRIB_COUNTRY_NAME:
|
|
if (attribs->country) {
|
|
psFree(attribs->country, pool);
|
|
}
|
|
attribs->country = stringOut;
|
|
attribs->countryType = (short)stringType;
|
|
attribs->countryLen = (short)llen;
|
|
break;
|
|
case ATTRIB_STATE_PROVINCE:
|
|
if (attribs->state) {
|
|
psFree(attribs->state, pool);
|
|
}
|
|
attribs->state = stringOut;
|
|
attribs->stateType = (short)stringType;
|
|
attribs->stateLen = (short)llen;
|
|
break;
|
|
case ATTRIB_LOCALITY:
|
|
if (attribs->locality) {
|
|
psFree(attribs->locality, pool);
|
|
}
|
|
attribs->locality = stringOut;
|
|
attribs->localityType = (short)stringType;
|
|
attribs->localityLen = (short)llen;
|
|
break;
|
|
case ATTRIB_ORGANIZATION:
|
|
if (attribs->organization) {
|
|
psFree(attribs->organization, pool);
|
|
}
|
|
attribs->organization = stringOut;
|
|
attribs->organizationType = (short)stringType;
|
|
attribs->organizationLen = (short)llen;
|
|
break;
|
|
case ATTRIB_ORG_UNIT:
|
|
if (attribs->orgUnit) {
|
|
psFree(attribs->orgUnit, pool);
|
|
}
|
|
attribs->orgUnit = stringOut;
|
|
attribs->orgUnitType = (short)stringType;
|
|
attribs->orgUnitLen = (short)llen;
|
|
break;
|
|
case ATTRIB_COMMON_NAME:
|
|
if (attribs->commonName) {
|
|
psFree(attribs->commonName, pool);
|
|
}
|
|
attribs->commonName = stringOut;
|
|
attribs->commonNameType = (short)stringType;
|
|
attribs->commonNameLen = (short)llen;
|
|
break;
|
|
default:
|
|
/* Not a MUST support, so just ignore unknown */
|
|
psFree(stringOut, pool);
|
|
stringOut = NULL;
|
|
break;
|
|
}
|
|
if (moreInSet) {
|
|
goto MORE_IN_SET;
|
|
}
|
|
}
|
|
/* Hash is used to quickly compare DNs */
|
|
#ifdef USE_SHA1
|
|
psSha1Init(&hash);
|
|
psSha1Update(&hash, dnStart, (dnEnd - dnStart));
|
|
psSha1Final(&hash, (unsigned char*)attribs->hash);
|
|
#else
|
|
psSha256Init(&hash);
|
|
psSha256Update(&hash, dnStart, (dnEnd - dnStart));
|
|
psSha256Final(&hash, (unsigned char*)attribs->hash);
|
|
#endif
|
|
*pp = p;
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Free helper
|
|
*/
|
|
void psX509FreeDNStruct(x509DNattributes_t *dn, psPool_t *allocPool)
|
|
{
|
|
if (dn->country) psFree(dn->country, allocPool);
|
|
if (dn->state) psFree(dn->state, allocPool);
|
|
if (dn->locality) psFree(dn->locality, allocPool);
|
|
if (dn->organization) psFree(dn->organization, allocPool);
|
|
if (dn->orgUnit) psFree(dn->orgUnit, allocPool);
|
|
if (dn->commonName) psFree(dn->commonName, allocPool);
|
|
if (dn->dnenc) psFree(dn->dnenc, allocPool);
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Fundamental routine to test whether the supplied issuerCert issued
|
|
the supplied subjectCert. There are currently two tests that are
|
|
performed here:
|
|
1. A strict SHA1 hash comparison of the Distinguished Name details
|
|
2. A test of the public key cryptographic cert signature
|
|
|
|
subjectCert may be a chain. Cert chains must always be passed with
|
|
the child-most as the first in the list (the 'next' structure member
|
|
points to the parent). The authentication of the entire chain
|
|
will be tested before the issuerCert is used to authenticate the
|
|
parent-most certificate
|
|
|
|
issuerCert will always be a treated as a single certificate even if it
|
|
is a chain
|
|
|
|
If there is no issuerCert the parent-most subejct cert will always
|
|
be tested as a self-signed CA certificate.
|
|
|
|
So there are three uses:
|
|
1. Test a cert was issued by another (single subjectCert, single issuerCert)
|
|
1. Test a self signed cert (single cert to subjectCert, no issuerCert)
|
|
2. Test a CA terminated chain (cert chain to subjectCert, no issuerCert)
|
|
|
|
This function exits with a failure code on the first authentication
|
|
that doesn't succeed. The 'authStatus' members may be examined for more
|
|
information of where the authentication failed.
|
|
|
|
The 'authStatus' member of the issuerCert will be set to PS_FALSE
|
|
since it will not be authenticated.
|
|
|
|
The 'authStatus' members of the subjectCert structures will always
|
|
be reset to PS_FALSE when this routine is called and set to PS_TRUE
|
|
when authenticated. Any error during the authentication will set the
|
|
current subject cert 'authStatus' member to PS_CERT_AUTH_FAIL and the
|
|
function will return with an error code.
|
|
|
|
Return codes:
|
|
PS_SUCCESS - yes
|
|
|
|
PS_CERT_AUTH_FAIL - nope. these certs are not a match
|
|
PS_UNSUPPORTED_FAIL - unrecognized cert format
|
|
PS_ARG_FAIL - local, psRsaDecryptPub
|
|
PS_LIMIT_FAIL - psRsaDecryptPub
|
|
PS_FAILURE - internal psRsaDecryptPub failure
|
|
|
|
There is nothing for the caller to free at the completion of this
|
|
routine.
|
|
*/
|
|
int32 psX509AuthenticateCert(psPool_t *pool, psX509Cert_t *subjectCert,
|
|
psX509Cert_t *issuerCert, psX509Cert_t **foundIssuer,
|
|
void *hwCtx, void *poolUserPtr)
|
|
{
|
|
psX509Cert_t *ic, *sc;
|
|
int32 sigType, rc;
|
|
uint32 sigLen;
|
|
void *rsaData;
|
|
#ifdef USE_ECC
|
|
int32 sigStat;
|
|
#endif /* USE_ECC */
|
|
#ifdef USE_RSA
|
|
unsigned char sigOut[10 + MAX_HASH_SIZE + 9]; /* Max size */
|
|
unsigned char *tempSig = NULL;
|
|
#endif /* USE_RSA */
|
|
psPool_t *pkiPool = NULL;
|
|
#ifdef USE_CRL
|
|
x509revoked_t *curr, *next;
|
|
#endif
|
|
#ifdef USE_PKCS1_PSS
|
|
uint16_t pssLen;
|
|
#endif
|
|
|
|
rc = 0;
|
|
sigLen = 0;
|
|
if (subjectCert == NULL) {
|
|
psTraceCrypto("No subject cert given to psX509AuthenticateCert\n");
|
|
return PS_ARG_FAIL;
|
|
}
|
|
|
|
/*
|
|
Determine what we've been passed
|
|
*/
|
|
if (issuerCert == NULL) {
|
|
/* reset auth flags in subjectCert chain and find first sc and ic */
|
|
sc = subjectCert;
|
|
while (sc) {
|
|
sc->authStatus = PS_FALSE;
|
|
sc = sc->next;
|
|
}
|
|
/* Now see if this is a chain or just a single cert */
|
|
sc = subjectCert;
|
|
if (sc->next == NULL) {
|
|
ic = sc; /* A single subject cert for self-signed test */
|
|
} else {
|
|
ic = sc->next;
|
|
}
|
|
} else {
|
|
issuerCert->authStatus = PS_FALSE;
|
|
ic = issuerCert; /* Easy case of single subject and single issuer */
|
|
sc = subjectCert;
|
|
}
|
|
|
|
/*
|
|
Error on first problem seen and set the subject status to FAIL
|
|
*/
|
|
while (ic) {
|
|
/*
|
|
Certificate authority constraint only available in version 3 certs.
|
|
Only parsing version 3 certs by default though.
|
|
*/
|
|
if ((ic->version > 1) && (ic->extensions.bc.cA <= 0)) {
|
|
if (sc != ic) {
|
|
psTraceCrypto("Issuer does not have basicConstraint CA permissions\n");
|
|
sc->authStatus = PS_CERT_AUTH_FAIL_BC;
|
|
return PS_CERT_AUTH_FAIL_BC;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Use sha1 hash of issuer fields computed at parse time to compare
|
|
*/
|
|
if (memcmp(sc->issuer.hash, ic->subject.hash, SHA1_HASH_SIZE) != 0) {
|
|
if (sc == ic) {
|
|
psTraceCrypto("Info: not a self-signed certificate\n");
|
|
} else {
|
|
//psTraceCrypto("Issuer DN attributes do not match subject\n");
|
|
}
|
|
sc->authStatus = PS_CERT_AUTH_FAIL_DN;
|
|
return PS_CERT_AUTH_FAIL_DN;
|
|
}
|
|
|
|
#ifdef USE_CRL
|
|
/* Does this issuer have a list of revoked serial numbers that needs
|
|
to be checked? */
|
|
if (ic->revoked) {
|
|
curr = ic->revoked;
|
|
while (curr != NULL) {
|
|
next = curr->next;
|
|
if (curr->serialLen == sc->serialNumberLen) {
|
|
if (memcmp(curr->serial, sc->serialNumber, curr->serialLen)
|
|
== 0) {
|
|
sc->authStatus = PS_CERT_AUTH_FAIL_REVOKED;
|
|
return -1;
|
|
}
|
|
}
|
|
curr = next;
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
Signature confirmation
|
|
The sigLen is the ASN.1 size in bytes for encoding the hash.
|
|
The magic 10 is comprised of the SEQUENCE and ALGORITHM ID overhead.
|
|
The magic 9, 8, or 5 is the OID length of the corresponding algorithm.
|
|
*/
|
|
sigType = PS_UNSUPPORTED_FAIL;
|
|
switch (sc->sigAlgorithm) {
|
|
#ifdef USE_RSA
|
|
#ifdef ENABLE_MD5_SIGNED_CERTS
|
|
#ifdef USE_MD2
|
|
case OID_MD2_RSA_SIG:
|
|
#endif
|
|
case OID_MD5_RSA_SIG:
|
|
sigType = RSA_TYPE_SIG;
|
|
sigLen = 10 + MD5_HASH_SIZE + 8;
|
|
break;
|
|
#endif
|
|
#ifdef ENABLE_SHA1_SIGNED_CERTS
|
|
case OID_SHA1_RSA_SIG:
|
|
sigLen = 10 + SHA1_HASH_SIZE + 5;
|
|
sigType = RSA_TYPE_SIG;
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA256
|
|
case OID_SHA256_RSA_SIG:
|
|
sigLen = 10 + SHA256_HASH_SIZE + 9;
|
|
sigType = RSA_TYPE_SIG;
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA384
|
|
case OID_SHA384_RSA_SIG:
|
|
sigLen = 10 + SHA384_HASH_SIZE + 9;
|
|
sigType = RSA_TYPE_SIG;
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA512
|
|
case OID_SHA512_RSA_SIG:
|
|
sigLen = 10 + SHA512_HASH_SIZE + 9;
|
|
sigType = RSA_TYPE_SIG;
|
|
break;
|
|
#endif
|
|
#endif /* USE_RSA */
|
|
#ifdef USE_ECC
|
|
#ifdef ENABLE_SHA1_SIGNED_CERTS
|
|
case OID_SHA1_ECDSA_SIG:
|
|
sigLen = SHA1_HASH_SIZE;
|
|
sigType = ECDSA_TYPE_SIG;
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA256
|
|
case OID_SHA256_ECDSA_SIG:
|
|
sigLen = SHA256_HASH_SIZE;
|
|
sigType = ECDSA_TYPE_SIG;
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA384
|
|
case OID_SHA384_ECDSA_SIG:
|
|
sigLen = SHA384_HASH_SIZE;
|
|
sigType = ECDSA_TYPE_SIG;
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA512
|
|
case OID_SHA512_ECDSA_SIG:
|
|
sigLen = SHA512_HASH_SIZE;
|
|
sigType = ECDSA_TYPE_SIG;
|
|
break;
|
|
#endif
|
|
#endif /* USE_ECC */
|
|
|
|
#ifdef USE_PKCS1_PSS
|
|
case OID_RSASSA_PSS:
|
|
switch (sc->pssHash) {
|
|
#ifdef ENABLE_MD5_SIGNED_CERTS
|
|
case PKCS1_MD5_ID:
|
|
sigLen = MD5_HASH_SIZE;
|
|
break;
|
|
#endif
|
|
#ifdef ENABLE_SHA1_SIGNED_CERTS
|
|
case PKCS1_SHA1_ID:
|
|
sigLen = SHA1_HASH_SIZE;
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA256
|
|
case PKCS1_SHA256_ID:
|
|
sigLen = SHA256_HASH_SIZE;
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA384
|
|
case PKCS1_SHA384_ID:
|
|
sigLen = SHA384_HASH_SIZE;
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA512
|
|
case PKCS1_SHA512_ID:
|
|
sigLen = SHA512_HASH_SIZE;
|
|
break;
|
|
#endif
|
|
default:
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
sigType = RSAPSS_TYPE_SIG;
|
|
break;
|
|
#endif
|
|
default:
|
|
sigType = PS_UNSUPPORTED_FAIL;
|
|
break;
|
|
}
|
|
|
|
if (sigType == PS_UNSUPPORTED_FAIL) {
|
|
sc->authStatus = PS_CERT_AUTH_FAIL_SIG;
|
|
psTraceIntCrypto("Unsupported certificate signature algorithm %d\n",
|
|
subjectCert->sigAlgorithm);
|
|
return sigType;
|
|
}
|
|
|
|
#ifdef USE_RSA
|
|
if (sigType == RSA_TYPE_SIG || sigType == RSAPSS_TYPE_SIG) {
|
|
}
|
|
/* Now do the signature validation */
|
|
if (sigType == RSA_TYPE_SIG) {
|
|
psAssert(sigLen <= sizeof(sigOut));
|
|
/*
|
|
psRsaDecryptPub destroys the 'in' parameter so let it be a tmp
|
|
*/
|
|
tempSig = psMalloc(pool, sc->signatureLen);
|
|
if (tempSig == NULL) {
|
|
psError("Memory allocation error: psX509AuthenticateCert\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memcpy(tempSig, sc->signature, sc->signatureLen);
|
|
|
|
rsaData = NULL;
|
|
|
|
if ((rc = psRsaDecryptPub(pkiPool, &ic->publicKey.key.rsa,
|
|
tempSig, sc->signatureLen, sigOut, sigLen, rsaData)) < 0) {
|
|
|
|
psTraceCrypto("Unable to RSA decrypt certificate signature\n");
|
|
sc->authStatus = PS_CERT_AUTH_FAIL_SIG;
|
|
psFree(tempSig, pool);
|
|
return rc;
|
|
}
|
|
psFree(tempSig, pool);
|
|
rc = x509ConfirmSignature(sc->sigHash, sigOut, sigLen);
|
|
}
|
|
#if defined(USE_PKCS1_PSS) && !defined(USE_PKCS1_PSS_VERIFY_ONLY)
|
|
if (sigType == RSAPSS_TYPE_SIG) {
|
|
tempSig = psMalloc(pool, sc->signatureLen);
|
|
if (tempSig == NULL) {
|
|
psError("Memory allocation error: psX509AuthenticateCert\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
pssLen = sc->signatureLen;
|
|
if ((rc = psRsaCrypt(pkiPool, &ic->publicKey.key.rsa,
|
|
sc->signature, sc->signatureLen, tempSig, &pssLen,
|
|
PS_PUBKEY, rsaData)) < 0) {
|
|
psFree(tempSig, pool);
|
|
return rc;
|
|
}
|
|
|
|
if (pkcs1PssDecode(pkiPool, sc->sigHash, sigLen, tempSig,
|
|
pssLen, sc->saltLen, sc->pssHash, ic->publicKey.keysize * 8,
|
|
&rc) < 0) {
|
|
psFree(tempSig, pool);
|
|
return PS_FAILURE;
|
|
}
|
|
psFree(tempSig, pool);
|
|
|
|
if (rc == 0) {
|
|
/* This is an indication the hash did NOT match */
|
|
rc = -1; /* The test below is looking for < 0 */
|
|
}
|
|
}
|
|
#endif /* defined(USE_PKCS1_PSS) && !defined(USE_PKCS1_PSS_VERIFY_ONLY) */
|
|
#endif /* USE_RSA */
|
|
|
|
#ifdef USE_ECC
|
|
if (sigType == ECDSA_TYPE_SIG) {
|
|
rsaData = NULL;
|
|
if ((rc = psEccDsaVerify(pkiPool,
|
|
&ic->publicKey.key.ecc,
|
|
sc->sigHash, sigLen,
|
|
sc->signature, sc->signatureLen,
|
|
&sigStat, rsaData)) != 0) {
|
|
psTraceCrypto("Error validating ECDSA certificate signature\n");
|
|
sc->authStatus = PS_CERT_AUTH_FAIL_SIG;
|
|
return rc;
|
|
}
|
|
if (sigStat == -1) {
|
|
/* No errors, but signature didn't pass */
|
|
psTraceCrypto("ECDSA certificate signature failed\n");
|
|
rc = -1;
|
|
}
|
|
}
|
|
#endif /* USE_ECC */
|
|
|
|
/*
|
|
Test what happen in the signature test?
|
|
*/
|
|
if (rc < PS_SUCCESS) {
|
|
sc->authStatus = PS_CERT_AUTH_FAIL_SIG;
|
|
return rc;
|
|
}
|
|
|
|
|
|
/* X.509 extension tests. Problems below here will be collected
|
|
in flags and given to the user */
|
|
|
|
/* If date was out of range in parse, flag it here */
|
|
if (sc->authFailFlags & PS_CERT_AUTH_FAIL_DATE_FLAG) {
|
|
sc->authStatus = PS_CERT_AUTH_FAIL_EXTENSION;
|
|
}
|
|
|
|
/* Verify subject key and auth key if either is non-zero */
|
|
if (sc->extensions.ak.keyLen > 0 || ic->extensions.sk.len > 0) {
|
|
if (ic->extensions.sk.len != sc->extensions.ak.keyLen) {
|
|
/* The one exception to this test would be if this is a
|
|
self-signed CA being authenticated with the exact same
|
|
self-signed CA and that certificate does not popluate
|
|
the Authority Key Identifier extension */
|
|
if ((sc->signatureLen == ic->signatureLen) &&
|
|
(memcmp(sc->signature, ic->signature, ic->signatureLen)
|
|
== 0)) {
|
|
if (sc->extensions.ak.keyLen != 0) {
|
|
psTraceCrypto("Subject/Issuer key id mismatch\n");
|
|
sc->authStatus = PS_CERT_AUTH_FAIL_AUTHKEY;
|
|
}
|
|
} else {
|
|
psTraceCrypto("Subject/Issuer key id mismatch\n");
|
|
sc->authStatus = PS_CERT_AUTH_FAIL_AUTHKEY;
|
|
}
|
|
} else {
|
|
if (memcmp(ic->extensions.sk.id, sc->extensions.ak.keyId,
|
|
ic->extensions.sk.len) != 0) {
|
|
psTraceCrypto("Subject/Issuer key id data mismatch\n");
|
|
sc->authStatus = PS_CERT_AUTH_FAIL_AUTHKEY;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Ensure keyCertSign of KeyUsage. The second byte of the BIT STRING
|
|
will always contain the relevant information. */
|
|
if ( ! (ic->extensions.keyUsageFlags & KEY_USAGE_KEY_CERT_SIGN)) {
|
|
/* @security If keyUsageFlags is zero, it may not exist at all
|
|
in the cert. This is allowed if the cert was issued before
|
|
the RFC was updated to require this field for CA certificates.
|
|
RFC3280 and above specify this as a MUST for CACerts. */
|
|
if (ic->extensions.keyUsageFlags == 0) {
|
|
rc = issuedBefore(RFC_3280, ic);
|
|
} else {
|
|
rc = 0; /* Awkward code to force the compare below */
|
|
}
|
|
/* Iff rc == 1 we won't error */
|
|
if (!rc) {
|
|
psTraceCrypto("Issuer does not allow keyCertSign in keyUsage\n");
|
|
sc->authFailFlags |= PS_CERT_AUTH_FAIL_KEY_USAGE_FLAG;
|
|
sc->authStatus = PS_CERT_AUTH_FAIL_EXTENSION;
|
|
} else if (rc < 0) {
|
|
psTraceCrypto("Issue date check failed\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
}
|
|
/*
|
|
Fall through to here only if passed all non-failure checks.
|
|
*/
|
|
if (sc->authStatus == PS_FALSE) { /* Hasn't been touched */
|
|
sc->authStatus = PS_CERT_AUTH_PASS;
|
|
}
|
|
/*
|
|
Loop control for finding next ic and sc.
|
|
*/
|
|
if (ic == sc) {
|
|
*foundIssuer = ic;
|
|
ic = NULL; /* Single self-signed test completed */
|
|
} else if (ic == issuerCert) {
|
|
*foundIssuer = ic;
|
|
ic = NULL; /* If issuerCert was used, that is always final test */
|
|
} else {
|
|
sc = ic;
|
|
ic = sc->next;
|
|
if (ic == NULL) { /* Reached end of chain */
|
|
*foundIssuer = ic;
|
|
ic = sc; /* Self-signed test on final subectCert chain */
|
|
}
|
|
}
|
|
|
|
}
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
#ifdef USE_RSA
|
|
/******************************************************************************/
|
|
/*
|
|
Do the signature validation for a subject certificate against a
|
|
known CA certificate
|
|
*/
|
|
static int32_t x509ConfirmSignature(const unsigned char *sigHash,
|
|
const unsigned char *sigOut, uint16_t sigLen)
|
|
{
|
|
const unsigned char *end;
|
|
const unsigned char *p = sigOut;
|
|
unsigned char hash[MAX_HASH_SIZE];
|
|
int32_t oi;
|
|
uint16_t len, plen;
|
|
|
|
end = p + sigLen;
|
|
/*
|
|
DigestInfo ::= SEQUENCE {
|
|
digestAlgorithm DigestAlgorithmIdentifier,
|
|
digest Digest }
|
|
|
|
DigestAlgorithmIdentifier ::= AlgorithmIdentifier
|
|
|
|
Digest ::= OCTET STRING
|
|
*/
|
|
if (getAsnSequence(&p, (uint32)(end - p), &len) < 0) {
|
|
psTraceCrypto("Initial parse error in x509ConfirmSignature\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
|
|
/* Could be MD5 or SHA1 */
|
|
if (getAsnAlgorithmIdentifier(&p, (uint32)(end - p), &oi, &plen) < 0) {
|
|
psTraceCrypto("Algorithm ID parse error in x509ConfirmSignature\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
psAssert(plen == 0);
|
|
if ((*p++ != ASN_OCTET_STRING) ||
|
|
getAsnLength(&p, (uint32)(end - p), &len) < 0 ||
|
|
(uint32)(end - p) < len) {
|
|
psTraceCrypto("getAsnLength parse error in x509ConfirmSignature\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
memcpy(hash, p, len);
|
|
switch (oi) {
|
|
#ifdef ENABLE_MD5_SIGNED_CERTS
|
|
#ifdef USE_MD2
|
|
case OID_MD2_ALG:
|
|
#endif
|
|
case OID_MD5_ALG:
|
|
if (len != MD5_HASH_SIZE) {
|
|
psTraceCrypto("MD5_HASH_SIZE error in x509ConfirmSignature\n");
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
break;
|
|
#endif
|
|
#ifdef ENABLE_SHA1_SIGNED_CERTS
|
|
case OID_SHA1_ALG:
|
|
if (len != SHA1_HASH_SIZE) {
|
|
psTraceCrypto("SHA1_HASH_SIZE error in x509ConfirmSignature\n");
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA256
|
|
case OID_SHA256_ALG:
|
|
if (len != SHA256_HASH_SIZE) {
|
|
psTraceCrypto("SHA256_HASH_SIZE error in x509ConfirmSignature\n");
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA384
|
|
case OID_SHA384_ALG:
|
|
if (len != SHA384_HASH_SIZE) {
|
|
psTraceCrypto("SHA384_HASH_SIZE error in x509ConfirmSignature\n");
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA512
|
|
case OID_SHA512_ALG:
|
|
if (len != SHA512_HASH_SIZE) {
|
|
psTraceCrypto("SHA512_HASH_SIZE error in x509ConfirmSignature\n");
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
break;
|
|
#endif
|
|
default:
|
|
psTraceCrypto("Unsupported alg ID error in x509ConfirmSignature\n");
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
/* hash should match sigHash */
|
|
if (memcmp(hash, sigHash, len) != 0) {
|
|
psTraceCrypto("Signature failure in x509ConfirmSignature\n");
|
|
return PS_SIGNATURE_MISMATCH;
|
|
}
|
|
return PS_SUCCESS;
|
|
}
|
|
#endif /* USE_RSA */
|
|
|
|
/******************************************************************************/
|
|
#ifdef USE_CRL
|
|
static void x509FreeRevoked(x509revoked_t **revoked)
|
|
{
|
|
x509revoked_t *next, *curr = *revoked;
|
|
|
|
while (curr) {
|
|
next = curr->next;
|
|
psFree(curr->serial, curr->pool);
|
|
psFree(curr, curr->pool);
|
|
curr = next;
|
|
}
|
|
*revoked = NULL;
|
|
}
|
|
|
|
/*
|
|
Parse a CRL and confirm was issued by supplied CA.
|
|
|
|
Only interested in the revoked serial numbers which are stored in the
|
|
CA structure if all checks out. Used during cert validation as part of
|
|
the default tests
|
|
|
|
poolUserPtr is for the TMP_PKI pool
|
|
*/
|
|
int32 psX509ParseCrl(psPool_t *pool, psX509Cert_t *CA, int append,
|
|
unsigned char *crlBin, int32 crlBinLen,
|
|
void *poolUserPtr)
|
|
{
|
|
unsigned char *end, *start, *revStart, *sigStart, *sigEnd,*p = crlBin;
|
|
int32 oi, plen, sigLen, version, rc;
|
|
unsigned char sigHash[SHA512_HASH_SIZE], sigOut[SHA512_HASH_SIZE];
|
|
x509revoked_t *curr, *next;
|
|
x509DNattributes_t issuer;
|
|
x509v3extensions_t ext;
|
|
psDigestContext_t hashCtx;
|
|
psPool_t *pkiPool = MATRIX_NO_POOL;
|
|
uint16_t glen, ilen, timelen;
|
|
|
|
end = p + crlBinLen;
|
|
/*
|
|
CertificateList ::= SEQUENCE {
|
|
tbsCertList TBSCertList,
|
|
signatureAlgorithm AlgorithmIdentifier,
|
|
signatureValue BIT STRING }
|
|
|
|
TBSCertList ::= SEQUENCE {
|
|
version Version OPTIONAL,
|
|
-- if present, shall be v2
|
|
signature AlgorithmIdentifier,
|
|
issuer Name,
|
|
thisUpdate Time,
|
|
nextUpdate Time OPTIONAL,
|
|
revokedCertificates SEQUENCE OF SEQUENCE {
|
|
userCertificate CertificateSerialNumber,
|
|
revocationDate Time,
|
|
crlEntryExtensions Extensions OPTIONAL
|
|
-- if present, shall be v2
|
|
} OPTIONAL,
|
|
crlExtensions [0] EXPLICIT Extensions OPTIONAL
|
|
-- if present, shall be v2
|
|
}
|
|
*/
|
|
if (getAsnSequence(&p, (uint32)(end - p), &glen) < 0) {
|
|
psTraceCrypto("Initial parse error in psX509ParseCrl\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
|
|
sigStart = p;
|
|
if (getAsnSequence(&p, (uint32)(end - p), &glen) < 0) {
|
|
psTraceCrypto("Initial parse error in psX509ParseCrl\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (*p == ASN_INTEGER) {
|
|
version = 0;
|
|
if (getAsnInteger(&p, (uint32)(end - p), &version) < 0 || version != 1){
|
|
psTraceIntCrypto("Version parse error in psX509ParseCrl %d\n",
|
|
version);
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
}
|
|
/* signature */
|
|
if (getAsnAlgorithmIdentifier(&p, (int32)(end - p), &oi, &plen) < 0) {
|
|
psTraceCrypto("Couldn't parse crl sig algorithm identifier\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
|
|
/*
|
|
Name ::= CHOICE { -- only one possibility for now --
|
|
rdnSequence RDNSequence }
|
|
|
|
RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
|
|
|
|
DistinguishedName ::= RDNSequence
|
|
|
|
RelativeDistinguishedName ::=
|
|
SET SIZE (1 .. MAX) OF AttributeTypeAndValue
|
|
*/
|
|
memset(&issuer, 0x0, sizeof(x509DNattributes_t));
|
|
if ((rc = psX509GetDNAttributes(pool, &p, (uint32)(end - p),
|
|
&issuer, 0)) < 0) {
|
|
psTraceCrypto("Couldn't parse crl issuer DN attributes\n");
|
|
return rc;
|
|
}
|
|
/* Ensure crlSign flag of KeyUsage for the given CA. */
|
|
if ( ! (CA->extensions.keyUsageFlags & KEY_USAGE_CRL_SIGN)) {
|
|
psTraceCrypto("Issuer does not allow crlSign in keyUsage\n");
|
|
CA->authFailFlags |= PS_CERT_AUTH_FAIL_KEY_USAGE_FLAG;
|
|
CA->authStatus = PS_CERT_AUTH_FAIL_EXTENSION;
|
|
psX509FreeDNStruct(&issuer, pool);
|
|
return PS_CERT_AUTH_FAIL_EXTENSION;
|
|
}
|
|
if (memcmp(issuer.hash, CA->subject.hash, SHA1_HASH_SIZE) != 0) {
|
|
psTraceCrypto("CRL NOT ISSUED BY THIS CA\n");
|
|
psX509FreeDNStruct(&issuer, pool);
|
|
return PS_CERT_AUTH_FAIL_DN;
|
|
}
|
|
psX509FreeDNStruct(&issuer, pool);
|
|
|
|
/* thisUpdate TIME */
|
|
if ((end - p) < 1 || ((*p != ASN_UTCTIME) && (*p != ASN_GENERALIZEDTIME))) {
|
|
psTraceCrypto("Malformed thisUpdate CRL\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p++;
|
|
if (getAsnLength(&p, (uint32)(end - p), &timelen) < 0 ||
|
|
(uint32)(end - p) < timelen) {
|
|
psTraceCrypto("Malformed thisUpdate CRL\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p += timelen; /* Skip it */
|
|
/* nextUpdateTIME - Optional */
|
|
if ((end - p) < 1 || ((*p == ASN_UTCTIME) || (*p == ASN_GENERALIZEDTIME))) {
|
|
p++;
|
|
if (getAsnLength(&p, (uint32)(end - p), &timelen) < 0 ||
|
|
(uint32)(end - p) < timelen) {
|
|
psTraceCrypto("Malformed nextUpdateTIME CRL\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p += timelen; /* Skip it */
|
|
}
|
|
/*
|
|
revokedCertificates SEQUENCE OF SEQUENCE {
|
|
userCertificate CertificateSerialNumber,
|
|
revocationDate Time,
|
|
crlEntryExtensions Extensions OPTIONAL
|
|
-- if present, shall be v2
|
|
} OPTIONAL,
|
|
*/
|
|
if (getAsnSequence(&p, (uint32)(end - p), &glen) < 0) {
|
|
psTraceCrypto("Initial revokedCertificates error in psX509ParseCrl\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
|
|
if (CA->revoked) {
|
|
/* Append or refresh */
|
|
if (append == 0) {
|
|
/* refresh */
|
|
x509FreeRevoked(&CA->revoked);
|
|
CA->revoked = curr = psMalloc(pool, sizeof(x509revoked_t));
|
|
if (curr == NULL) {
|
|
return PS_MEM_FAIL;
|
|
}
|
|
} else {
|
|
/* append. not looking for duplicates */
|
|
curr = psMalloc(pool, sizeof(x509revoked_t));
|
|
if (curr == NULL) {
|
|
return PS_MEM_FAIL;
|
|
}
|
|
curr->pool = pool;
|
|
next = CA->revoked;
|
|
while (next->next != NULL) {
|
|
next = next->next;
|
|
}
|
|
next->next = curr;
|
|
}
|
|
} else {
|
|
CA->revoked = curr = psMalloc(pool, sizeof(x509revoked_t));
|
|
if (curr == NULL) {
|
|
return PS_MEM_FAIL;
|
|
}
|
|
}
|
|
memset(curr, 0x0, sizeof(x509revoked_t));
|
|
curr->pool = pool;
|
|
|
|
|
|
while (glen > 0) {
|
|
revStart = p;
|
|
if (getAsnSequence(&p, (uint32)(end - p), &ilen) < 0) {
|
|
psTraceCrypto("Deep revokedCertificates error in psX509ParseCrl\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
start = p;
|
|
if ((rc = getSerialNum(pool, &p, (uint32)(end - p), &curr->serial,
|
|
&curr->serialLen)) < 0) {
|
|
psTraceCrypto("ASN serial number parse error\n");
|
|
return rc;
|
|
}
|
|
/* skipping time and extensions */
|
|
p += ilen - (uint32)(p - start);
|
|
if (glen < (uint32)(p - revStart)) {
|
|
psTraceCrypto("Deeper revokedCertificates err in psX509ParseCrl\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
glen -= (uint32)(p - revStart);
|
|
|
|
// psTraceBytes("revoked", curr->serial, curr->serialLen);
|
|
if (glen > 0) {
|
|
if ((next = psMalloc(pool, sizeof(x509revoked_t))) == NULL) {
|
|
x509FreeRevoked(&CA->revoked);
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memset(next, 0x0, sizeof(x509revoked_t));
|
|
next->pool = pool;
|
|
curr->next = next;
|
|
curr = next;
|
|
}
|
|
}
|
|
memset(&ext, 0x0, sizeof(x509v3extensions_t));
|
|
if (getExplicitExtensions(pool, &p, (uint32)(end - p), 0, &ext, 0) < 0) {
|
|
psTraceCrypto("Extension parse error in psX509ParseCrl\n");
|
|
x509FreeRevoked(&CA->revoked);
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
x509FreeExtensions(&ext);
|
|
sigEnd = p;
|
|
|
|
if (getAsnAlgorithmIdentifier(&p, (int32)(end - p), &oi, &plen) < 0) {
|
|
x509FreeRevoked(&CA->revoked);
|
|
psTraceCrypto("Couldn't parse crl sig algorithm identifier\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
|
|
if ((rc = psX509GetSignature(pool, &p, (uint32)(end - p), &revStart, &ilen))
|
|
< 0) {
|
|
x509FreeRevoked(&CA->revoked);
|
|
psTraceCrypto("Couldn't parse signature\n");
|
|
return rc;
|
|
}
|
|
|
|
switch (oi) {
|
|
#ifdef ENABLE_MD5_SIGNED_CERTS
|
|
case OID_MD5_RSA_SIG:
|
|
sigLen = MD5_HASH_SIZE;
|
|
psMd5Init(&hashCtx);
|
|
psMd5Update(&hashCtx, sigStart, (uint32)(sigEnd - sigStart));
|
|
psMd5Final(&hashCtx, sigHash);
|
|
break;
|
|
#endif
|
|
#ifdef ENABLE_SHA1_SIGNED_CERTS
|
|
case OID_SHA1_RSA_SIG:
|
|
sigLen = SHA1_HASH_SIZE;
|
|
psSha1Init(&hashCtx);
|
|
psSha1Update(&hashCtx, sigStart, (uint32)(sigEnd - sigStart));
|
|
psSha1Final(&hashCtx, sigHash);
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA256
|
|
case OID_SHA256_RSA_SIG:
|
|
sigLen = SHA256_HASH_SIZE;
|
|
psSha256Init(&hashCtx);
|
|
psSha256Update(&hashCtx, sigStart, (uint32)(sigEnd - sigStart));
|
|
psSha256Final(&hashCtx, sigHash);
|
|
break;
|
|
#endif
|
|
default:
|
|
psTraceCrypto("Need more signatuare alg support for CRL\n");
|
|
x509FreeRevoked(&CA->revoked);
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
|
|
|
|
if ((rc = pubRsaDecryptSignedElement(pkiPool, &CA->publicKey.key.rsa,
|
|
revStart, ilen, sigOut, sigLen, NULL)) < 0) {
|
|
x509FreeRevoked(&CA->revoked);
|
|
psTraceCrypto("Unable to RSA decrypt CRL signature\n");
|
|
return rc;
|
|
}
|
|
|
|
if (memcmp(sigHash, sigOut, sigLen) != 0) {
|
|
x509FreeRevoked(&CA->revoked);
|
|
psTraceCrypto("Unable to verify CRL signature\n");
|
|
return PS_CERT_AUTH_FAIL_SIG;
|
|
}
|
|
|
|
return PS_SUCCESS;
|
|
}
|
|
#endif /* USE_CRL */
|
|
#endif /* USE_CERT_PARSE */
|
|
|
|
|
|
#ifdef USE_OCSP
|
|
static int32_t parseSingleResponse(uint32_t len, const unsigned char **cp,
|
|
const unsigned char *end, mOCSPSingleResponse_t *res)
|
|
{
|
|
const unsigned char *p;
|
|
uint16_t glen, plen;
|
|
int32_t oi;
|
|
|
|
p = *cp;
|
|
|
|
/* SingleResponse ::= SEQUENCE {
|
|
certID CertID,
|
|
certStatus CertStatus,
|
|
thisUpdate GeneralizedTime,
|
|
nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
|
|
singleExtensions [1] EXPLICIT Extensions OPTIONAL }
|
|
*/
|
|
if (getAsnSequence(&p, (int32)(end - p), &glen) < 0) {
|
|
psTraceCrypto("Initial parseSingleResponse parse failure\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/* CertID ::= SEQUENCE {
|
|
hashAlgorithm AlgorithmIdentifier
|
|
{DIGEST-ALGORITHM, {...}},
|
|
issuerNameHash OCTET STRING, -- Hash of issuer's DN
|
|
issuerKeyHash OCTET STRING, -- Hash of issuer's public key
|
|
serialNumber CertificateSerialNumber }
|
|
*/
|
|
if (getAsnSequence(&p, (int32)(end - p), &glen) < 0) {
|
|
psTraceCrypto("Initial parseSingleResponse parse failure\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (getAsnAlgorithmIdentifier(&p, (int32)(end - p), &oi, &plen) < 0){
|
|
return PS_FAILURE;
|
|
}
|
|
psAssert(plen == 0);
|
|
res->certIdHashAlg = oi;
|
|
|
|
if ((*p++ != ASN_OCTET_STRING) ||
|
|
getAsnLength(&p, (int32)(end - p), &glen) < 0 ||
|
|
(uint32)(end - p) < glen) {
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
res->certIdNameHash = p;
|
|
p += glen;
|
|
|
|
if ((*p++ != ASN_OCTET_STRING) ||
|
|
getAsnLength(&p, (int32)(end - p), &glen) < 0 ||
|
|
(uint32)(end - p) < glen) {
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
res->certIdKeyHash = p;
|
|
p += glen;
|
|
|
|
/* serialNumber CertificateSerialNumber
|
|
|
|
CertificateSerialNumber ::= INTEGER
|
|
*/
|
|
if ((*p != (ASN_CONTEXT_SPECIFIC | ASN_PRIMITIVE | 2)) &&
|
|
(*p != ASN_INTEGER)) {
|
|
psTraceCrypto("X.509 getSerialNum failed on first bytes\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p++;
|
|
|
|
if (getAsnLength(&p, (int32)(end - p), &glen) < 0 ||
|
|
(uint32)(end - p) < glen) {
|
|
psTraceCrypto("ASN getSerialNum failed\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
res->certIdSerialLen = glen;
|
|
res->certIdSerial = p;
|
|
p += glen;
|
|
|
|
/* CertStatus ::= CHOICE {
|
|
good [0] IMPLICIT NULL,
|
|
revoked [1] IMPLICIT RevokedInfo,
|
|
unknown [2] IMPLICIT UnknownInfo }
|
|
*/
|
|
if (*p == (ASN_CONTEXT_SPECIFIC | ASN_PRIMITIVE | 0)) {
|
|
res->certStatus = 0;
|
|
p += 2;
|
|
} else if (*p == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 1)) {
|
|
res->certStatus = 1;
|
|
psTraceCrypto("OCSP CertStatus is revoked. Skipping details\n");
|
|
/* RevokedInfo ::= SEQUENCE {
|
|
revocationTime GeneralizedTime,
|
|
revocationReason [0] EXPLICIT CRLReason OPTIONAL }
|
|
*/
|
|
if (getAsnSequence(&p, (int32)(end - p), &glen) < 0) {
|
|
psTraceCrypto("Initial parseSingleResponse parse failure\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/* skip it */
|
|
p += glen;
|
|
} else if (*p == (ASN_CONTEXT_SPECIFIC | ASN_PRIMITIVE | 2)) {
|
|
res->certStatus = 2;
|
|
p += 2; /* TOOD: Untested parse. Might be CONSTRUCTED encoding */
|
|
/* UnknownInfo ::= NULL */
|
|
} else {
|
|
psTraceCrypto("OCSP CertStatus parse fail\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
|
|
/* thisUpdate GeneralizedTime, */
|
|
if ((end - p) < 1 || (*p != ASN_GENERALIZEDTIME)) {
|
|
psTraceCrypto("Malformed thisUpdate OCSP\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p++;
|
|
if (getAsnLength(&p, (uint32)(end - p), &glen) < 0 ||
|
|
(uint32)(end - p) < glen) {
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
res->thisUpdateLen = glen;
|
|
res->thisUpdate = p;
|
|
p += glen;
|
|
|
|
/* nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL, */
|
|
|
|
if (*p == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 0)) {
|
|
p++;
|
|
if (getAsnLength(&p, (uint32)(end - p), &glen) < 0 ||
|
|
(uint32)(end - p) < glen) {
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p += glen; /* SKIPPING */
|
|
}
|
|
|
|
/* singleExtensions [1] EXPLICIT Extensions OPTIONAL */
|
|
if (*p == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 1)) {
|
|
p++;
|
|
if (getAsnLength(&p, (uint32)(end - p), &glen) < 0 ||
|
|
(uint32)(end - p) < glen) {
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/* TODO */
|
|
p += glen; /* SKIPPING */
|
|
}
|
|
|
|
*cp = (unsigned char*)p;
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
static int32_t parseBasicOCSPResponse(psPool_t *pool, uint32_t len,
|
|
const unsigned char **cp, unsigned char *end,
|
|
mOCSPResponse_t *res)
|
|
{
|
|
const unsigned char *p, *seqend, *startRes, *endRes;
|
|
mOCSPSingleResponse_t *singleResponse;
|
|
psSha1_t sha;
|
|
#ifdef USE_SHA256
|
|
psSha256_t sha2;
|
|
#endif
|
|
#ifdef USE_SHA384
|
|
psSha384_t sha3;
|
|
#endif
|
|
uint16_t glen, plen;
|
|
uint32_t blen;
|
|
int32_t version, oid;
|
|
|
|
/* id-pkix-ocsp-basic
|
|
|
|
BasicOCSPResponse ::= SEQUENCE {
|
|
tbsResponseData ResponseData,
|
|
signatureAlgorithm AlgorithmIdentifier,
|
|
signature BIT STRING,
|
|
certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
|
|
*/
|
|
p = *cp;
|
|
|
|
if (getAsnSequence(&p, (uint32)(end - p), &glen) < 0) {
|
|
psTraceCrypto("Initial parse error in parseBasicOCSPResponse\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/*
|
|
ResponseData ::= SEQUENCE {
|
|
version [0] EXPLICIT Version DEFAULT v1,
|
|
responderID ResponderID,
|
|
producedAt GeneralizedTime,
|
|
responses SEQUENCE OF SingleResponse,
|
|
responseExtensions [1] EXPLICIT Extensions OPTIONAL }
|
|
*/
|
|
startRes = p; /* A response signature will be over ResponseData */
|
|
if (getAsnSequence(&p, (uint32)(end - p), &glen) < 0) {
|
|
psTraceCrypto("Early ResponseData parse error in parseOCSPResponse\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (getExplicitVersion(&p, (uint32)(end - p), 0, &version) < 0) {
|
|
psTraceCrypto("Version parse error in ResponseData\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (version != 0) {
|
|
psTraceIntCrypto("WARNING: Unknown OCSP ResponseData version %d\n",
|
|
version);
|
|
}
|
|
/*
|
|
ResponderID ::= CHOICE {
|
|
byName [1] Name,
|
|
byKey [2] KeyHash }
|
|
*/
|
|
if (*p == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 1)) {
|
|
psTraceCrypto("TODO: Unsupported byName ResponderID in ResponseData\n");
|
|
return PS_PARSE_FAIL;
|
|
} else if (*p == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 2)) {
|
|
p++;
|
|
if (getAsnLength32(&p, (uint32_t)(end - p), &blen, 0) < 0 ||
|
|
(uint32_t)(end - p) < blen) {
|
|
psTraceCrypto("Error parsing KeyHash in ResponseData\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/* KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key
|
|
-- (i.e., the SHA-1 hash of the value of the
|
|
-- BIT STRING subjectPublicKey [excluding
|
|
-- the tag, length, and number of unused
|
|
-- bits] in the responder's certificate) */
|
|
if ((*p++ != ASN_OCTET_STRING) ||
|
|
getAsnLength(&p, (int32)(end - p), &glen) < 0 ||
|
|
(uint32)(end - p) < glen) {
|
|
|
|
psTraceCrypto("Couldn't parse KeyHash in ResponseData\n");
|
|
return PS_FAILURE;
|
|
}
|
|
psAssert(glen == SHA1_HASH_SIZE);
|
|
res->responderKeyHash = p;
|
|
p += SHA1_HASH_SIZE;
|
|
} else {
|
|
psTraceCrypto("ResponderID parse error in ResponseData\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
|
|
/* producedAt GeneralizedTime, */
|
|
if ((end - p) < 1 || (*p != ASN_GENERALIZEDTIME)) {
|
|
psTraceCrypto("Malformed thisUpdate CRL\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p++;
|
|
if (getAsnLength(&p, (uint32)(end - p), &glen) < 0 ||
|
|
(uint32)(end - p) < glen) {
|
|
psTraceCrypto("Malformed producedAt in ResponseData\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
psAssert(glen <= 20); /* TODO: length hardcoded in structure */
|
|
res->timeProducedLen = glen;
|
|
res->timeProduced = p;
|
|
p += glen;
|
|
|
|
/* responses SEQUENCE OF SingleResponse, */
|
|
if (getAsnSequence(&p, (int32)(end - p), &glen) < 0) {
|
|
psTraceCrypto("Initial SingleResponse parse failure\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
|
|
seqend = p + glen;
|
|
|
|
plen = 0; /* for MAX_OCSP_RESPONSES control */
|
|
while (p < seqend) {
|
|
singleResponse = &res->singleResponse[plen];
|
|
if (parseSingleResponse(glen, &p, seqend, singleResponse) < 0) {
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
plen++;
|
|
if (p < seqend) {
|
|
/* Additional responses */
|
|
if (plen == MAX_OCSP_RESPONSES) {
|
|
psTraceCrypto("ERROR: Multiple OCSP SingleResponse items. ");
|
|
psTraceCrypto("Increase MAX_OCSP_RESPONSES to support\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* responseExtensions [1] EXPLICIT Extensions OPTIONAL } */
|
|
if (*p == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 1)) {
|
|
p++;
|
|
if (getAsnLength(&p, (uint32)(end - p), &glen) < 0 ||
|
|
(uint32)(end - p) < glen) {
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/* TODO: */
|
|
p += glen; /* SKIPPING */
|
|
}
|
|
endRes = p;
|
|
|
|
/* ResponseData DONE. On to signature:
|
|
|
|
signatureAlgorithm AlgorithmIdentifier
|
|
signature BIT STRING,
|
|
|
|
The value for signature SHALL be computed on the hash of the DER
|
|
encoding of ResponseData. The responder MAY include certificates in
|
|
the certs field of BasicOCSPResponse that help the OCSP client
|
|
verify the responder's signature. If no certificates are included,
|
|
then certs SHOULD be absent. */
|
|
if (getAsnAlgorithmIdentifier(&p, (uint32)(end - p), &oid, &plen) < 0) {
|
|
psTraceCrypto("Initial SingleResponse parse failure\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (plen > 0) {
|
|
psTraceCrypto("Algorithm parameters on ResponseData sigAlg\n");
|
|
p += plen;
|
|
}
|
|
res->sigAlg = oid;
|
|
|
|
switch (oid) {
|
|
/* OSCP requires SHA1 so no wrapper here */
|
|
case OID_SHA1_RSA_SIG:
|
|
#ifdef USE_ECC
|
|
case OID_SHA1_ECDSA_SIG:
|
|
#endif
|
|
res->hashLen = SHA1_HASH_SIZE;
|
|
psSha1Init(&sha);
|
|
psSha1Update(&sha, startRes, (int32)(endRes - startRes));
|
|
psSha1Final(&sha, res->hashResult);
|
|
break;
|
|
#ifdef USE_SHA256
|
|
case OID_SHA256_RSA_SIG:
|
|
#ifdef USE_ECC
|
|
case OID_SHA256_ECDSA_SIG:
|
|
#endif
|
|
res->hashLen = SHA256_HASH_SIZE;
|
|
psSha256Init(&sha2);
|
|
psSha256Update(&sha2, startRes, (int32)(endRes - startRes));
|
|
psSha256Final(&sha2, res->hashResult);
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA384
|
|
case OID_SHA384_RSA_SIG:
|
|
#ifdef USE_ECC
|
|
case OID_SHA384_ECDSA_SIG:
|
|
#endif
|
|
res->hashLen = SHA384_HASH_SIZE;
|
|
psSha384Init(&sha3);
|
|
psSha384Update(&sha3, startRes, (int32)(endRes - startRes));
|
|
psSha384Final(&sha3, res->hashResult);
|
|
break;
|
|
#endif
|
|
default:
|
|
psTraceCrypto("No support for sigAlg in OCSP ResponseData\n");
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
|
|
if (*p++ != ASN_BIT_STRING) {
|
|
psTraceCrypto("Error parsing signature in ResponseData\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (getAsnLength(&p, (int32)(end - p), &glen) < 0 ||
|
|
(uint32)(end - p) < glen) {
|
|
psTraceCrypto("Error parsing signature in ResponseData\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (*p++ != 0) {
|
|
psTraceCrypto("Error parsing ignore bits in ResponseData sig\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
glen--; /* ignore bits above */
|
|
res->sig = p;
|
|
res->sigLen = glen;
|
|
p += glen;
|
|
|
|
/* certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } */
|
|
if (end != p) {
|
|
/* The responder MAY include certificates in the certs field of
|
|
BasicOCSPResponse that help the OCSP client verify the responder's
|
|
signature. */
|
|
if (*p != (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 0)) {
|
|
psTraceCrypto("Unexpected Certificage encoding in OCSPResponse\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p++;
|
|
if (getAsnLength(&p, (uint32)(end - p), &glen) < 0 ||
|
|
(uint32)(end - p) < glen) {
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/* If here, this is the cert that issued the OCSPResponse. Will
|
|
authenticate during validateOCSPResponse */
|
|
if (getAsnSequence(&p, (uint32)(end - p), &glen) < 0) {
|
|
psTraceCrypto("\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
psAssert(glen == (end - p));
|
|
/* reusing oid. will handle multiple certs if needed */
|
|
oid = psX509ParseCert(pool, p, glen, &res->OCSPResponseCert, 0);
|
|
if (oid < 0) {
|
|
psX509FreeCert(res->OCSPResponseCert);
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
p += oid;
|
|
}
|
|
psAssert(p == end);
|
|
|
|
*cp = (unsigned char*)p;
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
int32_t parseOCSPResponse(psPool_t *pool, int32_t len, unsigned char **cp,
|
|
unsigned char *end, mOCSPResponse_t *response)
|
|
{
|
|
const unsigned char *p;
|
|
int32_t status, oi;
|
|
uint16_t glen;
|
|
uint32_t blen;
|
|
|
|
p = *cp;
|
|
//psTraceBytes("OCSPResponse", p, len);
|
|
/*
|
|
OCSPResponse ::= SEQUENCE {
|
|
responseStatus OCSPResponseStatus,
|
|
responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
|
|
*/
|
|
if (getAsnSequence(&p, (uint32)(end - p), &glen) < 0) {
|
|
psTraceCrypto("Initial parse error in parseOCSPResponse\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (getAsnEnumerated(&p, (uint32)(end - p), &status) < 0) {
|
|
psTraceCrypto("Enum parse error in parseOCSPResponse\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
/*
|
|
OCSPResponseStatus ::= ENUMERATED {
|
|
successful (0), -- Response has valid confirmations
|
|
malformedRequest (1), -- Illegal confirmation request
|
|
internalError (2), -- Internal error in issuer
|
|
tryLater (3), -- Try again later
|
|
-- (4) is not used
|
|
sigRequired (5), -- Must sign the request
|
|
unauthorized (6) -- Request unauthorized
|
|
}
|
|
*/
|
|
if (status != 0) {
|
|
/* Something other than success. List right above here */
|
|
psTraceCrypto("OCSPResponse contains no valid confirmations\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
|
|
/* responseBytes [0] EXPLICIT ResponseBytes OPTIONAL, */
|
|
if (*p == (ASN_CONSTRUCTED | ASN_CONTEXT_SPECIFIC | 0)) {
|
|
p++;
|
|
if (getAsnLength32(&p, (uint32_t)(end - p), &blen, 0) < 0 ||
|
|
(uint32_t)(end - p) < blen) {
|
|
psTraceCrypto("Error parsing UserKeyingMaterial\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
|
|
/* ResponseBytes ::= SEQUENCE {
|
|
responseType OBJECT IDENTIFIER,
|
|
response OCTET STRING }
|
|
*/
|
|
if (getAsnSequence(&p, (uint32)(end - p), &glen) < 0) {
|
|
psTraceCrypto("ResponseBytes parse error in parseOCSPResponse\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (getAsnOID(&p, (uint32)(end - p), &oi, 1, &glen) < 0) {
|
|
psTraceCrypto("responseType parse error in parseOCSPResponse\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if ((*p++ != ASN_OCTET_STRING) ||
|
|
getAsnLength32(&p, (int32)(end - p), &blen, 0) < 0 ||
|
|
(uint32)(end - p) < blen) {
|
|
|
|
psTraceCrypto("Couldn't parse response in parseOCSPResponse\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
if (oi == 117) {
|
|
/* id-pkix-ocsp-basic
|
|
|
|
BasicOCSPResponse ::= SEQUENCE {
|
|
tbsResponseData ResponseData,
|
|
signatureAlgorithm AlgorithmIdentifier,
|
|
signature BIT STRING,
|
|
certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
|
|
*/
|
|
if (parseBasicOCSPResponse(pool, blen, &p, end, response) < 0) {
|
|
psTraceCrypto("parseBasicOCSPResponse failure\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
} else if (oi == 116) {
|
|
/* id-pkix-ocsp */
|
|
psTraceCrypto("unsupported id-pkix-ocsp in parseOCSPResponse\n");
|
|
return PS_PARSE_FAIL;
|
|
} else {
|
|
psTraceCrypto("unsupported responseType in parseOCSPResponse\n");
|
|
return PS_PARSE_FAIL;
|
|
}
|
|
|
|
}
|
|
psAssert(end == p);
|
|
*cp = (unsigned char*)p;
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
/* Diff the current time against the OCSP timestamp and confirm it's not
|
|
longer than the user is willing to trust */
|
|
static int16_t checkOCSPtimestamp(mOCSPResponse_t *response)
|
|
{
|
|
/* GeneralizedTime values MUST be
|
|
expressed in Greenwich Mean Time (Zulu) and MUST include seconds
|
|
(i.e., times are YYYYMMDDHHMMSSZ), even where the number of seconds
|
|
is zero. GeneralizedTime values MUST NOT include fractional seconds.
|
|
*/
|
|
#ifdef POSIX
|
|
struct tm mytime;
|
|
time_t currrawtime, myrawtime;
|
|
double diffsecs;
|
|
const unsigned char *c;
|
|
|
|
/* timeProduced here is the producedAt of the response which is the
|
|
time the signature was generated */
|
|
c = response->timeProduced;
|
|
|
|
/* Goal is to create time_t from this GeneralizedTime to perform a
|
|
diff against current time (also a time_t) */
|
|
memset(&mytime, 0x0, sizeof(struct tm));
|
|
mytime.tm_year = 1000 * (c[0] - '0') + 100 * (c[1] - '0') +
|
|
10 * (c[2] - '0') + (c[3] - '0'); c += 4;
|
|
mytime.tm_year -= 1900;
|
|
mytime.tm_mon = 10 * (c[0] - '0') + (c[1] - '0'); c += 2;
|
|
mytime.tm_mon--; /* month is 0 based for some reason */
|
|
mytime.tm_mday = 10 * (c[0] - '0') + (c[1] - '0'); c += 2;
|
|
mytime.tm_hour = 10 * (c[0] - '0') + (c[1] - '0'); c += 2;
|
|
mytime.tm_min = 10 * (c[0] - '0') + (c[1] - '0'); c += 2;
|
|
mytime.tm_sec = 10 * (c[0] - '0') + (c[1] - '0');
|
|
|
|
/* If there is no timegm on the platform, maybe there will be a mktime
|
|
which will interpret 'mytime' as local time rather than GMT. This
|
|
change will cause a margin of error in the difftime below if
|
|
the current time is not adjusted for GMT */
|
|
//myrawtime = mktime(&mytime); /* margin of error will exist */
|
|
myrawtime = timegm(&mytime); /* Adds missing members of mytime */
|
|
|
|
/* current time */
|
|
time(&currrawtime);
|
|
|
|
diffsecs = difftime(currrawtime, myrawtime);
|
|
|
|
if (diffsecs < 0 || diffsecs > OCSP_VALID_TIME_WINDOW) {
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
return PS_SUCCESS;
|
|
|
|
#else
|
|
/* Warn if we are skipping the date validation checks. */
|
|
#ifdef WIN32
|
|
#pragma message("OCSP DATE VALIDITY NOT SUPPORTED ON THIS PLATFORM.")
|
|
#else
|
|
#warning "OCSP DATE VALIDITY NOT SUPPORTED ON THIS PLATFORM."
|
|
#endif
|
|
return PS_SUCCESS;
|
|
#endif /* POSIX */
|
|
}
|
|
|
|
int32_t validateOCSPResponse(psPool_t *pool, psX509Cert_t *trustedOCSP,
|
|
psX509Cert_t *srvCerts, mOCSPResponse_t *response)
|
|
{
|
|
psX509Cert_t *curr, *issuer, *subject, *ocspResIssuer;
|
|
mOCSPSingleResponse_t *subjectResponse;
|
|
unsigned char sigOut[MAX_HASH_SIZE];
|
|
int32 sigOutLen, sigType, index;
|
|
psPool_t *pkiPool = NULL;
|
|
|
|
/* Find the OCSP cert that signed the response. First place to look is
|
|
within the OCSPResponse itself */
|
|
issuer = NULL;
|
|
if (response->OCSPResponseCert) {
|
|
/* If there is a cert here it is something that has to be authenticated.
|
|
We will either leave this case with a successful auth or failure */
|
|
curr = response->OCSPResponseCert;
|
|
while (curr != NULL) {
|
|
/* The outer responderKeyHash should be matching one of the certs
|
|
that was attached to the OCSPResonse itself */
|
|
if (memcmp(response->responderKeyHash, curr->sha1KeyHash, 20) == 0){
|
|
/* Found it... but now we have to authenticate it against
|
|
our known list of CAs. issuer in the context of this
|
|
function is the OCSPResponse issuer but here we are looking
|
|
for the CA of THAT cert so it's 'subject' in this area */
|
|
subject = curr;
|
|
ocspResIssuer = trustedOCSP; /* preloaded sslKeys->CA */
|
|
while (ocspResIssuer) {
|
|
if (memcmp(ocspResIssuer->subject.hash,
|
|
subject->issuer.hash, 20) == 0) {
|
|
|
|
if (psX509AuthenticateCert(pool, subject, ocspResIssuer,
|
|
&ocspResIssuer, NULL, NULL) == 0) {
|
|
/* OK, we held the CA that issued the OCSPResponse
|
|
so we'll now trust that cert that was provided
|
|
in the OCSPResponse */
|
|
ocspResIssuer = NULL;
|
|
issuer = subject;
|
|
} else {
|
|
/* Auth failure */
|
|
psTraceCrypto("Attached OCSP cert didn't auth\n");
|
|
return PS_FAILURE;
|
|
}
|
|
} else {
|
|
ocspResIssuer = ocspResIssuer->next;
|
|
}
|
|
}
|
|
curr = NULL;
|
|
} else {
|
|
curr = curr->next;
|
|
}
|
|
}
|
|
if (issuer == NULL) {
|
|
psTraceCrypto("Found no CA to authenticate attached OCSP cert\n");
|
|
return PS_FAILURE; /* no preloaded CA to auth cert in response */
|
|
}
|
|
}
|
|
|
|
/* Issuer will be NULL if there was no certificate attached to the
|
|
OCSP response. Now look to the user loaded CA files */
|
|
if (issuer == NULL) {
|
|
curr = trustedOCSP;
|
|
while (curr != NULL) {
|
|
/* Currently looking for the subjectKey extension to match the
|
|
public key hash from the response */
|
|
if (memcmp(response->responderKeyHash, curr->sha1KeyHash, 20) == 0){
|
|
issuer = curr;
|
|
curr = NULL;
|
|
} else {
|
|
curr = curr->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* It is possible a certificate embedded in the server certificate
|
|
chain was itself the OCSP responder */
|
|
if (issuer == NULL) {
|
|
/* Don't look at the first cert in the chain because that is the
|
|
one we are trying to find the OCSP responder public key for */
|
|
curr = srvCerts->next;
|
|
while (curr != NULL) {
|
|
/* Currently looking for the subjectKey extension to match the
|
|
public key hash from the response */
|
|
if (memcmp(response->responderKeyHash, curr->sha1KeyHash, 20) == 0){
|
|
issuer = curr;
|
|
curr = NULL;
|
|
} else {
|
|
curr = curr->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (issuer == NULL) {
|
|
psTraceCrypto("Unable to locate OCSP responder CA for validation\n");
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
/* Now check to see that the response is vouching for the subject cert
|
|
that we are interested in. The subject will always be the first
|
|
cert in the server CERTIFICATE chain */
|
|
subject = srvCerts;
|
|
|
|
/* Now look to match this cert within the singleResponse members.
|
|
|
|
There are three components to a CertID that should be used to validate
|
|
we are looking at the correct OCSP response for the subjecct cert.
|
|
|
|
It appears the only "unique" portion of our subject cert that
|
|
went into the signature of this response is the serial number.
|
|
The "issuer" information of the subject cert also went into the
|
|
signature but that isn't exactly unique. Seems a bit odd that the
|
|
combo of the issuer and the serial number are the only thing that tie
|
|
this subject cert back to the response but serial numbers are the basis
|
|
for CRL as well so it must be good enough */
|
|
index = 0;
|
|
while (index < MAX_OCSP_RESPONSES) {
|
|
subjectResponse = &response->singleResponse[index];
|
|
if ((subject->serialNumberLen == subjectResponse->certIdSerialLen) &&
|
|
(memcmp(subject->serialNumber, subjectResponse->certIdSerial,
|
|
subject->serialNumberLen) == 0)) {
|
|
break; /* got it */
|
|
}
|
|
index++;
|
|
}
|
|
if (index == MAX_OCSP_RESPONSES) {
|
|
psTraceCrypto("Unable to locate our subject cert in OCSP response\n");
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
/* If the response is reporting that this cert is bad right in the status,
|
|
just return that immediately and stop the connection */
|
|
if (subjectResponse->certStatus != 0) {
|
|
psTraceCrypto("ERROR: OCSP info: server cert is revoked!\n");
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
/* Is the response within the acceptable time window */
|
|
if (checkOCSPtimestamp(response) < 0) {
|
|
psTraceCrypto("ERROR: OCSP response older than threshold\n");
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
/* Issuer portion of the validation - the subject cert issuer key and name
|
|
hash should match what the subjectResponse reports
|
|
|
|
POSSIBLE PROBLEMS: Only supporting a SHA1 hash here. The MatrixSSL
|
|
parser will only use SHA1 for the DN and key hash. Just warning on
|
|
this for now. The signature validation will catch any key mismatch */
|
|
if (subjectResponse->certIdHashAlg != OID_SHA1_ALG) {
|
|
psTraceCrypto("WARNING: Non-SHA1 OCSP CertID. Issuer check bypassed\n");
|
|
} else {
|
|
if (memcmp(subjectResponse->certIdKeyHash, issuer->sha1KeyHash, 20)
|
|
!= 0) {
|
|
psTraceCrypto("Failed OCP issuer key hash validation\n");
|
|
return PS_FAILURE;
|
|
}
|
|
/* Either subject->issuer or issuer->subject would work for testing */
|
|
if (memcmp(subjectResponse->certIdNameHash, issuer->subject.hash, 20)
|
|
!= 0){
|
|
psTraceCrypto("Failed OCP issuer name hash validation\n");
|
|
return PS_FAILURE;
|
|
}
|
|
}
|
|
|
|
/* Finally do the sig validation */
|
|
switch (response->sigAlg) {
|
|
#ifdef USE_SHA256
|
|
case OID_SHA256_RSA_SIG:
|
|
sigOutLen = SHA256_HASH_SIZE;
|
|
sigType = PS_RSA;
|
|
break;
|
|
case OID_SHA256_ECDSA_SIG:
|
|
sigOutLen = SHA256_HASH_SIZE;
|
|
sigType = PS_ECC;
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA384
|
|
case OID_SHA384_RSA_SIG:
|
|
sigOutLen = SHA384_HASH_SIZE;
|
|
sigType = PS_RSA;
|
|
break;
|
|
case OID_SHA384_ECDSA_SIG:
|
|
sigOutLen = SHA384_HASH_SIZE;
|
|
sigType = PS_ECC;
|
|
break;
|
|
#endif
|
|
case OID_SHA1_RSA_SIG:
|
|
sigOutLen = SHA1_HASH_SIZE;
|
|
sigType = PS_RSA;
|
|
break;
|
|
case OID_SHA1_ECDSA_SIG:
|
|
sigOutLen = SHA1_HASH_SIZE;
|
|
sigType = PS_ECC;
|
|
break;
|
|
default:
|
|
/* Should have been caught in parse phase */
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
|
|
/* Finally test the signature */
|
|
if (sigType == PS_RSA) {
|
|
if (issuer->publicKey.type != PS_RSA) {
|
|
return PS_FAILURE;
|
|
}
|
|
if (pubRsaDecryptSignedElement(pkiPool, &issuer->publicKey.key.rsa,
|
|
(unsigned char*)response->sig, response->sigLen, sigOut,
|
|
sigOutLen, NULL) < 0) {
|
|
psTraceCrypto("Unable to decode signature in validateOCSPResponse\n");
|
|
return PS_FAILURE;
|
|
}
|
|
if (memcmp(response->hashResult, sigOut, sigOutLen) != 0) {
|
|
psTraceCrypto("OCSP RSA signature validation failed\n");
|
|
return PS_FAILURE;
|
|
}
|
|
}
|
|
#ifdef USE_ECC
|
|
else {
|
|
if (issuer->publicKey.type != PS_ECC) {
|
|
return PS_FAILURE;
|
|
}
|
|
/* ECDSA signature */
|
|
index = 0;
|
|
if (psEccDsaVerify(pkiPool, &issuer->publicKey.key.ecc,
|
|
response->hashResult, sigOutLen, (unsigned char*)response->sig,
|
|
response->sigLen, &index, NULL) < 0) {
|
|
psTraceCrypto("ECC OCSP sig validation");
|
|
return PS_FAILURE;
|
|
}
|
|
if (index != 1) {
|
|
psTraceCrypto("OCSP ECDSA signature validation failed\n");
|
|
return PS_FAILURE;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Was able to successfully confirm OCSP signature for our subject */
|
|
return PS_SUCCESS;
|
|
}
|
|
#endif /* USE_OCSP */
|
|
|
|
#endif /* USE_X509 */
|
|
/******************************************************************************/
|
|
|
|
|