diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /security/nss/lib/certdb | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/nss/lib/certdb')
23 files changed, 17747 insertions, 0 deletions
diff --git a/security/nss/lib/certdb/Makefile b/security/nss/lib/certdb/Makefile new file mode 100644 index 0000000000..bb88772853 --- /dev/null +++ b/security/nss/lib/certdb/Makefile @@ -0,0 +1,45 @@ +#! gmake +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +####################################################################### +# (1) Include initial platform-independent assignments (MANDATORY). # +####################################################################### + +include manifest.mn + +####################################################################### +# (2) Include "global" configuration information. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/config.mk + +####################################################################### +# (3) Include "component" configuration information. (OPTIONAL) # +####################################################################### + + + +####################################################################### +# (4) Include "local" platform-dependent assignments (OPTIONAL). # +####################################################################### + +####################################################################### +# (5) Execute "global" rules. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/rules.mk + +####################################################################### +# (6) Execute "component" rules. (OPTIONAL) # +####################################################################### + + + +####################################################################### +# (7) Execute "local" rules. (OPTIONAL). # +####################################################################### + + diff --git a/security/nss/lib/certdb/alg1485.c b/security/nss/lib/certdb/alg1485.c new file mode 100644 index 0000000000..0cf9602f18 --- /dev/null +++ b/security/nss/lib/certdb/alg1485.c @@ -0,0 +1,1590 @@ +/* alg1485.c - implementation of RFCs 1485, 1779 and 2253. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "prprf.h" +#include "cert.h" +#include "certi.h" +#include "xconst.h" +#include "genname.h" +#include "secitem.h" +#include "secerr.h" + +typedef struct NameToKindStr { + const char* name; + unsigned int maxLen; /* max bytes in UTF8 encoded string value */ + SECOidTag kind; + int valueType; +} NameToKind; + +/* local type for directory string--could be printable_string or utf8 */ +#define SEC_ASN1_DS SEC_ASN1_HIGH_TAG_NUMBER + +/* clang-format off */ + +/* Add new entries to this table, and maybe to function ParseRFC1485AVA */ +static const NameToKind name2kinds[] = { +/* IANA registered type names + * (See: http://www.iana.org/assignments/ldap-parameters) + */ +/* RFC 3280, 4630 MUST SUPPORT */ + { "CN", 640, SEC_OID_AVA_COMMON_NAME, SEC_ASN1_DS}, + { "ST", 128, SEC_OID_AVA_STATE_OR_PROVINCE, + SEC_ASN1_DS}, + { "O", 128, SEC_OID_AVA_ORGANIZATION_NAME, + SEC_ASN1_DS}, + { "OU", 128, SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME, + SEC_ASN1_DS}, + { "dnQualifier", 32767, SEC_OID_AVA_DN_QUALIFIER, SEC_ASN1_PRINTABLE_STRING}, + { "C", 2, SEC_OID_AVA_COUNTRY_NAME, SEC_ASN1_PRINTABLE_STRING}, + { "serialNumber", 64, SEC_OID_AVA_SERIAL_NUMBER,SEC_ASN1_PRINTABLE_STRING}, + +/* RFC 3280, 4630 SHOULD SUPPORT */ + { "L", 128, SEC_OID_AVA_LOCALITY, SEC_ASN1_DS}, + { "title", 64, SEC_OID_AVA_TITLE, SEC_ASN1_DS}, + { "SN", 64, SEC_OID_AVA_SURNAME, SEC_ASN1_DS}, + { "givenName", 64, SEC_OID_AVA_GIVEN_NAME, SEC_ASN1_DS}, + { "initials", 64, SEC_OID_AVA_INITIALS, SEC_ASN1_DS}, + { "generationQualifier", + 64, SEC_OID_AVA_GENERATION_QUALIFIER, + SEC_ASN1_DS}, +/* RFC 3280, 4630 MAY SUPPORT */ + { "DC", 128, SEC_OID_AVA_DC, SEC_ASN1_IA5_STRING}, + { "MAIL", 256, SEC_OID_RFC1274_MAIL, SEC_ASN1_IA5_STRING}, + { "UID", 256, SEC_OID_RFC1274_UID, SEC_ASN1_DS}, + +/* ------------------ "strict" boundary --------------------------------- + * In strict mode, cert_NameToAscii does not encode any of the attributes + * below this line. The first SECOidTag below this line must be used to + * conditionally define the "endKind" in function AppendAVA() below. + * Most new attribute names should be added below this line. + * Maybe this line should be up higher? Say, after the 3280 MUSTs and + * before the 3280 SHOULDs? + */ + +/* values from draft-ietf-ldapbis-user-schema-05 (not in RFC 3280) */ + { "postalAddress", 128, SEC_OID_AVA_POSTAL_ADDRESS, SEC_ASN1_DS}, + { "postalCode", 40, SEC_OID_AVA_POSTAL_CODE, SEC_ASN1_DS}, + { "postOfficeBox", 40, SEC_OID_AVA_POST_OFFICE_BOX,SEC_ASN1_DS}, + { "houseIdentifier",64, SEC_OID_AVA_HOUSE_IDENTIFIER,SEC_ASN1_DS}, +/* end of IANA registered type names */ + +/* legacy keywords */ + { "E", 128, SEC_OID_PKCS9_EMAIL_ADDRESS,SEC_ASN1_IA5_STRING}, + { "STREET", 128, SEC_OID_AVA_STREET_ADDRESS, SEC_ASN1_DS}, + { "pseudonym", 64, SEC_OID_AVA_PSEUDONYM, SEC_ASN1_DS}, + +/* values defined by the CAB Forum for EV */ + { "incorporationLocality", 128, SEC_OID_EV_INCORPORATION_LOCALITY, + SEC_ASN1_DS}, + { "incorporationState", 128, SEC_OID_EV_INCORPORATION_STATE, + SEC_ASN1_DS}, + { "incorporationCountry", 2, SEC_OID_EV_INCORPORATION_COUNTRY, + SEC_ASN1_PRINTABLE_STRING}, + { "businessCategory", 64, SEC_OID_BUSINESS_CATEGORY, SEC_ASN1_DS}, + +/* values defined in X.520 */ + { "name", 64, SEC_OID_AVA_NAME, SEC_ASN1_DS}, + + { 0, 256, SEC_OID_UNKNOWN, 0}, +}; + +/* Table facilitates conversion of ASCII hex to binary. */ +static const PRInt16 x2b[256] = { +/* #0x */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +/* #1x */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +/* #2x */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +/* #3x */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, +/* #4x */ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, +/* #5x */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +/* #6x */ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, +/* #7x */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +/* #8x */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +/* #9x */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +/* #ax */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +/* #bx */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +/* #cx */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +/* #dx */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +/* #ex */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +/* #fx */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +#define IS_HEX(c) (x2b[(PRUint8)(c)] >= 0) + +#define C_DOUBLE_QUOTE '\042' + +#define C_BACKSLASH '\134' + +#define C_EQUAL '=' + +#define OPTIONAL_SPACE(c) \ + (((c) == ' ') || ((c) == '\r') || ((c) == '\n')) + +#define SPECIAL_CHAR(c) \ + (((c) == ',') || ((c) == '=') || ((c) == C_DOUBLE_QUOTE) || \ + ((c) == '\r') || ((c) == '\n') || ((c) == '+') || \ + ((c) == '<') || ((c) == '>') || ((c) == '#') || \ + ((c) == ';') || ((c) == C_BACKSLASH)) + + +#define IS_PRINTABLE(c) \ + ((((c) >= 'a') && ((c) <= 'z')) || \ + (((c) >= 'A') && ((c) <= 'Z')) || \ + (((c) >= '0') && ((c) <= '9')) || \ + ((c) == ' ') || \ + ((c) == '\'') || \ + ((c) == '\050') || /* ( */ \ + ((c) == '\051') || /* ) */ \ + (((c) >= '+') && ((c) <= '/')) || /* + , - . / */ \ + ((c) == ':') || \ + ((c) == '=') || \ + ((c) == '?')) + +/* clang-format on */ + +/* RFC 2253 says we must escape ",+\"\\<>;=" EXCEPT inside a quoted string. + * Inside a quoted string, we only need to escape " and \ + * We choose to quote strings containing any of those special characters, + * so we only need to escape " and \ + */ +#define NEEDS_ESCAPE(c) (c == C_DOUBLE_QUOTE || c == C_BACKSLASH) + +#define NEEDS_HEX_ESCAPE(c) ((PRUint8)c < 0x20 || c == 0x7f) + +int +cert_AVAOidTagToMaxLen(SECOidTag tag) +{ + const NameToKind* n2k = name2kinds; + + while (n2k->kind != tag && n2k->kind != SEC_OID_UNKNOWN) { + ++n2k; + } + return (n2k->kind != SEC_OID_UNKNOWN) ? n2k->maxLen : -1; +} + +static PRBool +IsPrintable(unsigned char* data, unsigned len) +{ + unsigned char ch, *end; + + end = data + len; + while (data < end) { + ch = *data++; + if (!IS_PRINTABLE(ch)) { + return PR_FALSE; + } + } + return PR_TRUE; +} + +static void +skipSpace(const char** pbp, const char* endptr) +{ + const char* bp = *pbp; + while (bp < endptr && OPTIONAL_SPACE(*bp)) { + bp++; + } + *pbp = bp; +} + +static SECStatus +scanTag(const char** pbp, const char* endptr, char* tagBuf, int tagBufSize) +{ + const char* bp; + char* tagBufp; + int taglen; + + PORT_Assert(tagBufSize > 0); + + /* skip optional leading space */ + skipSpace(pbp, endptr); + if (*pbp == endptr) { + /* nothing left */ + return SECFailure; + } + + /* fill tagBuf */ + taglen = 0; + bp = *pbp; + tagBufp = tagBuf; + while (bp < endptr && !OPTIONAL_SPACE(*bp) && (*bp != C_EQUAL)) { + if (++taglen >= tagBufSize) { + *pbp = bp; + return SECFailure; + } + *tagBufp++ = *bp++; + } + /* null-terminate tagBuf -- guaranteed at least one space left */ + *tagBufp++ = 0; + *pbp = bp; + + /* skip trailing spaces till we hit something - should be an equal sign */ + skipSpace(pbp, endptr); + if (*pbp == endptr) { + /* nothing left */ + return SECFailure; + } + if (**pbp != C_EQUAL) { + /* should be an equal sign */ + return SECFailure; + } + /* skip over the equal sign */ + (*pbp)++; + + return SECSuccess; +} + +/* Returns the number of bytes in the value. 0 means failure. */ +static int +scanVal(const char** pbp, const char* endptr, char* valBuf, int valBufSize) +{ + const char* bp; + char* valBufp; + int vallen = 0; + PRBool isQuoted; + + PORT_Assert(valBufSize > 0); + + /* skip optional leading space */ + skipSpace(pbp, endptr); + if (*pbp == endptr) { + /* nothing left */ + return 0; + } + + bp = *pbp; + + /* quoted? */ + if (*bp == C_DOUBLE_QUOTE) { + isQuoted = PR_TRUE; + /* skip over it */ + bp++; + } else { + isQuoted = PR_FALSE; + } + + valBufp = valBuf; + while (bp < endptr) { + char c = *bp; + if (c == C_BACKSLASH) { + /* escape character */ + bp++; + if (bp >= endptr) { + /* escape charater must appear with paired char */ + *pbp = bp; + return 0; + } + c = *bp; + if (IS_HEX(c) && (endptr - bp) >= 2 && IS_HEX(bp[1])) { + bp++; + c = (char)((x2b[(PRUint8)c] << 4) | x2b[(PRUint8)*bp]); + } + } else if (c == '#' && bp == *pbp) { + /* ignore leading #, quotation not required for it. */ + } else if (!isQuoted && SPECIAL_CHAR(c)) { + /* unescaped special and not within quoted value */ + break; + } else if (c == C_DOUBLE_QUOTE) { + /* reached unescaped double quote */ + break; + } + /* append character */ + vallen++; + if (vallen >= valBufSize) { + *pbp = bp; + return 0; + } + *valBufp++ = c; + bp++; + } + + /* strip trailing spaces from unquoted values */ + if (!isQuoted) { + while (valBufp > valBuf) { + char c = valBufp[-1]; + if (!OPTIONAL_SPACE(c)) + break; + --valBufp; + } + vallen = valBufp - valBuf; + } + + if (isQuoted) { + /* insist that we stopped on a double quote */ + if (*bp != C_DOUBLE_QUOTE) { + *pbp = bp; + return 0; + } + /* skip over the quote and skip optional space */ + bp++; + skipSpace(&bp, endptr); + } + + *pbp = bp; + + /* null-terminate valBuf -- guaranteed at least one space left */ + *valBufp = 0; + + return vallen; +} + +/* Caller must set error code upon failure */ +static SECStatus +hexToBin(PLArenaPool* pool, SECItem* destItem, const char* src, int len) +{ + PRUint8* dest; + + destItem->data = NULL; + if (len <= 0 || (len & 1)) { + goto loser; + } + len >>= 1; + if (!SECITEM_AllocItem(pool, destItem, len)) { + goto loser; + } + dest = destItem->data; + for (; len > 0; len--, src += 2) { + PRUint16 bin = ((PRUint16)x2b[(PRUint8)src[0]] << 4); + bin |= (PRUint16)x2b[(PRUint8)src[1]]; + if (bin >> 15) { /* is negative */ + goto loser; + } + *dest++ = (PRUint8)bin; + } + return SECSuccess; +loser: + if (!pool) + SECITEM_FreeItem(destItem, PR_FALSE); + return SECFailure; +} + +/* Parses one AVA, starting at *pbp. Stops at endptr. + * Advances *pbp past parsed AVA and trailing separator (if present). + * On any error, returns NULL and *pbp is undefined. + * On success, returns CERTAVA allocated from arena, and (*pbp)[-1] was + * the last character parsed. *pbp is either equal to endptr or + * points to first character after separator. + */ +static CERTAVA* +ParseRFC1485AVA(PLArenaPool* arena, const char** pbp, const char* endptr) +{ + CERTAVA* a; + const NameToKind* n2k; + const char* bp; + int vt = -1; + int valLen; + PRBool isDottedOid = PR_FALSE; + SECOidTag kind = SEC_OID_UNKNOWN; + SECStatus rv = SECFailure; + SECItem derOid = { 0, NULL, 0 }; + SECItem derVal = { 0, NULL, 0 }; + char sep = 0; + + char tagBuf[32]; + char valBuf[1024]; + + PORT_Assert(arena); + if (SECSuccess != scanTag(pbp, endptr, tagBuf, sizeof tagBuf) || + !(valLen = scanVal(pbp, endptr, valBuf, sizeof valBuf))) { + goto loser; + } + + bp = *pbp; + if (bp < endptr) { + sep = *bp++; /* skip over separator */ + } + *pbp = bp; + /* if we haven't finished, insist that we've stopped on a separator */ + if (sep && sep != ',' && sep != ';' && sep != '+') { + goto loser; + } + + /* is this a dotted decimal OID attribute type ? */ + if (!PL_strncasecmp("oid.", tagBuf, 4) || isdigit(tagBuf[0])) { + rv = SEC_StringToOID(arena, &derOid, tagBuf, strlen(tagBuf)); + isDottedOid = (PRBool)(rv == SECSuccess); + } else { + for (n2k = name2kinds; n2k->name; n2k++) { + SECOidData* oidrec; + if (PORT_Strcasecmp(n2k->name, tagBuf) == 0) { + kind = n2k->kind; + vt = n2k->valueType; + oidrec = SECOID_FindOIDByTag(kind); + if (oidrec == NULL) + goto loser; + derOid = oidrec->oid; + break; + } + } + } + if (kind == SEC_OID_UNKNOWN && rv != SECSuccess) + goto loser; + + /* Is this a hex encoding of a DER attribute value ? */ + if ('#' == valBuf[0]) { + /* convert attribute value from hex to binary */ + rv = hexToBin(arena, &derVal, valBuf + 1, valLen - 1); + if (rv) + goto loser; + a = CERT_CreateAVAFromRaw(arena, &derOid, &derVal); + } else { + if (kind == SEC_OID_AVA_COUNTRY_NAME && valLen != 2) + goto loser; + if (vt == SEC_ASN1_PRINTABLE_STRING && + !IsPrintable((unsigned char*)valBuf, valLen)) + goto loser; + if (vt == SEC_ASN1_DS) { + /* RFC 4630: choose PrintableString or UTF8String */ + if (IsPrintable((unsigned char*)valBuf, valLen)) + vt = SEC_ASN1_PRINTABLE_STRING; + else + vt = SEC_ASN1_UTF8_STRING; + } + + derVal.data = (unsigned char*)valBuf; + derVal.len = valLen; + if (kind == SEC_OID_UNKNOWN && isDottedOid) { + a = CERT_CreateAVAFromRaw(arena, &derOid, &derVal); + } else { + a = CERT_CreateAVAFromSECItem(arena, kind, vt, &derVal); + } + } + return a; + +loser: + /* matched no kind -- invalid tag */ + PORT_SetError(SEC_ERROR_INVALID_AVA); + return 0; +} + +static CERTName* +ParseRFC1485Name(const char* buf, int len) +{ + SECStatus rv; + CERTName* name; + const char *bp, *e; + CERTAVA* ava; + CERTRDN* rdn = NULL; + + name = CERT_CreateName(NULL); + if (name == NULL) { + return NULL; + } + + e = buf + len; + bp = buf; + while (bp < e) { + ava = ParseRFC1485AVA(name->arena, &bp, e); + if (ava == 0) + goto loser; + if (!rdn) { + rdn = CERT_CreateRDN(name->arena, ava, (CERTAVA*)0); + if (rdn == 0) + goto loser; + rv = CERT_AddRDN(name, rdn); + } else { + rv = CERT_AddAVA(name->arena, rdn, ava); + } + if (rv) + goto loser; + if (bp[-1] != '+') + rdn = NULL; /* done with this RDN */ + skipSpace(&bp, e); + } + + if (name->rdns[0] == 0) { + /* empty name -- illegal */ + goto loser; + } + + /* Reverse order of RDNS to comply with RFC */ + { + CERTRDN** firstRdn; + CERTRDN** lastRdn; + CERTRDN* tmp; + + /* get first one */ + firstRdn = name->rdns; + + /* find last one */ + lastRdn = name->rdns; + while (*lastRdn) + lastRdn++; + lastRdn--; + + /* reverse list */ + for (; firstRdn < lastRdn; firstRdn++, lastRdn--) { + tmp = *firstRdn; + *firstRdn = *lastRdn; + *lastRdn = tmp; + } + } + + /* return result */ + return name; + +loser: + CERT_DestroyName(name); + return NULL; +} + +CERTName* +CERT_AsciiToName(const char* string) +{ + CERTName* name; + name = ParseRFC1485Name(string, PORT_Strlen(string)); + return name; +} + +/************************************************************************/ + +typedef struct stringBufStr { + char* buffer; + unsigned offset; + unsigned size; +} stringBuf; + +#define DEFAULT_BUFFER_SIZE 200 + +static SECStatus +AppendStr(stringBuf* bufp, char* str) +{ + char* buf; + unsigned bufLen, bufSize, len; + int size = 0; + + /* Figure out how much to grow buf by (add in the '\0') */ + buf = bufp->buffer; + bufLen = bufp->offset; + len = PORT_Strlen(str); + bufSize = bufLen + len; + if (!buf) { + bufSize++; + size = PR_MAX(DEFAULT_BUFFER_SIZE, bufSize * 2); + buf = (char*)PORT_Alloc(size); + bufp->size = size; + } else if (bufp->size < bufSize) { + size = bufSize * 2; + buf = (char*)PORT_Realloc(buf, size); + bufp->size = size; + } + if (!buf) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + bufp->buffer = buf; + bufp->offset = bufSize; + + /* Concatenate str onto buf */ + buf = buf + bufLen; + if (bufLen) + buf--; /* stomp on old '\0' */ + PORT_Memcpy(buf, str, len + 1); /* put in new null */ + return SECSuccess; +} + +typedef enum { + minimalEscape = 0, /* only hex escapes, and " and \ */ + minimalEscapeAndQuote, /* as above, plus quoting */ + fullEscape /* no quoting, full escaping */ +} EQMode; + +/* Some characters must be escaped as a hex string, e.g. c -> \nn . + * Others must be escaped by preceding with a '\', e.g. c -> \c , but + * there are certain "special characters" that may be handled by either + * escaping them, or by enclosing the entire attribute value in quotes. + * A NULL value for pEQMode implies selecting minimalEscape mode. + * Some callers will do quoting when needed, others will not. + * If a caller selects minimalEscapeAndQuote, and the string does not + * need quoting, then this function changes it to minimalEscape. + */ +static int +cert_RFC1485_GetRequiredLen(const char* src, int srclen, EQMode* pEQMode) +{ + int i, reqLen = 0; + EQMode mode = pEQMode ? *pEQMode : minimalEscape; + PRBool needsQuoting = PR_FALSE; + char lastC = 0; + + /* need to make an initial pass to determine if quoting is needed */ + for (i = 0; i < srclen; i++) { + char c = src[i]; + reqLen++; + if (NEEDS_HEX_ESCAPE(c)) { /* c -> \xx */ + reqLen += 2; + } else if (NEEDS_ESCAPE(c)) { /* c -> \c */ + reqLen++; + } else if (SPECIAL_CHAR(c)) { + if (mode == minimalEscapeAndQuote) /* quoting is allowed */ + needsQuoting = PR_TRUE; /* entirety will need quoting */ + else if (mode == fullEscape) + reqLen++; /* MAY escape this character */ + } else if (OPTIONAL_SPACE(c) && OPTIONAL_SPACE(lastC)) { + if (mode == minimalEscapeAndQuote) /* quoting is allowed */ + needsQuoting = PR_TRUE; /* entirety will need quoting */ + } + lastC = c; + } + /* if it begins or ends in optional space it needs quoting */ + if (!needsQuoting && srclen > 0 && mode == minimalEscapeAndQuote && + (OPTIONAL_SPACE(src[srclen - 1]) || OPTIONAL_SPACE(src[0]))) { + needsQuoting = PR_TRUE; + } + + if (needsQuoting) + reqLen += 2; + if (pEQMode && mode == minimalEscapeAndQuote && !needsQuoting) + *pEQMode = minimalEscape; + return reqLen; +} + +static const char hexChars[16] = { "0123456789abcdef" }; + +static SECStatus +escapeAndQuote(char* dst, int dstlen, char* src, int srclen, EQMode* pEQMode) +{ + int i, reqLen = 0; + EQMode mode = pEQMode ? *pEQMode : minimalEscape; + + /* space for terminal null */ + reqLen = cert_RFC1485_GetRequiredLen(src, srclen, &mode) + 1; + if (reqLen > dstlen) { + PORT_SetError(SEC_ERROR_OUTPUT_LEN); + return SECFailure; + } + + if (mode == minimalEscapeAndQuote) + *dst++ = C_DOUBLE_QUOTE; + for (i = 0; i < srclen; i++) { + char c = src[i]; + if (NEEDS_HEX_ESCAPE(c)) { + *dst++ = C_BACKSLASH; + *dst++ = hexChars[(c >> 4) & 0x0f]; + *dst++ = hexChars[c & 0x0f]; + } else { + if (NEEDS_ESCAPE(c) || (SPECIAL_CHAR(c) && mode == fullEscape)) { + *dst++ = C_BACKSLASH; + } + *dst++ = c; + } + } + if (mode == minimalEscapeAndQuote) + *dst++ = C_DOUBLE_QUOTE; + *dst++ = 0; + if (pEQMode) + *pEQMode = mode; + return SECSuccess; +} + +SECStatus +CERT_RFC1485_EscapeAndQuote(char* dst, int dstlen, char* src, int srclen) +{ + EQMode mode = minimalEscapeAndQuote; + return escapeAndQuote(dst, dstlen, src, srclen, &mode); +} + +/* convert an OID to dotted-decimal representation */ +/* Returns a string that must be freed with PR_smprintf_free(), */ +char* +CERT_GetOidString(const SECItem* oid) +{ + PRUint8* stop; /* points to first byte after OID string */ + PRUint8* first; /* byte of an OID component integer */ + PRUint8* last; /* byte of an OID component integer */ + char* rvString = NULL; + char* prefix = NULL; + +#define MAX_OID_LEN 1024 /* bytes */ + + if (oid->len > MAX_OID_LEN) { + PORT_SetError(SEC_ERROR_INPUT_LEN); + return NULL; + } + + /* If the OID has length 1, we bail. */ + if (oid->len < 2) { + return NULL; + } + + /* first will point to the next sequence of bytes to decode */ + first = (PRUint8*)oid->data; + /* stop points to one past the legitimate data */ + stop = &first[oid->len]; + + /* + * Check for our pseudo-encoded single-digit OIDs + */ + if ((*first == 0x80) && (2 == oid->len)) { + /* Funky encoding. The second byte is the number */ + rvString = PR_smprintf("%lu", (PRUint32)first[1]); + if (!rvString) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + return rvString; + } + + for (; first < stop; first = last + 1) { + unsigned int bytesBeforeLast; + + for (last = first; last < stop; last++) { + if (0 == (*last & 0x80)) { + break; + } + } + /* There's no first bit set, so this isn't valid. Bail.*/ + if (last == stop) { + goto unsupported; + } + bytesBeforeLast = (unsigned int)(last - first); + if (bytesBeforeLast <= 3U) { /* 0-28 bit number */ + PRUint32 n = 0; + PRUint32 c; + +#define CGET(i, m) \ + c = last[-i] & m; \ + n |= c << (7 * i) + +#define CASE(i, m) \ + case i: \ + CGET(i, m); \ + if (!n) \ + goto unsupported /* fall-through */ + + switch (bytesBeforeLast) { + CASE(3, 0x7f); + CASE(2, 0x7f); + CASE(1, 0x7f); + case 0: + n |= last[0] & 0x7f; + break; + } + if (last[0] & 0x80) { + goto unsupported; + } + + if (!rvString) { + /* This is the first number.. decompose it */ + PRUint32 one = PR_MIN(n / 40, 2); /* never > 2 */ + PRUint32 two = n - (one * 40); + + rvString = PR_smprintf("OID.%lu.%lu", one, two); + } else { + prefix = rvString; + rvString = PR_smprintf("%s.%lu", prefix, n); + } + } else if (bytesBeforeLast <= 9U) { /* 29-64 bit number */ + PRUint64 n = 0; + PRUint64 c; + + switch (bytesBeforeLast) { + CASE(9, 0x01); + CASE(8, 0x7f); + CASE(7, 0x7f); + CASE(6, 0x7f); + CASE(5, 0x7f); + CASE(4, 0x7f); + CGET(3, 0x7f); + CGET(2, 0x7f); + CGET(1, 0x7f); + CGET(0, 0x7f); + break; + } + if (last[0] & 0x80) + goto unsupported; + + if (!rvString) { + /* This is the first number.. decompose it */ + PRUint64 one = PR_MIN(n / 40, 2); /* never > 2 */ + PRUint64 two = n - (one * 40); + + rvString = PR_smprintf("OID.%llu.%llu", one, two); + } else { + prefix = rvString; + rvString = PR_smprintf("%s.%llu", prefix, n); + } + } else { + /* More than a 64-bit number, or not minimal encoding. */ + unsupported: + if (!rvString) + rvString = PR_smprintf("OID.UNSUPPORTED"); + else { + prefix = rvString; + rvString = PR_smprintf("%s.UNSUPPORTED", prefix); + } + } + + if (prefix) { + PR_smprintf_free(prefix); + prefix = NULL; + } + if (!rvString) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + break; + } + } + return rvString; +} + +/* convert DER-encoded hex to a string */ +static SECItem* +get_hex_string(SECItem* data) +{ + SECItem* rv; + unsigned int i, j; + static const char hex[] = { "0123456789ABCDEF" }; + + /* '#' + 2 chars per octet + terminator */ + rv = SECITEM_AllocItem(NULL, NULL, data->len * 2 + 2); + if (!rv) { + return NULL; + } + rv->data[0] = '#'; + rv->len = 1 + 2 * data->len; + for (i = 0; i < data->len; i++) { + j = data->data[i]; + rv->data[2 * i + 1] = hex[j >> 4]; + rv->data[2 * i + 2] = hex[j & 15]; + } + rv->data[rv->len] = 0; + return rv; +} + +/* For compliance with RFC 2253, RFC 3280 and RFC 4630, we choose to + * use the NAME=STRING form, rather than the OID.N.N=#hexXXXX form, + * when both of these conditions are met: + * 1) The attribute name OID (kind) has a known name string that is + * defined in one of those RFCs, or in RFCs that they cite, AND + * 2) The attribute's value encoding is RFC compliant for the kind + * (e.g., the value's encoding tag is correct for the kind, and + * the value's length is in the range allowed for the kind, and + * the value's contents are appropriate for the encoding tag). + * Otherwise, we use the OID.N.N=#hexXXXX form. + * + * If the caller prefers maximum human readability to RFC compliance, + * then + * - We print the kind in NAME= string form if we know the name + * string for the attribute type OID, regardless of whether the + * value is correctly encoded or not. else we use the OID.N.N= form. + * - We use the non-hex STRING form for the attribute value if the + * value can be represented in such a form. Otherwise, we use + * the hex string form. + * This implies that, for maximum human readability, in addition to + * the two forms allowed by the RFC, we allow two other forms of output: + * - the OID.N.N=STRING form, and + * - the NAME=#hexXXXX form + * When the caller prefers maximum human readability, we do not allow + * the value of any attribute to exceed the length allowed by the RFC. + * If the attribute value exceeds the allowed length, we truncate it to + * the allowed length and append "...". + * Also in this case, we arbitrarily impose a limit on the length of the + * entire AVA encoding, regardless of the form, of 384 bytes per AVA. + * This limit includes the trailing NULL character. If the encoded + * AVA length exceeds that limit, this function reports failure to encode + * the AVA. + * + * An ASCII representation of an AVA is said to be "invertible" if + * conversion back to DER reproduces the original DER encoding exactly. + * The RFC 2253 rules do not ensure that all ASCII AVAs derived according + * to its rules are invertible. That is because the RFCs allow some + * attribute values to be encoded in any of a number of encodings, + * and the encoding type information is lost in the non-hex STRING form. + * This is particularly true of attributes of type DirectoryString. + * The encoding type information is always preserved in the hex string + * form, because the hex includes the entire DER encoding of the value. + * + * So, when the caller perfers maximum invertibility, we apply the + * RFC compliance rules stated above, and add a third required + * condition on the use of the NAME=STRING form. + * 3) The attribute's kind is not is allowed to be encoded in any of + * several different encodings, such as DirectoryStrings. + * + * The chief difference between CERT_N2A_STRICT and CERT_N2A_INVERTIBLE + * is that the latter forces DirectoryStrings to be hex encoded. + * + * As a simplification, we assume the value is correctly encoded for + * its encoding type. That is, we do not test that all the characters + * in a string encoded type are allowed by that type. We assume it. + */ +static SECStatus +AppendAVA(stringBuf* bufp, CERTAVA* ava, CertStrictnessLevel strict) +{ +#define TMPBUF_LEN 2048 + const NameToKind* pn2k = name2kinds; + SECItem* avaValue = NULL; + char* unknownTag = NULL; + char* encodedAVA = NULL; + PRBool useHex = PR_FALSE; /* use =#hexXXXX form */ + PRBool truncateName = PR_FALSE; + PRBool truncateValue = PR_FALSE; + SECOidTag endKind; + SECStatus rv; + unsigned int len; + unsigned int nameLen, valueLen; + unsigned int maxName, maxValue; + EQMode mode = minimalEscapeAndQuote; + NameToKind n2k = { NULL, 32767, SEC_OID_UNKNOWN, SEC_ASN1_DS }; + char tmpBuf[TMPBUF_LEN]; + +#define tagName n2k.name /* non-NULL means use NAME= form */ +#define maxBytes n2k.maxLen +#define tag n2k.kind +#define vt n2k.valueType + + /* READABLE mode recognizes more names from the name2kinds table + * than do STRICT or INVERTIBLE modes. This assignment chooses the + * point in the table where the attribute type name scanning stops. + */ + endKind = (strict == CERT_N2A_READABLE) ? SEC_OID_UNKNOWN + : SEC_OID_AVA_POSTAL_ADDRESS; + tag = CERT_GetAVATag(ava); + while (pn2k->kind != tag && pn2k->kind != endKind) { + ++pn2k; + } + + if (pn2k->kind != endKind) { + n2k = *pn2k; + } else if (strict != CERT_N2A_READABLE) { + useHex = PR_TRUE; + } + /* For invertable form, force Directory Strings to use hex form. */ + if (strict == CERT_N2A_INVERTIBLE && vt == SEC_ASN1_DS) { + tagName = NULL; /* must use OID.N form */ + useHex = PR_TRUE; /* must use hex string */ + } + if (!useHex) { + avaValue = CERT_DecodeAVAValue(&ava->value); + if (!avaValue) { + useHex = PR_TRUE; + if (strict != CERT_N2A_READABLE) { + tagName = NULL; /* must use OID.N form */ + } + } + } + if (!tagName) { + /* handle unknown attribute types per RFC 2253 */ + tagName = unknownTag = CERT_GetOidString(&ava->type); + if (!tagName) { + if (avaValue) + SECITEM_FreeItem(avaValue, PR_TRUE); + return SECFailure; + } + } + if (useHex) { + avaValue = get_hex_string(&ava->value); + if (!avaValue) { + if (unknownTag) + PR_smprintf_free(unknownTag); + return SECFailure; + } + } + + nameLen = strlen(tagName); + valueLen = + (useHex ? avaValue->len : cert_RFC1485_GetRequiredLen((char*)avaValue->data, avaValue->len, &mode)); + len = nameLen + valueLen + 2; /* Add 2 for '=' and trailing NUL */ + + maxName = nameLen; + maxValue = valueLen; + if (len <= sizeof(tmpBuf)) { + encodedAVA = tmpBuf; + } else if (strict != CERT_N2A_READABLE) { + encodedAVA = PORT_Alloc(len); + if (!encodedAVA) { + SECITEM_FreeItem(avaValue, PR_TRUE); + if (unknownTag) + PR_smprintf_free(unknownTag); + return SECFailure; + } + } else { + /* Must make output fit in tmpbuf */ + unsigned int fair = (sizeof tmpBuf) / 2 - 1; /* for = and \0 */ + + if (nameLen < fair) { + /* just truncate the value */ + maxValue = (sizeof tmpBuf) - (nameLen + 6); /* for "=...\0", + and possibly '"' */ + } else if (valueLen < fair) { + /* just truncate the name */ + maxName = (sizeof tmpBuf) - (valueLen + 5); /* for "=...\0" */ + } else { + /* truncate both */ + maxName = maxValue = fair - 3; /* for "..." */ + } + if (nameLen > maxName) { + PORT_Assert(unknownTag && unknownTag == tagName); + truncateName = PR_TRUE; + nameLen = maxName; + } + encodedAVA = tmpBuf; + } + + memcpy(encodedAVA, tagName, nameLen); + if (truncateName) { + /* If tag name is too long, we know it is an OID form that was + * allocated from the heap, so we can modify it in place + */ + encodedAVA[nameLen - 1] = '.'; + encodedAVA[nameLen - 2] = '.'; + encodedAVA[nameLen - 3] = '.'; + } + encodedAVA[nameLen++] = '='; + if (unknownTag) + PR_smprintf_free(unknownTag); + + if (strict == CERT_N2A_READABLE && maxValue > maxBytes) + maxValue = maxBytes; + if (valueLen > maxValue) { + valueLen = maxValue; + truncateValue = PR_TRUE; + } + /* escape and quote as necessary - don't quote hex strings */ + if (useHex) { + char* end = encodedAVA + nameLen + valueLen; + memcpy(encodedAVA + nameLen, (char*)avaValue->data, valueLen); + end[0] = '\0'; + if (truncateValue) { + end[-1] = '.'; + end[-2] = '.'; + end[-3] = '.'; + } + rv = SECSuccess; + } else if (!truncateValue) { + rv = escapeAndQuote(encodedAVA + nameLen, len - nameLen, + (char*)avaValue->data, avaValue->len, &mode); + } else { + /* must truncate the escaped and quoted value */ + char bigTmpBuf[TMPBUF_LEN * 3 + 3]; + PORT_Assert(valueLen < sizeof tmpBuf); + rv = escapeAndQuote(bigTmpBuf, sizeof bigTmpBuf, (char*)avaValue->data, + PR_MIN(avaValue->len, valueLen), &mode); + + bigTmpBuf[valueLen--] = '\0'; /* hard stop here */ + /* See if we're in the middle of a multi-byte UTF8 character */ + while (((bigTmpBuf[valueLen] & 0xc0) == 0x80) && valueLen > 0) { + bigTmpBuf[valueLen--] = '\0'; + } + /* add ellipsis to signify truncation. */ + bigTmpBuf[++valueLen] = '.'; + bigTmpBuf[++valueLen] = '.'; + bigTmpBuf[++valueLen] = '.'; + if (bigTmpBuf[0] == '"') + bigTmpBuf[++valueLen] = '"'; + bigTmpBuf[++valueLen] = '\0'; + PORT_Assert(nameLen + valueLen <= (sizeof tmpBuf) - 1); + memcpy(encodedAVA + nameLen, bigTmpBuf, valueLen + 1); + } + + SECITEM_FreeItem(avaValue, PR_TRUE); + if (rv == SECSuccess) + rv = AppendStr(bufp, encodedAVA); + if (encodedAVA != tmpBuf) + PORT_Free(encodedAVA); + return rv; +} + +#undef tagName +#undef maxBytes +#undef tag +#undef vt + +char* +CERT_NameToAsciiInvertible(CERTName* name, CertStrictnessLevel strict) +{ + CERTRDN** rdns; + CERTRDN** lastRdn; + CERTRDN** rdn; + PRBool first = PR_TRUE; + stringBuf strBuf = { NULL, 0, 0 }; + + rdns = name->rdns; + if (rdns == NULL) { + return NULL; + } + + /* find last RDN */ + lastRdn = rdns; + while (*lastRdn) + lastRdn++; + lastRdn--; + + /* + * Loop over name contents in _reverse_ RDN order appending to string + */ + for (rdn = lastRdn; rdn >= rdns; rdn--) { + CERTAVA** avas = (*rdn)->avas; + CERTAVA* ava; + PRBool newRDN = PR_TRUE; + + /* + * XXX Do we need to traverse the AVAs in reverse order, too? + */ + while (avas && (ava = *avas++) != NULL) { + SECStatus rv; + /* Put in comma or plus separator */ + if (!first) { + /* Use of spaces is deprecated in RFC 2253. */ + rv = AppendStr(&strBuf, newRDN ? "," : "+"); + if (rv) + goto loser; + } else { + first = PR_FALSE; + } + + /* Add in tag type plus value into strBuf */ + rv = AppendAVA(&strBuf, ava, strict); + if (rv) + goto loser; + newRDN = PR_FALSE; + } + } + return strBuf.buffer; +loser: + if (strBuf.buffer) { + PORT_Free(strBuf.buffer); + } + return NULL; +} + +char* +CERT_NameToAscii(CERTName* name) +{ + return CERT_NameToAsciiInvertible(name, CERT_N2A_READABLE); +} + +/* + * Return the string representation of a DER encoded distinguished name + * "dername" - The DER encoded name to convert + */ +char* +CERT_DerNameToAscii(SECItem* dername) +{ + int rv; + PLArenaPool* arena = NULL; + CERTName name; + char* retstr = NULL; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + + if (arena == NULL) { + goto loser; + } + + rv = SEC_QuickDERDecodeItem(arena, &name, CERT_NameTemplate, dername); + + if (rv != SECSuccess) { + goto loser; + } + + retstr = CERT_NameToAscii(&name); + +loser: + if (arena != NULL) { + PORT_FreeArena(arena, PR_FALSE); + } + + return (retstr); +} + +static char* +avaToString(PLArenaPool* arena, CERTAVA* ava) +{ + char* buf = NULL; + SECItem* avaValue; + int valueLen; + + avaValue = CERT_DecodeAVAValue(&ava->value); + if (!avaValue) { + return buf; + } + valueLen = + cert_RFC1485_GetRequiredLen((char*)avaValue->data, avaValue->len, NULL) + 1; + if (arena) { + buf = (char*)PORT_ArenaZAlloc(arena, valueLen); + } else { + buf = (char*)PORT_ZAlloc(valueLen); + } + if (buf) { + SECStatus rv = + escapeAndQuote(buf, valueLen, (char*)avaValue->data, avaValue->len, NULL); + if (rv != SECSuccess) { + if (!arena) + PORT_Free(buf); + buf = NULL; + } + } + SECITEM_FreeItem(avaValue, PR_TRUE); + return buf; +} + +/* RDNs are sorted from most general to most specific. + * This code returns the FIRST one found, the most general one found. + */ +static char* +CERT_GetNameElement(PLArenaPool* arena, const CERTName* name, int wantedTag) +{ + CERTRDN** rdns = name->rdns; + CERTRDN* rdn; + CERTAVA* ava = NULL; + + while (rdns && (rdn = *rdns++) != 0) { + CERTAVA** avas = rdn->avas; + while (avas && (ava = *avas++) != 0) { + int tag = CERT_GetAVATag(ava); + if (tag == wantedTag) { + avas = NULL; + rdns = NULL; /* break out of all loops */ + } + } + } + return ava ? avaToString(arena, ava) : NULL; +} + +/* RDNs are sorted from most general to most specific. + * This code returns the LAST one found, the most specific one found. + * This is particularly appropriate for Common Name. See RFC 2818. + */ +static char* +CERT_GetLastNameElement(PLArenaPool* arena, const CERTName* name, int wantedTag) +{ + CERTRDN** rdns = name->rdns; + CERTRDN* rdn; + CERTAVA* lastAva = NULL; + + while (rdns && (rdn = *rdns++) != 0) { + CERTAVA** avas = rdn->avas; + CERTAVA* ava; + while (avas && (ava = *avas++) != 0) { + int tag = CERT_GetAVATag(ava); + if (tag == wantedTag) { + lastAva = ava; + } + } + } + return lastAva ? avaToString(arena, lastAva) : NULL; +} + +char* +CERT_GetCertificateEmailAddress(CERTCertificate* cert) +{ + char* rawEmailAddr = NULL; + SECItem subAltName; + SECStatus rv; + CERTGeneralName* nameList = NULL; + CERTGeneralName* current; + PLArenaPool* arena = NULL; + int i; + + subAltName.data = NULL; + + rawEmailAddr = CERT_GetNameElement(cert->arena, &(cert->subject), + SEC_OID_PKCS9_EMAIL_ADDRESS); + if (rawEmailAddr == NULL) { + rawEmailAddr = + CERT_GetNameElement(cert->arena, &(cert->subject), SEC_OID_RFC1274_MAIL); + } + if (rawEmailAddr == NULL) { + + rv = + CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME, &subAltName); + if (rv != SECSuccess) { + goto finish; + } + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) { + goto finish; + } + nameList = current = CERT_DecodeAltNameExtension(arena, &subAltName); + if (!nameList) { + goto finish; + } + if (nameList != NULL) { + do { + if (current->type == certDirectoryName) { + rawEmailAddr = + CERT_GetNameElement(cert->arena, &(current->name.directoryName), + SEC_OID_PKCS9_EMAIL_ADDRESS); + if (rawEmailAddr == + NULL) { + rawEmailAddr = + CERT_GetNameElement(cert->arena, &(current->name.directoryName), + SEC_OID_RFC1274_MAIL); + } + } else if (current->type == certRFC822Name) { + rawEmailAddr = + (char*)PORT_ArenaZAlloc(cert->arena, current->name.other.len + 1); + if (!rawEmailAddr) { + goto finish; + } + PORT_Memcpy(rawEmailAddr, current->name.other.data, + current->name.other.len); + rawEmailAddr[current->name.other.len] = + '\0'; + } + if (rawEmailAddr) { + break; + } + current = CERT_GetNextGeneralName(current); + } while (current != nameList); + } + } + if (rawEmailAddr) { + for (i = 0; i <= (int)PORT_Strlen(rawEmailAddr); i++) { + rawEmailAddr[i] = tolower(rawEmailAddr[i]); + } + } + +finish: + + /* Don't free nameList, it's part of the arena. */ + + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + + if (subAltName.data) { + SECITEM_FreeItem(&subAltName, PR_FALSE); + } + + return (rawEmailAddr); +} + +static char* +appendStringToBuf(char* dest, char* src, PRUint32* pRemaining) +{ + PRUint32 len; + if (dest && src && src[0] && *pRemaining > (len = PL_strlen(src))) { + PRUint32 i; + for (i = 0; i < len; ++i) + dest[i] = tolower(src[i]); + dest[len] = 0; + dest += len + 1; + *pRemaining -= len + 1; + } + return dest; +} + +#undef NEEDS_HEX_ESCAPE +#define NEEDS_HEX_ESCAPE(c) (c < 0x20) + +static char* +appendItemToBuf(char* dest, SECItem* src, PRUint32* pRemaining) +{ + if (dest && src && src->data && src->len && src->data[0]) { + PRUint32 len = src->len; + PRUint32 i; + PRUint32 reqLen = len + 1; + /* are there any embedded control characters ? */ + for (i = 0; i < len; i++) { + if (NEEDS_HEX_ESCAPE(src->data[i])) + reqLen += 2; + } + if (*pRemaining > reqLen) { + for (i = 0; i < len; ++i) { + PRUint8 c = src->data[i]; + if (NEEDS_HEX_ESCAPE(c)) { + *dest++ = + C_BACKSLASH; + *dest++ = + hexChars[(c >> 4) & 0x0f]; + *dest++ = + hexChars[c & 0x0f]; + } else { + *dest++ = + tolower(c); + } + } + *dest++ = '\0'; + *pRemaining -= reqLen; + } + } + return dest; +} + +/* Returns a pointer to an environment-like string, a series of +** null-terminated strings, terminated by a zero-length string. +** This function is intended to be internal to NSS. +*/ +char* +cert_GetCertificateEmailAddresses(CERTCertificate* cert) +{ + char* rawEmailAddr = NULL; + char* addrBuf = NULL; + char* pBuf = NULL; + PORTCheapArenaPool tmpArena; + PRUint32 maxLen = 0; + PRInt32 finalLen = 0; + SECStatus rv; + SECItem subAltName; + + PORT_InitCheapArena(&tmpArena, DER_DEFAULT_CHUNKSIZE); + + subAltName.data = NULL; + maxLen = cert->derCert.len; + PORT_Assert(maxLen); + if (!maxLen) + maxLen = 2000; /* a guess, should never happen */ + + pBuf = addrBuf = (char*)PORT_ArenaZAlloc(&tmpArena.arena, maxLen + 1); + if (!addrBuf) + goto loser; + + rawEmailAddr = CERT_GetNameElement(&tmpArena.arena, &cert->subject, + SEC_OID_PKCS9_EMAIL_ADDRESS); + pBuf = appendStringToBuf(pBuf, rawEmailAddr, &maxLen); + + rawEmailAddr = CERT_GetNameElement(&tmpArena.arena, &cert->subject, + SEC_OID_RFC1274_MAIL); + pBuf = appendStringToBuf(pBuf, rawEmailAddr, &maxLen); + + rv = CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME, &subAltName); + if (rv == SECSuccess && subAltName.data) { + CERTGeneralName* nameList = NULL; + + if (!!(nameList = CERT_DecodeAltNameExtension(&tmpArena.arena, &subAltName))) { + CERTGeneralName* current = nameList; + do { + if (current->type == certDirectoryName) { + rawEmailAddr = + CERT_GetNameElement(&tmpArena.arena, + ¤t->name.directoryName, + SEC_OID_PKCS9_EMAIL_ADDRESS); + pBuf = + appendStringToBuf(pBuf, rawEmailAddr, &maxLen); + + rawEmailAddr = + CERT_GetNameElement(&tmpArena.arena, + ¤t->name.directoryName, + SEC_OID_RFC1274_MAIL); + pBuf = + appendStringToBuf(pBuf, rawEmailAddr, &maxLen); + } else if (current->type == certRFC822Name) { + pBuf = + appendItemToBuf(pBuf, ¤t->name.other, &maxLen); + } + current = CERT_GetNextGeneralName(current); + } while (current != nameList); + } + SECITEM_FreeItem(&subAltName, PR_FALSE); + /* Don't free nameList, it's part of the tmpArena. */ + } + /* now copy superstring to cert's arena */ + finalLen = (pBuf - addrBuf) + 1; + pBuf = NULL; + if (finalLen > 1) { + pBuf = PORT_ArenaAlloc(cert->arena, finalLen); + if (pBuf) { + PORT_Memcpy(pBuf, addrBuf, finalLen); + } + } +loser: + PORT_DestroyCheapArena(&tmpArena); + + return pBuf; +} + +/* returns pointer to storage in cert's arena. Storage remains valid +** as long as cert's reference count doesn't go to zero. +** Caller should strdup or otherwise copy. +*/ +const char* /* const so caller won't muck with it. */ +CERT_GetFirstEmailAddress(CERTCertificate* cert) +{ + if (cert && cert->emailAddr && cert->emailAddr[0]) + return (const char*)cert->emailAddr; + return NULL; +} + +/* returns pointer to storage in cert's arena. Storage remains valid +** as long as cert's reference count doesn't go to zero. +** Caller should strdup or otherwise copy. +*/ +const char* /* const so caller won't muck with it. */ +CERT_GetNextEmailAddress(CERTCertificate* cert, const char* prev) +{ + if (cert && prev && prev[0]) { + PRUint32 len = PL_strlen(prev); + prev += len + 1; + if (prev && prev[0]) + return prev; + } + return NULL; +} + +/* This is seriously bogus, now that certs store their email addresses in +** subject Alternative Name extensions. +** Returns a string allocated by PORT_StrDup, which the caller must free. +*/ +char* +CERT_GetCertEmailAddress(const CERTName* name) +{ + char* rawEmailAddr; + char* emailAddr; + + rawEmailAddr = CERT_GetNameElement(NULL, name, SEC_OID_PKCS9_EMAIL_ADDRESS); + if (rawEmailAddr == NULL) { + rawEmailAddr = CERT_GetNameElement(NULL, name, SEC_OID_RFC1274_MAIL); + } + emailAddr = CERT_FixupEmailAddr(rawEmailAddr); + if (rawEmailAddr) { + PORT_Free(rawEmailAddr); + } + return (emailAddr); +} + +/* The return value must be freed with PORT_Free. */ +char* +CERT_GetCommonName(const CERTName* name) +{ + return (CERT_GetLastNameElement(NULL, name, SEC_OID_AVA_COMMON_NAME)); +} + +char* +CERT_GetCountryName(const CERTName* name) +{ + return (CERT_GetNameElement(NULL, name, SEC_OID_AVA_COUNTRY_NAME)); +} + +char* +CERT_GetLocalityName(const CERTName* name) +{ + return (CERT_GetNameElement(NULL, name, SEC_OID_AVA_LOCALITY)); +} + +char* +CERT_GetStateName(const CERTName* name) +{ + return (CERT_GetNameElement(NULL, name, SEC_OID_AVA_STATE_OR_PROVINCE)); +} + +char* +CERT_GetOrgName(const CERTName* name) +{ + return (CERT_GetNameElement(NULL, name, SEC_OID_AVA_ORGANIZATION_NAME)); +} + +char* +CERT_GetDomainComponentName(const CERTName* name) +{ + return (CERT_GetNameElement(NULL, name, SEC_OID_AVA_DC)); +} + +char* +CERT_GetOrgUnitName(const CERTName* name) +{ + return ( + CERT_GetNameElement(NULL, name, SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME)); +} + +char* +CERT_GetDnQualifier(const CERTName* name) +{ + return (CERT_GetNameElement(NULL, name, SEC_OID_AVA_DN_QUALIFIER)); +} + +char* +CERT_GetCertUid(const CERTName* name) +{ + return (CERT_GetNameElement(NULL, name, SEC_OID_RFC1274_UID)); +} diff --git a/security/nss/lib/certdb/cert.h b/security/nss/lib/certdb/cert.h new file mode 100644 index 0000000000..33d37b39a0 --- /dev/null +++ b/security/nss/lib/certdb/cert.h @@ -0,0 +1,1606 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * cert.h - public data structures and prototypes for the certificate library + */ + +#ifndef _CERT_H_ +#define _CERT_H_ + +#include "utilrename.h" +#include "plarena.h" +#include "plhash.h" +#include "prlong.h" +#include "prlog.h" + +#include "seccomon.h" +#include "secdert.h" +#include "secoidt.h" +#include "keythi.h" +#include "certt.h" + +SEC_BEGIN_PROTOS + +/**************************************************************************** + * + * RFC1485 ascii to/from X.? RelativeDistinguishedName (aka CERTName) + * + ****************************************************************************/ + +/* +** Convert an ascii RFC1485 encoded name into its CERTName equivalent. +*/ +extern CERTName *CERT_AsciiToName(const char *string); + +/* +** Convert an CERTName into its RFC1485 encoded equivalent. +** Returns a string that must be freed with PORT_Free(). +** This version produces a string for maximum human readability, +** not for strict RFC compliance. +*/ +extern char *CERT_NameToAscii(CERTName *name); + +/* +** Convert an CERTName into its RFC1485 encoded equivalent. +** Returns a string that must be freed with PORT_Free(). +** Caller chooses encoding rules. +*/ +extern char *CERT_NameToAsciiInvertible(CERTName *name, + CertStrictnessLevel strict); + +extern CERTAVA *CERT_CopyAVA(PLArenaPool *arena, CERTAVA *src); + +/* convert an OID to dotted-decimal representation */ +/* Returns a string that must be freed with PR_smprintf_free(). */ +extern char *CERT_GetOidString(const SECItem *oid); + +/* +** Examine an AVA and return the tag that refers to it. The AVA tags are +** defined as SEC_OID_AVA*. +*/ +extern SECOidTag CERT_GetAVATag(CERTAVA *ava); + +/* +** Compare two AVA's, returning the difference between them. +*/ +extern SECComparison CERT_CompareAVA(const CERTAVA *a, const CERTAVA *b); + +/* +** Create an RDN (relative-distinguished-name). The argument list is a +** NULL terminated list of AVA's. +*/ +extern CERTRDN *CERT_CreateRDN(PLArenaPool *arena, CERTAVA *avas, ...); + +/* +** Make a copy of "src" storing it in "dest". +*/ +extern SECStatus CERT_CopyRDN(PLArenaPool *arena, CERTRDN *dest, CERTRDN *src); + +/* +** Add an AVA to an RDN. +** "rdn" the RDN to add to +** "ava" the AVA to add +*/ +extern SECStatus CERT_AddAVA(PLArenaPool *arena, CERTRDN *rdn, CERTAVA *ava); + +/* +** Compare two RDN's, returning the difference between them. +*/ +extern SECComparison CERT_CompareRDN(const CERTRDN *a, const CERTRDN *b); + +/* +** Create an X.500 style name using a NULL terminated list of RDN's. +*/ +extern CERTName *CERT_CreateName(CERTRDN *rdn, ...); + +/* +** Make a copy of "src" storing it in "dest". Memory is allocated in +** "dest" for each of the appropriate sub objects. Memory is not freed in +** "dest" before allocation is done (use CERT_DestroyName(dest, PR_FALSE) to +** do that). +*/ +extern SECStatus CERT_CopyName(PLArenaPool *arena, CERTName *dest, + const CERTName *src); + +/* +** Destroy a Name object. +** "name" the CERTName to destroy +** "freeit" if PR_TRUE then free the object as well as its sub-objects +*/ +extern void CERT_DestroyName(CERTName *name); + +/* +** Add an RDN to a name. +** "name" the name to add the RDN to +** "rdn" the RDN to add to name +*/ +extern SECStatus CERT_AddRDN(CERTName *name, CERTRDN *rdn); + +/* +** Compare two names, returning the difference between them. +*/ +extern SECComparison CERT_CompareName(const CERTName *a, const CERTName *b); + +/* +** Convert a CERTName into something readable +*/ +extern char *CERT_FormatName(CERTName *name); + +/* +** Convert a der-encoded integer to a hex printable string form. +** Perhaps this should be a SEC function but it's only used for certs. +*/ +extern char *CERT_Hexify(SECItem *i, int do_colon); + +/* +** Converts DER string (with explicit length) into zString, if destination +** buffer is big enough to receive it. Does quoting and/or escaping as +** specified in RFC 1485. Input string must be single or multi-byte DER +** character set, (ASCII, UTF8, or ISO 8851-x) not a wide character set. +** Returns SECSuccess or SECFailure with error code set. If output buffer +** is too small, sets error code SEC_ERROR_OUTPUT_LEN. +*/ +extern SECStatus CERT_RFC1485_EscapeAndQuote(char *dst, int dstlen, char *src, + int srclen); + +/****************************************************************************** + * + * Certificate handling operations + * + *****************************************************************************/ + +/* +** Create a new validity object given two unix time values. +** "notBefore" the time before which the validity is not valid +** "notAfter" the time after which the validity is not valid +*/ +extern CERTValidity *CERT_CreateValidity(PRTime notBefore, PRTime notAfter); + +/* +** Destroy a validity object. +** "v" the validity to destroy +** "freeit" if PR_TRUE then free the object as well as its sub-objects +*/ +extern void CERT_DestroyValidity(CERTValidity *v); + +/* +** Copy the "src" object to "dest". Memory is allocated in "dest" for +** each of the appropriate sub-objects. Memory in "dest" is not freed +** before memory is allocated (use CERT_DestroyValidity(v, PR_FALSE) to do +** that). +*/ +extern SECStatus CERT_CopyValidity(PLArenaPool *arena, CERTValidity *dest, + CERTValidity *src); + +/* +** The cert lib considers a cert or CRL valid if the "notBefore" time is +** in the not-too-distant future, e.g. within the next 24 hours. This +** prevents freshly issued certificates from being considered invalid +** because the local system's time zone is incorrectly set. +** The amount of "pending slop time" is adjustable by the application. +** Units of SlopTime are seconds. Default is 86400 (24 hours). +** Negative SlopTime values are not allowed. +*/ +PRInt32 CERT_GetSlopTime(void); + +SECStatus CERT_SetSlopTime(PRInt32 slop); + +/* +** Create a new certificate object. The result must be wrapped with an +** CERTSignedData to create a signed certificate. +** "serialNumber" the serial number +** "issuer" the name of the certificate issuer +** "validity" the validity period of the certificate +** "req" the certificate request that prompted the certificate issuance +*/ +extern CERTCertificate *CERT_CreateCertificate(unsigned long serialNumber, + CERTName *issuer, + CERTValidity *validity, + CERTCertificateRequest *req); + +/* +** Destroy a certificate object +** "cert" the certificate to destroy +** NOTE: certificate's are reference counted. This call decrements the +** reference count, and if the result is zero, then the object is destroyed +** and optionally freed. +*/ +extern void CERT_DestroyCertificate(CERTCertificate *cert); + +/* +** Make a shallow copy of a certificate "c". Just increments the +** reference count on "c". +*/ +extern CERTCertificate *CERT_DupCertificate(CERTCertificate *c); + +/* Access the DER of the certificate. This only creates a reference to the DER + * in the outparam not a copy. To avoid the pointer becoming invalid, use + * CERT_DupCertificate() and keep a reference to the duplicate alive. + */ +extern SECStatus CERT_GetCertificateDer(const CERTCertificate *c, SECItem *der); + +/* +** Create a new certificate request. This result must be wrapped with an +** CERTSignedData to create a signed certificate request. +** "name" the subject name (who the certificate request is from) +** "spki" describes/defines the public key the certificate is for +** "attributes" if non-zero, some optional attribute data +*/ +extern CERTCertificateRequest *CERT_CreateCertificateRequest( + CERTName *name, CERTSubjectPublicKeyInfo *spki, SECItem **attributes); + +/* +** Destroy a certificate-request object +** "r" the certificate-request to destroy +** "freeit" if PR_TRUE then free the object as well as its sub-objects +*/ +extern void CERT_DestroyCertificateRequest(CERTCertificateRequest *r); + +/* +** Start adding extensions to a certificate request. +*/ +void *CERT_StartCertificateRequestAttributes(CERTCertificateRequest *req); + +/* +** Reformat the certificate extension list into a CertificateRequest +** attribute list. +*/ +SECStatus CERT_FinishCertificateRequestAttributes(CERTCertificateRequest *req); + +/* +** Extract the Extension Requests from a DER CertRequest attribute list. +*/ +SECStatus CERT_GetCertificateRequestExtensions(CERTCertificateRequest *req, + CERTCertExtension ***exts); + +/* +** Extract a public key object from a certificate +*/ +extern SECKEYPublicKey *CERT_ExtractPublicKey(CERTCertificate *cert); + +/* +** Retrieve the Key Type associated with the cert we're dealing with +*/ + +extern KeyType CERT_GetCertKeyType(const CERTSubjectPublicKeyInfo *spki); + +/* +** Initialize the certificate database. This is called to create +** the initial list of certificates in the database. +*/ +extern SECStatus CERT_InitCertDB(CERTCertDBHandle *handle); + +extern int CERT_GetDBContentVersion(CERTCertDBHandle *handle); + +/* +** Default certificate database routines +*/ +extern void CERT_SetDefaultCertDB(CERTCertDBHandle *handle); + +extern CERTCertDBHandle *CERT_GetDefaultCertDB(void); + +extern CERTCertList *CERT_GetCertChainFromCert(CERTCertificate *cert, + PRTime time, SECCertUsage usage); +extern CERTCertificate *CERT_NewTempCertificate(CERTCertDBHandle *handle, + SECItem *derCert, + char *nickname, PRBool isperm, + PRBool copyDER); + +/****************************************************************************** + * + * X.500 Name handling operations + * + *****************************************************************************/ + +/* +** Create an AVA (attribute-value-assertion) +** "arena" the memory arena to alloc from +** "kind" is one of SEC_OID_AVA_* +** "valueType" is one of DER_PRINTABLE_STRING, DER_IA5_STRING, or +** DER_T61_STRING +** "value" is the null terminated string containing the value +*/ +extern CERTAVA *CERT_CreateAVA(PLArenaPool *arena, SECOidTag kind, + int valueType, char *value); + +/* +** Extract the Distinguished Name from a DER encoded certificate +** "derCert" is the DER encoded certificate +** "derName" is the SECItem that the name is returned in +*/ +extern SECStatus CERT_NameFromDERCert(SECItem *derCert, SECItem *derName); + +/* +** Extract the Issuers Distinguished Name from a DER encoded certificate +** "derCert" is the DER encoded certificate +** "derName" is the SECItem that the name is returned in +*/ +extern SECStatus CERT_IssuerNameFromDERCert(SECItem *derCert, SECItem *derName); + +extern SECItem *CERT_EncodeGeneralName(CERTGeneralName *genName, SECItem *dest, + PLArenaPool *arena); + +extern CERTGeneralName *CERT_DecodeGeneralName(PLArenaPool *reqArena, + SECItem *encodedName, + CERTGeneralName *genName); + +/* +** Generate a database search key for a certificate, based on the +** issuer and serial number. +** "arena" the memory arena to alloc from +** "derCert" the DER encoded certificate +** "key" the returned key +*/ +extern SECStatus CERT_KeyFromDERCert(PLArenaPool *reqArena, SECItem *derCert, + SECItem *key); + +extern SECStatus CERT_KeyFromIssuerAndSN(PLArenaPool *arena, SECItem *issuer, + SECItem *sn, SECItem *key); + +extern SECStatus CERT_SerialNumberFromDERCert(SECItem *derCert, + SECItem *derName); + +/* +** Generate a database search key for a crl, based on the +** issuer. +** "arena" the memory arena to alloc from +** "derCrl" the DER encoded crl +** "key" the returned key +*/ +extern SECStatus CERT_KeyFromDERCrl(PLArenaPool *arena, SECItem *derCrl, + SECItem *key); + +/* +** Open the certificate database. Use callback to get name of database. +*/ +extern SECStatus CERT_OpenCertDB(CERTCertDBHandle *handle, PRBool readOnly, + CERTDBNameFunc namecb, void *cbarg); + +/* Open the certificate database. Use given filename for database. */ +extern SECStatus CERT_OpenCertDBFilename(CERTCertDBHandle *handle, + char *certdbname, PRBool readOnly); + +/* +** Open and initialize a cert database that is entirely in memory. This +** can be used when the permanent database can not be opened or created. +*/ +extern SECStatus CERT_OpenVolatileCertDB(CERTCertDBHandle *handle); + +/* +** Extract the list of host names, host name patters, IP address strings +** this cert is valid for. +** This function does NOT return nicknames. +** Type CERTCertNicknames is being used because it's a convenient +** data structure to carry a list of strings and its count. +*/ +extern CERTCertNicknames *CERT_GetValidDNSPatternsFromCert( + CERTCertificate *cert); + +/* +** Check the hostname to make sure that it matches the shexp that +** is given in the common name of the certificate. +*/ +extern SECStatus CERT_VerifyCertName(const CERTCertificate *cert, + const char *hostname); + +/* +** Add a domain name to the list of names that the user has explicitly +** allowed (despite cert name mismatches) for use with a server cert. +*/ +extern SECStatus CERT_AddOKDomainName(CERTCertificate *cert, + const char *hostname); + +/* +** Decode a DER encoded certificate into an CERTCertificate structure +** "derSignedCert" is the DER encoded signed certificate +** "copyDER" is true if the DER should be copied, false if the +** existing copy should be referenced +** "nickname" is the nickname to use in the database. If it is NULL +** then a temporary nickname is generated. +*/ +extern CERTCertificate *CERT_DecodeDERCertificate(SECItem *derSignedCert, + PRBool copyDER, + char *nickname); +/* +** Decode a DER encoded CRL into a CERTSignedCrl structure +** "derSignedCrl" is the DER encoded signed CRL. +** "type" must be SEC_CRL_TYPE. +*/ +#define SEC_CRL_TYPE 1 +#define SEC_KRL_TYPE 0 /* deprecated */ + +extern CERTSignedCrl *CERT_DecodeDERCrl(PLArenaPool *arena, + SECItem *derSignedCrl, int type); + +/* + * same as CERT_DecodeDERCrl, plus allow options to be passed in + */ + +extern CERTSignedCrl *CERT_DecodeDERCrlWithFlags(PLArenaPool *narena, + SECItem *derSignedCrl, + int type, PRInt32 options); + +/* CRL options to pass */ + +#define CRL_DECODE_DEFAULT_OPTIONS 0x00000000 + +/* when CRL_DECODE_DONT_COPY_DER is set, the DER is not copied . The + application must then keep derSignedCrl until it destroys the + CRL . Ideally, it should allocate derSignedCrl in an arena + and pass that arena in as the first argument to + CERT_DecodeDERCrlWithFlags */ + +#define CRL_DECODE_DONT_COPY_DER 0x00000001 +#define CRL_DECODE_SKIP_ENTRIES 0x00000002 +#define CRL_DECODE_KEEP_BAD_CRL 0x00000004 +#define CRL_DECODE_ADOPT_HEAP_DER 0x00000008 + +/* complete the decoding of a partially decoded CRL, ie. decode the + entries. Note that entries is an optional field in a CRL, so the + "entries" pointer in CERTCrlStr may still be NULL even after + function returns SECSuccess */ + +extern SECStatus CERT_CompleteCRLDecodeEntries(CERTSignedCrl *crl); + +/* Validate CRL then import it to the dbase. If there is already a CRL with the + * same CA in the dbase, it will be replaced if derCRL is more up to date. + * If the process successes, a CRL will be returned. Otherwise, a NULL will + * be returned. The caller should call PORT_GetError() for the exactly error + * code. + */ +extern CERTSignedCrl *CERT_ImportCRL(CERTCertDBHandle *handle, SECItem *derCRL, + char *url, int type, void *wincx); + +extern void CERT_DestroyCrl(CERTSignedCrl *crl); + +/* this is a hint to flush the CRL cache. crlKey is the DER subject of + the issuer (CA). */ +void CERT_CRLCacheRefreshIssuer(CERTCertDBHandle *dbhandle, SECItem *crlKey); + +/* add the specified DER CRL object to the CRL cache. Doing so will allow + certificate verification functions (such as CERT_VerifyCertificate) + to automatically find and make use of this CRL object. + Once a CRL is added to the CRL cache, the application must hold on to + the object's memory, because the cache will reference it directly. The + application can only free the object after it calls CERT_UncacheCRL to + remove it from the CRL cache. +*/ +SECStatus CERT_CacheCRL(CERTCertDBHandle *dbhandle, SECItem *newcrl); + +/* remove a previously added CRL object from the CRL cache. It is OK + for the application to free the memory after a successful removal +*/ +SECStatus CERT_UncacheCRL(CERTCertDBHandle *dbhandle, SECItem *oldcrl); + +/* +** Find a certificate in the database +** "key" is the database key to look for +*/ +extern CERTCertificate *CERT_FindCertByKey(CERTCertDBHandle *handle, + SECItem *key); + +/* +** Find a certificate in the database by name +** "name" is the distinguished name to look up +*/ +extern CERTCertificate *CERT_FindCertByName(CERTCertDBHandle *handle, + SECItem *name); + +/* +** Find a certificate in the database by name +** "name" is the distinguished name to look up (in ascii) +*/ +extern CERTCertificate *CERT_FindCertByNameString(CERTCertDBHandle *handle, + char *name); + +/* +** Find a certificate in the database by name and keyid +** "name" is the distinguished name to look up +** "keyID" is the value of the subjectKeyID to match +*/ +extern CERTCertificate *CERT_FindCertByKeyID(CERTCertDBHandle *handle, + SECItem *name, SECItem *keyID); + +/* +** Generate a certificate key from the issuer and serialnumber, then look it +** up in the database. Return the cert if found. +** "issuerAndSN" is the issuer and serial number to look for +*/ +extern CERTCertificate *CERT_FindCertByIssuerAndSN( + CERTCertDBHandle *handle, CERTIssuerAndSN *issuerAndSN); +extern CERTCertificate *CERT_FindCertByIssuerAndSNCX( + CERTCertDBHandle *handle, CERTIssuerAndSN *issuerAndSN, void *wincx); + +/* +** Find a certificate in the database by a subject key ID +** "subjKeyID" is the subject Key ID to look for +*/ +extern CERTCertificate *CERT_FindCertBySubjectKeyID(CERTCertDBHandle *handle, + SECItem *subjKeyID); + +/* +** Encode Certificate SKID (Subject Key ID) extension. +** +*/ +extern SECStatus CERT_EncodeSubjectKeyID(PLArenaPool *arena, + const SECItem *srcString, + SECItem *encodedValue); + +/* +** Find a certificate in the database by a nickname +** "nickname" is the ascii string nickname to look for +*/ +extern CERTCertificate *CERT_FindCertByNickname(CERTCertDBHandle *handle, + const char *nickname); + +/* +** Find a certificate in the database by a DER encoded certificate +** "derCert" is the DER encoded certificate +*/ +extern CERTCertificate *CERT_FindCertByDERCert(CERTCertDBHandle *handle, + SECItem *derCert); + +/* +** Find a certificate in the database by a email address +** "emailAddr" is the email address to look up +*/ +CERTCertificate *CERT_FindCertByEmailAddr(CERTCertDBHandle *handle, + char *emailAddr); + +/* +** Find a certificate in the database by a email address or nickname +** "name" is the email address or nickname to look up +*/ +CERTCertificate *CERT_FindCertByNicknameOrEmailAddr(CERTCertDBHandle *handle, + const char *name); +CERTCertificate *CERT_FindCertByNicknameOrEmailAddrCX(CERTCertDBHandle *handle, + const char *name, + void *wincx); + +/* +** Find a certificate in the database by a email address or nickname +** and require it to have the given usage. +** "name" is the email address or nickname to look up +*/ +CERTCertificate *CERT_FindCertByNicknameOrEmailAddrForUsage( + CERTCertDBHandle *handle, const char *name, SECCertUsage lookingForUsage); +CERTCertificate *CERT_FindCertByNicknameOrEmailAddrForUsageCX( + CERTCertDBHandle *handle, const char *name, SECCertUsage lookingForUsage, + void *wincx); + +/* +** Find a certificate in the database by a digest of a subject public key +** "spkDigest" is the digest to look up +*/ +extern CERTCertificate *CERT_FindCertBySPKDigest(CERTCertDBHandle *handle, + SECItem *spkDigest); + +/* + * Find the issuer of a cert + */ +CERTCertificate *CERT_FindCertIssuer(CERTCertificate *cert, PRTime validTime, + SECCertUsage usage); + +/* +** Check the validity times of a certificate vs. time 't', allowing +** some slop for broken clocks and stuff. +** "cert" is the certificate to be checked +** "t" is the time to check against +** "allowOverride" if true then check to see if the invalidity has +** been overridden by the user. +*/ +extern SECCertTimeValidity CERT_CheckCertValidTimes(const CERTCertificate *cert, + PRTime t, + PRBool allowOverride); + +/* +** WARNING - this function is deprecated, and will either go away or have +** a new API in the near future. +** +** Check the validity times of a certificate vs. the current time, allowing +** some slop for broken clocks and stuff. +** "cert" is the certificate to be checked +*/ +extern SECStatus CERT_CertTimesValid(CERTCertificate *cert); + +/* +** Extract the validity times from a certificate +** "c" is the certificate +** "notBefore" is the start of the validity period +** "notAfter" is the end of the validity period +*/ +extern SECStatus CERT_GetCertTimes(const CERTCertificate *c, PRTime *notBefore, + PRTime *notAfter); + +/* +** Extract the issuer and serial number from a certificate +*/ +extern CERTIssuerAndSN *CERT_GetCertIssuerAndSN(PLArenaPool *, + CERTCertificate *); + +/* +** verify the signature of a signed data object with a given certificate +** "sd" the signed data object to be verified +** "cert" the certificate to use to check the signature +*/ +extern SECStatus CERT_VerifySignedData(CERTSignedData *sd, + CERTCertificate *cert, PRTime t, + void *wincx); +/* +** verify the signature of a signed data object with the given DER publickey +*/ +extern SECStatus CERT_VerifySignedDataWithPublicKeyInfo( + CERTSignedData *sd, CERTSubjectPublicKeyInfo *pubKeyInfo, void *wincx); + +/* +** verify the signature of a signed data object with a SECKEYPublicKey. +*/ +extern SECStatus CERT_VerifySignedDataWithPublicKey(const CERTSignedData *sd, + SECKEYPublicKey *pubKey, + void *wincx); + +/* +** NEW FUNCTIONS with new bit-field-FIELD SECCertificateUsage - please use +** verify a certificate by checking validity times against a certain time, +** that we trust the issuer, and that the signature on the certificate is +** valid. +** "cert" the certificate to verify +** "checkSig" only check signatures if true +*/ +extern SECStatus CERT_VerifyCertificate(CERTCertDBHandle *handle, + CERTCertificate *cert, PRBool checkSig, + SECCertificateUsage requiredUsages, + PRTime t, void *wincx, + CERTVerifyLog *log, + SECCertificateUsage *returnedUsages); + +/* same as above, but uses current time */ +extern SECStatus CERT_VerifyCertificateNow(CERTCertDBHandle *handle, + CERTCertificate *cert, + PRBool checkSig, + SECCertificateUsage requiredUsages, + void *wincx, + SECCertificateUsage *returnedUsages); + +/* +** Verify that a CA cert can certify some (unspecified) leaf cert for a given +** purpose. This is used by UI code to help identify where a chain may be +** broken and why. This takes identical parameters to CERT_VerifyCert +*/ +extern SECStatus CERT_VerifyCACertForUsage(CERTCertDBHandle *handle, + CERTCertificate *cert, + PRBool checkSig, + SECCertUsage certUsage, PRTime t, + void *wincx, CERTVerifyLog *log); + +/* +** OLD OBSOLETE FUNCTIONS with enum SECCertUsage - DO NOT USE FOR NEW CODE +** verify a certificate by checking validity times against a certain time, +** that we trust the issuer, and that the signature on the certificate is +** valid. +** "cert" the certificate to verify +** "checkSig" only check signatures if true +*/ +extern SECStatus CERT_VerifyCert(CERTCertDBHandle *handle, + CERTCertificate *cert, PRBool checkSig, + SECCertUsage certUsage, PRTime t, void *wincx, + CERTVerifyLog *log); + +/* same as above, but uses current time */ +extern SECStatus CERT_VerifyCertNow(CERTCertDBHandle *handle, + CERTCertificate *cert, PRBool checkSig, + SECCertUsage certUsage, void *wincx); + +SECStatus CERT_VerifyCertChain(CERTCertDBHandle *handle, CERTCertificate *cert, + PRBool checkSig, SECCertUsage certUsage, + PRTime t, void *wincx, CERTVerifyLog *log); + +/* +** Read a base64 ascii encoded DER certificate and convert it to our +** internal format. +** "certstr" is a null-terminated string containing the certificate +*/ +extern CERTCertificate *CERT_ConvertAndDecodeCertificate(char *certstr); + +/* +** Read a certificate in some foreign format, and convert it to our +** internal format. +** "certbuf" is the buffer containing the certificate +** "certlen" is the length of the buffer +** NOTE - currently supports netscape base64 ascii encoded raw certs +** and netscape binary DER typed files. +*/ +extern CERTCertificate *CERT_DecodeCertFromPackage(char *certbuf, int certlen); + +extern SECStatus CERT_ImportCAChain(SECItem *certs, int numcerts, + SECCertUsage certUsage); + +extern SECStatus CERT_ImportCAChainTrusted(SECItem *certs, int numcerts, + SECCertUsage certUsage); + +/* +** Read a certificate chain in some foreign format, and pass it to a +** callback function. +** "certbuf" is the buffer containing the certificate +** "certlen" is the length of the buffer +** "f" is the callback function +** "arg" is the callback argument +*/ +typedef SECStatus(PR_CALLBACK *CERTImportCertificateFunc)(void *arg, + SECItem **certs, + int numcerts); + +extern SECStatus CERT_DecodeCertPackage(char *certbuf, int certlen, + CERTImportCertificateFunc f, void *arg); + +/* +** Returns the value of an AVA. This was a formerly static +** function that has been exposed due to the need to decode +** and convert unicode strings to UTF8. +** +** XXX This function resides in certhtml.c, should it be +** moved elsewhere? +*/ +extern SECItem *CERT_DecodeAVAValue(const SECItem *derAVAValue); + +/* +** extract various element strings from a distinguished name. +** "name" the distinguished name +*/ + +extern char *CERT_GetCertificateEmailAddress(CERTCertificate *cert); + +extern char *CERT_GetCertEmailAddress(const CERTName *name); + +extern const char *CERT_GetFirstEmailAddress(CERTCertificate *cert); + +extern const char *CERT_GetNextEmailAddress(CERTCertificate *cert, + const char *prev); + +/* The return value must be freed with PORT_Free. */ +extern char *CERT_GetCommonName(const CERTName *name); + +extern char *CERT_GetCountryName(const CERTName *name); + +extern char *CERT_GetLocalityName(const CERTName *name); + +extern char *CERT_GetStateName(const CERTName *name); + +extern char *CERT_GetOrgName(const CERTName *name); + +extern char *CERT_GetOrgUnitName(const CERTName *name); + +extern char *CERT_GetDomainComponentName(const CERTName *name); + +extern char *CERT_GetCertUid(const CERTName *name); + +/* manipulate the trust parameters of a certificate */ + +extern SECStatus CERT_GetCertTrust(const CERTCertificate *cert, + CERTCertTrust *trust); + +extern SECStatus CERT_ChangeCertTrust(CERTCertDBHandle *handle, + CERTCertificate *cert, + CERTCertTrust *trust); + +extern SECStatus CERT_ChangeCertTrustByUsage(CERTCertDBHandle *certdb, + CERTCertificate *cert, + SECCertUsage usage); + +/************************************************************************* + * + * manipulate the extensions of a certificate + * + ************************************************************************/ + +/* +** Set up a cert for adding X509v3 extensions. Returns an opaque handle +** used by the next two routines. +** "cert" is the certificate we are adding extensions to +*/ +extern void *CERT_StartCertExtensions(CERTCertificate *cert); + +/* +** Add an extension to a certificate. +** "exthandle" is the handle returned by the previous function +** "idtag" is the integer tag for the OID that should ID this extension +** "value" is the value of the extension +** "critical" is the critical extension flag +** "copyData" is a flag indicating whether the value data should be +** copied. +*/ +extern SECStatus CERT_AddExtension(void *exthandle, int idtag, SECItem *value, + PRBool critical, PRBool copyData); + +extern SECStatus CERT_AddExtensionByOID(void *exthandle, SECItem *oid, + SECItem *value, PRBool critical, + PRBool copyData); + +extern SECStatus CERT_EncodeAndAddExtension(void *exthandle, int idtag, + void *value, PRBool critical, + const SEC_ASN1Template *atemplate); + +extern SECStatus CERT_EncodeAndAddBitStrExtension(void *exthandle, int idtag, + SECItem *value, + PRBool critical); + +extern SECStatus CERT_EncodeAltNameExtension(PLArenaPool *arena, + CERTGeneralName *value, + SECItem *encodedValue); + +/* +** Finish adding cert extensions. Does final processing on extension +** data, putting it in the right format, and freeing any temporary +** storage. +** "exthandle" is the handle used to add extensions to a certificate +*/ +extern SECStatus CERT_FinishExtensions(void *exthandle); + +/* +** Merge an external list of extensions into a cert's extension list, adding one +** only when its OID matches none of the cert's existing extensions. Call this +** immediately before calling CERT_FinishExtensions(). +*/ +SECStatus CERT_MergeExtensions(void *exthandle, CERTCertExtension **exts); + +/* If the extension is found, return its criticality and value. +** This allocate storage for the returning extension value. +*/ +extern SECStatus CERT_GetExtenCriticality(CERTCertExtension **extensions, + int tag, PRBool *isCritical); + +extern void CERT_DestroyOidSequence(CERTOidSequence *oidSeq); + +/**************************************************************************** + * + * DER encode and decode extension values + * + ****************************************************************************/ + +/* Encode the value of the basicConstraint extension. +** arena - where to allocate memory for the encoded value. +** value - extension value to encode +** encodedValue - output encoded value +*/ +extern SECStatus CERT_EncodeBasicConstraintValue(PLArenaPool *arena, + CERTBasicConstraints *value, + SECItem *encodedValue); + +/* +** Encode the value of the authorityKeyIdentifier extension. +*/ +extern SECStatus CERT_EncodeAuthKeyID(PLArenaPool *arena, CERTAuthKeyID *value, + SECItem *encodedValue); + +/* +** Encode the value of the crlDistributionPoints extension. +*/ +extern SECStatus CERT_EncodeCRLDistributionPoints( + PLArenaPool *arena, CERTCrlDistributionPoints *value, SECItem *derValue); + +/* +** Decodes a DER encoded basicConstaint extension value into a readable format +** value - decoded value +** encodedValue - value to decoded +*/ +extern SECStatus CERT_DecodeBasicConstraintValue(CERTBasicConstraints *value, + const SECItem *encodedValue); + +/* Decodes a DER encoded authorityKeyIdentifier extension value into a +** readable format. +** arena - where to allocate memory for the decoded value +** encodedValue - value to be decoded +** Returns a CERTAuthKeyID structure which contains the decoded value +*/ +extern CERTAuthKeyID *CERT_DecodeAuthKeyID(PLArenaPool *arena, + const SECItem *encodedValue); + +/* Decodes a DER encoded crlDistributionPoints extension value into a +** readable format. +** arena - where to allocate memory for the decoded value +** der - value to be decoded +** Returns a CERTCrlDistributionPoints structure which contains the +** decoded value +*/ +extern CERTCrlDistributionPoints *CERT_DecodeCRLDistributionPoints( + PLArenaPool *arena, SECItem *der); + +/* Extract certain name type from a generalName */ +extern void *CERT_GetGeneralNameByType(CERTGeneralName *genNames, + CERTGeneralNameType type, + PRBool derFormat); + +extern CERTOidSequence *CERT_DecodeOidSequence(const SECItem *seqItem); + +/**************************************************************************** + * + * Find extension values of a certificate + * + ***************************************************************************/ + +extern SECStatus CERT_FindCertExtension(const CERTCertificate *cert, int tag, + SECItem *value); + +extern SECStatus CERT_FindNSCertTypeExtension(CERTCertificate *cert, + SECItem *value); + +extern char *CERT_FindNSStringExtension(CERTCertificate *cert, int oidtag); + +extern SECStatus CERT_FindCertExtensionByOID(CERTCertificate *cert, + SECItem *oid, SECItem *value); + +/* Returns the decoded value of the authKeyID extension. +** Note that this uses passed in the arena to allocate storage for the result +*/ +extern CERTAuthKeyID *CERT_FindAuthKeyIDExten(PLArenaPool *arena, + CERTCertificate *cert); + +/* Returns the decoded value of the basicConstraint extension. + */ +extern SECStatus CERT_FindBasicConstraintExten(CERTCertificate *cert, + CERTBasicConstraints *value); + +/* Returns the decoded value of the crlDistributionPoints extension. +** Note that the arena in cert is used to allocate storage for the result +*/ +extern CERTCrlDistributionPoints *CERT_FindCRLDistributionPoints( + CERTCertificate *cert); + +/* Returns value of the keyUsage extension. This uses PR_Alloc to allocate +** buffer for the decoded value. The caller should free up the storage +** allocated in value->data. +*/ +extern SECStatus CERT_FindKeyUsageExtension(CERTCertificate *cert, + SECItem *value); + +/* Return the decoded value of the subjectKeyID extension. The caller should +** free up the storage allocated in retItem->data. +*/ +extern SECStatus CERT_FindSubjectKeyIDExtension(CERTCertificate *cert, + SECItem *retItem); + +/* +** If cert is a v3 certificate, and a critical keyUsage extension is included, +** then check the usage against the extension value. If a non-critical +** keyUsage extension is included, this will return SECSuccess without +** checking, since the extension is an advisory field, not a restriction. +** If cert is not a v3 certificate, this will return SECSuccess. +** cert - certificate +** usage - one of the x.509 v3 the Key Usage Extension flags +*/ +extern SECStatus CERT_CheckCertUsage(CERTCertificate *cert, + unsigned char usage); + +/**************************************************************************** + * + * CRL v2 Extensions supported routines + * + ****************************************************************************/ + +extern SECStatus CERT_FindCRLExtensionByOID(CERTCrl *crl, SECItem *oid, + SECItem *value); + +extern SECStatus CERT_FindCRLExtension(CERTCrl *crl, int tag, SECItem *value); + +extern SECStatus CERT_FindInvalidDateExten(CERTCrl *crl, PRTime *value); + +/* +** Set up a crl for adding X509v3 extensions. Returns an opaque handle +** used by routines that take an exthandle (void*) argument . +** "crl" is the CRL we are adding extensions to +*/ +extern void *CERT_StartCRLExtensions(CERTCrl *crl); + +/* +** Set up a crl entry for adding X509v3 extensions. Returns an opaque handle +** used by routines that take an exthandle (void*) argument . +** "crl" is the crl we are adding certs entries to +** "entry" is the crl entry we are adding extensions to +*/ +extern void *CERT_StartCRLEntryExtensions(CERTCrl *crl, CERTCrlEntry *entry); + +extern CERTCertNicknames *CERT_GetCertNicknames(CERTCertDBHandle *handle, + int what, void *wincx); + +/* +** Finds the crlNumber extension and decodes its value into 'value' +*/ +extern SECStatus CERT_FindCRLNumberExten(PLArenaPool *arena, CERTCrl *crl, + SECItem *value); + +extern SECStatus CERT_FindCRLEntryReasonExten(CERTCrlEntry *crlEntry, + CERTCRLEntryReasonCode *value); + +extern void CERT_FreeNicknames(CERTCertNicknames *nicknames); + +extern PRBool CERT_CompareCerts(const CERTCertificate *c1, + const CERTCertificate *c2); + +extern PRBool CERT_CompareCertsForRedirection(CERTCertificate *c1, + CERTCertificate *c2); + +/* +** Generate an array of the Distinguished Names that the given cert database +** "trusts" +*/ +extern CERTDistNames *CERT_GetSSLCACerts(CERTCertDBHandle *handle); + +extern void CERT_FreeDistNames(CERTDistNames *names); + +/* Duplicate distinguished name array */ +extern CERTDistNames *CERT_DupDistNames(CERTDistNames *orig); + +/* +** Generate an array of Distinguished names from an array of nicknames +*/ +extern CERTDistNames *CERT_DistNamesFromNicknames(CERTCertDBHandle *handle, + char **nicknames, int nnames); + +/* +** Generate an array of Distinguished names from a list of certs. +*/ +extern CERTDistNames *CERT_DistNamesFromCertList(CERTCertList *list); + +/* +** Generate a certificate chain from a certificate. +*/ +extern CERTCertificateList *CERT_CertChainFromCert(CERTCertificate *cert, + SECCertUsage usage, + PRBool includeRoot); + +extern CERTCertificateList *CERT_CertListFromCert(CERTCertificate *cert); + +extern CERTCertificateList *CERT_DupCertList( + const CERTCertificateList *oldList); + +extern void CERT_DestroyCertificateList(CERTCertificateList *list); + +/* +** is cert a user cert? i.e. does it have CERTDB_USER trust, +** i.e. a private key? +*/ +PRBool CERT_IsUserCert(CERTCertificate *cert); + +/* is cert a newer than cert b? */ +PRBool CERT_IsNewer(CERTCertificate *certa, CERTCertificate *certb); + +/* currently a stub for address book */ +PRBool CERT_IsCertRevoked(CERTCertificate *cert); + +void CERT_DestroyCertArray(CERTCertificate **certs, unsigned int ncerts); + +/* convert an email address to lower case */ +char *CERT_FixupEmailAddr(const char *emailAddr); + +/* decode string representation of trust flags into trust struct */ +SECStatus CERT_DecodeTrustString(CERTCertTrust *trust, const char *trusts); + +/* encode trust struct into string representation of trust flags */ +char *CERT_EncodeTrustString(CERTCertTrust *trust); + +/* find the next or prev cert in a subject list */ +CERTCertificate *CERT_PrevSubjectCert(CERTCertificate *cert); +CERTCertificate *CERT_NextSubjectCert(CERTCertificate *cert); + +/* + * import a collection of certs into the temporary or permanent cert + * database + */ +SECStatus CERT_ImportCerts(CERTCertDBHandle *certdb, SECCertUsage usage, + unsigned int ncerts, SECItem **derCerts, + CERTCertificate ***retCerts, PRBool keepCerts, + PRBool caOnly, char *nickname); + +char *CERT_MakeCANickname(CERTCertificate *cert); + +PRBool CERT_IsCACert(CERTCertificate *cert, unsigned int *rettype); + +PRBool CERT_IsCADERCert(SECItem *derCert, unsigned int *rettype); + +PRBool CERT_IsRootDERCert(SECItem *derCert); + +SECStatus CERT_SaveSMimeProfile(CERTCertificate *cert, SECItem *emailProfile, + SECItem *profileTime); + +/* + * find the smime symmetric capabilities profile for a given cert + */ +SECItem *CERT_FindSMimeProfile(CERTCertificate *cert); + +SECStatus CERT_AddNewCerts(CERTCertDBHandle *handle); + +CERTCertificatePolicies *CERT_DecodeCertificatePoliciesExtension( + const SECItem *extnValue); + +void CERT_DestroyCertificatePoliciesExtension( + CERTCertificatePolicies *policies); + +CERTCertificatePolicyMappings *CERT_DecodePolicyMappingsExtension( + SECItem *encodedCertPolicyMaps); + +SECStatus CERT_DestroyPolicyMappingsExtension( + CERTCertificatePolicyMappings *mappings); + +SECStatus CERT_DecodePolicyConstraintsExtension( + CERTCertificatePolicyConstraints *decodedValue, + const SECItem *encodedValue); + +SECStatus CERT_DecodeInhibitAnyExtension( + CERTCertificateInhibitAny *decodedValue, SECItem *extnValue); + +CERTUserNotice *CERT_DecodeUserNotice(SECItem *noticeItem); + +extern CERTGeneralName *CERT_DecodeAltNameExtension(PLArenaPool *reqArena, + SECItem *EncodedAltName); + +extern CERTNameConstraints *CERT_DecodeNameConstraintsExtension( + PLArenaPool *arena, const SECItem *encodedConstraints); + +/* returns addr of a NULL termainated array of pointers to CERTAuthInfoAccess */ +extern CERTAuthInfoAccess **CERT_DecodeAuthInfoAccessExtension( + PLArenaPool *reqArena, const SECItem *encodedExtension); + +extern CERTPrivKeyUsagePeriod *CERT_DecodePrivKeyUsagePeriodExtension( + PLArenaPool *arena, SECItem *extnValue); + +extern CERTGeneralName *CERT_GetNextGeneralName(CERTGeneralName *current); + +extern CERTGeneralName *CERT_GetPrevGeneralName(CERTGeneralName *current); + +/* + * Look up name constraints for some certs that do not include name constraints + * (Most importantly, root certificates) + * + * If a matching subject is found, |extensions| will be populated with a copy of + * the + * DER-encoded name constraints extension. The data in |extensions| will point + * to + * memory that the caller owns. + * + * There is no mechanism to configure imposed name constraints right now. All + * imposed name constraints are built into NSS. + */ +SECStatus CERT_GetImposedNameConstraints(const SECItem *derSubject, + SECItem *extensions); + +CERTNameConstraint *CERT_GetNextNameConstraint(CERTNameConstraint *current); + +CERTNameConstraint *CERT_GetPrevNameConstraint(CERTNameConstraint *current); + +void CERT_DestroyUserNotice(CERTUserNotice *userNotice); + +typedef char *(*CERTPolicyStringCallback)(char *org, unsigned long noticeNumber, + void *arg); +void CERT_SetCAPolicyStringCallback(CERTPolicyStringCallback cb, void *cbarg); + +char *CERT_GetCertCommentString(CERTCertificate *cert); + +PRBool CERT_GovtApprovedBitSet(CERTCertificate *cert); + +SECStatus CERT_AddPermNickname(CERTCertificate *cert, char *nickname); + +CERTCertList *CERT_MatchUserCert(CERTCertDBHandle *handle, SECCertUsage usage, + int nCANames, char **caNames, void *proto_win); + +CERTCertList *CERT_NewCertList(void); + +/* free the cert list and all the certs in the list */ +void CERT_DestroyCertList(CERTCertList *certs); + +/* remove the node and free the cert */ +void CERT_RemoveCertListNode(CERTCertListNode *node); + +/* equivalent to CERT_AddCertToListTailWithData(certs, cert, NULL) */ +SECStatus CERT_AddCertToListTail(CERTCertList *certs, CERTCertificate *cert); + +/* equivalent to CERT_AddCertToListHeadWithData(certs, cert, NULL) */ +SECStatus CERT_AddCertToListHead(CERTCertList *certs, CERTCertificate *cert); + +/* + * The new cert list node takes ownership of "cert". "cert" is freed + * when the list node is removed. + */ +SECStatus CERT_AddCertToListTailWithData(CERTCertList *certs, + CERTCertificate *cert, void *appData); + +/* + * The new cert list node takes ownership of "cert". "cert" is freed + * when the list node is removed. + */ +SECStatus CERT_AddCertToListHeadWithData(CERTCertList *certs, + CERTCertificate *cert, void *appData); + +typedef PRBool (*CERTSortCallback)(CERTCertificate *certa, + CERTCertificate *certb, void *arg); +SECStatus CERT_AddCertToListSorted(CERTCertList *certs, CERTCertificate *cert, + CERTSortCallback f, void *arg); + +/* callback for CERT_AddCertToListSorted that sorts based on validity + * period and a given time. + */ +PRBool CERT_SortCBValidity(CERTCertificate *certa, CERTCertificate *certb, + void *arg); + +SECStatus CERT_CheckForEvilCert(CERTCertificate *cert); + +CERTGeneralName *CERT_GetCertificateNames(CERTCertificate *cert, + PLArenaPool *arena); + +CERTGeneralName *CERT_GetConstrainedCertificateNames( + const CERTCertificate *cert, PLArenaPool *arena, + PRBool includeSubjectCommonName); + +/* + * Creates or adds to a list of all certs with a give subject name, sorted by + * validity time, newest first. Invalid certs are considered older than + * valid certs. If validOnly is set, do not include invalid certs on list. + */ +CERTCertList *CERT_CreateSubjectCertList(CERTCertList *certList, + CERTCertDBHandle *handle, + const SECItem *name, PRTime sorttime, + PRBool validOnly); + +/* + * remove certs from a list that don't have keyUsage and certType + * that match the given usage. + */ +SECStatus CERT_FilterCertListByUsage(CERTCertList *certList, SECCertUsage usage, + PRBool ca); + +/* + * check the key usage of a cert against a set of required values + */ +SECStatus CERT_CheckKeyUsage(CERTCertificate *cert, unsigned int requiredUsage); + +/* + * return required key usage and cert type based on cert usage + */ +SECStatus CERT_KeyUsageAndTypeForCertUsage(SECCertUsage usage, PRBool ca, + unsigned int *retKeyUsage, + unsigned int *retCertType); +/* + * return required trust flags for various cert usages for CAs + */ +SECStatus CERT_TrustFlagsForCACertUsage(SECCertUsage usage, + unsigned int *retFlags, + SECTrustType *retTrustType); + +/* + * Find all user certificates that match the given criteria. + * + * "handle" - database to search + * "usage" - certificate usage to match + * "oneCertPerName" - if set then only return the "best" cert per + * name + * "validOnly" - only return certs that are curently valid + * "proto_win" - window handle passed to pkcs11 + */ +CERTCertList *CERT_FindUserCertsByUsage(CERTCertDBHandle *handle, + SECCertUsage usage, + PRBool oneCertPerName, PRBool validOnly, + void *proto_win); + +/* + * Find a user certificate that matchs the given criteria. + * + * "handle" - database to search + * "nickname" - nickname to match + * "usage" - certificate usage to match + * "validOnly" - only return certs that are curently valid + * "proto_win" - window handle passed to pkcs11 + */ +CERTCertificate *CERT_FindUserCertByUsage(CERTCertDBHandle *handle, + const char *nickname, + SECCertUsage usage, PRBool validOnly, + void *proto_win); + +/* + * Filter a list of certificates, removing those certs that do not have + * one of the named CA certs somewhere in their cert chain. + * + * "certList" - the list of certificates to filter + * "nCANames" - number of CA names + * "caNames" - array of CA names in string(rfc 1485) form + * "usage" - what use the certs are for, this is used when + * selecting CA certs + */ +SECStatus CERT_FilterCertListByCANames(CERTCertList *certList, int nCANames, + char **caNames, SECCertUsage usage); + +/* + * Filter a list of certificates, removing those certs that aren't user certs + */ +SECStatus CERT_FilterCertListForUserCerts(CERTCertList *certList); + +/* + * Filter a list of certificates, removing those certs that don't match the + * nickname. + */ +SECStatus CERT_FilterCertListByNickname(CERTCertList *certList, char *nickname, + void *pwarg); + +/* return true if cert is in cert list */ +PRBool CERT_IsInList(const CERTCertificate *cert, const CERTCertList *certList); + +/* returned certList is the intersection of the certs on certList and the + * certs on filterList */ +SECStatus CERT_FilterCertListByCertList(CERTCertList *certList, + const CERTCertList *filterList); + +/* + * Collect the nicknames from all certs in a CertList. If the cert is not + * valid, append a string to that nickname. + * + * "certList" - the list of certificates + * "expiredString" - the string to append to the nickname of any expired cert + * "notYetGoodString" - the string to append to the nickname of any cert + * that is not yet valid + */ +CERTCertNicknames *CERT_NicknameStringsFromCertList(CERTCertList *certList, + char *expiredString, + char *notYetGoodString); + +/* + * Extract the nickname from a nickmake string that may have either + * expiredString or notYetGoodString appended. + * + * Args: + * "namestring" - the string containing the nickname, and possibly + * one of the validity label strings + * "expiredString" - the expired validity label string + * "notYetGoodString" - the not yet good validity label string + * + * Returns the raw nickname + */ +char *CERT_ExtractNicknameString(char *namestring, char *expiredString, + char *notYetGoodString); + +/* + * Given a certificate, return a string containing the nickname, and possibly + * one of the validity strings, based on the current validity state of the + * certificate. + * + * "arena" - arena to allocate returned string from. If NULL, then heap + * is used. + * "cert" - the cert to get nickname from + * "expiredString" - the string to append to the nickname if the cert is + * expired. + * "notYetGoodString" - the string to append to the nickname if the cert is + * not yet good. + */ +char *CERT_GetCertNicknameWithValidity(PLArenaPool *arena, + CERTCertificate *cert, + char *expiredString, + char *notYetGoodString); + +/* + * Return the string representation of a DER encoded distinguished name + * "dername" - The DER encoded name to convert + */ +char *CERT_DerNameToAscii(SECItem *dername); + +/* + * Supported usage values and types: + * certUsageSSLClient + * certUsageSSLServer + * certUsageSSLServerWithStepUp + * certUsageEmailSigner + * certUsageEmailRecipient + * certUsageObjectSigner + */ + +CERTCertificate *CERT_FindMatchingCert(CERTCertDBHandle *handle, + SECItem *derName, CERTCertOwner owner, + SECCertUsage usage, PRBool preferTrusted, + PRTime validTime, PRBool validOnly); + +/* + * Acquire the global lock on the cert database. + * This lock is currently used for the following operations: + * adding or deleting a cert to either the temp or perm databases + * converting a temp to perm or perm to temp + * changing(maybe just adding?) the trust of a cert + * adjusting the reference count of a cert + */ +void CERT_LockDB(CERTCertDBHandle *handle); + +/* + * Free the global cert database lock. + */ +void CERT_UnlockDB(CERTCertDBHandle *handle); + +/* + * Get the certificate status checking configuratino data for + * the certificate database + */ +CERTStatusConfig *CERT_GetStatusConfig(CERTCertDBHandle *handle); + +/* + * Set the certificate status checking information for the + * database. The input structure becomes part of the certificate + * database and will be freed by calling the 'Destroy' function in + * the configuration object. + */ +void CERT_SetStatusConfig(CERTCertDBHandle *handle, CERTStatusConfig *config); + +/* + * Acquire the cert reference count lock + * There is currently one global lock for all certs, but I'm putting a cert + * arg here so that it will be easy to make it per-cert in the future if + * that turns out to be necessary. + */ +void CERT_LockCertRefCount(CERTCertificate *cert); + +/* + * Release the cert reference count lock + */ +void CERT_UnlockCertRefCount(CERTCertificate *cert); + +/* + * Digest the cert's subject public key using the specified algorithm. + * NOTE: this digests the value of the BIT STRING subjectPublicKey (excluding + * the tag, length, and number of unused bits) rather than the whole + * subjectPublicKeyInfo field. + * + * The necessary storage for the digest data is allocated. If "fill" is + * non-null, the data is put there, otherwise a SECItem is allocated. + * Allocation from "arena" if it is non-null, heap otherwise. Any problem + * results in a NULL being returned (and an appropriate error set). + */ +extern SECItem *CERT_GetSubjectPublicKeyDigest(PLArenaPool *arena, + const CERTCertificate *cert, + SECOidTag digestAlg, + SECItem *fill); + +/* + * Digest the cert's subject name using the specified algorithm. + */ +extern SECItem *CERT_GetSubjectNameDigest(PLArenaPool *arena, + const CERTCertificate *cert, + SECOidTag digestAlg, SECItem *fill); + +SECStatus CERT_CheckCRL(CERTCertificate *cert, CERTCertificate *issuer, + const SECItem *dp, PRTime t, void *wincx); + +/* + * Add a CERTNameConstraint to the CERTNameConstraint list + */ +extern CERTNameConstraint *CERT_AddNameConstraint( + CERTNameConstraint *list, CERTNameConstraint *constraint); + +/* + * Allocate space and copy CERTNameConstraint from src to dest. + * Arena is used to allocate result(if dest eq NULL) and its members + * SECItem data. + */ +extern CERTNameConstraint *CERT_CopyNameConstraint(PLArenaPool *arena, + CERTNameConstraint *dest, + CERTNameConstraint *src); + +/* + * Verify name against all the constraints relevant to that type of + * the name. + */ +extern SECStatus CERT_CheckNameSpace(PLArenaPool *arena, + const CERTNameConstraints *constraints, + const CERTGeneralName *currentName); + +/* + * Extract and allocate the name constraints extension from the CA cert. + * If the certificate contains no name constraints extension, but + * CERT_GetImposedNameConstraints returns a name constraints extension + * for the subject of the certificate, then that extension will be returned. + */ +extern SECStatus CERT_FindNameConstraintsExten( + PLArenaPool *arena, CERTCertificate *cert, + CERTNameConstraints **constraints); + +/* + * Initialize a new GERTGeneralName fields (link) + */ +extern CERTGeneralName *CERT_NewGeneralName(PLArenaPool *arena, + CERTGeneralNameType type); + +/* + * Lookup a CERTGeneralNameType constant by its human readable string. + */ +extern CERTGeneralNameType CERT_GetGeneralNameTypeFromString( + const char *string); + +/* + * PKIX extension encoding routines + */ +extern SECStatus CERT_EncodePolicyConstraintsExtension( + PLArenaPool *arena, CERTCertificatePolicyConstraints *constr, + SECItem *dest); +extern SECStatus CERT_EncodeInhibitAnyExtension( + PLArenaPool *arena, CERTCertificateInhibitAny *inhibitAny, SECItem *dest); +extern SECStatus CERT_EncodePolicyMappingExtension( + PLArenaPool *arena, CERTCertificatePolicyMappings *maps, SECItem *dest); + +extern SECStatus CERT_EncodeInfoAccessExtension(PLArenaPool *arena, + CERTAuthInfoAccess **info, + SECItem *dest); +extern SECStatus CERT_EncodeUserNotice(PLArenaPool *arena, + CERTUserNotice *notice, SECItem *dest); + +extern SECStatus CERT_EncodeDisplayText(PLArenaPool *arena, SECItem *text, + SECItem *dest); + +extern SECStatus CERT_EncodeCertPoliciesExtension(PLArenaPool *arena, + CERTPolicyInfo **info, + SECItem *dest); +extern SECStatus CERT_EncodeNoticeReference(PLArenaPool *arena, + CERTNoticeReference *reference, + SECItem *dest); + +/* + * Returns a pointer to a static structure. + */ +extern const CERTRevocationFlags *CERT_GetPKIXVerifyNistRevocationPolicy(void); + +/* + * Returns a pointer to a static structure. + */ +extern const CERTRevocationFlags *CERT_GetClassicOCSPEnabledSoftFailurePolicy( + void); + +/* + * Returns a pointer to a static structure. + */ +extern const CERTRevocationFlags *CERT_GetClassicOCSPEnabledHardFailurePolicy( + void); + +/* + * Returns a pointer to a static structure. + */ +extern const CERTRevocationFlags *CERT_GetClassicOCSPDisabledPolicy(void); + +/* + * Verify a Cert with libpkix + * paramsIn control the verification options. If a value isn't specified + * in paramsIn, it reverts to the application default. + * paramsOut specifies the parameters the caller would like to get back. + * the caller may pass NULL, in which case no parameters are returned. + */ +extern SECStatus CERT_PKIXVerifyCert(CERTCertificate *cert, + SECCertificateUsage usages, + CERTValInParam *paramsIn, + CERTValOutParam *paramsOut, void *wincx); + +/* Makes old cert validation APIs(CERT_VerifyCert, CERT_VerifyCertificate) + * to use libpkix validation engine. The function should be called ones at + * application initialization time. + * Function is not thread safe.*/ +extern SECStatus CERT_SetUsePKIXForValidation(PRBool enable); + +/* The function return PR_TRUE if cert validation should use + * libpkix cert validation engine. */ +extern PRBool CERT_GetUsePKIXForValidation(void); + +/* + * Allocate a parameter container of type CERTRevocationFlags, + * and allocate the inner arrays of the given sizes. + * To cleanup call CERT_DestroyCERTRevocationFlags. + */ +extern CERTRevocationFlags *CERT_AllocCERTRevocationFlags( + PRUint32 number_leaf_methods, PRUint32 number_leaf_pref_methods, + PRUint32 number_chain_methods, PRUint32 number_chain_pref_methods); + +/* + * Destroy the arrays inside flags, + * and destroy the object pointed to by flags, too. + */ +extern void CERT_DestroyCERTRevocationFlags(CERTRevocationFlags *flags); + +/* + * Get istemp and isperm fields from a cert in a thread safe way. + */ +extern SECStatus CERT_GetCertIsTemp(const CERTCertificate *cert, PRBool *istemp); +extern SECStatus CERT_GetCertIsPerm(const CERTCertificate *cert, PRBool *isperm); + +SEC_END_PROTOS + +#endif /* _CERT_H_ */ diff --git a/security/nss/lib/certdb/certdb.c b/security/nss/lib/certdb/certdb.c new file mode 100644 index 0000000000..0d7cdab768 --- /dev/null +++ b/security/nss/lib/certdb/certdb.c @@ -0,0 +1,3411 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Certificate handling code + */ + +#include "nssilock.h" +#include "prmon.h" +#include "prtime.h" +#include "cert.h" +#include "certi.h" +#include "secder.h" +#include "secoid.h" +#include "secasn1.h" +#include "genname.h" +#include "keyhi.h" +#include "secitem.h" +#include "certdb.h" +#include "prprf.h" +#include "sechash.h" +#include "prlong.h" +#include "certxutl.h" +#include "portreg.h" +#include "secerr.h" +#include "sslerr.h" +#include "pk11func.h" +#include "xconst.h" /* for CERT_DecodeAltNameExtension */ + +#include "pki.h" +#include "pki3hack.h" + +SEC_ASN1_MKSUB(CERT_TimeChoiceTemplate) +SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate) +SEC_ASN1_MKSUB(SEC_BitStringTemplate) +SEC_ASN1_MKSUB(SEC_IntegerTemplate) +SEC_ASN1_MKSUB(SEC_SkipTemplate) + +/* + * Certificate database handling code + */ + +const SEC_ASN1Template CERT_CertExtensionTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertExtension) }, + { SEC_ASN1_OBJECT_ID, offsetof(CERTCertExtension, id) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_BOOLEAN, /* XXX DER_DEFAULT */ + offsetof(CERTCertExtension, critical) }, + { SEC_ASN1_OCTET_STRING, offsetof(CERTCertExtension, value) }, + { 0 } +}; + +const SEC_ASN1Template CERT_SequenceOfCertExtensionTemplate[] = { + { SEC_ASN1_SEQUENCE_OF, 0, CERT_CertExtensionTemplate } +}; + +const SEC_ASN1Template CERT_TimeChoiceTemplate[] = { + { SEC_ASN1_CHOICE, offsetof(SECItem, type), 0, sizeof(SECItem) }, + { SEC_ASN1_UTC_TIME, 0, 0, siUTCTime }, + { SEC_ASN1_GENERALIZED_TIME, 0, 0, siGeneralizedTime }, + { 0 } +}; + +const SEC_ASN1Template CERT_ValidityTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTValidity) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTValidity, notBefore), + SEC_ASN1_SUB(CERT_TimeChoiceTemplate), 0 }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTValidity, notAfter), + SEC_ASN1_SUB(CERT_TimeChoiceTemplate), 0 }, + { 0 } +}; + +const SEC_ASN1Template CERT_CertificateTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertificate) }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | + SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, /* XXX DER_DEFAULT */ + offsetof(CERTCertificate, version), + SEC_ASN1_SUB(SEC_IntegerTemplate) }, + { SEC_ASN1_INTEGER, offsetof(CERTCertificate, serialNumber) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCertificate, signature), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_SAVE, offsetof(CERTCertificate, derIssuer) }, + { SEC_ASN1_INLINE, offsetof(CERTCertificate, issuer), CERT_NameTemplate }, + { SEC_ASN1_INLINE, offsetof(CERTCertificate, validity), + CERT_ValidityTemplate }, + { SEC_ASN1_SAVE, offsetof(CERTCertificate, derSubject) }, + { SEC_ASN1_INLINE, offsetof(CERTCertificate, subject), CERT_NameTemplate }, + { SEC_ASN1_SAVE, offsetof(CERTCertificate, derPublicKey) }, + { SEC_ASN1_INLINE, offsetof(CERTCertificate, subjectPublicKeyInfo), + CERT_SubjectPublicKeyInfoTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 1, + offsetof(CERTCertificate, issuerID), + SEC_ASN1_SUB(SEC_BitStringTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 2, + offsetof(CERTCertificate, subjectID), + SEC_ASN1_SUB(SEC_BitStringTemplate) }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | + SEC_ASN1_CONTEXT_SPECIFIC | 3, + offsetof(CERTCertificate, extensions), + CERT_SequenceOfCertExtensionTemplate }, + { 0 } +}; + +const SEC_ASN1Template SEC_SignedCertificateTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertificate) }, + { SEC_ASN1_SAVE, offsetof(CERTCertificate, signatureWrap.data) }, + { SEC_ASN1_INLINE, 0, CERT_CertificateTemplate }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, + offsetof(CERTCertificate, signatureWrap.signatureAlgorithm), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_BIT_STRING, offsetof(CERTCertificate, signatureWrap.signature) }, + { 0 } +}; + +/* + * Find the subjectName in a DER encoded certificate + */ +const SEC_ASN1Template SEC_CertSubjectTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECItem) }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | + SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, + 0, SEC_ASN1_SUB(SEC_SkipTemplate) }, /* version */ + { SEC_ASN1_SKIP }, /* serial number */ + { SEC_ASN1_SKIP }, /* signature algorithm */ + { SEC_ASN1_SKIP }, /* issuer */ + { SEC_ASN1_SKIP }, /* validity */ + { SEC_ASN1_ANY, 0, NULL }, /* subject */ + { SEC_ASN1_SKIP_REST }, + { 0 } +}; + +/* + * Find the issuerName in a DER encoded certificate + */ +const SEC_ASN1Template SEC_CertIssuerTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECItem) }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | + SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, + 0, SEC_ASN1_SUB(SEC_SkipTemplate) }, /* version */ + { SEC_ASN1_SKIP }, /* serial number */ + { SEC_ASN1_SKIP }, /* signature algorithm */ + { SEC_ASN1_ANY, 0, NULL }, /* issuer */ + { SEC_ASN1_SKIP_REST }, + { 0 } +}; +/* + * Find the subjectName in a DER encoded certificate + */ +const SEC_ASN1Template SEC_CertSerialNumberTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECItem) }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | + SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, + 0, SEC_ASN1_SUB(SEC_SkipTemplate) }, /* version */ + { SEC_ASN1_ANY, 0, NULL }, /* serial number */ + { SEC_ASN1_SKIP_REST }, + { 0 } +}; + +/* + * Find the issuer and serialNumber in a DER encoded certificate. + * This data is used as the database lookup key since its the unique + * identifier of a certificate. + */ +const SEC_ASN1Template CERT_CertKeyTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertKey) }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | + SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, + 0, SEC_ASN1_SUB(SEC_SkipTemplate) }, /* version */ + { SEC_ASN1_INTEGER, offsetof(CERTCertKey, serialNumber) }, + { SEC_ASN1_SKIP }, /* signature algorithm */ + { SEC_ASN1_ANY, offsetof(CERTCertKey, derIssuer) }, + { SEC_ASN1_SKIP_REST }, + { 0 } +}; + +SEC_ASN1_CHOOSER_IMPLEMENT(CERT_TimeChoiceTemplate) +SEC_ASN1_CHOOSER_IMPLEMENT(CERT_CertificateTemplate) +SEC_ASN1_CHOOSER_IMPLEMENT(SEC_SignedCertificateTemplate) +SEC_ASN1_CHOOSER_IMPLEMENT(CERT_SequenceOfCertExtensionTemplate) + +SECStatus +CERT_KeyFromIssuerAndSN(PLArenaPool *arena, SECItem *issuer, SECItem *sn, + SECItem *key) +{ + key->len = sn->len + issuer->len; + + if ((sn->data == NULL) || (issuer->data == NULL)) { + goto loser; + } + + key->data = (unsigned char *)PORT_ArenaAlloc(arena, key->len); + if (!key->data) { + goto loser; + } + + /* copy the serialNumber */ + PORT_Memcpy(key->data, sn->data, sn->len); + + /* copy the issuer */ + PORT_Memcpy(&key->data[sn->len], issuer->data, issuer->len); + + return (SECSuccess); + +loser: + return (SECFailure); +} + +/* + * Extract the subject name from a DER certificate + */ +SECStatus +CERT_NameFromDERCert(SECItem *derCert, SECItem *derName) +{ + int rv; + PLArenaPool *arena; + CERTSignedData sd; + void *tmpptr; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + + if (!arena) { + return (SECFailure); + } + + PORT_Memset(&sd, 0, sizeof(CERTSignedData)); + rv = SEC_QuickDERDecodeItem(arena, &sd, CERT_SignedDataTemplate, derCert); + + if (rv) { + goto loser; + } + + PORT_Memset(derName, 0, sizeof(SECItem)); + rv = SEC_QuickDERDecodeItem(arena, derName, SEC_CertSubjectTemplate, + &sd.data); + + if (rv) { + goto loser; + } + + tmpptr = derName->data; + derName->data = (unsigned char *)PORT_Alloc(derName->len); + if (derName->data == NULL) { + goto loser; + } + + PORT_Memcpy(derName->data, tmpptr, derName->len); + + PORT_FreeArena(arena, PR_FALSE); + return (SECSuccess); + +loser: + PORT_FreeArena(arena, PR_FALSE); + return (SECFailure); +} + +SECStatus +CERT_IssuerNameFromDERCert(SECItem *derCert, SECItem *derName) +{ + int rv; + PORTCheapArenaPool tmpArena; + CERTSignedData sd; + void *tmpptr; + + PORT_InitCheapArena(&tmpArena, DER_DEFAULT_CHUNKSIZE); + + PORT_Memset(&sd, 0, sizeof(CERTSignedData)); + rv = SEC_QuickDERDecodeItem(&tmpArena.arena, &sd, CERT_SignedDataTemplate, + derCert); + if (rv) { + goto loser; + } + + PORT_Memset(derName, 0, sizeof(SECItem)); + rv = SEC_QuickDERDecodeItem(&tmpArena.arena, derName, + SEC_CertIssuerTemplate, &sd.data); + if (rv) { + goto loser; + } + + tmpptr = derName->data; + derName->data = (unsigned char *)PORT_Alloc(derName->len); + if (derName->data == NULL) { + goto loser; + } + + PORT_Memcpy(derName->data, tmpptr, derName->len); + + PORT_DestroyCheapArena(&tmpArena); + return (SECSuccess); + +loser: + PORT_DestroyCheapArena(&tmpArena); + return (SECFailure); +} + +SECStatus +CERT_SerialNumberFromDERCert(SECItem *derCert, SECItem *derName) +{ + int rv; + PORTCheapArenaPool tmpArena; + CERTSignedData sd; + void *tmpptr; + + PORT_InitCheapArena(&tmpArena, DER_DEFAULT_CHUNKSIZE); + + PORT_Memset(&sd, 0, sizeof(CERTSignedData)); + rv = SEC_QuickDERDecodeItem(&tmpArena.arena, &sd, CERT_SignedDataTemplate, + derCert); + if (rv) { + goto loser; + } + + PORT_Memset(derName, 0, sizeof(SECItem)); + rv = SEC_QuickDERDecodeItem(&tmpArena.arena, derName, + SEC_CertSerialNumberTemplate, &sd.data); + if (rv) { + goto loser; + } + + tmpptr = derName->data; + derName->data = (unsigned char *)PORT_Alloc(derName->len); + if (derName->data == NULL) { + goto loser; + } + + PORT_Memcpy(derName->data, tmpptr, derName->len); + + PORT_DestroyCheapArena(&tmpArena); + return (SECSuccess); + +loser: + PORT_DestroyCheapArena(&tmpArena); + return (SECFailure); +} + +/* + * Generate a database key, based on serial number and issuer, from a + * DER certificate. + */ +SECStatus +CERT_KeyFromDERCert(PLArenaPool *reqArena, SECItem *derCert, SECItem *key) +{ + int rv; + CERTSignedData sd; + CERTCertKey certkey; + + if (!reqArena) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + PORT_Memset(&sd, 0, sizeof(CERTSignedData)); + rv = + SEC_QuickDERDecodeItem(reqArena, &sd, CERT_SignedDataTemplate, derCert); + + if (rv) { + goto loser; + } + + PORT_Memset(&certkey, 0, sizeof(CERTCertKey)); + rv = SEC_QuickDERDecodeItem(reqArena, &certkey, CERT_CertKeyTemplate, + &sd.data); + + if (rv) { + goto loser; + } + + return (CERT_KeyFromIssuerAndSN(reqArena, &certkey.derIssuer, + &certkey.serialNumber, key)); +loser: + return (SECFailure); +} + +/* + * fill in keyUsage field of the cert based on the cert extension + * if the extension is not critical, then we allow all uses + */ +static SECStatus +GetKeyUsage(CERTCertificate *cert) +{ + SECStatus rv; + SECItem tmpitem; + + rv = CERT_FindKeyUsageExtension(cert, &tmpitem); + if (rv == SECSuccess) { + /* remember the actual value of the extension */ + cert->rawKeyUsage = tmpitem.len ? tmpitem.data[0] : 0; + cert->keyUsagePresent = PR_TRUE; + cert->keyUsage = cert->rawKeyUsage; + + PORT_Free(tmpitem.data); + tmpitem.data = NULL; + } else { + /* if the extension is not present, then we allow all uses */ + cert->keyUsage = KU_ALL; + cert->rawKeyUsage = KU_ALL; + cert->keyUsagePresent = PR_FALSE; + } + + if (CERT_GovtApprovedBitSet(cert)) { + cert->keyUsage |= KU_NS_GOVT_APPROVED; + cert->rawKeyUsage |= KU_NS_GOVT_APPROVED; + } + + return (SECSuccess); +} + +static SECStatus +findOIDinOIDSeqByTagNum(CERTOidSequence *seq, SECOidTag tagnum) +{ + SECItem **oids; + SECItem *oid; + SECStatus rv = SECFailure; + + if (seq != NULL) { + oids = seq->oids; + while (oids != NULL && *oids != NULL) { + oid = *oids; + if (SECOID_FindOIDTag(oid) == tagnum) { + rv = SECSuccess; + break; + } + oids++; + } + } + return rv; +} + +/* + * fill in nsCertType field of the cert based on the cert extension + */ +SECStatus +cert_GetCertType(CERTCertificate *cert) +{ + PRUint32 nsCertType; + + if (cert->nsCertType) { + /* once set, no need to recalculate */ + return SECSuccess; + } + nsCertType = cert_ComputeCertType(cert); + + /* Assert that it is safe to cast &cert->nsCertType to "PRInt32 *" */ + PORT_Assert(sizeof(cert->nsCertType) == sizeof(PRInt32)); + PR_ATOMIC_SET((PRInt32 *)&cert->nsCertType, nsCertType); + return SECSuccess; +} + +PRBool +cert_IsIPsecOID(CERTOidSequence *extKeyUsage) +{ + if (findOIDinOIDSeqByTagNum( + extKeyUsage, SEC_OID_EXT_KEY_USAGE_IPSEC_IKE) == SECSuccess) { + return PR_TRUE; + } + if (findOIDinOIDSeqByTagNum( + extKeyUsage, SEC_OID_IPSEC_IKE_END) == SECSuccess) { + return PR_TRUE; + } + if (findOIDinOIDSeqByTagNum( + extKeyUsage, SEC_OID_IPSEC_IKE_INTERMEDIATE) == SECSuccess) { + return PR_TRUE; + } + /* these are now deprecated, but may show up. Treat them the same as IKE */ + if (findOIDinOIDSeqByTagNum( + extKeyUsage, SEC_OID_EXT_KEY_USAGE_IPSEC_END) == SECSuccess) { + return PR_TRUE; + } + if (findOIDinOIDSeqByTagNum( + extKeyUsage, SEC_OID_EXT_KEY_USAGE_IPSEC_TUNNEL) == SECSuccess) { + return PR_TRUE; + } + if (findOIDinOIDSeqByTagNum( + extKeyUsage, SEC_OID_EXT_KEY_USAGE_IPSEC_USER) == SECSuccess) { + return PR_TRUE; + } + /* this one should probably be in cert_ComputeCertType and set all usages? */ + if (findOIDinOIDSeqByTagNum( + extKeyUsage, SEC_OID_X509_ANY_EXT_KEY_USAGE) == SECSuccess) { + return PR_TRUE; + } + return PR_FALSE; +} + +PRUint32 +cert_ComputeCertType(CERTCertificate *cert) +{ + SECStatus rv; + SECItem tmpitem; + SECItem encodedExtKeyUsage; + CERTOidSequence *extKeyUsage = NULL; + CERTBasicConstraints basicConstraint; + PRUint32 nsCertType = 0; + PRBool isCA = PR_FALSE; + + tmpitem.data = NULL; + CERT_FindNSCertTypeExtension(cert, &tmpitem); + encodedExtKeyUsage.data = NULL; + rv = CERT_FindCertExtension(cert, SEC_OID_X509_EXT_KEY_USAGE, + &encodedExtKeyUsage); + if (rv == SECSuccess) { + extKeyUsage = CERT_DecodeOidSequence(&encodedExtKeyUsage); + } + rv = CERT_FindBasicConstraintExten(cert, &basicConstraint); + if (rv == SECSuccess) { + isCA = basicConstraint.isCA; + } + if (tmpitem.data != NULL || extKeyUsage != NULL) { + if (tmpitem.data == NULL || tmpitem.len == 0) { + nsCertType = 0; + } else { + nsCertType = tmpitem.data[0]; + } + + /* free tmpitem data pointer to avoid memory leak */ + PORT_Free(tmpitem.data); + tmpitem.data = NULL; + + /* + * for this release, we will allow SSL certs with an email address + * to be used for email + */ + if ((nsCertType & NS_CERT_TYPE_SSL_CLIENT) && cert->emailAddr && + cert->emailAddr[0]) { + nsCertType |= NS_CERT_TYPE_EMAIL; + } + /* + * for this release, we will allow SSL intermediate CAs to be + * email intermediate CAs too. + */ + if (nsCertType & NS_CERT_TYPE_SSL_CA) { + nsCertType |= NS_CERT_TYPE_EMAIL_CA; + } + /* + * allow a cert with the extended key usage of EMail Protect + * to be used for email or as an email CA, if basic constraints + * indicates that it is a CA. + */ + if (findOIDinOIDSeqByTagNum(extKeyUsage, + SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT) == + SECSuccess) { + nsCertType |= isCA ? NS_CERT_TYPE_EMAIL_CA : NS_CERT_TYPE_EMAIL; + } + if (findOIDinOIDSeqByTagNum( + extKeyUsage, SEC_OID_EXT_KEY_USAGE_SERVER_AUTH) == SECSuccess) { + nsCertType |= isCA ? NS_CERT_TYPE_SSL_CA : NS_CERT_TYPE_SSL_SERVER; + } + /* + * Treat certs with step-up OID as also having SSL server type. + * COMODO needs this behaviour until June 2020. See Bug 737802. + */ + if (findOIDinOIDSeqByTagNum(extKeyUsage, + SEC_OID_NS_KEY_USAGE_GOVT_APPROVED) == + SECSuccess) { + nsCertType |= isCA ? NS_CERT_TYPE_SSL_CA : NS_CERT_TYPE_SSL_SERVER; + } + if (findOIDinOIDSeqByTagNum( + extKeyUsage, SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH) == SECSuccess) { + nsCertType |= isCA ? NS_CERT_TYPE_SSL_CA : NS_CERT_TYPE_SSL_CLIENT; + } + if (cert_IsIPsecOID(extKeyUsage)) { + nsCertType |= isCA ? NS_CERT_TYPE_IPSEC_CA : NS_CERT_TYPE_IPSEC; + } + if (findOIDinOIDSeqByTagNum( + extKeyUsage, SEC_OID_EXT_KEY_USAGE_CODE_SIGN) == SECSuccess) { + nsCertType |= isCA ? NS_CERT_TYPE_OBJECT_SIGNING_CA : NS_CERT_TYPE_OBJECT_SIGNING; + } + if (findOIDinOIDSeqByTagNum( + extKeyUsage, SEC_OID_EXT_KEY_USAGE_TIME_STAMP) == SECSuccess) { + nsCertType |= EXT_KEY_USAGE_TIME_STAMP; + } + if (findOIDinOIDSeqByTagNum(extKeyUsage, SEC_OID_OCSP_RESPONDER) == + SECSuccess) { + nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER; + } + } else { + /* If no NS Cert Type extension and no EKU extension, then */ + nsCertType = 0; + if (CERT_IsCACert(cert, &nsCertType)) + nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER; + /* if the basic constraint extension says the cert is a CA, then + allow SSL CA and EMAIL CA and Status Responder */ + if (isCA) { + nsCertType |= (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA | + EXT_KEY_USAGE_STATUS_RESPONDER); + } + /* allow any ssl or email (no ca or object signing. */ + nsCertType |= NS_CERT_TYPE_SSL_CLIENT | NS_CERT_TYPE_SSL_SERVER | + NS_CERT_TYPE_EMAIL; + } + + /* IPSEC is allowed to use SSL client and server certs as well as email certs */ + if (nsCertType & (NS_CERT_TYPE_SSL_CLIENT | NS_CERT_TYPE_SSL_SERVER | NS_CERT_TYPE_EMAIL)) { + nsCertType |= NS_CERT_TYPE_IPSEC; + } + if (nsCertType & (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA)) { + nsCertType |= NS_CERT_TYPE_IPSEC_CA; + } + + if (encodedExtKeyUsage.data != NULL) { + PORT_Free(encodedExtKeyUsage.data); + } + if (extKeyUsage != NULL) { + CERT_DestroyOidSequence(extKeyUsage); + } + return nsCertType; +} + +/* + * cert_GetKeyID() - extract or generate the subjectKeyID from a certificate + */ +SECStatus +cert_GetKeyID(CERTCertificate *cert) +{ + SECItem tmpitem; + SECStatus rv; + + cert->subjectKeyID.len = 0; + + /* see of the cert has a key identifier extension */ + rv = CERT_FindSubjectKeyIDExtension(cert, &tmpitem); + if (rv == SECSuccess) { + cert->subjectKeyID.data = + (unsigned char *)PORT_ArenaAlloc(cert->arena, tmpitem.len); + if (cert->subjectKeyID.data != NULL) { + PORT_Memcpy(cert->subjectKeyID.data, tmpitem.data, tmpitem.len); + cert->subjectKeyID.len = tmpitem.len; + cert->keyIDGenerated = PR_FALSE; + } + + PORT_Free(tmpitem.data); + } + + /* if the cert doesn't have a key identifier extension, then generate one*/ + if (cert->subjectKeyID.len == 0) { + /* + * pkix says that if the subjectKeyID is not present, then we should + * use the SHA-1 hash of the DER-encoded publicKeyInfo from the cert + */ + cert->subjectKeyID.data = + (unsigned char *)PORT_ArenaAlloc(cert->arena, SHA1_LENGTH); + if (cert->subjectKeyID.data != NULL) { + rv = PK11_HashBuf(SEC_OID_SHA1, cert->subjectKeyID.data, + cert->derPublicKey.data, cert->derPublicKey.len); + if (rv == SECSuccess) { + cert->subjectKeyID.len = SHA1_LENGTH; + } + } + } + + if (cert->subjectKeyID.len == 0) { + return (SECFailure); + } + return (SECSuccess); +} + +static PRBool +cert_IsRootCert(CERTCertificate *cert) +{ + SECStatus rv; + SECItem tmpitem; + + /* cache the authKeyID extension, if present */ + cert->authKeyID = CERT_FindAuthKeyIDExten(cert->arena, cert); + + /* it MUST be self-issued to be a root */ + if (cert->derIssuer.len == 0 || + !SECITEM_ItemsAreEqual(&cert->derIssuer, &cert->derSubject)) { + return PR_FALSE; + } + + /* check the authKeyID extension */ + if (cert->authKeyID) { + /* authority key identifier is present */ + if (cert->authKeyID->keyID.len > 0) { + /* the keyIdentifier field is set, look for subjectKeyID */ + rv = CERT_FindSubjectKeyIDExtension(cert, &tmpitem); + if (rv == SECSuccess) { + PRBool match; + /* also present, they MUST match for it to be a root */ + match = + SECITEM_ItemsAreEqual(&cert->authKeyID->keyID, &tmpitem); + PORT_Free(tmpitem.data); + if (!match) + return PR_FALSE; /* else fall through */ + } else { + /* the subject key ID is required when AKI is present */ + return PR_FALSE; + } + } + if (cert->authKeyID->authCertIssuer) { + SECItem *caName; + caName = (SECItem *)CERT_GetGeneralNameByType( + cert->authKeyID->authCertIssuer, certDirectoryName, PR_TRUE); + if (caName) { + if (!SECITEM_ItemsAreEqual(&cert->derIssuer, caName)) { + return PR_FALSE; + } /* else fall through */ + } /* else ??? could not get general name as directory name? */ + } + if (cert->authKeyID->authCertSerialNumber.len > 0) { + if (!SECITEM_ItemsAreEqual( + &cert->serialNumber, + &cert->authKeyID->authCertSerialNumber)) { + return PR_FALSE; + } /* else fall through */ + } + /* all of the AKI fields that were present passed the test */ + return PR_TRUE; + } + /* else the AKI was not present, so this is a root */ + return PR_TRUE; +} + +/* + * take a DER certificate and decode it into a certificate structure + */ +CERTCertificate * +CERT_DecodeDERCertificate(SECItem *derSignedCert, PRBool copyDER, + char *nickname) +{ + CERTCertificate *cert; + PLArenaPool *arena; + void *data; + int rv; + int len; + char *tmpname; + + /* make a new arena */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + + if (!arena) { + return 0; + } + + /* allocate the certificate structure */ + cert = (CERTCertificate *)PORT_ArenaZAlloc(arena, sizeof(CERTCertificate)); + + if (!cert) { + goto loser; + } + + cert->arena = arena; + + if (copyDER) { + /* copy the DER data for the cert into this arena */ + data = (void *)PORT_ArenaAlloc(arena, derSignedCert->len); + if (!data) { + goto loser; + } + cert->derCert.data = (unsigned char *)data; + cert->derCert.len = derSignedCert->len; + PORT_Memcpy(data, derSignedCert->data, derSignedCert->len); + } else { + /* point to passed in DER data */ + cert->derCert = *derSignedCert; + } + + /* decode the certificate info */ + rv = SEC_QuickDERDecodeItem(arena, cert, SEC_SignedCertificateTemplate, + &cert->derCert); + + if (rv) { + goto loser; + } + + if (cert_HasUnknownCriticalExten(cert->extensions) == PR_TRUE) { + cert->options.bits.hasUnsupportedCriticalExt = PR_TRUE; + } + + /* generate and save the database key for the cert */ + rv = CERT_KeyFromIssuerAndSN(arena, &cert->derIssuer, &cert->serialNumber, + &cert->certKey); + if (rv) { + goto loser; + } + + /* set the nickname */ + if (nickname == NULL) { + cert->nickname = NULL; + } else { + /* copy and install the nickname */ + len = PORT_Strlen(nickname) + 1; + cert->nickname = (char *)PORT_ArenaAlloc(arena, len); + if (cert->nickname == NULL) { + goto loser; + } + + PORT_Memcpy(cert->nickname, nickname, len); + } + + /* set the email address */ + cert->emailAddr = cert_GetCertificateEmailAddresses(cert); + + /* initialize the subjectKeyID */ + rv = cert_GetKeyID(cert); + if (rv != SECSuccess) { + goto loser; + } + + /* initialize keyUsage */ + rv = GetKeyUsage(cert); + if (rv != SECSuccess) { + goto loser; + } + + /* determine if this is a root cert */ + cert->isRoot = cert_IsRootCert(cert); + + /* initialize the certType */ + rv = cert_GetCertType(cert); + if (rv != SECSuccess) { + goto loser; + } + + tmpname = CERT_NameToAscii(&cert->subject); + if (tmpname != NULL) { + cert->subjectName = PORT_ArenaStrdup(cert->arena, tmpname); + PORT_Free(tmpname); + } + + tmpname = CERT_NameToAscii(&cert->issuer); + if (tmpname != NULL) { + cert->issuerName = PORT_ArenaStrdup(cert->arena, tmpname); + PORT_Free(tmpname); + } + + cert->referenceCount = 1; + cert->slot = NULL; + cert->pkcs11ID = CK_INVALID_HANDLE; + cert->dbnickname = NULL; + + return (cert); + +loser: + + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + + return (0); +} + +CERTCertificate * +__CERT_DecodeDERCertificate(SECItem *derSignedCert, PRBool copyDER, + char *nickname) +{ + return CERT_DecodeDERCertificate(derSignedCert, copyDER, nickname); +} + +CERTValidity * +CERT_CreateValidity(PRTime notBefore, PRTime notAfter) +{ + CERTValidity *v; + int rv; + PLArenaPool *arena; + + if (notBefore > notAfter) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + + if (!arena) { + return (0); + } + + v = (CERTValidity *)PORT_ArenaZAlloc(arena, sizeof(CERTValidity)); + if (v) { + v->arena = arena; + rv = DER_EncodeTimeChoice(arena, &v->notBefore, notBefore); + if (rv) + goto loser; + rv = DER_EncodeTimeChoice(arena, &v->notAfter, notAfter); + if (rv) + goto loser; + } + return v; + +loser: + CERT_DestroyValidity(v); + return 0; +} + +SECStatus +CERT_CopyValidity(PLArenaPool *arena, CERTValidity *to, CERTValidity *from) +{ + SECStatus rv; + + CERT_DestroyValidity(to); + to->arena = arena; + + rv = SECITEM_CopyItem(arena, &to->notBefore, &from->notBefore); + if (rv) + return rv; + rv = SECITEM_CopyItem(arena, &to->notAfter, &from->notAfter); + return rv; +} + +void +CERT_DestroyValidity(CERTValidity *v) +{ + if (v && v->arena) { + PORT_FreeArena(v->arena, PR_FALSE); + } + return; +} + +/* +** Amount of time that a certifiate is allowed good before it is actually +** good. This is used for pending certificates, ones that are about to be +** valid. The slop is designed to allow for some variance in the clocks +** of the machine checking the certificate. +*/ +#define PENDING_SLOP (24L * 60L * 60L) /* seconds per day */ +static PRInt32 pendingSlop = PENDING_SLOP; /* seconds */ + +PRInt32 +CERT_GetSlopTime(void) +{ + return pendingSlop; /* seconds */ +} + +SECStatus +CERT_SetSlopTime(PRInt32 slop) /* seconds */ +{ + if (slop < 0) + return SECFailure; + pendingSlop = slop; + return SECSuccess; +} + +SECStatus +CERT_GetCertTimes(const CERTCertificate *c, PRTime *notBefore, PRTime *notAfter) +{ + SECStatus rv; + + if (!c || !notBefore || !notAfter) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + /* convert DER not-before time */ + rv = DER_DecodeTimeChoice(notBefore, &c->validity.notBefore); + if (rv) { + return (SECFailure); + } + + /* convert DER not-after time */ + rv = DER_DecodeTimeChoice(notAfter, &c->validity.notAfter); + if (rv) { + return (SECFailure); + } + + return (SECSuccess); +} + +/* + * Check the validity times of a certificate + */ +SECCertTimeValidity +CERT_CheckCertValidTimes(const CERTCertificate *c, PRTime t, + PRBool allowOverride) +{ + PRTime notBefore, notAfter, llPendingSlop, tmp1; + SECStatus rv; + + if (!c) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return (secCertTimeUndetermined); + } + /* if cert is already marked OK, then don't bother to check */ + if (allowOverride && c->timeOK) { + return (secCertTimeValid); + } + + rv = CERT_GetCertTimes(c, ¬Before, ¬After); + + if (rv) { + return (secCertTimeExpired); /*XXX is this the right thing to do here?*/ + } + + LL_I2L(llPendingSlop, pendingSlop); + /* convert to micro seconds */ + LL_UI2L(tmp1, PR_USEC_PER_SEC); + LL_MUL(llPendingSlop, llPendingSlop, tmp1); + LL_SUB(notBefore, notBefore, llPendingSlop); + if (LL_CMP(t, <, notBefore)) { + PORT_SetError(SEC_ERROR_EXPIRED_CERTIFICATE); + return (secCertTimeNotValidYet); + } + if (LL_CMP(t, >, notAfter)) { + PORT_SetError(SEC_ERROR_EXPIRED_CERTIFICATE); + return (secCertTimeExpired); + } + + return (secCertTimeValid); +} + +SECStatus +SEC_GetCrlTimes(CERTCrl *date, PRTime *notBefore, PRTime *notAfter) +{ + int rv; + + /* convert DER not-before time */ + rv = DER_DecodeTimeChoice(notBefore, &date->lastUpdate); + if (rv) { + return (SECFailure); + } + + /* convert DER not-after time */ + if (date->nextUpdate.data) { + rv = DER_DecodeTimeChoice(notAfter, &date->nextUpdate); + if (rv) { + return (SECFailure); + } + } else { + LL_I2L(*notAfter, 0L); + } + return (SECSuccess); +} + +/* These routines should probably be combined with the cert + * routines using an common extraction routine. + */ +SECCertTimeValidity +SEC_CheckCrlTimes(CERTCrl *crl, PRTime t) +{ + PRTime notBefore, notAfter, llPendingSlop, tmp1; + SECStatus rv; + + if (!crl) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return (secCertTimeUndetermined); + } + + rv = SEC_GetCrlTimes(crl, ¬Before, ¬After); + + if (rv) { + return (secCertTimeExpired); + } + + LL_I2L(llPendingSlop, pendingSlop); + /* convert to micro seconds */ + LL_I2L(tmp1, PR_USEC_PER_SEC); + LL_MUL(llPendingSlop, llPendingSlop, tmp1); + LL_SUB(notBefore, notBefore, llPendingSlop); + if (LL_CMP(t, <, notBefore)) { + PORT_SetError(SEC_ERROR_CRL_EXPIRED); + return (secCertTimeNotValidYet); + } + + /* If next update is omitted and the test for notBefore passes, then + we assume that the crl is up to date. + */ + if (LL_IS_ZERO(notAfter)) { + return (secCertTimeValid); + } + + if (LL_CMP(t, >, notAfter)) { + PORT_SetError(SEC_ERROR_CRL_EXPIRED); + return (secCertTimeExpired); + } + + return (secCertTimeValid); +} + +PRBool +SEC_CrlIsNewer(CERTCrl *inNew, CERTCrl *old) +{ + PRTime newNotBefore, newNotAfter; + PRTime oldNotBefore, oldNotAfter; + SECStatus rv; + + /* problems with the new CRL? reject it */ + rv = SEC_GetCrlTimes(inNew, &newNotBefore, &newNotAfter); + if (rv) + return PR_FALSE; + + /* problems with the old CRL? replace it */ + rv = SEC_GetCrlTimes(old, &oldNotBefore, &oldNotAfter); + if (rv) + return PR_TRUE; + + /* Question: what about the notAfter's? */ + return ((PRBool)LL_CMP(oldNotBefore, <, newNotBefore)); +} + +/* + * return required key usage and cert type based on cert usage + */ +SECStatus +CERT_KeyUsageAndTypeForCertUsage(SECCertUsage usage, PRBool ca, + unsigned int *retKeyUsage, + unsigned int *retCertType) +{ + unsigned int requiredKeyUsage = 0; + unsigned int requiredCertType = 0; + + if (ca) { + switch (usage) { + case certUsageSSLServerWithStepUp: + requiredKeyUsage = KU_NS_GOVT_APPROVED | KU_KEY_CERT_SIGN; + requiredCertType = NS_CERT_TYPE_SSL_CA; + break; + case certUsageSSLClient: + requiredKeyUsage = KU_KEY_CERT_SIGN; + requiredCertType = NS_CERT_TYPE_SSL_CA; + break; + case certUsageSSLServer: + requiredKeyUsage = KU_KEY_CERT_SIGN; + requiredCertType = NS_CERT_TYPE_SSL_CA; + break; + case certUsageIPsec: + requiredKeyUsage = KU_KEY_CERT_SIGN; + requiredCertType = NS_CERT_TYPE_IPSEC_CA; + break; + case certUsageSSLCA: + requiredKeyUsage = KU_KEY_CERT_SIGN; + requiredCertType = NS_CERT_TYPE_SSL_CA; + break; + case certUsageEmailSigner: + requiredKeyUsage = KU_KEY_CERT_SIGN; + requiredCertType = NS_CERT_TYPE_EMAIL_CA; + break; + case certUsageEmailRecipient: + requiredKeyUsage = KU_KEY_CERT_SIGN; + requiredCertType = NS_CERT_TYPE_EMAIL_CA; + break; + case certUsageObjectSigner: + requiredKeyUsage = KU_KEY_CERT_SIGN; + requiredCertType = NS_CERT_TYPE_OBJECT_SIGNING_CA; + break; + case certUsageAnyCA: + case certUsageVerifyCA: + case certUsageStatusResponder: + requiredKeyUsage = KU_KEY_CERT_SIGN; + requiredCertType = NS_CERT_TYPE_OBJECT_SIGNING_CA | + NS_CERT_TYPE_EMAIL_CA | NS_CERT_TYPE_SSL_CA; + break; + default: + PORT_Assert(0); + goto loser; + } + } else { + switch (usage) { + case certUsageSSLClient: + /* + * RFC 5280 lists digitalSignature and keyAgreement for + * id-kp-clientAuth. NSS does not support the *_fixed_dh and + * *_fixed_ecdh client certificate types. + */ + requiredKeyUsage = KU_DIGITAL_SIGNATURE; + requiredCertType = NS_CERT_TYPE_SSL_CLIENT; + break; + case certUsageSSLServer: + requiredKeyUsage = KU_KEY_AGREEMENT_OR_ENCIPHERMENT; + requiredCertType = NS_CERT_TYPE_SSL_SERVER; + break; + case certUsageIPsec: + /* RFC 4945 Section 5.1.3.2 */ + requiredKeyUsage = KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION; + requiredCertType = NS_CERT_TYPE_IPSEC; + break; + case certUsageSSLServerWithStepUp: + requiredKeyUsage = + KU_KEY_AGREEMENT_OR_ENCIPHERMENT | KU_NS_GOVT_APPROVED; + requiredCertType = NS_CERT_TYPE_SSL_SERVER; + break; + case certUsageSSLCA: + requiredKeyUsage = KU_KEY_CERT_SIGN; + requiredCertType = NS_CERT_TYPE_SSL_CA; + break; + case certUsageEmailSigner: + requiredKeyUsage = KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION; + requiredCertType = NS_CERT_TYPE_EMAIL; + break; + case certUsageEmailRecipient: + requiredKeyUsage = KU_KEY_AGREEMENT_OR_ENCIPHERMENT; + requiredCertType = NS_CERT_TYPE_EMAIL; + break; + case certUsageObjectSigner: + /* RFC 5280 lists only digitalSignature for id-kp-codeSigning. + */ + requiredKeyUsage = KU_DIGITAL_SIGNATURE; + requiredCertType = NS_CERT_TYPE_OBJECT_SIGNING; + break; + case certUsageStatusResponder: + requiredKeyUsage = KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION; + requiredCertType = EXT_KEY_USAGE_STATUS_RESPONDER; + break; + default: + PORT_Assert(0); + goto loser; + } + } + + if (retKeyUsage != NULL) { + *retKeyUsage = requiredKeyUsage; + } + if (retCertType != NULL) { + *retCertType = requiredCertType; + } + + return (SECSuccess); +loser: + return (SECFailure); +} + +/* + * check the key usage of a cert against a set of required values + */ +SECStatus +CERT_CheckKeyUsage(CERTCertificate *cert, unsigned int requiredUsage) +{ + if (!cert) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + /* choose between key agreement or key encipherment based on key + * type in cert + */ + if (requiredUsage & KU_KEY_AGREEMENT_OR_ENCIPHERMENT) { + KeyType keyType = CERT_GetCertKeyType(&cert->subjectPublicKeyInfo); + /* turn off the special bit */ + requiredUsage &= (~KU_KEY_AGREEMENT_OR_ENCIPHERMENT); + + switch (keyType) { + case rsaKey: + requiredUsage |= KU_KEY_ENCIPHERMENT; + break; + case rsaPssKey: + case dsaKey: + requiredUsage |= KU_DIGITAL_SIGNATURE; + break; + case dhKey: + requiredUsage |= KU_KEY_AGREEMENT; + break; + case ecKey: + /* Accept either signature or agreement. */ + if (!(cert->keyUsage & + (KU_DIGITAL_SIGNATURE | KU_KEY_AGREEMENT))) + goto loser; + break; + default: + goto loser; + } + } + + /* Allow either digital signature or non-repudiation */ + if (requiredUsage & KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION) { + /* turn off the special bit */ + requiredUsage &= (~KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION); + + if (!(cert->keyUsage & (KU_DIGITAL_SIGNATURE | KU_NON_REPUDIATION))) + goto loser; + } + + if ((cert->keyUsage & requiredUsage) == requiredUsage) + return SECSuccess; + +loser: + PORT_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE); + return SECFailure; +} + +CERTCertificate * +CERT_DupCertificate(CERTCertificate *c) +{ + if (c) { + NSSCertificate *tmp = STAN_GetNSSCertificate(c); + nssCertificate_AddRef(tmp); + } + return c; +} + +SECStatus +CERT_GetCertificateDer(const CERTCertificate *c, SECItem *der) +{ + if (!c || !der) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + *der = c->derCert; + return SECSuccess; +} + +/* + * Allow use of default cert database, so that apps(such as mozilla) don't + * have to pass the handle all over the place. + */ +static CERTCertDBHandle *default_cert_db_handle = 0; + +void +CERT_SetDefaultCertDB(CERTCertDBHandle *handle) +{ + default_cert_db_handle = handle; + + return; +} + +CERTCertDBHandle * +CERT_GetDefaultCertDB(void) +{ + return (default_cert_db_handle); +} + +/* XXX this would probably be okay/better as an xp routine? */ +static void +sec_lower_string(char *s) +{ + if (s == NULL) { + return; + } + + while (*s) { + *s = PORT_Tolower(*s); + s++; + } + + return; +} + +static PRBool +cert_IsIPAddr(const char *hn) +{ + PRBool isIPaddr = PR_FALSE; + PRNetAddr netAddr; + isIPaddr = (PR_SUCCESS == PR_StringToNetAddr(hn, &netAddr)); + return isIPaddr; +} + +/* +** Add a domain name to the list of names that the user has explicitly +** allowed (despite cert name mismatches) for use with a server cert. +*/ +SECStatus +CERT_AddOKDomainName(CERTCertificate *cert, const char *hn) +{ + CERTOKDomainName *domainOK; + int newNameLen; + + if (!hn || !(newNameLen = strlen(hn))) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + domainOK = (CERTOKDomainName *)PORT_ArenaZAlloc(cert->arena, sizeof(*domainOK)); + if (!domainOK) { + return SECFailure; /* error code is already set. */ + } + domainOK->name = (char *)PORT_ArenaZAlloc(cert->arena, newNameLen + 1); + if (!domainOK->name) { + return SECFailure; /* error code is already set. */ + } + + PORT_Strncpy(domainOK->name, hn, newNameLen + 1); + sec_lower_string(domainOK->name); + + /* put at head of list. */ + domainOK->next = cert->domainOK; + cert->domainOK = domainOK; + return SECSuccess; +} + +/* returns SECSuccess if hn matches pattern cn, +** returns SECFailure with SSL_ERROR_BAD_CERT_DOMAIN if no match, +** returns SECFailure with some other error code if another error occurs. +** +** This function may modify string cn, so caller must pass a modifiable copy. +*/ +static SECStatus +cert_TestHostName(char *cn, const char *hn) +{ + static int useShellExp = -1; + + if (useShellExp < 0) { + useShellExp = (NULL != PR_GetEnvSecure("NSS_USE_SHEXP_IN_CERT_NAME")); + } + if (useShellExp) { + /* Backward compatible code, uses Shell Expressions (SHEXP). */ + int regvalid = PORT_RegExpValid(cn); + if (regvalid != NON_SXP) { + SECStatus rv; + /* cn is a regular expression, try to match the shexp */ + int match = PORT_RegExpCaseSearch(hn, cn); + + if (match == 0) { + rv = SECSuccess; + } else { + PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN); + rv = SECFailure; + } + return rv; + } + } else { + /* New approach conforms to RFC 6125. */ + char *wildcard = PORT_Strchr(cn, '*'); + char *firstcndot = PORT_Strchr(cn, '.'); + char *secondcndot = + firstcndot ? PORT_Strchr(firstcndot + 1, '.') : NULL; + char *firsthndot = PORT_Strchr(hn, '.'); + + /* For a cn pattern to be considered valid, the wildcard character... + * - may occur only in a DNS name with at least 3 components, and + * - may occur only as last character in the first component, and + * - may be preceded by additional characters, and + * - must not be preceded by an IDNA ACE prefix (xn--) + */ + if (wildcard && secondcndot && secondcndot[1] && firsthndot && + firstcndot - wildcard == 1 /* wildcard is last char in first component */ + && secondcndot - firstcndot > 1 /* second component is non-empty */ + && PORT_Strrchr(cn, '*') == wildcard /* only one wildcard in cn */ + && !PORT_Strncasecmp(cn, hn, wildcard - cn) && + !PORT_Strcasecmp(firstcndot, firsthndot) + /* If hn starts with xn--, then cn must start with wildcard */ + && (PORT_Strncasecmp(hn, "xn--", 4) || wildcard == cn)) { + /* valid wildcard pattern match */ + return SECSuccess; + } + } + /* String cn has no wildcard or shell expression. + * Compare entire string hn with cert name. + */ + if (PORT_Strcasecmp(hn, cn) == 0) { + return SECSuccess; + } + + PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN); + return SECFailure; +} + +SECStatus +cert_VerifySubjectAltName(const CERTCertificate *cert, const char *hn) +{ + PLArenaPool *arena = NULL; + CERTGeneralName *nameList = NULL; + CERTGeneralName *current; + char *cn; + int cnBufLen; + int DNSextCount = 0; + int IPextCount = 0; + PRBool isIPaddr = PR_FALSE; + SECStatus rv = SECFailure; + SECItem subAltName; + PRNetAddr netAddr; + char cnbuf[128]; + + subAltName.data = NULL; + cn = cnbuf; + cnBufLen = sizeof cnbuf; + + rv = CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME, + &subAltName); + if (rv != SECSuccess) { + goto fail; + } + isIPaddr = (PR_SUCCESS == PR_StringToNetAddr(hn, &netAddr)); + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) + goto fail; + + nameList = current = CERT_DecodeAltNameExtension(arena, &subAltName); + if (!current) + goto fail; + + do { + switch (current->type) { + case certDNSName: + if (!isIPaddr) { + /* DNS name current->name.other.data is not null terminated. + ** so must copy it. + */ + int cnLen = current->name.other.len; + rv = CERT_RFC1485_EscapeAndQuote( + cn, cnBufLen, (char *)current->name.other.data, cnLen); + if (rv != SECSuccess && + PORT_GetError() == SEC_ERROR_OUTPUT_LEN) { + cnBufLen = + cnLen * 3 + 3; /* big enough for worst case */ + cn = (char *)PORT_ArenaAlloc(arena, cnBufLen); + if (!cn) + goto fail; + rv = CERT_RFC1485_EscapeAndQuote( + cn, cnBufLen, (char *)current->name.other.data, + cnLen); + } + if (rv == SECSuccess) + rv = cert_TestHostName(cn, hn); + if (rv == SECSuccess) + goto finish; + } + DNSextCount++; + break; + case certIPAddress: + if (isIPaddr) { + int match = 0; + PRIPv6Addr v6Addr; + if (current->name.other.len == 4 && /* IP v4 address */ + netAddr.inet.family == PR_AF_INET) { + match = !memcmp(&netAddr.inet.ip, + current->name.other.data, 4); + } else if (current->name.other.len == + 16 && /* IP v6 address */ + netAddr.ipv6.family == PR_AF_INET6) { + match = !memcmp(&netAddr.ipv6.ip, + current->name.other.data, 16); + } else if (current->name.other.len == + 16 && /* IP v6 address */ + netAddr.inet.family == PR_AF_INET) { + /* convert netAddr to ipv6, then compare. */ + /* ipv4 must be in Network Byte Order on input. */ + PR_ConvertIPv4AddrToIPv6(netAddr.inet.ip, &v6Addr); + match = !memcmp(&v6Addr, current->name.other.data, 16); + } else if (current->name.other.len == 4 && /* IP v4 address */ + netAddr.inet.family == PR_AF_INET6) { + /* convert netAddr to ipv6, then compare. */ + PRUint32 ipv4 = (current->name.other.data[0] << 24) | + (current->name.other.data[1] << 16) | + (current->name.other.data[2] << 8) | + current->name.other.data[3]; + /* ipv4 must be in Network Byte Order on input. */ + PR_ConvertIPv4AddrToIPv6(PR_htonl(ipv4), &v6Addr); + match = !memcmp(&netAddr.ipv6.ip, &v6Addr, 16); + } + if (match) { + rv = SECSuccess; + goto finish; + } + } + IPextCount++; + break; + default: + break; + } + current = CERT_GetNextGeneralName(current); + } while (current != nameList); + +fail: + + if (!(isIPaddr ? IPextCount : DNSextCount)) { + /* no relevant value in the extension was found. */ + PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND); + } else { + PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN); + } + rv = SECFailure; + +finish: + + /* Don't free nameList, it's part of the arena. */ + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + + if (subAltName.data) { + SECITEM_FreeItem(&subAltName, PR_FALSE); + } + + return rv; +} + +/* + * If found: + * - subAltName contains the extension (caller must free) + * - return value is the decoded namelist (allocated off arena) + * if not found, or if failure to decode: + * - return value is NULL + */ +CERTGeneralName * +cert_GetSubjectAltNameList(const CERTCertificate *cert, PLArenaPool *arena) +{ + CERTGeneralName *nameList = NULL; + SECStatus rv = SECFailure; + SECItem subAltName; + + if (!cert || !arena) + return NULL; + + subAltName.data = NULL; + + rv = CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME, + &subAltName); + if (rv != SECSuccess) + return NULL; + + nameList = CERT_DecodeAltNameExtension(arena, &subAltName); + SECITEM_FreeItem(&subAltName, PR_FALSE); + return nameList; +} + +PRUint32 +cert_CountDNSPatterns(CERTGeneralName *firstName) +{ + CERTGeneralName *current; + PRUint32 count = 0; + + if (!firstName) + return 0; + + current = firstName; + do { + switch (current->type) { + case certDNSName: + case certIPAddress: + ++count; + break; + default: + break; + } + current = CERT_GetNextGeneralName(current); + } while (current != firstName); + + return count; +} + +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN 46 +#endif + +/* will fill nickNames, + * will allocate all data from nickNames->arena, + * numberOfGeneralNames should have been obtained from cert_CountDNSPatterns, + * will ensure the numberOfGeneralNames matches the number of output entries. + */ +SECStatus +cert_GetDNSPatternsFromGeneralNames(CERTGeneralName *firstName, + PRUint32 numberOfGeneralNames, + CERTCertNicknames *nickNames) +{ + CERTGeneralName *currentInput; + char **currentOutput; + + if (!firstName || !nickNames || !numberOfGeneralNames) + return SECFailure; + + nickNames->numnicknames = numberOfGeneralNames; + nickNames->nicknames = PORT_ArenaAlloc( + nickNames->arena, sizeof(char *) * numberOfGeneralNames); + if (!nickNames->nicknames) + return SECFailure; + + currentInput = firstName; + currentOutput = nickNames->nicknames; + do { + char *cn = NULL; + char ipbuf[INET6_ADDRSTRLEN]; + PRNetAddr addr; + + if (numberOfGeneralNames < 1) { + /* internal consistency error */ + return SECFailure; + } + + switch (currentInput->type) { + case certDNSName: + /* DNS name currentInput->name.other.data is not null + *terminated. + ** so must copy it. + */ + cn = (char *)PORT_ArenaAlloc(nickNames->arena, + currentInput->name.other.len + 1); + if (!cn) + return SECFailure; + PORT_Memcpy(cn, currentInput->name.other.data, + currentInput->name.other.len); + cn[currentInput->name.other.len] = 0; + break; + case certIPAddress: + if (currentInput->name.other.len == 4) { + addr.inet.family = PR_AF_INET; + memcpy(&addr.inet.ip, currentInput->name.other.data, + currentInput->name.other.len); + } else if (currentInput->name.other.len == 16) { + addr.ipv6.family = PR_AF_INET6; + memcpy(&addr.ipv6.ip, currentInput->name.other.data, + currentInput->name.other.len); + } + if (PR_NetAddrToString(&addr, ipbuf, sizeof(ipbuf)) == + PR_FAILURE) + return SECFailure; + cn = PORT_ArenaStrdup(nickNames->arena, ipbuf); + if (!cn) + return SECFailure; + break; + default: + break; + } + if (cn) { + *currentOutput = cn; + nickNames->totallen += PORT_Strlen(cn); + ++currentOutput; + --numberOfGeneralNames; + } + currentInput = CERT_GetNextGeneralName(currentInput); + } while (currentInput != firstName); + + return (numberOfGeneralNames == 0) ? SECSuccess : SECFailure; +} + +/* + * Collect all valid DNS names from the given cert. + * The output arena will reference some temporaray data, + * but this saves us from dealing with two arenas. + * The caller may free all data by freeing CERTCertNicknames->arena. + */ +CERTCertNicknames * +CERT_GetValidDNSPatternsFromCert(CERTCertificate *cert) +{ + CERTGeneralName *generalNames; + CERTCertNicknames *nickNames; + PLArenaPool *arena; + char *singleName; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) { + return NULL; + } + + nickNames = PORT_ArenaAlloc(arena, sizeof(CERTCertNicknames)); + if (!nickNames) { + PORT_FreeArena(arena, PR_FALSE); + return NULL; + } + + /* init the structure */ + nickNames->arena = arena; + nickNames->head = NULL; + nickNames->numnicknames = 0; + nickNames->nicknames = NULL; + nickNames->totallen = 0; + + generalNames = cert_GetSubjectAltNameList(cert, arena); + if (generalNames) { + SECStatus rv_getnames = SECFailure; + PRUint32 numNames = cert_CountDNSPatterns(generalNames); + + if (numNames) { + rv_getnames = cert_GetDNSPatternsFromGeneralNames( + generalNames, numNames, nickNames); + } + + /* if there were names, we'll exit now, either with success or failure + */ + if (numNames) { + if (rv_getnames == SECSuccess) { + return nickNames; + } + + /* failure to produce output */ + PORT_FreeArena(arena, PR_FALSE); + return NULL; + } + } + + /* no SAN extension or no names found in extension */ + singleName = CERT_GetCommonName(&cert->subject); + if (singleName) { + nickNames->numnicknames = 1; + nickNames->nicknames = PORT_ArenaAlloc(arena, sizeof(char *)); + if (nickNames->nicknames) { + *nickNames->nicknames = PORT_ArenaStrdup(arena, singleName); + } + PORT_Free(singleName); + + /* Did we allocate both the buffer of pointers and the string? */ + if (nickNames->nicknames && *nickNames->nicknames) { + return nickNames; + } + } + + PORT_FreeArena(arena, PR_FALSE); + return NULL; +} + +/* Make sure that the name of the host we are connecting to matches the + * name that is incoded in the common-name component of the certificate + * that they are using. + */ +SECStatus +CERT_VerifyCertName(const CERTCertificate *cert, const char *hn) +{ + char *cn; + SECStatus rv; + CERTOKDomainName *domainOK; + + if (!hn || !strlen(hn)) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + /* if the name is one that the user has already approved, it's OK. */ + for (domainOK = cert->domainOK; domainOK; domainOK = domainOK->next) { + if (0 == PORT_Strcasecmp(hn, domainOK->name)) { + return SECSuccess; + } + } + + /* Per RFC 2818, if the SubjectAltName extension is present, it must + ** be used as the cert's identity. + */ + rv = cert_VerifySubjectAltName(cert, hn); + if (rv == SECSuccess || PORT_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND) + return rv; + + cn = CERT_GetCommonName(&cert->subject); + if (cn) { + PRBool isIPaddr = cert_IsIPAddr(hn); + if (isIPaddr) { + if (PORT_Strcasecmp(hn, cn) == 0) { + rv = SECSuccess; + } else { + PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN); + rv = SECFailure; + } + } else { + rv = cert_TestHostName(cn, hn); + } + PORT_Free(cn); + } else + PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN); + return rv; +} + +PRBool +CERT_CompareCerts(const CERTCertificate *c1, const CERTCertificate *c2) +{ + SECComparison comp; + + comp = SECITEM_CompareItem(&c1->derCert, &c2->derCert); + if (comp == SECEqual) { /* certs are the same */ + return (PR_TRUE); + } else { + return (PR_FALSE); + } +} + +static SECStatus +StringsEqual(char *s1, char *s2) +{ + if ((s1 == NULL) || (s2 == NULL)) { + if (s1 != s2) { /* only one is null */ + return (SECFailure); + } + return (SECSuccess); /* both are null */ + } + + if (PORT_Strcmp(s1, s2) != 0) { + return (SECFailure); /* not equal */ + } + + return (SECSuccess); /* strings are equal */ +} + +PRBool +CERT_CompareCertsForRedirection(CERTCertificate *c1, CERTCertificate *c2) +{ + SECComparison comp; + char *c1str, *c2str; + SECStatus eq; + + comp = SECITEM_CompareItem(&c1->derCert, &c2->derCert); + if (comp == SECEqual) { /* certs are the same */ + return (PR_TRUE); + } + + /* check if they are issued by the same CA */ + comp = SECITEM_CompareItem(&c1->derIssuer, &c2->derIssuer); + if (comp != SECEqual) { /* different issuer */ + return (PR_FALSE); + } + + /* check country name */ + c1str = CERT_GetCountryName(&c1->subject); + c2str = CERT_GetCountryName(&c2->subject); + eq = StringsEqual(c1str, c2str); + PORT_Free(c1str); + PORT_Free(c2str); + if (eq != SECSuccess) { + return (PR_FALSE); + } + + /* check locality name */ + c1str = CERT_GetLocalityName(&c1->subject); + c2str = CERT_GetLocalityName(&c2->subject); + eq = StringsEqual(c1str, c2str); + PORT_Free(c1str); + PORT_Free(c2str); + if (eq != SECSuccess) { + return (PR_FALSE); + } + + /* check state name */ + c1str = CERT_GetStateName(&c1->subject); + c2str = CERT_GetStateName(&c2->subject); + eq = StringsEqual(c1str, c2str); + PORT_Free(c1str); + PORT_Free(c2str); + if (eq != SECSuccess) { + return (PR_FALSE); + } + + /* check org name */ + c1str = CERT_GetOrgName(&c1->subject); + c2str = CERT_GetOrgName(&c2->subject); + eq = StringsEqual(c1str, c2str); + PORT_Free(c1str); + PORT_Free(c2str); + if (eq != SECSuccess) { + return (PR_FALSE); + } + +#ifdef NOTDEF + /* check orgUnit name */ + /* + * We need to revisit this and decide which fields should be allowed to be + * different + */ + c1str = CERT_GetOrgUnitName(&c1->subject); + c2str = CERT_GetOrgUnitName(&c2->subject); + eq = StringsEqual(c1str, c2str); + PORT_Free(c1str); + PORT_Free(c2str); + if (eq != SECSuccess) { + return (PR_FALSE); + } +#endif + + return (PR_TRUE); /* all fields but common name are the same */ +} + +/* CERT_CertChainFromCert and CERT_DestroyCertificateList moved + to certhigh.c */ + +CERTIssuerAndSN * +CERT_GetCertIssuerAndSN(PLArenaPool *arena, CERTCertificate *cert) +{ + CERTIssuerAndSN *result; + SECStatus rv; + + if (arena == NULL) { + arena = cert->arena; + } + + result = (CERTIssuerAndSN *)PORT_ArenaZAlloc(arena, sizeof(*result)); + if (result == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + rv = SECITEM_CopyItem(arena, &result->derIssuer, &cert->derIssuer); + if (rv != SECSuccess) + return NULL; + + rv = CERT_CopyName(arena, &result->issuer, &cert->issuer); + if (rv != SECSuccess) + return NULL; + + rv = SECITEM_CopyItem(arena, &result->serialNumber, &cert->serialNumber); + if (rv != SECSuccess) + return NULL; + + return result; +} + +char * +CERT_MakeCANickname(CERTCertificate *cert) +{ + char *firstname = NULL; + char *org = NULL; + char *nickname = NULL; + int count; + CERTCertificate *dummycert; + + firstname = CERT_GetCommonName(&cert->subject); + if (firstname == NULL) { + firstname = CERT_GetOrgUnitName(&cert->subject); + } + + org = CERT_GetOrgName(&cert->issuer); + if (org == NULL) { + org = CERT_GetDomainComponentName(&cert->issuer); + if (org == NULL) { + if (firstname) { + org = firstname; + firstname = NULL; + } else { + org = PORT_Strdup("Unknown CA"); + } + } + } + + /* can only fail if PORT_Strdup fails, in which case + * we're having memory problems. */ + if (org == NULL) { + goto done; + } + + count = 1; + while (1) { + + if (firstname) { + if (count == 1) { + nickname = PR_smprintf("%s - %s", firstname, org); + } else { + nickname = PR_smprintf("%s - %s #%d", firstname, org, count); + } + } else { + if (count == 1) { + nickname = PR_smprintf("%s", org); + } else { + nickname = PR_smprintf("%s #%d", org, count); + } + } + if (nickname == NULL) { + goto done; + } + + /* look up the nickname to make sure it isn't in use already */ + dummycert = CERT_FindCertByNickname(cert->dbhandle, nickname); + + if (dummycert == NULL) { + goto done; + } + + /* found a cert, destroy it and loop */ + CERT_DestroyCertificate(dummycert); + + /* free the nickname */ + PORT_Free(nickname); + + count++; + } + +done: + if (firstname) { + PORT_Free(firstname); + } + if (org) { + PORT_Free(org); + } + + return (nickname); +} + +/* CERT_Import_CAChain moved to certhigh.c */ + +void +CERT_DestroyCrl(CERTSignedCrl *crl) +{ + SEC_DestroyCrl(crl); +} + +static int +cert_Version(CERTCertificate *cert) +{ + int version = 0; + if (cert && cert->version.data && cert->version.len) { + version = DER_GetInteger(&cert->version); + if (version < 0) + version = 0; + } + return version; +} + +static unsigned int +cert_ComputeTrustOverrides(CERTCertificate *cert, unsigned int cType) +{ + CERTCertTrust trust; + SECStatus rv = SECFailure; + + rv = CERT_GetCertTrust(cert, &trust); + + if (rv == SECSuccess && + (trust.sslFlags | trust.emailFlags | trust.objectSigningFlags)) { + + if (trust.sslFlags & (CERTDB_TERMINAL_RECORD | CERTDB_TRUSTED)) + cType |= NS_CERT_TYPE_SSL_SERVER | NS_CERT_TYPE_SSL_CLIENT; + if (trust.sslFlags & (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) + cType |= NS_CERT_TYPE_SSL_CA; +#if defined(CERTDB_NOT_TRUSTED) + if (trust.sslFlags & CERTDB_NOT_TRUSTED) + cType &= ~(NS_CERT_TYPE_SSL_SERVER | NS_CERT_TYPE_SSL_CLIENT | + NS_CERT_TYPE_SSL_CA); +#endif + if (trust.emailFlags & (CERTDB_TERMINAL_RECORD | CERTDB_TRUSTED)) + cType |= NS_CERT_TYPE_EMAIL; + if (trust.emailFlags & (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) + cType |= NS_CERT_TYPE_EMAIL_CA; +#if defined(CERTDB_NOT_TRUSTED) + if (trust.emailFlags & CERTDB_NOT_TRUSTED) + cType &= ~(NS_CERT_TYPE_EMAIL | NS_CERT_TYPE_EMAIL_CA); +#endif + if (trust.objectSigningFlags & + (CERTDB_TERMINAL_RECORD | CERTDB_TRUSTED)) + cType |= NS_CERT_TYPE_OBJECT_SIGNING; + if (trust.objectSigningFlags & (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) + cType |= NS_CERT_TYPE_OBJECT_SIGNING_CA; +#if defined(CERTDB_NOT_TRUSTED) + if (trust.objectSigningFlags & CERTDB_NOT_TRUSTED) + cType &= + ~(NS_CERT_TYPE_OBJECT_SIGNING | NS_CERT_TYPE_OBJECT_SIGNING_CA); +#endif + } + return cType; +} + +/* + * Does a cert belong to a CA? We decide based on perm database trust + * flags, Netscape Cert Type Extension, and KeyUsage Extension. + */ +PRBool +CERT_IsCACert(CERTCertificate *cert, unsigned int *rettype) +{ + unsigned int cType = cert->nsCertType; + PRBool ret = PR_FALSE; + + /* + * Check if the constraints are available and it's a CA, OR if it's + * a X.509 v1 Root CA. + */ + CERTBasicConstraints constraints; + if ((CERT_FindBasicConstraintExten(cert, &constraints) == SECSuccess && + constraints.isCA) || + (cert->isRoot && cert_Version(cert) < SEC_CERTIFICATE_VERSION_3)) + cType |= (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA); + + /* + * Apply trust overrides, if any. + */ + cType = cert_ComputeTrustOverrides(cert, cType); + ret = (cType & (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA | + NS_CERT_TYPE_OBJECT_SIGNING_CA)) + ? PR_TRUE + : PR_FALSE; + + if (rettype) { + *rettype = cType; + } + + return ret; +} + +PRBool +CERT_IsCADERCert(SECItem *derCert, unsigned int *type) +{ + CERTCertificate *cert; + PRBool isCA; + + /* This is okay -- only looks at extensions */ + cert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL); + if (cert == NULL) + return PR_FALSE; + + isCA = CERT_IsCACert(cert, type); + CERT_DestroyCertificate(cert); + return isCA; +} + +PRBool +CERT_IsRootDERCert(SECItem *derCert) +{ + CERTCertificate *cert; + PRBool isRoot; + + /* This is okay -- only looks at extensions */ + cert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL); + if (cert == NULL) + return PR_FALSE; + + isRoot = cert->isRoot; + CERT_DestroyCertificate(cert); + return isRoot; +} + +CERTCompareValidityStatus +CERT_CompareValidityTimes(CERTValidity *val_a, CERTValidity *val_b) +{ + PRTime notBeforeA, notBeforeB, notAfterA, notAfterB; + + if (!val_a || !val_b) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return certValidityUndetermined; + } + + if (SECSuccess != DER_DecodeTimeChoice(¬BeforeA, &val_a->notBefore) || + SECSuccess != DER_DecodeTimeChoice(¬BeforeB, &val_b->notBefore) || + SECSuccess != DER_DecodeTimeChoice(¬AfterA, &val_a->notAfter) || + SECSuccess != DER_DecodeTimeChoice(¬AfterB, &val_b->notAfter)) { + return certValidityUndetermined; + } + + /* sanity check */ + if (LL_CMP(notBeforeA, >, notAfterA) || LL_CMP(notBeforeB, >, notAfterB)) { + PORT_SetError(SEC_ERROR_INVALID_TIME); + return certValidityUndetermined; + } + + if (LL_CMP(notAfterA, !=, notAfterB)) { + /* one cert validity goes farther into the future, select it */ + return LL_CMP(notAfterA, <, notAfterB) ? certValidityChooseB + : certValidityChooseA; + } + /* the two certs have the same expiration date */ + PORT_Assert(LL_CMP(notAfterA, ==, notAfterB)); + /* do they also have the same start date ? */ + if (LL_CMP(notBeforeA, ==, notBeforeB)) { + return certValidityEqual; + } + /* choose cert with the later start date */ + return LL_CMP(notBeforeA, <, notBeforeB) ? certValidityChooseB + : certValidityChooseA; +} + +/* + * is certa newer than certb? If one is expired, pick the other one. + */ +PRBool +CERT_IsNewer(CERTCertificate *certa, CERTCertificate *certb) +{ + PRTime notBeforeA, notAfterA, notBeforeB, notAfterB, now; + SECStatus rv; + PRBool newerbefore, newerafter; + + rv = CERT_GetCertTimes(certa, ¬BeforeA, ¬AfterA); + if (rv != SECSuccess) { + return (PR_FALSE); + } + + rv = CERT_GetCertTimes(certb, ¬BeforeB, ¬AfterB); + if (rv != SECSuccess) { + return (PR_TRUE); + } + + newerbefore = PR_FALSE; + if (LL_CMP(notBeforeA, >, notBeforeB)) { + newerbefore = PR_TRUE; + } + + newerafter = PR_FALSE; + if (LL_CMP(notAfterA, >, notAfterB)) { + newerafter = PR_TRUE; + } + + if (newerbefore && newerafter) { + return (PR_TRUE); + } + + if ((!newerbefore) && (!newerafter)) { + return (PR_FALSE); + } + + /* get current time */ + now = PR_Now(); + + if (newerbefore) { + /* cert A was issued after cert B, but expires sooner */ + /* if A is expired, then pick B */ + if (LL_CMP(notAfterA, <, now)) { + return (PR_FALSE); + } + return (PR_TRUE); + } else { + /* cert B was issued after cert A, but expires sooner */ + /* if B is expired, then pick A */ + if (LL_CMP(notAfterB, <, now)) { + return (PR_TRUE); + } + return (PR_FALSE); + } +} + +void +CERT_DestroyCertArray(CERTCertificate **certs, unsigned int ncerts) +{ + unsigned int i; + + if (certs) { + for (i = 0; i < ncerts; i++) { + if (certs[i]) { + CERT_DestroyCertificate(certs[i]); + } + } + + PORT_Free(certs); + } + + return; +} + +char * +CERT_FixupEmailAddr(const char *emailAddr) +{ + char *retaddr; + char *str; + + if (emailAddr == NULL) { + return (NULL); + } + + /* copy the string */ + str = retaddr = PORT_Strdup(emailAddr); + if (str == NULL) { + return (NULL); + } + + /* make it lower case */ + while (*str) { + *str = tolower(*str); + str++; + } + + return (retaddr); +} + +/* + * NOTE - don't allow encode of govt-approved or invisible bits + */ +SECStatus +CERT_DecodeTrustString(CERTCertTrust *trust, const char *trusts) +{ + unsigned int i; + unsigned int *pflags; + + if (!trust) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + trust->sslFlags = 0; + trust->emailFlags = 0; + trust->objectSigningFlags = 0; + if (!trusts) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + pflags = &trust->sslFlags; + + for (i = 0; i < PORT_Strlen(trusts); i++) { + switch (trusts[i]) { + case 'p': + *pflags = *pflags | CERTDB_TERMINAL_RECORD; + break; + + case 'P': + *pflags = *pflags | CERTDB_TRUSTED | CERTDB_TERMINAL_RECORD; + break; + + case 'w': + *pflags = *pflags | CERTDB_SEND_WARN; + break; + + case 'c': + *pflags = *pflags | CERTDB_VALID_CA; + break; + + case 'T': + *pflags = *pflags | CERTDB_TRUSTED_CLIENT_CA | CERTDB_VALID_CA; + break; + + case 'C': + *pflags = *pflags | CERTDB_TRUSTED_CA | CERTDB_VALID_CA; + break; + + case 'u': + *pflags = *pflags | CERTDB_USER; + break; + + case 'i': + *pflags = *pflags | CERTDB_INVISIBLE_CA; + break; + case 'g': + *pflags = *pflags | CERTDB_GOVT_APPROVED_CA; + break; + + case ',': + if (pflags == &trust->sslFlags) { + pflags = &trust->emailFlags; + } else { + pflags = &trust->objectSigningFlags; + } + break; + default: + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + } + + return SECSuccess; +} + +static void +EncodeFlags(char *trusts, unsigned int flags) +{ + if (flags & CERTDB_VALID_CA) + if (!(flags & CERTDB_TRUSTED_CA) && !(flags & CERTDB_TRUSTED_CLIENT_CA)) + PORT_Strcat(trusts, "c"); + if (flags & CERTDB_TERMINAL_RECORD) + if (!(flags & CERTDB_TRUSTED)) + PORT_Strcat(trusts, "p"); + if (flags & CERTDB_TRUSTED_CA) + PORT_Strcat(trusts, "C"); + if (flags & CERTDB_TRUSTED_CLIENT_CA) + PORT_Strcat(trusts, "T"); + if (flags & CERTDB_TRUSTED) + PORT_Strcat(trusts, "P"); + if (flags & CERTDB_USER) + PORT_Strcat(trusts, "u"); + if (flags & CERTDB_SEND_WARN) + PORT_Strcat(trusts, "w"); + if (flags & CERTDB_INVISIBLE_CA) + PORT_Strcat(trusts, "I"); + if (flags & CERTDB_GOVT_APPROVED_CA) + PORT_Strcat(trusts, "G"); + return; +} + +char * +CERT_EncodeTrustString(CERTCertTrust *trust) +{ + char tmpTrustSSL[32]; + char tmpTrustEmail[32]; + char tmpTrustSigning[32]; + char *retstr = NULL; + + if (trust) { + tmpTrustSSL[0] = '\0'; + tmpTrustEmail[0] = '\0'; + tmpTrustSigning[0] = '\0'; + + EncodeFlags(tmpTrustSSL, trust->sslFlags); + EncodeFlags(tmpTrustEmail, trust->emailFlags); + EncodeFlags(tmpTrustSigning, trust->objectSigningFlags); + + retstr = PR_smprintf("%s,%s,%s", tmpTrustSSL, tmpTrustEmail, + tmpTrustSigning); + } + + return (retstr); +} + +SECStatus +CERT_ImportCerts(CERTCertDBHandle *certdb, SECCertUsage usage, + unsigned int ncerts, SECItem **derCerts, + CERTCertificate ***retCerts, PRBool keepCerts, PRBool caOnly, + char *nickname) +{ + unsigned int i; + CERTCertificate **certs = NULL; + unsigned int fcerts = 0; + + if (ncerts) { + certs = PORT_ZNewArray(CERTCertificate *, ncerts); + if (certs == NULL) { + return (SECFailure); + } + + /* decode all of the certs into the temporary DB */ + for (i = 0, fcerts = 0; i < ncerts; i++) { + certs[fcerts] = CERT_NewTempCertificate(certdb, derCerts[i], NULL, + PR_FALSE, PR_TRUE); + if (certs[fcerts]) { + SECItem subjKeyID = { siBuffer, NULL, 0 }; + if (CERT_FindSubjectKeyIDExtension(certs[fcerts], &subjKeyID) == + SECSuccess) { + if (subjKeyID.data) { + cert_AddSubjectKeyIDMapping(&subjKeyID, certs[fcerts]); + } + SECITEM_FreeItem(&subjKeyID, PR_FALSE); + } + fcerts++; + } + } + + if (keepCerts) { + for (i = 0; i < fcerts; i++) { + char *canickname = NULL; + PRBool isCA; + + SECKEY_UpdateCertPQG(certs[i]); + + isCA = CERT_IsCACert(certs[i], NULL); + if (isCA) { + canickname = CERT_MakeCANickname(certs[i]); + } + + if (isCA && (fcerts > 1)) { + /* if we are importing only a single cert and specifying + * a nickname, we want to use that nickname if it a CA, + * otherwise if there are more than one cert, we don't + * know which cert it belongs to. But we still may try + * the individual canickname from the cert itself. + */ + /* Bug 1192442 - propagate errors from these calls. */ + (void)CERT_AddTempCertToPerm(certs[i], canickname, NULL); + } else { + (void)CERT_AddTempCertToPerm( + certs[i], nickname ? nickname : canickname, NULL); + } + + PORT_Free(canickname); + /* don't care if it fails - keep going */ + } + } + } + + if (retCerts) { + *retCerts = certs; + } else { + if (certs) { + CERT_DestroyCertArray(certs, fcerts); + } + } + + return (fcerts || !ncerts) ? SECSuccess : SECFailure; +} + +/* + * a real list of certificates - need to convert CERTCertificateList + * stuff and ASN 1 encoder/decoder over to using this... + */ +CERTCertList * +CERT_NewCertList(void) +{ + PLArenaPool *arena = NULL; + CERTCertList *ret = NULL; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + goto loser; + } + + ret = (CERTCertList *)PORT_ArenaZAlloc(arena, sizeof(CERTCertList)); + if (ret == NULL) { + goto loser; + } + + ret->arena = arena; + + PR_INIT_CLIST(&ret->list); + + return (ret); + +loser: + if (arena != NULL) { + PORT_FreeArena(arena, PR_FALSE); + } + + return (NULL); +} + +void +CERT_DestroyCertList(CERTCertList *certs) +{ + PRCList *node; + + if (!certs) { + return; + } + + while (!PR_CLIST_IS_EMPTY(&certs->list)) { + node = PR_LIST_HEAD(&certs->list); + CERT_DestroyCertificate(((CERTCertListNode *)node)->cert); + PR_REMOVE_LINK(node); + } + + PORT_FreeArena(certs->arena, PR_FALSE); + + return; +} + +void +CERT_RemoveCertListNode(CERTCertListNode *node) +{ + CERT_DestroyCertificate(node->cert); + PR_REMOVE_LINK(&node->links); + return; +} + +SECStatus +CERT_AddCertToListTailWithData(CERTCertList *certs, CERTCertificate *cert, + void *appData) +{ + CERTCertListNode *node; + + node = (CERTCertListNode *)PORT_ArenaZAlloc(certs->arena, + sizeof(CERTCertListNode)); + if (node == NULL) { + goto loser; + } + + PR_INSERT_BEFORE(&node->links, &certs->list); + /* certs->count++; */ + node->cert = cert; + node->appData = appData; + return (SECSuccess); + +loser: + return (SECFailure); +} + +SECStatus +CERT_AddCertToListTail(CERTCertList *certs, CERTCertificate *cert) +{ + return CERT_AddCertToListTailWithData(certs, cert, NULL); +} + +SECStatus +CERT_AddCertToListHeadWithData(CERTCertList *certs, CERTCertificate *cert, + void *appData) +{ + CERTCertListNode *node; + CERTCertListNode *head; + + head = CERT_LIST_HEAD(certs); + if (head == NULL) { + goto loser; + } + + node = (CERTCertListNode *)PORT_ArenaZAlloc(certs->arena, + sizeof(CERTCertListNode)); + if (node == NULL) { + goto loser; + } + + PR_INSERT_BEFORE(&node->links, &head->links); + /* certs->count++; */ + node->cert = cert; + node->appData = appData; + return (SECSuccess); + +loser: + return (SECFailure); +} + +SECStatus +CERT_AddCertToListHead(CERTCertList *certs, CERTCertificate *cert) +{ + return CERT_AddCertToListHeadWithData(certs, cert, NULL); +} + +/* + * Sort callback function to determine if cert a is newer than cert b. + * Not valid certs are considered older than valid certs. + */ +PRBool +CERT_SortCBValidity(CERTCertificate *certa, CERTCertificate *certb, void *arg) +{ + PRTime sorttime; + PRTime notBeforeA, notAfterA, notBeforeB, notAfterB; + SECStatus rv; + PRBool newerbefore, newerafter; + PRBool aNotValid = PR_FALSE, bNotValid = PR_FALSE; + + sorttime = *(PRTime *)arg; + + rv = CERT_GetCertTimes(certa, ¬BeforeA, ¬AfterA); + if (rv != SECSuccess) { + return (PR_FALSE); + } + + rv = CERT_GetCertTimes(certb, ¬BeforeB, ¬AfterB); + if (rv != SECSuccess) { + return (PR_TRUE); + } + newerbefore = PR_FALSE; + if (LL_CMP(notBeforeA, >, notBeforeB)) { + newerbefore = PR_TRUE; + } + newerafter = PR_FALSE; + if (LL_CMP(notAfterA, >, notAfterB)) { + newerafter = PR_TRUE; + } + + /* check if A is valid at sorttime */ + if (CERT_CheckCertValidTimes(certa, sorttime, PR_FALSE) != + secCertTimeValid) { + aNotValid = PR_TRUE; + } + + /* check if B is valid at sorttime */ + if (CERT_CheckCertValidTimes(certb, sorttime, PR_FALSE) != + secCertTimeValid) { + bNotValid = PR_TRUE; + } + + /* a is valid, b is not */ + if (bNotValid && (!aNotValid)) { + return (PR_TRUE); + } + + /* b is valid, a is not */ + if (aNotValid && (!bNotValid)) { + return (PR_FALSE); + } + + /* a and b are either valid or not valid */ + if (newerbefore && newerafter) { + return (PR_TRUE); + } + + if ((!newerbefore) && (!newerafter)) { + return (PR_FALSE); + } + + if (newerbefore) { + /* cert A was issued after cert B, but expires sooner */ + return (PR_TRUE); + } else { + /* cert B was issued after cert A, but expires sooner */ + return (PR_FALSE); + } +} + +SECStatus +CERT_AddCertToListSorted(CERTCertList *certs, CERTCertificate *cert, + CERTSortCallback f, void *arg) +{ + CERTCertListNode *node; + CERTCertListNode *head; + PRBool ret; + + node = (CERTCertListNode *)PORT_ArenaZAlloc(certs->arena, + sizeof(CERTCertListNode)); + if (node == NULL) { + goto loser; + } + + head = CERT_LIST_HEAD(certs); + + while (!CERT_LIST_END(head, certs)) { + + /* if cert is already in the list, then don't add it again */ + if (cert == head->cert) { + /*XXX*/ + /* don't keep a reference */ + CERT_DestroyCertificate(cert); + goto done; + } + + ret = (*f)(cert, head->cert, arg); + /* if sort function succeeds, then insert before current node */ + if (ret) { + PR_INSERT_BEFORE(&node->links, &head->links); + goto done; + } + + head = CERT_LIST_NEXT(head); + } + /* if we get to the end, then just insert it at the tail */ + PR_INSERT_BEFORE(&node->links, &certs->list); + +done: + /* certs->count++; */ + node->cert = cert; + return (SECSuccess); + +loser: + return (SECFailure); +} + +/* This routine is here because pcertdb.c still has a call to it. + * The SMIME profile code in pcertdb.c should be split into high (find + * the email cert) and low (store the profile) code. At that point, we + * can move this to certhigh.c where it belongs. + * + * remove certs from a list that don't have keyUsage and certType + * that match the given usage. + */ +SECStatus +CERT_FilterCertListByUsage(CERTCertList *certList, SECCertUsage usage, + PRBool ca) +{ + unsigned int requiredKeyUsage; + unsigned int requiredCertType; + CERTCertListNode *node, *savenode; + SECStatus rv; + + if (certList == NULL) + goto loser; + + rv = CERT_KeyUsageAndTypeForCertUsage(usage, ca, &requiredKeyUsage, + &requiredCertType); + if (rv != SECSuccess) { + goto loser; + } + + node = CERT_LIST_HEAD(certList); + + while (!CERT_LIST_END(node, certList)) { + + PRBool bad = (PRBool)(!node->cert); + + /* bad key usage ? */ + if (!bad && + CERT_CheckKeyUsage(node->cert, requiredKeyUsage) != SECSuccess) { + bad = PR_TRUE; + } + /* bad cert type ? */ + if (!bad) { + unsigned int certType = 0; + if (ca) { + /* This function returns a more comprehensive cert type that + * takes trust flags into consideration. Should probably + * fix the cert decoding code to do this. + */ + (void)CERT_IsCACert(node->cert, &certType); + } else { + certType = node->cert->nsCertType; + } + if (!(certType & requiredCertType)) { + bad = PR_TRUE; + } + } + + if (bad) { + /* remove the node if it is bad */ + savenode = CERT_LIST_NEXT(node); + CERT_RemoveCertListNode(node); + node = savenode; + } else { + node = CERT_LIST_NEXT(node); + } + } + return (SECSuccess); + +loser: + return (SECFailure); +} + +PRBool +CERT_IsUserCert(CERTCertificate *cert) +{ + CERTCertTrust trust; + SECStatus rv = SECFailure; + + rv = CERT_GetCertTrust(cert, &trust); + if (rv == SECSuccess && + ((trust.sslFlags & CERTDB_USER) || (trust.emailFlags & CERTDB_USER) || + (trust.objectSigningFlags & CERTDB_USER))) { + return PR_TRUE; + } else { + return PR_FALSE; + } +} + +SECStatus +CERT_FilterCertListForUserCerts(CERTCertList *certList) +{ + CERTCertListNode *node, *freenode; + CERTCertificate *cert; + + if (!certList) { + return SECFailure; + } + + node = CERT_LIST_HEAD(certList); + + while (!CERT_LIST_END(node, certList)) { + cert = node->cert; + if (PR_TRUE != CERT_IsUserCert(cert)) { + /* Not a User Cert, so remove this cert from the list */ + freenode = node; + node = CERT_LIST_NEXT(node); + CERT_RemoveCertListNode(freenode); + } else { + /* Is a User cert, so leave it in the list */ + node = CERT_LIST_NEXT(node); + } + } + + return (SECSuccess); +} + +/* return true if cert is in the list */ +PRBool +CERT_IsInList(const CERTCertificate *cert, const CERTCertList *certList) +{ + CERTCertListNode *node; + for (node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node, certList); + node = CERT_LIST_NEXT(node)) { + if (node->cert == cert) { + return PR_TRUE; + } + } + return PR_FALSE; +} + +/* returned certList is the intersection of the certs on certList and the + * certs on filterList */ +SECStatus +CERT_FilterCertListByCertList(CERTCertList *certList, + const CERTCertList *filterList) +{ + CERTCertListNode *node, *freenode; + CERTCertificate *cert; + + if (!certList) { + return SECFailure; + } + + if (!filterList || CERT_LIST_EMPTY(certList)) { + /* if the filterList is empty, just clear out certList and return */ + for (node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node, certList);) { + freenode = node; + node = CERT_LIST_NEXT(node); + CERT_RemoveCertListNode(freenode); + } + return SECSuccess; + } + + node = CERT_LIST_HEAD(certList); + + while (!CERT_LIST_END(node, certList)) { + cert = node->cert; + if (!CERT_IsInList(cert, filterList)) { + // no matching cert on filter list, remove it from certlist */ + freenode = node; + node = CERT_LIST_NEXT(node); + CERT_RemoveCertListNode(freenode); + } else { + /* matching cert, keep it around */ + node = CERT_LIST_NEXT(node); + } + } + + return (SECSuccess); +} + +SECStatus +CERT_FilterCertListByNickname(CERTCertList *certList, char *nickname, + void *pwarg) +{ + CERTCertList *nameList; + SECStatus rv; + + if (!certList) { + return SECFailure; + } + + /* we could try to match the nickname to the individual cert, + * but nickname parsing is quite complicated, so it's best just + * to use the existing code and get a list of certs that match the + * nickname. We can then compare that list with our input cert list + * and return only those certs that are on both. */ + nameList = PK11_FindCertsFromNickname(nickname, pwarg); + + /* namelist could be NULL, this will force certList to become empty */ + rv = CERT_FilterCertListByCertList(certList, nameList); + /* CERT_DestroyCertList can now accept a NULL pointer */ + CERT_DestroyCertList(nameList); + return rv; +} + +static PZLock *certRefCountLock = NULL; + +/* + * Acquire the cert reference count lock + * There is currently one global lock for all certs, but I'm putting a cert + * arg here so that it will be easy to make it per-cert in the future if + * that turns out to be necessary. + */ +void +CERT_LockCertRefCount(CERTCertificate *cert) +{ + PORT_Assert(certRefCountLock != NULL); + PZ_Lock(certRefCountLock); + return; +} + +/* + * Free the cert reference count lock + */ +void +CERT_UnlockCertRefCount(CERTCertificate *cert) +{ + PORT_Assert(certRefCountLock != NULL); + PRStatus prstat = PZ_Unlock(certRefCountLock); + PORT_AssertArg(prstat == PR_SUCCESS); +} + +static PZLock *certTrustLock = NULL; + +/* + * Acquire the cert trust lock + * There is currently one global lock for all certs, but I'm putting a cert + * arg here so that it will be easy to make it per-cert in the future if + * that turns out to be necessary. + */ +void +CERT_LockCertTrust(const CERTCertificate *cert) +{ + PORT_Assert(certTrustLock != NULL); + PZ_Lock(certTrustLock); +} + +static PZLock *certTempPermCertLock = NULL; + +/* + * Acquire the cert temp/perm/nssCert lock + */ +void +CERT_LockCertTempPerm(const CERTCertificate *cert) +{ + PORT_Assert(certTempPermCertLock != NULL); + PZ_Lock(certTempPermCertLock); +} + +/* Maybe[Lock, Unlock] variants are only to be used by + * CERT_DestroyCertificate, since an application could + * call this after NSS_Shutdown destroys cert locks. */ +void +CERT_MaybeLockCertTempPerm(const CERTCertificate *cert) +{ + if (certTempPermCertLock) { + PZ_Lock(certTempPermCertLock); + } +} + +SECStatus +cert_InitLocks(void) +{ + if (certRefCountLock == NULL) { + certRefCountLock = PZ_NewLock(nssILockRefLock); + PORT_Assert(certRefCountLock != NULL); + if (!certRefCountLock) { + return SECFailure; + } + } + + if (certTrustLock == NULL) { + certTrustLock = PZ_NewLock(nssILockCertDB); + PORT_Assert(certTrustLock != NULL); + if (!certTrustLock) { + PZ_DestroyLock(certRefCountLock); + certRefCountLock = NULL; + return SECFailure; + } + } + + if (certTempPermCertLock == NULL) { + certTempPermCertLock = PZ_NewLock(nssILockCertDB); + PORT_Assert(certTempPermCertLock != NULL); + if (!certTempPermCertLock) { + PZ_DestroyLock(certTrustLock); + PZ_DestroyLock(certRefCountLock); + certRefCountLock = NULL; + certTrustLock = NULL; + return SECFailure; + } + } + + return SECSuccess; +} + +SECStatus +cert_DestroyLocks(void) +{ + SECStatus rv = SECSuccess; + + PORT_Assert(certRefCountLock != NULL); + if (certRefCountLock) { + PZ_DestroyLock(certRefCountLock); + certRefCountLock = NULL; + } else { + rv = SECFailure; + } + + PORT_Assert(certTrustLock != NULL); + if (certTrustLock) { + PZ_DestroyLock(certTrustLock); + certTrustLock = NULL; + } else { + rv = SECFailure; + } + + PORT_Assert(certTempPermCertLock != NULL); + if (certTempPermCertLock) { + PZ_DestroyLock(certTempPermCertLock); + certTempPermCertLock = NULL; + } else { + rv = SECFailure; + } + return rv; +} + +/* + * Free the cert trust lock + */ +void +CERT_UnlockCertTrust(const CERTCertificate *cert) +{ + PORT_Assert(certTrustLock != NULL); + PRStatus prstat = PZ_Unlock(certTrustLock); + PORT_AssertArg(prstat == PR_SUCCESS); +} + +/* + * Free the temp/perm/nssCert lock + */ +void +CERT_UnlockCertTempPerm(const CERTCertificate *cert) +{ + PORT_Assert(certTempPermCertLock != NULL); + PRStatus prstat = PZ_Unlock(certTempPermCertLock); + PORT_AssertArg(prstat == PR_SUCCESS); +} + +void +CERT_MaybeUnlockCertTempPerm(const CERTCertificate *cert) +{ + if (certTempPermCertLock) { + PZ_Unlock(certTempPermCertLock); + } +} + +/* + * Get the StatusConfig data for this handle + */ +CERTStatusConfig * +CERT_GetStatusConfig(CERTCertDBHandle *handle) +{ + return handle->statusConfig; +} + +/* + * Set the StatusConfig data for this handle. There + * should not be another configuration set. + */ +void +CERT_SetStatusConfig(CERTCertDBHandle *handle, CERTStatusConfig *statusConfig) +{ + PORT_Assert(handle->statusConfig == NULL); + handle->statusConfig = statusConfig; +} + +/* + * Code for dealing with subjKeyID to cert mappings. + */ + +static PLHashTable *gSubjKeyIDHash = NULL; +static PRLock *gSubjKeyIDLock = NULL; +static PLHashTable *gSubjKeyIDSlotCheckHash = NULL; +static PRLock *gSubjKeyIDSlotCheckLock = NULL; + +static void * +cert_AllocTable(void *pool, PRSize size) +{ + return PORT_Alloc(size); +} + +static void +cert_FreeTable(void *pool, void *item) +{ + PORT_Free(item); +} + +static PLHashEntry * +cert_AllocEntry(void *pool, const void *key) +{ + return PORT_New(PLHashEntry); +} + +static void +cert_FreeEntry(void *pool, PLHashEntry *he, PRUintn flag) +{ + SECITEM_FreeItem((SECItem *)(he->value), PR_TRUE); + if (flag == HT_FREE_ENTRY) { + SECITEM_FreeItem((SECItem *)(he->key), PR_TRUE); + PORT_Free(he); + } +} + +static PLHashAllocOps cert_AllocOps = { cert_AllocTable, cert_FreeTable, + cert_AllocEntry, cert_FreeEntry }; + +SECStatus +cert_CreateSubjectKeyIDSlotCheckHash(void) +{ + /* + * This hash is used to remember the series of a slot + * when we last checked for user certs + */ + gSubjKeyIDSlotCheckHash = + PL_NewHashTable(0, SECITEM_Hash, SECITEM_HashCompare, + SECITEM_HashCompare, &cert_AllocOps, NULL); + if (!gSubjKeyIDSlotCheckHash) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + gSubjKeyIDSlotCheckLock = PR_NewLock(); + if (!gSubjKeyIDSlotCheckLock) { + PL_HashTableDestroy(gSubjKeyIDSlotCheckHash); + gSubjKeyIDSlotCheckHash = NULL; + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + return SECSuccess; +} + +SECStatus +cert_CreateSubjectKeyIDHashTable(void) +{ + gSubjKeyIDHash = PL_NewHashTable(0, SECITEM_Hash, SECITEM_HashCompare, + SECITEM_HashCompare, &cert_AllocOps, NULL); + if (!gSubjKeyIDHash) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + gSubjKeyIDLock = PR_NewLock(); + if (!gSubjKeyIDLock) { + PL_HashTableDestroy(gSubjKeyIDHash); + gSubjKeyIDHash = NULL; + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + /* initialize the companion hash (for remembering slot series) */ + if (cert_CreateSubjectKeyIDSlotCheckHash() != SECSuccess) { + cert_DestroySubjectKeyIDHashTable(); + return SECFailure; + } + return SECSuccess; +} + +SECStatus +cert_AddSubjectKeyIDMapping(SECItem *subjKeyID, CERTCertificate *cert) +{ + SECItem *newKeyID, *oldVal, *newVal; + SECStatus rv = SECFailure; + + if (!gSubjKeyIDLock) { + /* If one is created, then both are there. So only check for one. */ + return SECFailure; + } + + newVal = SECITEM_DupItem(&cert->derCert); + if (!newVal) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto done; + } + newKeyID = SECITEM_DupItem(subjKeyID); + if (!newKeyID) { + SECITEM_FreeItem(newVal, PR_TRUE); + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto done; + } + + PR_Lock(gSubjKeyIDLock); + /* The hash table implementation does not free up the memory + * associated with the key of an already existing entry if we add a + * duplicate, so we would wind up leaking the previously allocated + * key if we don't remove before adding. + */ + oldVal = (SECItem *)PL_HashTableLookup(gSubjKeyIDHash, subjKeyID); + if (oldVal) { + PL_HashTableRemove(gSubjKeyIDHash, subjKeyID); + } + + rv = (PL_HashTableAdd(gSubjKeyIDHash, newKeyID, newVal)) ? SECSuccess + : SECFailure; + PR_Unlock(gSubjKeyIDLock); +done: + return rv; +} + +SECStatus +cert_RemoveSubjectKeyIDMapping(SECItem *subjKeyID) +{ + SECStatus rv; + if (!gSubjKeyIDLock) + return SECFailure; + + PR_Lock(gSubjKeyIDLock); + rv = (PL_HashTableRemove(gSubjKeyIDHash, subjKeyID)) ? SECSuccess + : SECFailure; + PR_Unlock(gSubjKeyIDLock); + return rv; +} + +SECStatus +cert_UpdateSubjectKeyIDSlotCheck(SECItem *slotid, int series) +{ + SECItem *oldSeries, *newSlotid, *newSeries; + SECStatus rv = SECFailure; + + if (!gSubjKeyIDSlotCheckLock) { + return rv; + } + + newSlotid = SECITEM_DupItem(slotid); + newSeries = SECITEM_AllocItem(NULL, NULL, sizeof(int)); + if (!newSlotid || !newSeries) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + PORT_Memcpy(newSeries->data, &series, sizeof(int)); + + PR_Lock(gSubjKeyIDSlotCheckLock); + oldSeries = (SECItem *)PL_HashTableLookup(gSubjKeyIDSlotCheckHash, slotid); + if (oldSeries) { + /* + * make sure we don't leak the key of an existing entry + * (similar to cert_AddSubjectKeyIDMapping, see comment there) + */ + PL_HashTableRemove(gSubjKeyIDSlotCheckHash, slotid); + } + rv = (PL_HashTableAdd(gSubjKeyIDSlotCheckHash, newSlotid, newSeries)) + ? SECSuccess + : SECFailure; + PR_Unlock(gSubjKeyIDSlotCheckLock); + if (rv == SECSuccess) { + return rv; + } + +loser: + if (newSlotid) { + SECITEM_FreeItem(newSlotid, PR_TRUE); + } + if (newSeries) { + SECITEM_FreeItem(newSeries, PR_TRUE); + } + return rv; +} + +int +cert_SubjectKeyIDSlotCheckSeries(SECItem *slotid) +{ + SECItem *seriesItem = NULL; + int series; + + if (!gSubjKeyIDSlotCheckLock) { + PORT_SetError(SEC_ERROR_NOT_INITIALIZED); + return -1; + } + + PR_Lock(gSubjKeyIDSlotCheckLock); + seriesItem = (SECItem *)PL_HashTableLookup(gSubjKeyIDSlotCheckHash, slotid); + PR_Unlock(gSubjKeyIDSlotCheckLock); + /* getting a null series just means we haven't registered one yet, + * just return 0 */ + if (seriesItem == NULL) { + return 0; + } + /* if we got a series back, assert if it's not the proper length. */ + PORT_Assert(seriesItem->len == sizeof(int)); + if (seriesItem->len != sizeof(int)) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return -1; + } + PORT_Memcpy(&series, seriesItem->data, sizeof(int)); + return series; +} + +SECStatus +cert_DestroySubjectKeyIDSlotCheckHash(void) +{ + if (gSubjKeyIDSlotCheckHash) { + PR_Lock(gSubjKeyIDSlotCheckLock); + PL_HashTableDestroy(gSubjKeyIDSlotCheckHash); + gSubjKeyIDSlotCheckHash = NULL; + PR_Unlock(gSubjKeyIDSlotCheckLock); + PR_DestroyLock(gSubjKeyIDSlotCheckLock); + gSubjKeyIDSlotCheckLock = NULL; + } + return SECSuccess; +} + +SECStatus +cert_DestroySubjectKeyIDHashTable(void) +{ + if (gSubjKeyIDHash) { + PR_Lock(gSubjKeyIDLock); + PL_HashTableDestroy(gSubjKeyIDHash); + gSubjKeyIDHash = NULL; + PR_Unlock(gSubjKeyIDLock); + PR_DestroyLock(gSubjKeyIDLock); + gSubjKeyIDLock = NULL; + } + cert_DestroySubjectKeyIDSlotCheckHash(); + return SECSuccess; +} + +SECItem * +cert_FindDERCertBySubjectKeyID(SECItem *subjKeyID) +{ + SECItem *val; + + if (!gSubjKeyIDLock) + return NULL; + + PR_Lock(gSubjKeyIDLock); + val = (SECItem *)PL_HashTableLookup(gSubjKeyIDHash, subjKeyID); + if (val) { + val = SECITEM_DupItem(val); + } + PR_Unlock(gSubjKeyIDLock); + return val; +} + +CERTCertificate * +CERT_FindCertBySubjectKeyID(CERTCertDBHandle *handle, SECItem *subjKeyID) +{ + CERTCertificate *cert = NULL; + SECItem *derCert; + + derCert = cert_FindDERCertBySubjectKeyID(subjKeyID); + if (derCert) { + cert = CERT_FindCertByDERCert(handle, derCert); + SECITEM_FreeItem(derCert, PR_TRUE); + } + return cert; +} diff --git a/security/nss/lib/certdb/certdb.gyp b/security/nss/lib/certdb/certdb.gyp new file mode 100644 index 0000000000..673d56da31 --- /dev/null +++ b/security/nss/lib/certdb/certdb.gyp @@ -0,0 +1,34 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +{ + 'includes': [ + '../../coreconf/config.gypi' + ], + 'targets': [ + { + 'target_name': 'certdb', + 'type': 'static_library', + 'sources': [ + 'alg1485.c', + 'certdb.c', + 'certv3.c', + 'certxutl.c', + 'crl.c', + 'genname.c', + 'polcyxtn.c', + 'secname.c', + 'stanpcertdb.c', + 'xauthkid.c', + 'xbsconst.c', + 'xconst.c' + ], + 'dependencies': [ + '<(DEPTH)/exports.gyp:nss_exports' + ] + } + ], + 'variables': { + 'module': 'nss' + } +}
\ No newline at end of file diff --git a/security/nss/lib/certdb/certdb.h b/security/nss/lib/certdb/certdb.h new file mode 100644 index 0000000000..53d8a39190 --- /dev/null +++ b/security/nss/lib/certdb/certdb.h @@ -0,0 +1,89 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _CERTDB_H_ +#define _CERTDB_H_ + +/* common flags for all types of certificates */ +#define CERTDB_TERMINAL_RECORD (1u << 0) +#define CERTDB_TRUSTED (1u << 1) +#define CERTDB_SEND_WARN (1u << 2) +#define CERTDB_VALID_CA (1u << 3) +#define CERTDB_TRUSTED_CA (1u << 4) /* trusted for issuing server certs */ +#define CERTDB_NS_TRUSTED_CA (1u << 5) +#define CERTDB_USER (1u << 6) +#define CERTDB_TRUSTED_CLIENT_CA (1u << 7) /* trusted for issuing client certs */ +#define CERTDB_INVISIBLE_CA (1u << 8) /* don't show in UI */ +#define CERTDB_GOVT_APPROVED_CA (1u << 9) /* can do strong crypto in export ver */ + +/* old usage, to keep old programs compiling */ +/* On Windows, Mac, and Linux (and other gcc platforms), we can give compile + * time deprecation warnings when applications use the old CERTDB_VALID_PEER + * define */ +#if __GNUC__ > 3 +#if (__GNUC__ == 4) && (__GNUC_MINOR__ < 5) +typedef unsigned int __CERTDB_VALID_PEER __attribute__((deprecated)); +#else +typedef unsigned int __CERTDB_VALID_PEER __attribute__(( + deprecated("CERTDB_VALID_PEER is now CERTDB_TERMINAL_RECORD"))); +#endif +#define CERTDB_VALID_PEER ((__CERTDB_VALID_PEER)CERTDB_TERMINAL_RECORD) +#else +#ifdef _WIN32 +#pragma deprecated(CERTDB_VALID_PEER) +#endif +#define CERTDB_VALID_PEER CERTDB_TERMINAL_RECORD +#endif + +SEC_BEGIN_PROTOS + +CERTSignedCrl *SEC_FindCrlByKey(CERTCertDBHandle *handle, SECItem *crlKey, + int type); + +CERTSignedCrl *SEC_FindCrlByName(CERTCertDBHandle *handle, SECItem *crlKey, + int type); + +CERTSignedCrl *SEC_FindCrlByDERCert(CERTCertDBHandle *handle, SECItem *derCrl, + int type); + +PRBool SEC_CertNicknameConflict(const char *nickname, const SECItem *derSubject, + CERTCertDBHandle *handle); +CERTSignedCrl *SEC_NewCrl(CERTCertDBHandle *handle, char *url, SECItem *derCrl, + int type); + +SECStatus SEC_DeletePermCRL(CERTSignedCrl *crl); + +SECStatus SEC_LookupCrls(CERTCertDBHandle *handle, CERTCrlHeadNode **nodes, + int type); + +SECStatus SEC_DestroyCrl(CERTSignedCrl *crl); + +CERTSignedCrl *SEC_DupCrl(CERTSignedCrl *acrl); + +SECStatus CERT_AddTempCertToPerm(CERTCertificate *cert, char *nickname, + CERTCertTrust *trust); + +SECStatus SEC_DeletePermCertificate(CERTCertificate *cert); + +PRBool SEC_CrlIsNewer(CERTCrl *inNew, CERTCrl *old); + +/* +** Extract the validity times from a CRL +** "crl" is the CRL +** "notBefore" is the start of the validity period (last update) +** "notAfter" is the end of the validity period (next update) +*/ +SECStatus SEC_GetCrlTimes(CERTCrl *crl, PRTime *notBefore, PRTime *notAfter); + +/* +** Check the validity times of a crl vs. time 't', allowing +** some slop for broken clocks and stuff. +** "crl" is the certificate to be checked +** "t" is the time to check against +*/ +SECCertTimeValidity SEC_CheckCrlTimes(CERTCrl *crl, PRTime t); + +SEC_END_PROTOS + +#endif /* _CERTDB_H_ */ diff --git a/security/nss/lib/certdb/certi.h b/security/nss/lib/certdb/certi.h new file mode 100644 index 0000000000..2a8ae2758f --- /dev/null +++ b/security/nss/lib/certdb/certi.h @@ -0,0 +1,407 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +/* + * certi.h - private data structures for the certificate library + */ +#ifndef _CERTI_H_ +#define _CERTI_H_ + +#include "certt.h" +#include "nssrwlkt.h" + +/* +#define GLOBAL_RWLOCK 1 +*/ + +#define DPC_RWLOCK 1 + +/* all definitions in this file are subject to change */ + +typedef struct OpaqueCRLFieldsStr OpaqueCRLFields; +typedef struct CRLEntryCacheStr CRLEntryCache; +typedef struct CRLDPCacheStr CRLDPCache; +typedef struct CRLIssuerCacheStr CRLIssuerCache; +typedef struct CRLCacheStr CRLCache; +typedef struct CachedCrlStr CachedCrl; +typedef struct NamedCRLCacheStr NamedCRLCache; +typedef struct NamedCRLCacheEntryStr NamedCRLCacheEntry; + +struct OpaqueCRLFieldsStr { + PRBool partial; + PRBool decodingError; + PRBool badEntries; + PRBool badDER; + PRBool badExtensions; + PRBool heapDER; +}; + +typedef struct PreAllocatorStr PreAllocator; + +struct PreAllocatorStr { + PRSize len; + void* data; + PRSize used; + PLArenaPool* arena; + PRSize extra; +}; + +/* CRL entry cache. + This is the same as an entry plus the next/prev pointers for the hash table +*/ + +struct CRLEntryCacheStr { + CERTCrlEntry entry; + CRLEntryCache *prev, *next; +}; + +#define CRL_CACHE_INVALID_CRLS 0x0001 /* this state will be set \ + if we have CRL objects with an invalid DER or signature. Can be \ + cleared if the invalid objects are deleted from the token */ +#define CRL_CACHE_LAST_FETCH_FAILED 0x0002 /* this state will be set \ + if the last CRL fetch encountered an error. Can be cleared if a \ + new fetch succeeds */ + +#define CRL_CACHE_OUT_OF_MEMORY 0x0004 /* this state will be set \ + if we don't have enough memory to build the hash table of entries */ + +typedef enum { + CRL_OriginToken = 0, /* CRL came from PKCS#11 token */ + CRL_OriginExplicit = 1 /* CRL was explicitly added to the cache, from RAM */ +} CRLOrigin; + +typedef enum { + dpcacheNoEntry = 0, /* no entry found for this SN */ + dpcacheFoundEntry = 1, /* entry found for this SN */ + dpcacheCallerError = 2, /* invalid args */ + dpcacheInvalidCacheError = 3, /* CRL in cache may be bad DER */ + /* or unverified */ + dpcacheEmpty = 4, /* no CRL in cache */ + dpcacheLookupError = 5 /* internal error */ +} dpcacheStatus; + +struct CachedCrlStr { + CERTSignedCrl* crl; + CRLOrigin origin; + /* hash table of entries. We use a PLHashTable and pre-allocate the + required amount of memory in one shot, so that our allocator can + simply pass offsets into it when hashing. + + This won't work anymore when we support delta CRLs and iCRLs, because + the size of the hash table will vary over time. At that point, the best + solution will be to allocate large CRLEntry structures by modifying + the DER decoding template. The extra space would be for next/prev + pointers. This would allow entries from different CRLs to be mixed in + the same hash table. + */ + PLHashTable* entries; + PreAllocator* prebuffer; /* big pre-allocated buffer mentioned above */ + PRBool sigChecked; /* this CRL signature has already been checked */ + PRBool sigValid; /* signature verification status . + Only meaningful if checked is PR_TRUE . */ + PRBool unbuildable; /* Avoid using assosiated CRL is it fails + * a decoding step */ +}; + +/* CRL distribution point cache object + This is a cache of CRL entries for a given distribution point of an issuer + It is built from a collection of one full and 0 or more delta CRLs. +*/ + +struct CRLDPCacheStr { +#ifdef DPC_RWLOCK + NSSRWLock* lock; +#else + PRLock* lock; +#endif + SECItem* issuerDERCert; /* issuer DER cert. Don't hold a reference + to the actual cert so the trust can be + updated on the cert automatically. + XXX there may be multiple issuer certs, + with different validity dates. Also + need to deal with SKID/AKID . See + bugzilla 217387, 233118 */ + + CERTCertDBHandle* dbHandle; + + SECItem* subject; /* DER of issuer subject */ + SECItem* distributionPoint; /* DER of distribution point. This may be + NULL when distribution points aren't + in use (ie. the CA has a single CRL). + Currently not used. */ + + /* array of full CRLs matching this distribution point */ + PRUint32 ncrls; /* total number of CRLs in crls */ + CachedCrl** crls; /* array of all matching CRLs */ + /* XCRL With iCRLs and multiple DPs, the CRL can be shared accross several + issuers. In the future, we'll need to globally recycle the CRL in a + separate list in order to avoid extra lookups, decodes, and copies */ + + /* pointers to good decoded CRLs used to build the cache */ + CachedCrl* selected; /* full CRL selected for use in the cache */ +#if 0 + /* for future use */ + PRInt32 numdeltas; /* number of delta CRLs used for the cache */ + CachedCrl** deltas; /* delta CRLs used for the cache */ +#endif + /* cache invalidity bitflag */ + PRUint16 invalid; /* this state will be set if either + CRL_CACHE_INVALID_CRLS or CRL_CACHE_LAST_FETCH_FAILED is set. + In those cases, all certs are considered to have unknown status. + The invalid state can only be cleared during an update if all + error states are cleared */ + PRBool refresh; /* manual refresh from tokens has been forced */ + PRBool mustchoose; /* trigger reselection algorithm, for case when + RAM CRL objects are dropped from the cache */ + PRTime lastfetch; /* time a CRL token fetch was last performed */ + PRTime lastcheck; /* time CRL token objects were last checked for + existence */ +}; + +/* CRL issuer cache object + This object tracks all the distribution point caches for a given issuer. + XCRL once we support multiple issuing distribution points, this object + will be a hash table. For now, it just holds the single CRL distribution + point cache structure. +*/ + +struct CRLIssuerCacheStr { + SECItem* subject; /* DER of issuer subject */ + CRLDPCache* dpp; +}; + +/* CRL revocation cache object + This object tracks all the issuer caches +*/ + +struct CRLCacheStr { +#ifdef GLOBAL_RWLOCK + NSSRWLock* lock; +#else + PRLock* lock; +#endif + /* hash table of issuer to CRLIssuerCacheStr, + indexed by issuer DER subject */ + PLHashTable* issuers; +}; + +SECStatus InitCRLCache(void); +SECStatus ShutdownCRLCache(void); + +/* Returns a pointer to an environment-like string, a series of +** null-terminated strings, terminated by a zero-length string. +** This function is intended to be internal to NSS. +*/ +extern char* cert_GetCertificateEmailAddresses(CERTCertificate* cert); + +/* + * These functions are used to map subjectKeyID extension values to certs + * and to keep track of the checks for user certificates in each slot + */ +SECStatus cert_CreateSubjectKeyIDHashTable(void); + +SECStatus cert_AddSubjectKeyIDMapping(SECItem* subjKeyID, + CERTCertificate* cert); + +SECStatus cert_UpdateSubjectKeyIDSlotCheck(SECItem* slotid, int series); + +int cert_SubjectKeyIDSlotCheckSeries(SECItem* slotid); + +/* + * Call this function to remove an entry from the mapping table. + */ +SECStatus cert_RemoveSubjectKeyIDMapping(SECItem* subjKeyID); + +SECStatus cert_DestroySubjectKeyIDHashTable(void); + +SECItem* cert_FindDERCertBySubjectKeyID(SECItem* subjKeyID); + +/* return maximum length of AVA value based on its type OID tag. */ +extern int cert_AVAOidTagToMaxLen(SECOidTag tag); + +/* Make an AVA, allocated from pool, from OID and DER encoded value */ +extern CERTAVA* CERT_CreateAVAFromRaw(PLArenaPool* pool, const SECItem* OID, + const SECItem* value); + +/* Make an AVA from binary input specified by SECItem */ +extern CERTAVA* CERT_CreateAVAFromSECItem(PLArenaPool* arena, SECOidTag kind, + int valueType, SECItem* value); + +/* + * get a DPCache object for the given issuer subject and dp + * Automatically creates the cache object if it doesn't exist yet. + */ +SECStatus AcquireDPCache(CERTCertificate* issuer, const SECItem* subject, + const SECItem* dp, PRTime t, void* wincx, + CRLDPCache** dpcache, PRBool* writeLocked); + +/* check if a particular SN is in the CRL cache and return its entry */ +dpcacheStatus DPCache_Lookup(CRLDPCache* cache, const SECItem* sn, + CERTCrlEntry** returned); + +/* release a DPCache object that was previously acquired */ +void ReleaseDPCache(CRLDPCache* dpcache, PRBool writeLocked); + +/* + * map Stan errors into NSS errors + * This function examines the stan error stack and automatically sets + * PORT_SetError(); to the appropriate SEC_ERROR value. + */ +void CERT_MapStanError(); + +/* Like CERT_VerifyCert, except with an additional argument, flags. The + * flags are defined immediately below. + */ +SECStatus cert_VerifyCertWithFlags(CERTCertDBHandle* handle, + CERTCertificate* cert, PRBool checkSig, + SECCertUsage certUsage, PRTime t, + PRUint32 flags, void* wincx, + CERTVerifyLog* log); + +/* Use the default settings. + * cert_VerifyCertWithFlags(..., CERT_VERIFYCERT_USE_DEFAULTS, ...) is + * equivalent to CERT_VerifyCert(...); + */ +#define CERT_VERIFYCERT_USE_DEFAULTS 0 + +/* Skip all the OCSP checks during certificate verification, regardless of + * the global OCSP settings. By default, certificate |cert| will have its + * revocation status checked via OCSP according to the global OCSP settings. + * + * OCSP checking is always skipped when certUsage is certUsageStatusResponder. + */ +#define CERT_VERIFYCERT_SKIP_OCSP 1 + +/* Interface function for libpkix cert validation engine: + * cert_verify wrapper. */ +SECStatus cert_VerifyCertChainPkix(CERTCertificate* cert, PRBool checkSig, + SECCertUsage requiredUsage, PRTime time, + void* wincx, CERTVerifyLog* log, + PRBool* sigError, PRBool* revoked); + +SECStatus cert_InitLocks(void); + +SECStatus cert_DestroyLocks(void); + +/* + * fill in nsCertType field of the cert based on the cert extension + */ +extern SECStatus cert_GetCertType(CERTCertificate* cert); + +/* + * compute and return the value of nsCertType for cert, but do not + * update the CERTCertificate. + */ +extern PRUint32 cert_ComputeCertType(CERTCertificate* cert); + +extern PRBool cert_EKUAllowsIPsecIKE(CERTCertificate* cert, + PRBool* isCritical); + +void cert_AddToVerifyLog(CERTVerifyLog* log, CERTCertificate* cert, + long errorCode, unsigned int depth, void* arg); + +/* Insert a DER CRL into the CRL cache, and take ownership of it. + * + * cert_CacheCRLByGeneralName takes ownership of the memory in crl argument + * completely. crl must be freeable by SECITEM_FreeItem. It will be freed + * immediately if it is rejected from the CRL cache, or later during cache + * updates when a new crl is available, or at shutdown time. + * + * canonicalizedName represents the source of the CRL, a GeneralName. + * The format of the encoding is not restricted, but all callers of + * cert_CacheCRLByGeneralName and cert_FindCRLByGeneralName must use + * the same encoding. To facilitate X.500 name matching, a canonicalized + * encoding of the GeneralName should be used, if available. + */ + +SECStatus cert_CacheCRLByGeneralName(CERTCertDBHandle* dbhandle, SECItem* crl, + const SECItem* canonicalizedName); + +struct NamedCRLCacheStr { + PRLock* lock; + PLHashTable* entries; +}; + +/* NamedCRLCacheEntryStr is filled in by cert_CacheCRLByGeneralName, + * and read by cert_FindCRLByGeneralName */ +struct NamedCRLCacheEntryStr { + SECItem* canonicalizedName; + SECItem* crl; /* DER, kept only if CRL + * is successfully cached */ + PRBool inCRLCache; + PRTime successfulInsertionTime; /* insertion time */ + PRTime lastAttemptTime; /* time of last call to + cert_CacheCRLByGeneralName with this name */ + PRBool badDER; /* ASN.1 error */ + PRBool dupe; /* matching DER CRL already in CRL cache */ + PRBool unsupported; /* IDP, delta, any other reason */ +}; + +typedef enum { + certRevocationStatusRevoked = 0, + certRevocationStatusValid = 1, + certRevocationStatusUnknown = 2 +} CERTRevocationStatus; + +/* Returns detailed status of the cert(revStatus variable). Tells if + * issuer cache has OriginFetchedWithTimeout crl in it. */ +SECStatus cert_CheckCertRevocationStatus(CERTCertificate* cert, + CERTCertificate* issuer, + const SECItem* dp, PRTime t, + void* wincx, + CERTRevocationStatus* revStatus, + CERTCRLEntryReasonCode* revReason); + +SECStatus cert_AcquireNamedCRLCache(NamedCRLCache** returned); + +/* cert_FindCRLByGeneralName must be called only while the named cache is + * acquired, and the entry is only valid until cache is released. + */ +SECStatus cert_FindCRLByGeneralName(NamedCRLCache* ncc, + const SECItem* canonicalizedName, + NamedCRLCacheEntry** retEntry); + +SECStatus cert_ReleaseNamedCRLCache(NamedCRLCache* ncc); + +/* This is private for now. Maybe shoule be public. */ +CERTGeneralName* cert_GetSubjectAltNameList(const CERTCertificate* cert, + PLArenaPool* arena); + +/* Count DNS names and IP addresses in a list of GeneralNames */ +PRUint32 cert_CountDNSPatterns(CERTGeneralName* firstName); + +/* + * returns the trust status of the leaf certificate based on usage. + * If the leaf is explicitly untrusted, this function will fail and + * failedFlags will be set to the trust bit value that lead to the failure. + * If the leaf is trusted, isTrusted is set to true and the function returns + * SECSuccess. This function does not check if the cert is fit for a + * particular usage. + */ +SECStatus cert_CheckLeafTrust(CERTCertificate* cert, SECCertUsage usage, + unsigned int* failedFlags, PRBool* isTrusted); + +/* + * Acquire the cert temp/perm lock + */ +void CERT_LockCertTempPerm(const CERTCertificate* cert); + +/* + * Release the temp/perm lock + */ +void CERT_UnlockCertTempPerm(const CERTCertificate* cert); + +/* + * Acquire the cert trust lock + * There is currently one global lock for all certs, but I'm putting a cert + * arg here so that it will be easy to make it per-cert in the future if + * that turns out to be necessary. + */ +void CERT_LockCertTrust(const CERTCertificate* cert); + +/* + * Release the cert trust lock + */ +void CERT_UnlockCertTrust(const CERTCertificate* cert); + +#endif /* _CERTI_H_ */ diff --git a/security/nss/lib/certdb/certt.h b/security/nss/lib/certdb/certt.h new file mode 100644 index 0000000000..aae1184a84 --- /dev/null +++ b/security/nss/lib/certdb/certt.h @@ -0,0 +1,1359 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +/* + * certt.h - public data structures for the certificate library + */ +#ifndef _CERTT_H_ +#define _CERTT_H_ + +#include "prclist.h" +#include "pkcs11t.h" +#include "seccomon.h" +#include "secmodt.h" +#include "secoidt.h" +#include "plarena.h" +#include "prcvar.h" +#include "nssilock.h" +#include "prio.h" +#include "prmon.h" + +/* Stan data types */ +struct NSSCertificateStr; +struct NSSTrustDomainStr; + +/* Non-opaque objects */ +typedef struct CERTAVAStr CERTAVA; +typedef struct CERTAttributeStr CERTAttribute; +typedef struct CERTAuthInfoAccessStr CERTAuthInfoAccess; +typedef struct CERTAuthKeyIDStr CERTAuthKeyID; +typedef struct CERTBasicConstraintsStr CERTBasicConstraints; +typedef struct NSSTrustDomainStr CERTCertDBHandle; +typedef struct CERTCertExtensionStr CERTCertExtension; +typedef struct CERTCertKeyStr CERTCertKey; +typedef struct CERTCertListStr CERTCertList; +typedef struct CERTCertListNodeStr CERTCertListNode; +typedef struct CERTCertNicknamesStr CERTCertNicknames; +typedef struct CERTCertTrustStr CERTCertTrust; +typedef struct CERTCertDistrustStr CERTCertDistrust; +typedef struct CERTCertificateStr CERTCertificate; +typedef struct CERTCertificateListStr CERTCertificateList; +typedef struct CERTCertificateRequestStr CERTCertificateRequest; +typedef struct CERTCrlStr CERTCrl; +typedef struct CERTCrlDistributionPointsStr CERTCrlDistributionPoints; +typedef struct CERTCrlEntryStr CERTCrlEntry; +typedef struct CERTCrlHeadNodeStr CERTCrlHeadNode; +typedef struct CERTCrlKeyStr CERTCrlKey; +typedef struct CERTCrlNodeStr CERTCrlNode; +typedef struct CERTDERCertsStr CERTDERCerts; +typedef struct CERTDistNamesStr CERTDistNames; +typedef struct CERTGeneralNameStr CERTGeneralName; +typedef struct CERTGeneralNameListStr CERTGeneralNameList; +typedef struct CERTIssuerAndSNStr CERTIssuerAndSN; +typedef struct CERTNameStr CERTName; +typedef struct CERTNameConstraintStr CERTNameConstraint; +typedef struct CERTNameConstraintsStr CERTNameConstraints; +typedef struct CERTOKDomainNameStr CERTOKDomainName; +typedef struct CERTPrivKeyUsagePeriodStr CERTPrivKeyUsagePeriod; +typedef struct CERTPublicKeyAndChallengeStr CERTPublicKeyAndChallenge; +typedef struct CERTRDNStr CERTRDN; +typedef struct CERTSignedCrlStr CERTSignedCrl; +typedef struct CERTSignedDataStr CERTSignedData; +typedef struct CERTStatusConfigStr CERTStatusConfig; +typedef struct CERTSubjectListStr CERTSubjectList; +typedef struct CERTSubjectNodeStr CERTSubjectNode; +typedef struct CERTSubjectPublicKeyInfoStr CERTSubjectPublicKeyInfo; +typedef struct CERTValidityStr CERTValidity; +typedef struct CERTVerifyLogStr CERTVerifyLog; +typedef struct CERTVerifyLogNodeStr CERTVerifyLogNode; +typedef struct CRLDistributionPointStr CRLDistributionPoint; + +/* CRL extensions type */ +typedef unsigned long CERTCrlNumber; + +/* +** An X.500 AVA object +*/ +struct CERTAVAStr { + SECItem type; + SECItem value; +}; + +/* +** An X.500 RDN object +*/ +struct CERTRDNStr { + CERTAVA **avas; +}; + +/* +** An X.500 name object +*/ +struct CERTNameStr { + PLArenaPool *arena; + CERTRDN **rdns; +}; + +/* +** An X.509 validity object +*/ +struct CERTValidityStr { + PLArenaPool *arena; + SECItem notBefore; + SECItem notAfter; +}; + +/* + * A serial number and issuer name, which is used as a database key + */ +struct CERTCertKeyStr { + SECItem serialNumber; + SECItem derIssuer; +}; + +/* +** A signed data object. Used to implement the "signed" macro used +** in the X.500 specs. +*/ +struct CERTSignedDataStr { + SECItem data; + SECAlgorithmID signatureAlgorithm; + SECItem signature; +}; + +/* +** An X.509 subject-public-key-info object +*/ +struct CERTSubjectPublicKeyInfoStr { + PLArenaPool *arena; + SECAlgorithmID algorithm; + SECItem subjectPublicKey; +}; + +struct CERTPublicKeyAndChallengeStr { + SECItem spki; + SECItem challenge; +}; + +struct CERTCertTrustStr { + unsigned int sslFlags; + unsigned int emailFlags; + unsigned int objectSigningFlags; +}; + +/* + * Distrust dates for specific certificate usages. + * These dates are hardcoded in nssckbi/builtins. They are DER encoded to be + * compatible with the format of certdata.txt, other date fields in certs and + * existing functions to read these dates. Clients should check the distrust + * date in certificates to avoid trusting a CA for service they have ceased to + * support */ +struct CERTCertDistrustStr { + SECItem serverDistrustAfter; + SECItem emailDistrustAfter; +}; + +/* + * defined the types of trust that exist + */ +typedef enum SECTrustTypeEnum { + trustSSL = 0, + trustEmail = 1, + trustObjectSigning = 2, + trustTypeNone = 3 +} SECTrustType; + +#define SEC_GET_TRUST_FLAGS(trust, type) \ + (((type) == trustSSL) \ + ? ((trust)->sslFlags) \ + : (((type) == trustEmail) ? ((trust)->emailFlags) \ + : (((type) == trustObjectSigning) \ + ? ((trust)->objectSigningFlags) \ + : 0))) + +/* +** An X.509.3 certificate extension +*/ +struct CERTCertExtensionStr { + SECItem id; + SECItem critical; + SECItem value; +}; + +struct CERTSubjectNodeStr { + struct CERTSubjectNodeStr *next; + struct CERTSubjectNodeStr *prev; + SECItem certKey; + SECItem keyID; +}; + +struct CERTSubjectListStr { + PLArenaPool *arena; + int ncerts; + char *emailAddr; + CERTSubjectNode *head; + CERTSubjectNode *tail; /* do we need tail? */ + void *entry; +}; + +/* +** An X.509 certificate object (the unsigned form) +*/ +struct CERTCertificateStr { + /* the arena is used to allocate any data structures that have the same + * lifetime as the cert. This is all stuff that hangs off of the cert + * structure, and is all freed at the same time. It is used when the + * cert is decoded, destroyed, and at some times when it changes + * state + */ + PLArenaPool *arena; + + /* The following fields are static after the cert has been decoded */ + char *subjectName; + char *issuerName; + CERTSignedData signatureWrap; /* XXX */ + SECItem derCert; /* original DER for the cert */ + SECItem derIssuer; /* DER for issuer name */ + SECItem derSubject; /* DER for subject name */ + SECItem derPublicKey; /* DER for the public key */ + SECItem certKey; /* database key for this cert */ + SECItem version; + SECItem serialNumber; + SECAlgorithmID signature; + CERTName issuer; + CERTValidity validity; + CERTName subject; + CERTSubjectPublicKeyInfo subjectPublicKeyInfo; + SECItem issuerID; + SECItem subjectID; + CERTCertExtension **extensions; + char *emailAddr; + CERTCertDBHandle *dbhandle; + SECItem subjectKeyID; /* x509v3 subject key identifier */ + PRBool keyIDGenerated; /* was the keyid generated? */ + unsigned int keyUsage; /* what uses are allowed for this cert */ + unsigned int rawKeyUsage; /* value of the key usage extension */ + PRBool keyUsagePresent; /* was the key usage extension present */ + PRUint32 nsCertType; /* value of the ns cert type extension */ + /* must be 32-bit for PR_ATOMIC_SET */ + + /* these values can be set by the application to bypass certain checks + * or to keep the cert in memory for an entire session. + * XXX - need an api to set these + */ + PRBool keepSession; /* keep this cert for entire session*/ + PRBool timeOK; /* is the bad validity time ok? */ + CERTOKDomainName *domainOK; /* these domain names are ok */ + + /* + * these values can change when the cert changes state. These state + * changes include transitions from temp to perm or vice-versa, and + * changes of trust flags + */ + PRBool isperm; + PRBool istemp; + char *nickname; + char *dbnickname; + struct NSSCertificateStr *nssCertificate; /* This is Stan stuff. */ + CERTCertTrust *trust; + + /* the reference count is modified whenever someone looks up, dups + * or destroys a certificate + */ + int referenceCount; + + /* The subject list is a list of all certs with the same subject name. + * It can be modified any time a cert is added or deleted from either + * the in-memory(temporary) or on-disk(permanent) database. + */ + CERTSubjectList *subjectList; + + /* these belong in the static section, but are here to maintain + * the structure's integrity + */ + CERTAuthKeyID *authKeyID; /* x509v3 authority key identifier */ + PRBool isRoot; /* cert is the end of a chain */ + + /* these fields are used by client GUI code to keep track of ssl sockets + * that are blocked waiting on GUI feedback related to this cert. + * XXX - these should be moved into some sort of application specific + * data structure. They are only used by the browser right now. + */ + union { + void *apointer; /* was struct SECSocketNode* authsocketlist */ + struct { + unsigned int hasUnsupportedCriticalExt : 1; + /* add any new option bits needed here */ + } bits; + } options; + int series; /* was int authsocketcount; record the series of the pkcs11ID */ + + /* This is PKCS #11 stuff. */ + PK11SlotInfo *slot; /*if this cert came of a token, which is it*/ + CK_OBJECT_HANDLE pkcs11ID; /*and which object on that token is it */ + PRBool ownSlot; /*true if the cert owns the slot reference */ + /* These fields are used in nssckbi/builtins CAs. */ + CERTCertDistrust *distrust; +}; +#define SEC_CERTIFICATE_VERSION_1 0 /* default created */ +#define SEC_CERTIFICATE_VERSION_2 1 /* v2 */ +#define SEC_CERTIFICATE_VERSION_3 2 /* v3 extensions */ + +#define SEC_CRL_VERSION_1 0 /* default */ +#define SEC_CRL_VERSION_2 1 /* v2 extensions */ + +/* + * used to identify class of cert in mime stream code + */ +#define SEC_CERT_CLASS_CA 1 +#define SEC_CERT_CLASS_SERVER 2 +#define SEC_CERT_CLASS_USER 3 +#define SEC_CERT_CLASS_EMAIL 4 + +struct CERTDERCertsStr { + PLArenaPool *arena; + int numcerts; + SECItem *rawCerts; +}; + +/* +** A PKCS ? Attribute +** XXX this is duplicated through out the code, it *should* be moved +** to a central location. Where would be appropriate? +*/ +struct CERTAttributeStr { + SECItem attrType; + SECItem **attrValue; +}; + +/* +** A PKCS#10 certificate-request object (the unsigned form) +*/ +struct CERTCertificateRequestStr { + PLArenaPool *arena; + SECItem version; + CERTName subject; + CERTSubjectPublicKeyInfo subjectPublicKeyInfo; + CERTAttribute **attributes; +}; +#define SEC_CERTIFICATE_REQUEST_VERSION 0 /* what we *create* */ + +/* +** A certificate list object. +*/ +struct CERTCertificateListStr { + SECItem *certs; + int len; /* number of certs */ + PLArenaPool *arena; +}; + +struct CERTCertListNodeStr { + PRCList links; + CERTCertificate *cert; + void *appData; +}; + +struct CERTCertListStr { + PRCList list; + PLArenaPool *arena; +}; + +#define CERT_LIST_HEAD(l) ((CERTCertListNode *)PR_LIST_HEAD(&l->list)) +#define CERT_LIST_TAIL(l) ((CERTCertListNode *)PR_LIST_TAIL(&l->list)) +#define CERT_LIST_NEXT(n) ((CERTCertListNode *)n->links.next) +#define CERT_LIST_END(n, l) (((void *)n) == ((void *)&l->list)) +#define CERT_LIST_EMPTY(l) CERT_LIST_END(CERT_LIST_HEAD(l), l) + +struct CERTCrlEntryStr { + SECItem serialNumber; + SECItem revocationDate; + CERTCertExtension **extensions; +}; + +struct CERTCrlStr { + PLArenaPool *arena; + SECItem version; + SECAlgorithmID signatureAlg; + SECItem derName; + CERTName name; + SECItem lastUpdate; + SECItem nextUpdate; /* optional for x.509 CRL */ + CERTCrlEntry **entries; + CERTCertExtension **extensions; + /* can't add anything there for binary backwards compatibility reasons */ +}; + +struct CERTCrlKeyStr { + SECItem derName; + SECItem dummy; /* The decoder can not skip a primitive, + this serves as a place holder for the + decoder to finish its task only + */ +}; + +struct CERTSignedCrlStr { + PLArenaPool *arena; + CERTCrl crl; + void *reserved1; + PRBool reserved2; + PRBool isperm; + PRBool istemp; + int referenceCount; + CERTCertDBHandle *dbhandle; + CERTSignedData signatureWrap; /* XXX */ + char *url; + SECItem *derCrl; + PK11SlotInfo *slot; + CK_OBJECT_HANDLE pkcs11ID; + void *opaque; /* do not touch */ +}; + +struct CERTCrlHeadNodeStr { + PLArenaPool *arena; + CERTCertDBHandle *dbhandle; + CERTCrlNode *first; + CERTCrlNode *last; +}; + +struct CERTCrlNodeStr { + CERTCrlNode *next; + int type; + CERTSignedCrl *crl; +}; + +/* + * Array of X.500 Distinguished Names + */ +struct CERTDistNamesStr { + PLArenaPool *arena; + int nnames; + SECItem *names; + void *head; /* private */ +}; + +/* + * NS_CERT_TYPE defines are used in two areas: + * 1) The old NSS Cert Type Extension, which is a certificate extension in the + * actual cert. It was created before the x509 Extended Key Usage Extension, + * which has now taken over it's function. This field is only 8 bits wide + * 2) The nsCertType entry in the CERTCertificate structure. This field is + * 32 bits wide. + * Any entries in this table greater than 0x80 will not be able to be encoded + * in an NSS Cert Type Extension, but can still be represented internally in + * the nsCertType field. + */ +#define NS_CERT_TYPE_IPSEC_CA (0x200) /* outside the NS Cert Type Extenstion */ +#define NS_CERT_TYPE_IPSEC (0x100) /* outside the NS Cert Type Extenstion */ +#define NS_CERT_TYPE_SSL_CLIENT (0x80) /* bit 0 */ +#define NS_CERT_TYPE_SSL_SERVER (0x40) /* bit 1 */ +#define NS_CERT_TYPE_EMAIL (0x20) /* bit 2 */ +#define NS_CERT_TYPE_OBJECT_SIGNING (0x10) /* bit 3 */ +#define NS_CERT_TYPE_RESERVED (0x08) /* bit 4 */ +#define NS_CERT_TYPE_SSL_CA (0x04) /* bit 5 */ +#define NS_CERT_TYPE_EMAIL_CA (0x02) /* bit 6 */ +#define NS_CERT_TYPE_OBJECT_SIGNING_CA (0x01) /* bit 7 */ + +#define EXT_KEY_USAGE_TIME_STAMP (0x8000) +#define EXT_KEY_USAGE_STATUS_RESPONDER (0x4000) + +#define NS_CERT_TYPE_APP \ + (NS_CERT_TYPE_SSL_CLIENT | NS_CERT_TYPE_SSL_SERVER | NS_CERT_TYPE_EMAIL | \ + NS_CERT_TYPE_IPSEC | NS_CERT_TYPE_OBJECT_SIGNING) + +#define NS_CERT_TYPE_CA \ + (NS_CERT_TYPE_SSL_CA | NS_CERT_TYPE_EMAIL_CA | \ + NS_CERT_TYPE_OBJECT_SIGNING_CA | EXT_KEY_USAGE_STATUS_RESPONDER | \ + NS_CERT_TYPE_IPSEC_CA) +typedef enum SECCertUsageEnum { + certUsageSSLClient = 0, + certUsageSSLServer = 1, + certUsageSSLServerWithStepUp = 2, + certUsageSSLCA = 3, + certUsageEmailSigner = 4, + certUsageEmailRecipient = 5, + certUsageObjectSigner = 6, + certUsageUserCertImport = 7, + certUsageVerifyCA = 8, + certUsageProtectedObjectSigner = 9, + certUsageStatusResponder = 10, + certUsageAnyCA = 11, + certUsageIPsec = 12 +} SECCertUsage; + +typedef PRInt64 SECCertificateUsage; + +#define certificateUsageCheckAllUsages (0x0000) +#define certificateUsageSSLClient (0x0001) +#define certificateUsageSSLServer (0x0002) +#define certificateUsageSSLServerWithStepUp (0x0004) +#define certificateUsageSSLCA (0x0008) +#define certificateUsageEmailSigner (0x0010) +#define certificateUsageEmailRecipient (0x0020) +#define certificateUsageObjectSigner (0x0040) +#define certificateUsageUserCertImport (0x0080) +#define certificateUsageVerifyCA (0x0100) +#define certificateUsageProtectedObjectSigner (0x0200) +#define certificateUsageStatusResponder (0x0400) +#define certificateUsageAnyCA (0x0800) +#define certificateUsageIPsec (0x1000) + +#define certificateUsageHighest certificateUsageIPsec + +/* + * Does the cert belong to the user, a peer, or a CA. + */ +typedef enum CERTCertOwnerEnum { + certOwnerUser = 0, + certOwnerPeer = 1, + certOwnerCA = 2 +} CERTCertOwner; + +/* + * This enum represents the state of validity times of a certificate + */ +typedef enum SECCertTimeValidityEnum { + secCertTimeValid = 0, + secCertTimeExpired = 1, + secCertTimeNotValidYet = 2, + secCertTimeUndetermined = 3 /* validity could not be decoded from the + cert, most likely because it was NULL */ +} SECCertTimeValidity; + +/* + * This is used as return status in functions that compare the validity + * periods of two certificates A and B, currently only + * CERT_CompareValidityTimes. + */ + +typedef enum CERTCompareValidityStatusEnum { + certValidityUndetermined = 0, /* the function is unable to select one cert + over another */ + certValidityChooseB = 1, /* cert B should be preferred */ + certValidityEqual = 2, /* both certs have the same validity period */ + certValidityChooseA = 3 /* cert A should be preferred */ +} CERTCompareValidityStatus; + +/* + * Interface for getting certificate nickname strings out of the database + */ + +/* these are values for the what argument below */ +#define SEC_CERT_NICKNAMES_ALL 1 +#define SEC_CERT_NICKNAMES_USER 2 +#define SEC_CERT_NICKNAMES_SERVER 3 +#define SEC_CERT_NICKNAMES_CA 4 + +struct CERTCertNicknamesStr { + PLArenaPool *arena; + void *head; + int numnicknames; + char **nicknames; + int what; + int totallen; +}; + +struct CERTIssuerAndSNStr { + SECItem derIssuer; + CERTName issuer; + SECItem serialNumber; +}; + +/* X.509 v3 Key Usage Extension flags */ +#define KU_DIGITAL_SIGNATURE (0x80) /* bit 0 */ +#define KU_NON_REPUDIATION (0x40) /* bit 1 */ +#define KU_KEY_ENCIPHERMENT (0x20) /* bit 2 */ +#define KU_DATA_ENCIPHERMENT (0x10) /* bit 3 */ +#define KU_KEY_AGREEMENT (0x08) /* bit 4 */ +#define KU_KEY_CERT_SIGN (0x04) /* bit 5 */ +#define KU_CRL_SIGN (0x02) /* bit 6 */ +#define KU_ENCIPHER_ONLY (0x01) /* bit 7 */ +#define KU_ALL \ + (KU_DIGITAL_SIGNATURE | KU_NON_REPUDIATION | KU_KEY_ENCIPHERMENT | \ + KU_DATA_ENCIPHERMENT | KU_KEY_AGREEMENT | KU_KEY_CERT_SIGN | \ + KU_CRL_SIGN | KU_ENCIPHER_ONLY) + +/* This value will not occur in certs. It is used internally for the case + * when either digital signature or non-repudiation is the correct value. + */ +#define KU_DIGITAL_SIGNATURE_OR_NON_REPUDIATION (0x2000) + +/* This value will not occur in certs. It is used internally for the case + * when the key type is not know ahead of time and either key agreement or + * key encipherment are the correct value based on key type + */ +#define KU_KEY_AGREEMENT_OR_ENCIPHERMENT (0x4000) + +/* internal bits that do not match bits in the x509v3 spec, but are used + * for similar purposes + */ +#define KU_NS_GOVT_APPROVED (0x8000) /*don't make part of KU_ALL!*/ +/* +* x.509 v3 Basic Constraints Extension +* If isCA is false, the pathLenConstraint is ignored. +* Otherwise, the following pathLenConstraint values will apply: +* < 0 - there is no limit to the certificate path +* 0 - CA can issues end-entity certificates only +* > 0 - the number of certificates in the certificate path is +* limited to this number +*/ +#define CERT_UNLIMITED_PATH_CONSTRAINT -2 + +struct CERTBasicConstraintsStr { + PRBool isCA; /* on if is CA */ + int pathLenConstraint; /* maximum number of certificates that can be + in the cert path. Only applies to a CA + certificate; otherwise, it's ignored. + */ +}; + +/* Maximum length of a certificate chain */ +#define CERT_MAX_CERT_CHAIN 20 + +#define CERT_MAX_SERIAL_NUMBER_BYTES 20 /* from RFC 3280 */ +#define CERT_MAX_DN_BYTES 4096 /* arbitrary */ + +/* x.509 v3 Reason Flags, used in CRLDistributionPoint Extension */ +#define RF_UNUSED (0x80) /* bit 0 */ +#define RF_KEY_COMPROMISE (0x40) /* bit 1 */ +#define RF_CA_COMPROMISE (0x20) /* bit 2 */ +#define RF_AFFILIATION_CHANGED (0x10) /* bit 3 */ +#define RF_SUPERSEDED (0x08) /* bit 4 */ +#define RF_CESSATION_OF_OPERATION (0x04) /* bit 5 */ +#define RF_CERTIFICATE_HOLD (0x02) /* bit 6 */ + +/* enum for CRL Entry Reason Code */ +typedef enum CERTCRLEntryReasonCodeEnum { + crlEntryReasonUnspecified = 0, + crlEntryReasonKeyCompromise = 1, + crlEntryReasonCaCompromise = 2, + crlEntryReasonAffiliationChanged = 3, + crlEntryReasonSuperseded = 4, + crlEntryReasonCessationOfOperation = 5, + crlEntryReasoncertificatedHold = 6, + crlEntryReasonRemoveFromCRL = 8, + crlEntryReasonPrivilegeWithdrawn = 9, + crlEntryReasonAaCompromise = 10 +} CERTCRLEntryReasonCode; + +/* If we needed to extract the general name field, use this */ +/* General Name types */ +typedef enum CERTGeneralNameTypeEnum { + certOtherName = 1, + certRFC822Name = 2, + certDNSName = 3, + certX400Address = 4, + certDirectoryName = 5, + certEDIPartyName = 6, + certURI = 7, + certIPAddress = 8, + certRegisterID = 9 +} CERTGeneralNameType; + +typedef struct OtherNameStr { + SECItem name; + SECItem oid; +} OtherName; + +struct CERTGeneralNameStr { + CERTGeneralNameType type; /* name type */ + union { + CERTName directoryName; /* distinguish name */ + OtherName OthName; /* Other Name */ + SECItem other; /* the rest of the name forms */ + } name; + SECItem derDirectoryName; /* this is saved to simplify directory name + comparison */ + PRCList l; +}; + +struct CERTGeneralNameListStr { + PLArenaPool *arena; + CERTGeneralName *name; + int refCount; + int len; + PZLock *lock; +}; + +struct CERTNameConstraintStr { + CERTGeneralName name; + SECItem DERName; + SECItem min; + SECItem max; + PRCList l; +}; + +struct CERTNameConstraintsStr { + CERTNameConstraint *permited; + CERTNameConstraint *excluded; + SECItem **DERPermited; + SECItem **DERExcluded; +}; + +/* Private Key Usage Period extension struct. */ +struct CERTPrivKeyUsagePeriodStr { + SECItem notBefore; + SECItem notAfter; + PLArenaPool *arena; +}; + +/* X.509 v3 Authority Key Identifier extension. For the authority certificate + issuer field, we only support URI now. + */ +struct CERTAuthKeyIDStr { + SECItem keyID; /* unique key identifier */ + CERTGeneralName *authCertIssuer; /* CA's issuer name. End with a NULL */ + SECItem authCertSerialNumber; /* CA's certificate serial number */ + SECItem **DERAuthCertIssuer; /* This holds the DER encoded format of + the authCertIssuer field. It is used + by the encoding engine. It should be + used as a read only field by the caller. + */ +}; + +/* x.509 v3 CRL Distributeion Point */ + +/* + * defined the types of CRL Distribution points + */ +typedef enum DistributionPointTypesEnum { + generalName = 1, /* only support this for now */ + relativeDistinguishedName = 2 +} DistributionPointTypes; + +struct CRLDistributionPointStr { + DistributionPointTypes distPointType; + union { + CERTGeneralName *fullName; + CERTRDN relativeName; + } distPoint; + SECItem reasons; + CERTGeneralName *crlIssuer; + + /* Reserved for internal use only*/ + SECItem derDistPoint; + SECItem derRelativeName; + SECItem **derCrlIssuer; + SECItem **derFullName; + SECItem bitsmap; +}; + +struct CERTCrlDistributionPointsStr { + CRLDistributionPoint **distPoints; +}; + +/* + * This structure is used to keep a log of errors when verifying + * a cert chain. This allows multiple errors to be reported all at + * once. + */ +struct CERTVerifyLogNodeStr { + CERTCertificate *cert; /* what cert had the error */ + long error; /* what error was it? */ + unsigned int depth; /* how far up the chain are we */ + void *arg; /* error specific argument */ + struct CERTVerifyLogNodeStr *next; /* next in the list */ + struct CERTVerifyLogNodeStr *prev; /* next in the list */ +}; + +struct CERTVerifyLogStr { + PLArenaPool *arena; + unsigned int count; + struct CERTVerifyLogNodeStr *head; + struct CERTVerifyLogNodeStr *tail; +}; + +struct CERTOKDomainNameStr { + CERTOKDomainName *next; + char *name; +}; + +typedef SECStatus(PR_CALLBACK *CERTStatusChecker)(CERTCertDBHandle *handle, + CERTCertificate *cert, + PRTime time, void *pwArg); + +typedef SECStatus(PR_CALLBACK *CERTStatusDestroy)(CERTStatusConfig *handle); + +struct CERTStatusConfigStr { + CERTStatusChecker statusChecker; /* NULL means no checking enabled */ + CERTStatusDestroy statusDestroy; /* enabled or no, will clean up */ + void *statusContext; /* cx specific to checking protocol */ +}; + +struct CERTAuthInfoAccessStr { + SECItem method; + SECItem derLocation; + CERTGeneralName *location; /* decoded location */ +}; + +/* This is the typedef for the callback passed to CERT_OpenCertDB() */ +/* callback to return database name based on version number */ +typedef char *(*CERTDBNameFunc)(void *arg, int dbVersion); + +/* + * types of cert packages that we can decode + */ +typedef enum CERTPackageTypeEnum { + certPackageNone = 0, + certPackageCert = 1, + certPackagePKCS7 = 2, + certPackageNSCertSeq = 3, + certPackageNSCertWrap = 4 +} CERTPackageType; + +/* + * these types are for the PKIX Certificate Policies extension + */ +typedef struct { + SECOidTag oid; + SECItem qualifierID; + SECItem qualifierValue; +} CERTPolicyQualifier; + +typedef struct { + SECOidTag oid; + SECItem policyID; + CERTPolicyQualifier **policyQualifiers; +} CERTPolicyInfo; + +typedef struct { + PLArenaPool *arena; + CERTPolicyInfo **policyInfos; +} CERTCertificatePolicies; + +typedef struct { + SECItem organization; + SECItem **noticeNumbers; +} CERTNoticeReference; + +typedef struct { + PLArenaPool *arena; + CERTNoticeReference noticeReference; + SECItem derNoticeReference; + SECItem displayText; +} CERTUserNotice; + +typedef struct { + PLArenaPool *arena; + SECItem **oids; +} CERTOidSequence; + +/* + * these types are for the PKIX Policy Mappings extension + */ +typedef struct { + SECItem issuerDomainPolicy; + SECItem subjectDomainPolicy; +} CERTPolicyMap; + +typedef struct { + PLArenaPool *arena; + CERTPolicyMap **policyMaps; +} CERTCertificatePolicyMappings; + +/* + * these types are for the PKIX inhibitAnyPolicy extension + */ +typedef struct { + SECItem inhibitAnySkipCerts; +} CERTCertificateInhibitAny; + +/* + * these types are for the PKIX Policy Constraints extension + */ +typedef struct { + SECItem explicitPolicySkipCerts; + SECItem inhibitMappingSkipCerts; +} CERTCertificatePolicyConstraints; + +/* + * These types are for the validate chain callback param. + * + * CERTChainVerifyCallback is an application-supplied callback that can be used + * to augment libpkix's certificate chain validation with additional + * application-specific checks. It may be called multiple times if there are + * multiple potentially-valid paths for the certificate being validated. This + * callback is called before revocation checking is done on the certificates in + * the given chain. + * + * - isValidChainArg contains the application-provided opaque argument + * - currentChain is the currently validated chain. It is ordered with the leaf + * certificate at the head and the trust anchor at the tail. + * + * The callback should set *chainOK = PR_TRUE and return SECSuccess if the + * certificate chain is acceptable. It should set *chainOK = PR_FALSE and + * return SECSuccess if the chain is unacceptable, to indicate that the given + * chain is bad and path building should continue. It should return SECFailure + * to indicate an fatal error that will cause path validation to fail + * immediately. + */ +typedef SECStatus (*CERTChainVerifyCallbackFunc)( + void *isChainValidArg, const CERTCertList *currentChain, PRBool *chainOK); + +/* + * Note: If extending this structure, it will be necessary to change the + * associated CERTValParamInType + */ +typedef struct { + CERTChainVerifyCallbackFunc isChainValid; + void *isChainValidArg; +} CERTChainVerifyCallback; + +/* + * these types are for the CERT_PKIX* Verification functions + * These are all optional parameters. + */ + +typedef enum { + cert_pi_end = 0, /* SPECIAL: signifies end of array of + * CERTValParam* */ + cert_pi_nbioContext = 1, /* specify a non-blocking IO context used to + * resume a session. If this argument is + * specified, no other arguments should be. + * Specified in value.pointer.p. If the + * operation completes the context will be + * freed. */ + cert_pi_nbioAbort = 2, /* specify a non-blocking IO context for an + * existing operation which the caller wants + * to abort. If this argument is + * specified, no other arguments should be. + * Specified in value.pointer.p. If the + * operation succeeds the context will be + * freed. */ + cert_pi_certList = 3, /* specify the chain to validate against. If + * this value is given, then the path + * construction step in the validation is + * skipped. Specified in value.pointer.chain */ + cert_pi_policyOID = 4, /* validate certificate for policy OID. + * Specified in value.array.oids. Cert must + * be good for at least one OID in order + * to validate. Default is that the user is not + * concerned about certificate policy. */ + cert_pi_policyFlags = 5, /* flags for each policy specified in policyOID. + * Specified in value.scalar.ul. Policy flags + * apply to all specified oids. + * Use CERT_POLICY_FLAG_* macros below. If not + * specified policy flags default to 0 */ + cert_pi_keyusage = 6, /* specify what the keyusages the certificate + * will be evaluated against, specified in + * value.scalar.ui. The cert must validate for + * at least one of the specified key usages. + * Values match the KU_ bit flags defined + * in this file. Default is derived from + * the 'usages' function argument */ + cert_pi_extendedKeyusage = 7, /* specify what the required extended key + * usage of the certificate. Specified as + * an array of oidTags in value.array.oids. + * The cert must validate for at least one + * of the specified extended key usages. + * If not specified, no extended key usages + * will be checked. */ + cert_pi_date = 8, /* validate certificate is valid as of date + * specified in value.scalar.time. A special + * value '0' indicates 'now'. default is '0' */ + cert_pi_revocationFlags = 9, /* Specify what revocation checking to do. + * See CERT_REV_FLAG_* macros below + * Set in value.pointer.revocation */ + cert_pi_certStores = 10, /* Bitmask of Cert Store flags (see below) + * Set in value.scalar.ui */ + cert_pi_trustAnchors = + 11, /* Specify the list of trusted roots to + * validate against. + * The default set of trusted roots, these are + * root CA certs from libnssckbi.so or CA + * certs trusted by user, are used in any of + * the following cases: + * * when the parameter is not set. + * * when the list of trust anchors is + * empty. + * Note that this handling can be further + * altered by altering the + * cert_pi_useOnlyTrustAnchors flag + * Specified in value.pointer.chain */ + cert_pi_useAIACertFetch = 12, /* Enables cert fetching using AIA extension. + * In NSS 3.12.1 or later. Default is off. + * Value is in value.scalar.b */ + cert_pi_chainVerifyCallback = 13, + /* The callback container for doing extra + * validation on the currently calculated chain. + * Value is in value.pointer.chainVerifyCallback */ + cert_pi_useOnlyTrustAnchors = 14, + /* If true, disables trusting any + * certificates other than the ones passed in via cert_pi_trustAnchors. + * If false, then the certificates specified via cert_pi_trustAnchors + * will be combined with the pre-existing trusted roots, but only + * for the certificate validation being performed. + * If no value has been supplied via cert_pi_trustAnchors, this has + * no effect. + * The default value is true, meaning if this is not supplied, only + * trust anchors supplied via cert_pi_trustAnchors are trusted. + * Specified in value.scalar.b */ + cert_pi_max /* SPECIAL: signifies maximum allowed value, + * can increase in future releases */ +} CERTValParamInType; + +/* + * for all out parameters: + * out parameters are only returned if the caller asks for them in + * the CERTValOutParam array. Caller is responsible for the CERTValOutParam + * array itself. The pkix verify function will allocate and other arrays + * pointers, or objects. The Caller is responsible for freeing those results. + * If SECWouldBlock is returned, only cert_pi_nbioContext is returned. + */ +typedef enum { + cert_po_end = 0, /* SPECIAL: signifies end of array of + * CERTValParam* */ + cert_po_nbioContext = 1, /* Return a nonblocking context. If no + * non-blocking context is specified, then + * blocking IO will be used. + * Returned in value.pointer.p. The context is + * freed after an abort or a complete operation. + * This value is only returned on SECWouldBlock. + */ + cert_po_trustAnchor = 2, /* Return the trust anchor for the chain that + * was validated. Returned in + * value.pointer.cert, this value is only + * returned on SECSuccess. */ + cert_po_certList = 3, /* Return the entire chain that was validated. + * Returned in value.pointer.certList. If no + * chain could be constructed, this value + * would be NULL. */ + cert_po_policyOID = 4, /* Return the policies that were found to be + * valid. Returned in value.array.oids as an + * array. This is only returned on + * SECSuccess. */ + cert_po_errorLog = 5, /* Return a log of problems with the chain. + * Returned in value.pointer.log */ + cert_po_usages = 6, /* Return what usages the certificate is valid + for. Returned in value.scalar.usages */ + cert_po_keyUsage = 7, /* Return what key usages the certificate + * is valid for. + * Returned in value.scalar.usage */ + cert_po_extendedKeyusage = 8, /* Return what extended key usages the + * certificate is valid for. + * Returned in value.array.oids */ + cert_po_max /* SPECIAL: signifies maximum allowed value, + * can increase in future releases */ + +} CERTValParamOutType; + +typedef enum { + cert_revocation_method_crl = 0, + cert_revocation_method_ocsp, + cert_revocation_method_count +} CERTRevocationMethodIndex; + +/* + * The following flags are supposed to be used to control bits in + * each integer contained in the array pointed to be: + * CERTRevocationTests.cert_rev_flags_per_method + * All Flags are prefixed by CERT_REV_M_, where _M_ indicates + * this is a method dependent flag. + */ + +/* + * Whether or not to use a method for revocation testing. + * If set to "do not test", then all other flags are ignored. + */ +#define CERT_REV_M_DO_NOT_TEST_USING_THIS_METHOD 0UL +#define CERT_REV_M_TEST_USING_THIS_METHOD 1UL + +/* + * Whether or not NSS is allowed to attempt to fetch fresh information + * from the network. + * (Although fetching will never happen if fresh information for the + * method is already locally available.) + */ +#define CERT_REV_M_ALLOW_NETWORK_FETCHING 0UL +#define CERT_REV_M_FORBID_NETWORK_FETCHING 2UL + +/* + * Example for an implicit default source: + * The globally configured default OCSP responder. + * IGNORE means: + * ignore the implicit default source, whether it's configured or not. + * ALLOW means: + * if an implicit default source is configured, + * then it overrides any available or missing source in the cert. + * if no implicit default source is configured, + * then we continue to use what's available (or not available) + * in the certs. + */ +#define CERT_REV_M_ALLOW_IMPLICIT_DEFAULT_SOURCE 0UL +#define CERT_REV_M_IGNORE_IMPLICIT_DEFAULT_SOURCE 4UL + +/* + * Defines the behavior if no fresh information is available, + * fetching from the network is allowed, but the source of revocation + * information is unknown (even after considering implicit sources, + * if allowed by other flags). + * SKIPT_TEST means: + * We ignore that no fresh information is available and + * skip this test. + * REQUIRE_INFO means: + * We still require that fresh information is available. + * Other flags define what happens on missing fresh info. + */ +#define CERT_REV_M_SKIP_TEST_ON_MISSING_SOURCE 0UL +#define CERT_REV_M_REQUIRE_INFO_ON_MISSING_SOURCE 8UL + +/* + * Defines the behavior if we are unable to obtain fresh information. + * INGORE means: + * Return "cert status unknown" + * FAIL means: + * Return "cert revoked". + */ +#define CERT_REV_M_IGNORE_MISSING_FRESH_INFO 0UL +#define CERT_REV_M_FAIL_ON_MISSING_FRESH_INFO 16UL + +/* + * What should happen if we were able to find fresh information using + * this method, and the data indicated the cert is good? + * STOP_TESTING means: + * Our success is sufficient, do not continue testing + * other methods. + * CONTINUE_TESTING means: + * We will continue and test the next allowed + * specified method. + */ +#define CERT_REV_M_STOP_TESTING_ON_FRESH_INFO 0UL +#define CERT_REV_M_CONTINUE_TESTING_ON_FRESH_INFO 32UL + +/* When this flag is used, libpkix will never attempt to use the GET HTTP + * method for OCSP requests; it will always use POST. + */ +#define CERT_REV_M_FORCE_POST_METHOD_FOR_OCSP 64UL + +/* + * The following flags are supposed to be used to control bits in + * CERTRevocationTests.cert_rev_method_independent_flags + * All Flags are prefixed by CERT_REV_M_, where _M_ indicates + * this is a method independent flag. + */ + +/* + * This defines the order to checking. + * EACH_METHOD_SEPARATELY means: + * Do all tests related to a particular allowed method + * (both local information and network fetching) in a single step. + * Only after testing for a particular method is done, + * then switching to the next method will happen. + * ALL_LOCAL_INFORMATION_FIRST means: + * Start by testing the information for all allowed methods + * which are already locally available. Only after that is done + * consider to fetch from the network (as allowed by other flags). + */ +#define CERT_REV_MI_TEST_EACH_METHOD_SEPARATELY 0UL +#define CERT_REV_MI_TEST_ALL_LOCAL_INFORMATION_FIRST 1UL + +/* + * Use this flag to specify that it's necessary that fresh information + * is available for at least one of the allowed methods, but it's + * irrelevant which of the mechanisms succeeded. + * NO_OVERALL_INFO_REQUIREMENT means: + * We strictly follow the requirements for each individual method. + * REQUIRE_SOME_FRESH_INFO_AVAILABLE means: + * After the individual tests have been executed, we must have + * been able to find fresh information using at least one method. + * If we were unable to find fresh info, it's a failure. + * This setting overrides the CERT_REV_M_FAIL_ON_MISSING_FRESH_INFO + * flag on all methods. + */ +#define CERT_REV_MI_NO_OVERALL_INFO_REQUIREMENT 0UL +#define CERT_REV_MI_REQUIRE_SOME_FRESH_INFO_AVAILABLE 2UL + +typedef struct { + /* + * The size of the array that cert_rev_flags_per_method points to, + * meaning, the number of methods that are known and defined + * by the caller. + */ + PRUint32 number_of_defined_methods; + + /* + * A pointer to an array of integers. + * Each integer defines revocation checking for a single method, + * by having individual CERT_REV_M_* bits set or not set. + * The meaning of index numbers into this array are defined by + * enum CERTRevocationMethodIndex + * The size of the array must be specified by the caller in the separate + * variable number_of_defined_methods. + * The size of the array may be smaller than + * cert_revocation_method_count, it can happen if a caller + * is not yet aware of the latest revocation methods + * (or does not want to use them). + */ + PRUint64 *cert_rev_flags_per_method; + + /* + * How many preferred methods are specified? + * This is equivalent to the size of the array that + * preferred_methods points to. + * It's allowed to set this value to zero, + * then NSS will decide which methods to prefer. + */ + PRUint32 number_of_preferred_methods; + + /* Array that may specify an optional order of preferred methods. + * Each array entry shall contain a method identifier as defined + * by CERTRevocationMethodIndex. + * The entry at index [0] specifies the method with highest preference. + * These methods will be tested first for locally available information. + * Methods allowed for downloading will be attempted in the same order. + */ + CERTRevocationMethodIndex *preferred_methods; + + /* + * An integer which defines certain aspects of revocation checking + * (independent of individual methods) by having individual + * CERT_REV_MI_* bits set or not set. + */ + PRUint64 cert_rev_method_independent_flags; +} CERTRevocationTests; + +typedef struct { + CERTRevocationTests leafTests; + CERTRevocationTests chainTests; +} CERTRevocationFlags; + +typedef struct CERTValParamInValueStr { + union { + PRBool b; + PRInt32 i; + PRUint32 ui; + PRInt64 l; + PRUint64 ul; + PRTime time; + } scalar; + union { + const void *p; + const char *s; + const CERTCertificate *cert; + const CERTCertList *chain; + const CERTRevocationFlags *revocation; + const CERTChainVerifyCallback *chainVerifyCallback; + } pointer; + union { + const PRInt32 *pi; + const PRUint32 *pui; + const PRInt64 *pl; + const PRUint64 *pul; + const SECOidTag *oids; + } array; + int arraySize; +} CERTValParamInValue; + +typedef struct CERTValParamOutValueStr { + union { + PRBool b; + PRInt32 i; + PRUint32 ui; + PRInt64 l; + PRUint64 ul; + SECCertificateUsage usages; + } scalar; + union { + void *p; + char *s; + CERTVerifyLog *log; + CERTCertificate *cert; + CERTCertList *chain; + } pointer; + union { + void *p; + SECOidTag *oids; + } array; + int arraySize; +} CERTValParamOutValue; + +typedef struct { + CERTValParamInType type; + CERTValParamInValue value; +} CERTValInParam; + +typedef struct { + CERTValParamOutType type; + CERTValParamOutValue value; +} CERTValOutParam; + +/* + * Levels of standards conformance strictness for CERT_NameToAsciiInvertible + */ +typedef enum CertStrictnessLevels { + CERT_N2A_READABLE = 0, /* maximum human readability */ + CERT_N2A_STRICT = 10, /* strict RFC compliance */ + CERT_N2A_INVERTIBLE = 20 /* maximum invertibility, + all DirectoryStrings encoded in hex */ +} CertStrictnessLevel; + +/* + * policy flag defines + */ +#define CERT_POLICY_FLAG_NO_MAPPING 1 +#define CERT_POLICY_FLAG_EXPLICIT 2 +#define CERT_POLICY_FLAG_NO_ANY 4 + +/* + * CertStore flags + */ +#define CERT_ENABLE_LDAP_FETCH 1 +#define CERT_ENABLE_HTTP_FETCH 2 + +/* This functin pointer type may be used for any function that takes + * a CERTCertificate * and returns an allocated string, which must be + * freed by a call to PORT_Free. + */ +typedef char *(*CERT_StringFromCertFcn)(CERTCertificate *cert); + +/* XXX Lisa thinks the template declarations belong in cert.h, not here? */ + +#include "secasn1t.h" /* way down here because I expect template stuff to + * move out of here anyway */ + +SEC_BEGIN_PROTOS + +extern const SEC_ASN1Template CERT_CertificateRequestTemplate[]; +extern const SEC_ASN1Template CERT_CertificateTemplate[]; +extern const SEC_ASN1Template SEC_SignedCertificateTemplate[]; +extern const SEC_ASN1Template CERT_CertExtensionTemplate[]; +extern const SEC_ASN1Template CERT_SequenceOfCertExtensionTemplate[]; +extern const SEC_ASN1Template SECKEY_PublicKeyTemplate[]; +extern const SEC_ASN1Template CERT_SubjectPublicKeyInfoTemplate[]; +extern const SEC_ASN1Template CERT_TimeChoiceTemplate[]; +extern const SEC_ASN1Template CERT_ValidityTemplate[]; +extern const SEC_ASN1Template CERT_PublicKeyAndChallengeTemplate[]; +extern const SEC_ASN1Template SEC_CertSequenceTemplate[]; + +extern const SEC_ASN1Template CERT_IssuerAndSNTemplate[]; +extern const SEC_ASN1Template CERT_NameTemplate[]; +extern const SEC_ASN1Template CERT_SetOfSignedCrlTemplate[]; +extern const SEC_ASN1Template CERT_RDNTemplate[]; +extern const SEC_ASN1Template CERT_SignedDataTemplate[]; +extern const SEC_ASN1Template CERT_CrlTemplate[]; +extern const SEC_ASN1Template CERT_SignedCrlTemplate[]; + +/* +** XXX should the attribute stuff be centralized for all of ns/security? +*/ +extern const SEC_ASN1Template CERT_AttributeTemplate[]; +extern const SEC_ASN1Template CERT_SetOfAttributeTemplate[]; + +/* These functions simply return the address of the above-declared templates. +** This is necessary for Windows DLLs. Sigh. +*/ +SEC_ASN1_CHOOSER_DECLARE(CERT_CertificateRequestTemplate) +SEC_ASN1_CHOOSER_DECLARE(CERT_CertificateTemplate) +SEC_ASN1_CHOOSER_DECLARE(CERT_CrlTemplate) +SEC_ASN1_CHOOSER_DECLARE(CERT_IssuerAndSNTemplate) +SEC_ASN1_CHOOSER_DECLARE(CERT_NameTemplate) +SEC_ASN1_CHOOSER_DECLARE(CERT_SequenceOfCertExtensionTemplate) +SEC_ASN1_CHOOSER_DECLARE(CERT_SetOfSignedCrlTemplate) +SEC_ASN1_CHOOSER_DECLARE(CERT_SignedDataTemplate) +SEC_ASN1_CHOOSER_DECLARE(CERT_SubjectPublicKeyInfoTemplate) +SEC_ASN1_CHOOSER_DECLARE(SEC_SignedCertificateTemplate) +SEC_ASN1_CHOOSER_DECLARE(CERT_SignedCrlTemplate) +SEC_ASN1_CHOOSER_DECLARE(CERT_TimeChoiceTemplate) + +SEC_END_PROTOS + +#endif /* _CERTT_H_ */ diff --git a/security/nss/lib/certdb/certv3.c b/security/nss/lib/certdb/certv3.c new file mode 100644 index 0000000000..f00b88f1d7 --- /dev/null +++ b/security/nss/lib/certdb/certv3.c @@ -0,0 +1,222 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Code for dealing with X509.V3 extensions. + */ + +#include "cert.h" +#include "secitem.h" +#include "secoid.h" +#include "secder.h" +#include "secasn1.h" +#include "certxutl.h" +#include "secerr.h" + +SECStatus +CERT_FindCertExtensionByOID(CERTCertificate *cert, SECItem *oid, SECItem *value) +{ + return (cert_FindExtensionByOID(cert->extensions, oid, value)); +} + +SECStatus +CERT_FindCertExtension(const CERTCertificate *cert, int tag, SECItem *value) +{ + return (cert_FindExtension(cert->extensions, tag, value)); +} + +static void +SetExts(void *object, CERTCertExtension **exts) +{ + CERTCertificate *cert = (CERTCertificate *)object; + + cert->extensions = exts; + DER_SetUInteger(cert->arena, &(cert->version), SEC_CERTIFICATE_VERSION_3); +} + +void * +CERT_StartCertExtensions(CERTCertificate *cert) +{ + return (cert_StartExtensions((void *)cert, cert->arena, SetExts)); +} + +/* + * get the value of the Netscape Certificate Type Extension + */ +SECStatus +CERT_FindNSCertTypeExtension(CERTCertificate *cert, SECItem *retItem) +{ + + return (CERT_FindBitStringExtension( + cert->extensions, SEC_OID_NS_CERT_EXT_CERT_TYPE, retItem)); +} + +/* + * get the value of a string type extension + */ +char * +CERT_FindNSStringExtension(CERTCertificate *cert, int oidtag) +{ + SECItem wrapperItem, tmpItem = { siBuffer, 0 }; + SECStatus rv; + PLArenaPool *arena = NULL; + char *retstring = NULL; + + wrapperItem.data = NULL; + tmpItem.data = NULL; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + + if (!arena) { + goto loser; + } + + rv = cert_FindExtension(cert->extensions, oidtag, &wrapperItem); + if (rv != SECSuccess) { + goto loser; + } + + rv = SEC_QuickDERDecodeItem( + arena, &tmpItem, SEC_ASN1_GET(SEC_IA5StringTemplate), &wrapperItem); + + if (rv != SECSuccess) { + goto loser; + } + + retstring = (char *)PORT_Alloc(tmpItem.len + 1); + if (retstring == NULL) { + goto loser; + } + + PORT_Memcpy(retstring, tmpItem.data, tmpItem.len); + retstring[tmpItem.len] = '\0'; + +loser: + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + + if (wrapperItem.data) { + PORT_Free(wrapperItem.data); + } + + return (retstring); +} + +/* + * get the value of the X.509 v3 Key Usage Extension + */ +SECStatus +CERT_FindKeyUsageExtension(CERTCertificate *cert, SECItem *retItem) +{ + + return (CERT_FindBitStringExtension(cert->extensions, + SEC_OID_X509_KEY_USAGE, retItem)); +} + +/* + * get the value of the X.509 v3 Key Usage Extension + */ +SECStatus +CERT_FindSubjectKeyIDExtension(CERTCertificate *cert, SECItem *retItem) +{ + + SECStatus rv; + SECItem encodedValue = { siBuffer, NULL, 0 }; + SECItem decodedValue = { siBuffer, NULL, 0 }; + + rv = cert_FindExtension(cert->extensions, SEC_OID_X509_SUBJECT_KEY_ID, + &encodedValue); + if (rv == SECSuccess) { + PORTCheapArenaPool tmpArena; + PORT_InitCheapArena(&tmpArena, DER_DEFAULT_CHUNKSIZE); + rv = SEC_QuickDERDecodeItem(&tmpArena.arena, &decodedValue, + SEC_ASN1_GET(SEC_OctetStringTemplate), + &encodedValue); + if (rv == SECSuccess) { + rv = SECITEM_CopyItem(NULL, retItem, &decodedValue); + } + PORT_DestroyCheapArena(&tmpArena); + } + SECITEM_FreeItem(&encodedValue, PR_FALSE); + return rv; +} + +SECStatus +CERT_FindBasicConstraintExten(CERTCertificate *cert, + CERTBasicConstraints *value) +{ + SECItem encodedExtenValue; + SECStatus rv; + + encodedExtenValue.data = NULL; + encodedExtenValue.len = 0; + + rv = cert_FindExtension(cert->extensions, SEC_OID_X509_BASIC_CONSTRAINTS, + &encodedExtenValue); + if (rv != SECSuccess) { + return (rv); + } + + rv = CERT_DecodeBasicConstraintValue(value, &encodedExtenValue); + + /* free the raw extension data */ + PORT_Free(encodedExtenValue.data); + encodedExtenValue.data = NULL; + + return (rv); +} + +CERTAuthKeyID * +CERT_FindAuthKeyIDExten(PLArenaPool *arena, CERTCertificate *cert) +{ + SECItem encodedExtenValue; + SECStatus rv; + CERTAuthKeyID *ret; + + encodedExtenValue.data = NULL; + encodedExtenValue.len = 0; + + rv = cert_FindExtension(cert->extensions, SEC_OID_X509_AUTH_KEY_ID, + &encodedExtenValue); + if (rv != SECSuccess) { + return (NULL); + } + + ret = CERT_DecodeAuthKeyID(arena, &encodedExtenValue); + + PORT_Free(encodedExtenValue.data); + encodedExtenValue.data = NULL; + + return (ret); +} + +SECStatus +CERT_CheckCertUsage(CERTCertificate *cert, unsigned char usage) +{ + SECItem keyUsage; + SECStatus rv; + + /* There is no extension, v1 or v2 certificate */ + if (cert->extensions == NULL) { + return (SECSuccess); + } + + keyUsage.data = NULL; + + /* This code formerly ignored the Key Usage extension if it was + ** marked non-critical. That was wrong. Since we do understand it, + ** we are obligated to honor it, whether or not it is critical. + */ + rv = CERT_FindKeyUsageExtension(cert, &keyUsage); + if (rv == SECFailure) { + rv = (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND) ? SECSuccess + : SECFailure; + } else if (!keyUsage.data || !keyUsage.len || !(keyUsage.data[0] & usage)) { + PORT_SetError(SEC_ERROR_CERT_USAGES_INVALID); + rv = SECFailure; + } + PORT_Free(keyUsage.data); + return (rv); +} diff --git a/security/nss/lib/certdb/certxutl.c b/security/nss/lib/certdb/certxutl.c new file mode 100644 index 0000000000..5db2eb9ef0 --- /dev/null +++ b/security/nss/lib/certdb/certxutl.c @@ -0,0 +1,496 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Certificate Extensions handling code + * + */ + +#include "cert.h" +#include "secitem.h" +#include "secoid.h" +#include "secder.h" +#include "secasn1.h" +#include "certxutl.h" +#include "secerr.h" + +#ifdef OLD +#include "ocspti.h" /* XXX a better extensions interface would not + * require knowledge of data structures of callers */ +#endif + +static CERTCertExtension * +GetExtension(CERTCertExtension **extensions, SECItem *oid) +{ + CERTCertExtension **exts; + CERTCertExtension *ext = NULL; + SECComparison comp; + + exts = extensions; + + if (exts) { + while (*exts) { + ext = *exts; + comp = SECITEM_CompareItem(oid, &ext->id); + if (comp == SECEqual) + break; + + exts++; + } + return (*exts ? ext : NULL); + } + return (NULL); +} + +SECStatus +cert_FindExtensionByOID(CERTCertExtension **extensions, SECItem *oid, + SECItem *value) +{ + CERTCertExtension *ext; + SECStatus rv = SECSuccess; + + ext = GetExtension(extensions, oid); + if (ext == NULL) { + PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND); + return (SECFailure); + } + if (value) + rv = SECITEM_CopyItem(NULL, value, &ext->value); + return (rv); +} + +SECStatus +CERT_GetExtenCriticality(CERTCertExtension **extensions, int tag, + PRBool *isCritical) +{ + CERTCertExtension *ext; + SECOidData *oid; + + if (!isCritical) + return (SECSuccess); + + /* find the extension in the extensions list */ + oid = SECOID_FindOIDByTag((SECOidTag)tag); + if (!oid) { + return (SECFailure); + } + ext = GetExtension(extensions, &oid->oid); + if (ext == NULL) { + PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND); + return (SECFailure); + } + + /* If the criticality is omitted, then it is false by default. + ex->critical.data is NULL */ + if (ext->critical.data == NULL) + *isCritical = PR_FALSE; + else + *isCritical = (ext->critical.data[0] == 0xff) ? PR_TRUE : PR_FALSE; + return (SECSuccess); +} + +SECStatus +cert_FindExtension(CERTCertExtension **extensions, int tag, SECItem *value) +{ + SECOidData *oid; + + oid = SECOID_FindOIDByTag((SECOidTag)tag); + if (!oid) { + return (SECFailure); + } + + return (cert_FindExtensionByOID(extensions, &oid->oid, value)); +} + +typedef struct _extNode { + struct _extNode *next; + CERTCertExtension *ext; +} extNode; + +typedef struct { + void (*setExts)(void *object, CERTCertExtension **exts); + void *object; + PLArenaPool *ownerArena; + PLArenaPool *arena; + extNode *head; + int count; +} extRec; + +/* + * cert_StartExtensions + * + * NOTE: This interface changed significantly to remove knowledge + * about callers data structures (owner objects) + */ +void * +cert_StartExtensions(void *owner, PLArenaPool *ownerArena, + void (*setExts)(void *object, CERTCertExtension **exts)) +{ + PLArenaPool *arena; + extRec *handle; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) { + return (0); + } + + handle = (extRec *)PORT_ArenaAlloc(arena, sizeof(extRec)); + if (!handle) { + PORT_FreeArena(arena, PR_FALSE); + return (0); + } + + handle->object = owner; + handle->ownerArena = ownerArena; + handle->setExts = setExts; + + handle->arena = arena; + handle->head = 0; + handle->count = 0; + + return (handle); +} + +static unsigned char hextrue = 0xff; + +/* + * Note - assumes that data pointed to by oid->data will not move + */ +SECStatus +CERT_AddExtensionByOID(void *exthandle, SECItem *oid, SECItem *value, + PRBool critical, PRBool copyData) +{ + CERTCertExtension *ext; + SECStatus rv; + extNode *node; + extRec *handle; + + handle = (extRec *)exthandle; + + /* allocate space for extension and list node */ + ext = (CERTCertExtension *)PORT_ArenaZAlloc(handle->ownerArena, + sizeof(CERTCertExtension)); + if (!ext) { + return (SECFailure); + } + + node = (extNode *)PORT_ArenaAlloc(handle->arena, sizeof(extNode)); + if (!node) { + return (SECFailure); + } + + /* add to list */ + node->next = handle->head; + handle->head = node; + + /* point to ext struct */ + node->ext = ext; + + /* set critical field */ + if (critical) { + ext->critical.data = (unsigned char *)&hextrue; + ext->critical.len = 1; + } + + /* set object ID of the extension and its value */ + if (copyData) { + rv = SECITEM_CopyItem(handle->ownerArena, &ext->id, oid); + if (rv) { + return (SECFailure); + } + + rv = SECITEM_CopyItem(handle->ownerArena, &ext->value, value); + if (rv) { + return (SECFailure); + } + } else { + ext->id = *oid; + ext->value = *value; + } + + handle->count++; + + return (SECSuccess); +} + +SECStatus +CERT_AddExtension(void *exthandle, int idtag, SECItem *value, PRBool critical, + PRBool copyData) +{ + SECOidData *oid; + + oid = SECOID_FindOIDByTag((SECOidTag)idtag); + if (!oid) { + return (SECFailure); + } + + return (CERT_AddExtensionByOID(exthandle, &oid->oid, value, critical, + copyData)); +} + +SECStatus +CERT_EncodeAndAddExtension(void *exthandle, int idtag, void *value, + PRBool critical, const SEC_ASN1Template *atemplate) +{ + extRec *handle; + SECItem *encitem; + + handle = (extRec *)exthandle; + + encitem = SEC_ASN1EncodeItem(handle->ownerArena, NULL, value, atemplate); + if (encitem == NULL) { + return (SECFailure); + } + + return CERT_AddExtension(exthandle, idtag, encitem, critical, PR_FALSE); +} + +void +PrepareBitStringForEncoding(SECItem *bitsmap, SECItem *value) +{ + unsigned char onebyte; + unsigned int i, len = 0; + + /* to prevent warning on some platform at compile time */ + onebyte = '\0'; + /* Get the position of the right-most turn-on bit */ + for (i = 0; i < (value->len) * 8; ++i) { + if (i % 8 == 0) + onebyte = value->data[i / 8]; + if (onebyte & 0x80) + len = i; + onebyte <<= 1; + } + bitsmap->data = value->data; + /* Add one here since we work with base 1 */ + bitsmap->len = len + 1; +} + +SECStatus +CERT_EncodeAndAddBitStrExtension(void *exthandle, int idtag, SECItem *value, + PRBool critical) +{ + SECItem bitsmap; + + PrepareBitStringForEncoding(&bitsmap, value); + return (CERT_EncodeAndAddExtension(exthandle, idtag, &bitsmap, critical, + SEC_ASN1_GET(SEC_BitStringTemplate))); +} + +SECStatus +CERT_FinishExtensions(void *exthandle) +{ + extRec *handle; + extNode *node; + CERTCertExtension **exts; + SECStatus rv = SECFailure; + + handle = (extRec *)exthandle; + + /* allocate space for extensions array */ + exts = PORT_ArenaNewArray(handle->ownerArena, CERTCertExtension *, + handle->count + 1); + if (exts == NULL) { + goto loser; + } + + /* put extensions in owner object and update its version number */ + +#ifdef OLD + switch (handle->type) { + case CertificateExtensions: + handle->owner.cert->extensions = exts; + DER_SetUInteger(ownerArena, &(handle->owner.cert->version), + SEC_CERTIFICATE_VERSION_3); + break; + case CrlExtensions: + handle->owner.crl->extensions = exts; + DER_SetUInteger(ownerArena, &(handle->owner.crl->version), + SEC_CRL_VERSION_2); + break; + case OCSPRequestExtensions: + handle->owner.request->tbsRequest->requestExtensions = exts; + break; + case OCSPSingleRequestExtensions: + handle->owner.singleRequest->singleRequestExtensions = exts; + break; + case OCSPResponseSingleExtensions: + handle->owner.singleResponse->singleExtensions = exts; + break; + } +#endif + + handle->setExts(handle->object, exts); + + /* update the version number */ + + /* copy each extension pointer */ + node = handle->head; + while (node) { + *exts = node->ext; + + node = node->next; + exts++; + } + + /* terminate the array of extensions */ + *exts = 0; + + rv = SECSuccess; + +loser: + /* free working arena */ + PORT_FreeArena(handle->arena, PR_FALSE); + return rv; +} + +SECStatus +CERT_MergeExtensions(void *exthandle, CERTCertExtension **extensions) +{ + CERTCertExtension *ext; + SECStatus rv = SECSuccess; + SECOidTag tag; + extNode *node; + extRec *handle = exthandle; + + if (!exthandle || !extensions) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + while ((ext = *extensions++) != NULL) { + tag = SECOID_FindOIDTag(&ext->id); + for (node = handle->head; node != NULL; node = node->next) { + if (tag == 0) { + if (SECITEM_ItemsAreEqual(&ext->id, &node->ext->id)) + break; + } else { + if (SECOID_FindOIDTag(&node->ext->id) == tag) { + break; + } + } + } + if (node == NULL) { + PRBool critical = (ext->critical.len != 0 && + ext->critical.data[ext->critical.len - 1] != 0); + if (critical && tag == SEC_OID_UNKNOWN) { + PORT_SetError(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION); + rv = SECFailure; + break; + } + /* add to list */ + rv = CERT_AddExtensionByOID(exthandle, &ext->id, &ext->value, + critical, PR_TRUE); + if (rv != SECSuccess) + break; + } + } + return rv; +} + +/* + * get the value of the Netscape Certificate Type Extension + */ +SECStatus +CERT_FindBitStringExtension(CERTCertExtension **extensions, int tag, + SECItem *retItem) +{ + SECItem wrapperItem, tmpItem = { siBuffer, 0 }; + SECStatus rv; + PORTCheapArenaPool tmpArena; + + wrapperItem.data = NULL; + tmpItem.data = NULL; + + PORT_InitCheapArena(&tmpArena, DER_DEFAULT_CHUNKSIZE); + + rv = cert_FindExtension(extensions, tag, &wrapperItem); + if (rv != SECSuccess) { + goto loser; + } + + rv = SEC_QuickDERDecodeItem(&tmpArena.arena, &tmpItem, + SEC_ASN1_GET(SEC_BitStringTemplate), + &wrapperItem); + + if (rv != SECSuccess) { + goto loser; + } + + retItem->data = (unsigned char *)PORT_ZAlloc((tmpItem.len + 7) >> 3); + if (retItem->data == NULL) { + goto loser; + } + + if (tmpItem.len > 0) { + PORT_Memcpy(retItem->data, tmpItem.data, (tmpItem.len + 7) >> 3); + } + + retItem->len = tmpItem.len; + + rv = SECSuccess; + goto done; + +loser: + rv = SECFailure; + +done: + PORT_DestroyCheapArena(&tmpArena); + + if (wrapperItem.data) { + PORT_Free(wrapperItem.data); + } + + return (rv); +} + +PRBool +cert_HasCriticalExtension(CERTCertExtension **extensions) +{ + CERTCertExtension **exts; + CERTCertExtension *ext = NULL; + PRBool hasCriticalExten = PR_FALSE; + + exts = extensions; + + if (exts) { + while (*exts) { + ext = *exts; + /* If the criticality is omitted, it's non-critical */ + if (ext->critical.data && ext->critical.data[0] == 0xff) { + hasCriticalExten = PR_TRUE; + break; + } + exts++; + } + } + return (hasCriticalExten); +} + +PRBool +cert_HasUnknownCriticalExten(CERTCertExtension **extensions) +{ + CERTCertExtension **exts; + CERTCertExtension *ext = NULL; + PRBool hasUnknownCriticalExten = PR_FALSE; + + exts = extensions; + + if (exts) { + while (*exts) { + ext = *exts; + /* If the criticality is omitted, it's non-critical. + If an extension is critical, make sure that we know + how to process the extension. + */ + if (ext->critical.data && ext->critical.data[0] == 0xff) { + if (SECOID_KnownCertExtenOID(&ext->id) == PR_FALSE) { + hasUnknownCriticalExten = PR_TRUE; + break; + } + } + exts++; + } + } + return (hasUnknownCriticalExten); +} diff --git a/security/nss/lib/certdb/certxutl.h b/security/nss/lib/certdb/certxutl.h new file mode 100644 index 0000000000..a8c76b5cfa --- /dev/null +++ b/security/nss/lib/certdb/certxutl.h @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * x.509 v3 certificate extension helper routines + * + */ + +#ifndef _CERTXUTL_H_ +#define _CERTXUTL_H_ + +#include "nspr.h" + +#ifdef OLD +typedef enum { + CertificateExtensions, + CrlExtensions, + OCSPRequestExtensions, + OCSPSingleRequestExtensions, + OCSPResponseSingleExtensions +} ExtensionsType; +#endif + +extern PRBool cert_HasCriticalExtension(CERTCertExtension **extensions); + +extern SECStatus CERT_FindBitStringExtension(CERTCertExtension **extensions, + int tag, SECItem *retItem); +extern void *cert_StartExtensions(void *owner, PLArenaPool *arena, + void (*setExts)(void *object, + CERTCertExtension **exts)); + +extern SECStatus cert_FindExtension(CERTCertExtension **extensions, int tag, + SECItem *value); + +extern SECStatus cert_FindExtensionByOID(CERTCertExtension **extensions, + SECItem *oid, SECItem *value); + +extern SECStatus cert_GetExtenCriticality(CERTCertExtension **extensions, + int tag, PRBool *isCritical); + +extern PRBool cert_HasUnknownCriticalExten(CERTCertExtension **extensions); + +#endif diff --git a/security/nss/lib/certdb/crl.c b/security/nss/lib/certdb/crl.c new file mode 100644 index 0000000000..d110841241 --- /dev/null +++ b/security/nss/lib/certdb/crl.c @@ -0,0 +1,3046 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Moved from secpkcs7.c + */ + +#include "cert.h" +#include "certi.h" +#include "secder.h" +#include "secasn1.h" +#include "secoid.h" +#include "certdb.h" +#include "certxutl.h" +#include "prtime.h" +#include "secerr.h" +#include "pk11func.h" +#include "dev.h" +#include "dev3hack.h" +#include "nssbase.h" +#if defined(DPC_RWLOCK) || defined(GLOBAL_RWLOCK) +#include "nssrwlk.h" +#endif +#include "pk11priv.h" + +const SEC_ASN1Template SEC_CERTExtensionTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertExtension) }, + { SEC_ASN1_OBJECT_ID, offsetof(CERTCertExtension, id) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_BOOLEAN, /* XXX DER_DEFAULT */ + offsetof(CERTCertExtension, critical) }, + { SEC_ASN1_OCTET_STRING, offsetof(CERTCertExtension, value) }, + { 0 } +}; + +static const SEC_ASN1Template SEC_CERTExtensionsTemplate[] = { + { SEC_ASN1_SEQUENCE_OF, 0, SEC_CERTExtensionTemplate } +}; + +/* + * XXX Also, these templates need to be tested; Lisa did the obvious + * translation but they still should be verified. + */ + +const SEC_ASN1Template CERT_IssuerAndSNTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTIssuerAndSN) }, + { SEC_ASN1_SAVE, offsetof(CERTIssuerAndSN, derIssuer) }, + { SEC_ASN1_INLINE, offsetof(CERTIssuerAndSN, issuer), CERT_NameTemplate }, + { SEC_ASN1_INTEGER, offsetof(CERTIssuerAndSN, serialNumber) }, + { 0 } +}; + +SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate) +SEC_ASN1_MKSUB(CERT_TimeChoiceTemplate) + +static const SEC_ASN1Template cert_CrlKeyTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCrlKey) }, + { SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL, offsetof(CERTCrlKey, dummy) }, + { SEC_ASN1_SKIP }, + { SEC_ASN1_ANY, offsetof(CERTCrlKey, derName) }, + { SEC_ASN1_SKIP_REST }, + { 0 } +}; + +static const SEC_ASN1Template cert_CrlEntryTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCrlEntry) }, + { SEC_ASN1_INTEGER, offsetof(CERTCrlEntry, serialNumber) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCrlEntry, revocationDate), + SEC_ASN1_SUB(CERT_TimeChoiceTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_SEQUENCE_OF, + offsetof(CERTCrlEntry, extensions), SEC_CERTExtensionTemplate }, + { 0 } +}; + +const SEC_ASN1Template CERT_CrlTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCrl) }, + { SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL, offsetof(CERTCrl, version) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCrl, signatureAlg), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_SAVE, offsetof(CERTCrl, derName) }, + { SEC_ASN1_INLINE, offsetof(CERTCrl, name), CERT_NameTemplate }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCrl, lastUpdate), + SEC_ASN1_SUB(CERT_TimeChoiceTemplate) }, + { SEC_ASN1_INLINE | SEC_ASN1_OPTIONAL | SEC_ASN1_XTRN, + offsetof(CERTCrl, nextUpdate), SEC_ASN1_SUB(CERT_TimeChoiceTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_SEQUENCE_OF, offsetof(CERTCrl, entries), + cert_CrlEntryTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | + SEC_ASN1_EXPLICIT | 0, + offsetof(CERTCrl, extensions), SEC_CERTExtensionsTemplate }, + { 0 } +}; + +const SEC_ASN1Template CERT_CrlTemplateNoEntries[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCrl) }, + { SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL, offsetof(CERTCrl, version) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCrl, signatureAlg), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_SAVE, offsetof(CERTCrl, derName) }, + { SEC_ASN1_INLINE, offsetof(CERTCrl, name), CERT_NameTemplate }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CERTCrl, lastUpdate), + SEC_ASN1_SUB(CERT_TimeChoiceTemplate) }, + { SEC_ASN1_INLINE | SEC_ASN1_OPTIONAL | SEC_ASN1_XTRN, + offsetof(CERTCrl, nextUpdate), SEC_ASN1_SUB(CERT_TimeChoiceTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_SEQUENCE_OF | + SEC_ASN1_SKIP }, /* skip entries */ + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | + SEC_ASN1_EXPLICIT | 0, + offsetof(CERTCrl, extensions), SEC_CERTExtensionsTemplate }, + { 0 } +}; + +const SEC_ASN1Template CERT_CrlTemplateEntriesOnly[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCrl) }, + { SEC_ASN1_SKIP | SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL }, + { SEC_ASN1_SKIP }, + { SEC_ASN1_SKIP }, + { SEC_ASN1_SKIP | SEC_ASN1_INLINE | SEC_ASN1_XTRN, + offsetof(CERTCrl, lastUpdate), SEC_ASN1_SUB(CERT_TimeChoiceTemplate) }, + { SEC_ASN1_SKIP | SEC_ASN1_INLINE | SEC_ASN1_OPTIONAL | SEC_ASN1_XTRN, + offsetof(CERTCrl, nextUpdate), SEC_ASN1_SUB(CERT_TimeChoiceTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_SEQUENCE_OF, offsetof(CERTCrl, entries), + cert_CrlEntryTemplate }, /* decode entries */ + { SEC_ASN1_SKIP_REST }, + { 0 } +}; + +const SEC_ASN1Template CERT_SignedCrlTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTSignedCrl) }, + { SEC_ASN1_SAVE, offsetof(CERTSignedCrl, signatureWrap.data) }, + { SEC_ASN1_INLINE, offsetof(CERTSignedCrl, crl), CERT_CrlTemplate }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, + offsetof(CERTSignedCrl, signatureWrap.signatureAlgorithm), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_BIT_STRING, offsetof(CERTSignedCrl, signatureWrap.signature) }, + { 0 } +}; + +static const SEC_ASN1Template cert_SignedCrlTemplateNoEntries[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTSignedCrl) }, + { SEC_ASN1_SAVE, offsetof(CERTSignedCrl, signatureWrap.data) }, + { SEC_ASN1_INLINE, offsetof(CERTSignedCrl, crl), + CERT_CrlTemplateNoEntries }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, + offsetof(CERTSignedCrl, signatureWrap.signatureAlgorithm), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_BIT_STRING, offsetof(CERTSignedCrl, signatureWrap.signature) }, + { 0 } +}; + +const SEC_ASN1Template CERT_SetOfSignedCrlTemplate[] = { + { SEC_ASN1_SET_OF, 0, CERT_SignedCrlTemplate }, +}; + +/* get CRL version */ +int +cert_get_crl_version(CERTCrl* crl) +{ + /* CRL version is defaulted to v1 */ + int version = SEC_CRL_VERSION_1; + if (crl && crl->version.data != 0) { + version = (int)DER_GetUInteger(&crl->version); + } + return version; +} + +/* check the entries in the CRL */ +SECStatus +cert_check_crl_entries(CERTCrl* crl) +{ + CERTCrlEntry** entries; + CERTCrlEntry* entry; + PRBool hasCriticalExten = PR_FALSE; + SECStatus rv = SECSuccess; + + if (!crl) { + return SECFailure; + } + + if (crl->entries == NULL) { + /* CRLs with no entries are valid */ + return (SECSuccess); + } + + /* Look in the crl entry extensions. If there is a critical extension, + then the crl version must be v2; otherwise, it should be v1. + */ + entries = crl->entries; + while (*entries) { + entry = *entries; + if (entry->extensions) { + /* If there is a critical extension in the entries, then the + CRL must be of version 2. If we already saw a critical + extension, + there is no need to check the version again. + */ + if (hasCriticalExten == PR_FALSE) { + hasCriticalExten = cert_HasCriticalExtension(entry->extensions); + if (hasCriticalExten) { + if (cert_get_crl_version(crl) != SEC_CRL_VERSION_2) { + /* only CRL v2 critical extensions are supported */ + PORT_SetError(SEC_ERROR_CRL_V1_CRITICAL_EXTENSION); + rv = SECFailure; + break; + } + } + } + + /* For each entry, make sure that it does not contain an unknown + critical extension. If it does, we must reject the CRL since + we don't know how to process the extension. + */ + if (cert_HasUnknownCriticalExten(entry->extensions) == PR_TRUE) { + PORT_SetError(SEC_ERROR_CRL_UNKNOWN_CRITICAL_EXTENSION); + rv = SECFailure; + break; + } + } + ++entries; + } + return (rv); +} + +/* Check the version of the CRL. If there is a critical extension in the crl + or crl entry, then the version must be v2. Otherwise, it should be v1. If + the crl contains critical extension(s), then we must recognized the + extension's OID. + */ +SECStatus +cert_check_crl_version(CERTCrl* crl) +{ + PRBool hasCriticalExten = PR_FALSE; + int version = cert_get_crl_version(crl); + + if (version > SEC_CRL_VERSION_2) { + PORT_SetError(SEC_ERROR_CRL_INVALID_VERSION); + return (SECFailure); + } + + /* Check the crl extensions for a critial extension. If one is found, + and the version is not v2, then we are done. + */ + if (crl->extensions) { + hasCriticalExten = cert_HasCriticalExtension(crl->extensions); + if (hasCriticalExten) { + if (version != SEC_CRL_VERSION_2) { + /* only CRL v2 critical extensions are supported */ + PORT_SetError(SEC_ERROR_CRL_V1_CRITICAL_EXTENSION); + return (SECFailure); + } + /* make sure that there is no unknown critical extension */ + if (cert_HasUnknownCriticalExten(crl->extensions) == PR_TRUE) { + PORT_SetError(SEC_ERROR_CRL_UNKNOWN_CRITICAL_EXTENSION); + return (SECFailure); + } + } + } + + return (SECSuccess); +} + +/* + * Generate a database key, based on the issuer name from a + * DER crl. + */ +SECStatus +CERT_KeyFromDERCrl(PLArenaPool* arena, SECItem* derCrl, SECItem* key) +{ + SECStatus rv; + CERTSignedData sd; + CERTCrlKey crlkey; + PLArenaPool* myArena; + + if (!arena) { + /* arena needed for QuickDER */ + myArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + } else { + myArena = arena; + } + PORT_Memset(&sd, 0, sizeof(sd)); + rv = SEC_QuickDERDecodeItem(myArena, &sd, CERT_SignedDataTemplate, derCrl); + if (SECSuccess == rv) { + PORT_Memset(&crlkey, 0, sizeof(crlkey)); + rv = SEC_QuickDERDecodeItem(myArena, &crlkey, cert_CrlKeyTemplate, + &sd.data); + } + + /* make a copy so the data doesn't point to memory inside derCrl, which + may be temporary */ + if (SECSuccess == rv) { + rv = SECITEM_CopyItem(arena, key, &crlkey.derName); + } + + if (myArena != arena) { + PORT_FreeArena(myArena, PR_FALSE); + } + + return rv; +} + +#define GetOpaqueCRLFields(x) ((OpaqueCRLFields*)x->opaque) + +SECStatus +CERT_CompleteCRLDecodeEntries(CERTSignedCrl* crl) +{ + SECStatus rv = SECSuccess; + SECItem* crldata = NULL; + OpaqueCRLFields* extended = NULL; + + if ((!crl) || (!(extended = (OpaqueCRLFields*)crl->opaque)) || + (PR_TRUE == extended->decodingError)) { + rv = SECFailure; + } else { + if (PR_FALSE == extended->partial) { + /* the CRL has already been fully decoded */ + return SECSuccess; + } + if (PR_TRUE == extended->badEntries) { + /* the entries decoding already failed */ + return SECFailure; + } + crldata = &crl->signatureWrap.data; + if (!crldata) { + rv = SECFailure; + } + } + + if (SECSuccess == rv) { + rv = SEC_QuickDERDecodeItem(crl->arena, &crl->crl, + CERT_CrlTemplateEntriesOnly, crldata); + if (SECSuccess == rv) { + extended->partial = PR_FALSE; /* successful decode, avoid + decoding again */ + } else { + extended->decodingError = PR_TRUE; + extended->badEntries = PR_TRUE; + /* cache the decoding failure. If it fails the first time, + it will fail again, which will grow the arena and leak + memory, so we want to avoid it */ + } + rv = cert_check_crl_entries(&crl->crl); + if (rv != SECSuccess) { + extended->badExtensions = PR_TRUE; + } + } + return rv; +} + +/* + * take a DER CRL and decode it into a CRL structure + * allow reusing the input DER without making a copy + */ +CERTSignedCrl* +CERT_DecodeDERCrlWithFlags(PLArenaPool* narena, SECItem* derSignedCrl, int type, + PRInt32 options) +{ + PLArenaPool* arena; + CERTSignedCrl* crl; + SECStatus rv; + OpaqueCRLFields* extended = NULL; + const SEC_ASN1Template* crlTemplate = CERT_SignedCrlTemplate; + PRInt32 testOptions = options; + + PORT_Assert(derSignedCrl); + if (!derSignedCrl) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + /* Adopting DER requires not copying it. Code that sets ADOPT flag + * but doesn't set DONT_COPY probably doesn't know What it is doing. + * That condition is a programming error in the caller. + */ + testOptions &= (CRL_DECODE_ADOPT_HEAP_DER | CRL_DECODE_DONT_COPY_DER); + PORT_Assert(testOptions != CRL_DECODE_ADOPT_HEAP_DER); + if (testOptions == CRL_DECODE_ADOPT_HEAP_DER) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + /* make a new arena if needed */ + if (narena == NULL) { + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) { + return NULL; + } + } else { + arena = narena; + } + + /* allocate the CRL structure */ + crl = (CERTSignedCrl*)PORT_ArenaZAlloc(arena, sizeof(CERTSignedCrl)); + if (!crl) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + crl->arena = arena; + + /* allocate opaque fields */ + crl->opaque = (void*)PORT_ArenaZAlloc(arena, sizeof(OpaqueCRLFields)); + if (!crl->opaque) { + goto loser; + } + extended = (OpaqueCRLFields*)crl->opaque; + if (options & CRL_DECODE_ADOPT_HEAP_DER) { + extended->heapDER = PR_TRUE; + } + if (options & CRL_DECODE_DONT_COPY_DER) { + crl->derCrl = derSignedCrl; /* DER is not copied . The application + must keep derSignedCrl until it + destroys the CRL */ + } else { + crl->derCrl = (SECItem*)PORT_ArenaZAlloc(arena, sizeof(SECItem)); + if (crl->derCrl == NULL) { + goto loser; + } + rv = SECITEM_CopyItem(arena, crl->derCrl, derSignedCrl); + if (rv != SECSuccess) { + goto loser; + } + } + + /* Save the arena in the inner crl for CRL extensions support */ + crl->crl.arena = arena; + if (options & CRL_DECODE_SKIP_ENTRIES) { + crlTemplate = cert_SignedCrlTemplateNoEntries; + extended->partial = PR_TRUE; + } + + /* decode the CRL info */ + switch (type) { + case SEC_CRL_TYPE: + rv = SEC_QuickDERDecodeItem(arena, crl, crlTemplate, crl->derCrl); + if (rv != SECSuccess) { + extended->badDER = PR_TRUE; + break; + } + /* check for critical extensions */ + rv = cert_check_crl_version(&crl->crl); + if (rv != SECSuccess) { + extended->badExtensions = PR_TRUE; + break; + } + + if (PR_TRUE == extended->partial) { + /* partial decoding, don't verify entries */ + break; + } + + rv = cert_check_crl_entries(&crl->crl); + if (rv != SECSuccess) { + extended->badExtensions = PR_TRUE; + } + + break; + + default: + PORT_SetError(SEC_ERROR_INVALID_ARGS); + rv = SECFailure; + break; + } + + if (rv != SECSuccess) { + goto loser; + } + + crl->referenceCount = 1; + + return (crl); + +loser: + if (options & CRL_DECODE_KEEP_BAD_CRL) { + if (extended) { + extended->decodingError = PR_TRUE; + } + if (crl) { + crl->referenceCount = 1; + return (crl); + } + } + + if ((narena == NULL) && arena) { + PORT_FreeArena(arena, PR_FALSE); + } + + return (0); +} + +/* + * take a DER CRL and decode it into a CRL structure + */ +CERTSignedCrl* +CERT_DecodeDERCrl(PLArenaPool* narena, SECItem* derSignedCrl, int type) +{ + return CERT_DecodeDERCrlWithFlags(narena, derSignedCrl, type, + CRL_DECODE_DEFAULT_OPTIONS); +} + +/* + * Lookup a CRL in the databases. We mirror the same fast caching data base + * caching stuff used by certificates....? + * return values : + * + * SECSuccess means we got a valid decodable DER CRL, or no CRL at all. + * Caller may distinguish those cases by the value returned in "decoded". + * When DER CRL is not found, error code will be SEC_ERROR_CRL_NOT_FOUND. + * + * SECFailure means we got a fatal error - most likely, we found a CRL, + * and it failed decoding, or there was an out of memory error. Do NOT ignore + * it and specifically do NOT treat it the same as having no CRL, as this + * can compromise security !!! Ideally, you should treat this case as if you + * received a "catch-all" CRL where all certs you were looking up are + * considered to be revoked + */ +static SECStatus +SEC_FindCrlByKeyOnSlot(PK11SlotInfo* slot, SECItem* crlKey, int type, + CERTSignedCrl** decoded, PRInt32 decodeoptions) +{ + SECStatus rv = SECSuccess; + CERTSignedCrl* crl = NULL; + SECItem* derCrl = NULL; + CK_OBJECT_HANDLE crlHandle = 0; + char* url = NULL; + + PORT_Assert(decoded); + if (!decoded) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + derCrl = PK11_FindCrlByName(&slot, &crlHandle, crlKey, type, &url); + if (derCrl == NULL) { + /* if we had a problem other than the CRL just didn't exist, return + * a failure to the upper level */ + int nsserror = PORT_GetError(); + if (nsserror != SEC_ERROR_CRL_NOT_FOUND) { + rv = SECFailure; + } + goto loser; + } + PORT_Assert(crlHandle != CK_INVALID_HANDLE); + /* PK11_FindCrlByName obtained a slot reference. */ + + /* derCRL is a fresh HEAP copy made for us by PK11_FindCrlByName. + Force adoption of the DER CRL from the heap - this will cause it + to be automatically freed when SEC_DestroyCrl is invoked */ + decodeoptions |= (CRL_DECODE_ADOPT_HEAP_DER | CRL_DECODE_DONT_COPY_DER); + + crl = CERT_DecodeDERCrlWithFlags(NULL, derCrl, type, decodeoptions); + if (crl) { + crl->slot = slot; + slot = NULL; /* adopt it */ + derCrl = NULL; /* adopted by the crl struct */ + crl->pkcs11ID = crlHandle; + if (url) { + crl->url = PORT_ArenaStrdup(crl->arena, url); + } + } else { + rv = SECFailure; + } + + if (url) { + PORT_Free(url); + } + + if (slot) { + PK11_FreeSlot(slot); + } + +loser: + if (derCrl) { + SECITEM_FreeItem(derCrl, PR_TRUE); + } + + *decoded = crl; + + return rv; +} + +CERTSignedCrl* +crl_storeCRL(PK11SlotInfo* slot, char* url, CERTSignedCrl* newCrl, + SECItem* derCrl, int type) +{ + CERTSignedCrl *oldCrl = NULL, *crl = NULL; + PRBool deleteOldCrl = PR_FALSE; + CK_OBJECT_HANDLE crlHandle = CK_INVALID_HANDLE; + + PORT_Assert(newCrl); + PORT_Assert(derCrl); + PORT_Assert(type == SEC_CRL_TYPE); + + if (type != SEC_CRL_TYPE) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + /* we can't use the cache here because we must look in the same + token */ + (void)SEC_FindCrlByKeyOnSlot(slot, &newCrl->crl.derName, type, &oldCrl, + CRL_DECODE_SKIP_ENTRIES); + /* if there is an old crl on the token, make sure the one we are + installing is newer. If not, exit out, otherwise delete the + old crl. + */ + if (oldCrl != NULL) { + /* if it's already there, quietly continue */ + if (SECITEM_CompareItem(newCrl->derCrl, oldCrl->derCrl) == SECEqual) { + crl = newCrl; + crl->slot = PK11_ReferenceSlot(slot); + crl->pkcs11ID = oldCrl->pkcs11ID; + if (oldCrl->url && !url) + url = oldCrl->url; + if (url) + crl->url = PORT_ArenaStrdup(crl->arena, url); + goto done; + } + if (!SEC_CrlIsNewer(&newCrl->crl, &oldCrl->crl)) { + PORT_SetError(SEC_ERROR_OLD_CRL); + goto done; + } + + /* if we have a url in the database, use that one */ + if (oldCrl->url && !url) { + url = oldCrl->url; + } + + /* really destroy this crl */ + /* first drum it out of the permanment Data base */ + deleteOldCrl = PR_TRUE; + } + + /* invalidate CRL cache for this issuer */ + CERT_CRLCacheRefreshIssuer(NULL, &newCrl->crl.derName); + /* Write the new entry into the data base */ + crlHandle = PK11_PutCrl(slot, derCrl, &newCrl->crl.derName, url, type); + if (crlHandle != CK_INVALID_HANDLE) { + crl = newCrl; + crl->slot = PK11_ReferenceSlot(slot); + crl->pkcs11ID = crlHandle; + if (url) { + crl->url = PORT_ArenaStrdup(crl->arena, url); + } + } + +done: + if (oldCrl) { + if (deleteOldCrl && crlHandle != CK_INVALID_HANDLE) { + SEC_DeletePermCRL(oldCrl); + } + SEC_DestroyCrl(oldCrl); + } + + return crl; +} + +/* + * + * create a new CRL from DER material. + * + * The signature on this CRL must be checked before you + * load it. ??? + */ +CERTSignedCrl* +SEC_NewCrl(CERTCertDBHandle* handle, char* url, SECItem* derCrl, int type) +{ + CERTSignedCrl* retCrl = NULL; + PK11SlotInfo* slot = PK11_GetInternalKeySlot(); + retCrl = + PK11_ImportCRL(slot, derCrl, url, type, NULL, CRL_IMPORT_BYPASS_CHECKS, + NULL, CRL_DECODE_DEFAULT_OPTIONS); + PK11_FreeSlot(slot); + + return retCrl; +} + +CERTSignedCrl* +SEC_FindCrlByDERCert(CERTCertDBHandle* handle, SECItem* derCrl, int type) +{ + PLArenaPool* arena; + SECItem crlKey; + SECStatus rv; + CERTSignedCrl* crl = NULL; + + /* create a scratch arena */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + return (NULL); + } + + /* extract the database key from the cert */ + rv = CERT_KeyFromDERCrl(arena, derCrl, &crlKey); + if (rv != SECSuccess) { + goto loser; + } + + /* find the crl */ + crl = SEC_FindCrlByName(handle, &crlKey, type); + +loser: + PORT_FreeArena(arena, PR_FALSE); + return (crl); +} + +CERTSignedCrl* +SEC_DupCrl(CERTSignedCrl* acrl) +{ + if (acrl) { + PR_ATOMIC_INCREMENT(&acrl->referenceCount); + return acrl; + } + return NULL; +} + +SECStatus +SEC_DestroyCrl(CERTSignedCrl* crl) +{ + if (crl) { + if (PR_ATOMIC_DECREMENT(&crl->referenceCount) < 1) { + if (crl->slot) { + PK11_FreeSlot(crl->slot); + } + if (GetOpaqueCRLFields(crl) && + PR_TRUE == GetOpaqueCRLFields(crl)->heapDER) { + SECITEM_FreeItem(crl->derCrl, PR_TRUE); + } + if (crl->arena) { + PORT_FreeArena(crl->arena, PR_FALSE); + } + } + return SECSuccess; + } else { + return SECFailure; + } +} + +SECStatus +SEC_LookupCrls(CERTCertDBHandle* handle, CERTCrlHeadNode** nodes, int type) +{ + CERTCrlHeadNode* head; + PLArenaPool* arena = NULL; + SECStatus rv; + + *nodes = NULL; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + return SECFailure; + } + + /* build a head structure */ + head = (CERTCrlHeadNode*)PORT_ArenaAlloc(arena, sizeof(CERTCrlHeadNode)); + head->arena = arena; + head->first = NULL; + head->last = NULL; + head->dbhandle = handle; + + /* Look up the proper crl types */ + *nodes = head; + + rv = PK11_LookupCrls(head, type, NULL); + + if (rv != SECSuccess) { + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + *nodes = NULL; + } + } + + return rv; +} + +/* These functions simply return the address of the above-declared templates. +** This is necessary for Windows DLLs. Sigh. +*/ +SEC_ASN1_CHOOSER_IMPLEMENT(CERT_IssuerAndSNTemplate) +SEC_ASN1_CHOOSER_IMPLEMENT(CERT_CrlTemplate) +SEC_ASN1_CHOOSER_IMPLEMENT(CERT_SignedCrlTemplate) +SEC_ASN1_CHOOSER_IMPLEMENT(CERT_SetOfSignedCrlTemplate) + +/* CRL cache code starts here */ + +/* constructor */ +static SECStatus CachedCrl_Create(CachedCrl** returned, CERTSignedCrl* crl, + CRLOrigin origin); +/* destructor */ +static SECStatus CachedCrl_Destroy(CachedCrl* crl); + +/* create hash table of CRL entries */ +static SECStatus CachedCrl_Populate(CachedCrl* crlobject); + +/* empty the cache content */ +static SECStatus CachedCrl_Depopulate(CachedCrl* crl); + +/* are these CRLs the same, as far as the cache is concerned ? + Or are they the same token object, but with different DER ? */ + +static SECStatus CachedCrl_Compare(CachedCrl* a, CachedCrl* b, PRBool* isDupe, + PRBool* isUpdated); + +/* create a DPCache object */ +static SECStatus DPCache_Create(CRLDPCache** returned, CERTCertificate* issuer, + const SECItem* subject, SECItem* dp); + +/* destructor for CRL DPCache object */ +static SECStatus DPCache_Destroy(CRLDPCache* cache); + +/* add a new CRL object to the dynamic array of CRLs of the DPCache, and + returns the cached CRL object . Needs write access to DPCache. */ +static SECStatus DPCache_AddCRL(CRLDPCache* cache, CachedCrl* crl, + PRBool* added); + +/* fetch the CRL for this DP from the PKCS#11 tokens */ +static SECStatus DPCache_FetchFromTokens(CRLDPCache* cache, PRTime vfdate, + void* wincx); + +/* update the content of the CRL cache, including fetching of CRLs, and + reprocessing with specified issuer and date */ +static SECStatus DPCache_GetUpToDate(CRLDPCache* cache, CERTCertificate* issuer, + PRBool readlocked, PRTime vfdate, + void* wincx); + +/* returns true if there are CRLs from PKCS#11 slots */ +static PRBool DPCache_HasTokenCRLs(CRLDPCache* cache); + +/* remove CRL at offset specified */ +static SECStatus DPCache_RemoveCRL(CRLDPCache* cache, PRUint32 offset); + +/* Pick best CRL to use . needs write access */ +static SECStatus DPCache_SelectCRL(CRLDPCache* cache); + +/* create an issuer cache object (per CA subject ) */ +static SECStatus IssuerCache_Create(CRLIssuerCache** returned, + CERTCertificate* issuer, + const SECItem* subject, const SECItem* dp); + +/* destructor for CRL IssuerCache object */ +SECStatus IssuerCache_Destroy(CRLIssuerCache* cache); + +/* add a DPCache to the issuer cache */ +static SECStatus IssuerCache_AddDP(CRLIssuerCache* cache, + CERTCertificate* issuer, + const SECItem* subject, const SECItem* dp, + CRLDPCache** newdpc); + +/* get a particular DPCache object from an IssuerCache */ +static CRLDPCache* IssuerCache_GetDPCache(CRLIssuerCache* cache, + const SECItem* dp); + +/* +** Pre-allocator hash allocator ops. +*/ + +/* allocate memory for hash table */ +static void* PR_CALLBACK +PreAllocTable(void* pool, PRSize size) +{ + PreAllocator* alloc = (PreAllocator*)pool; + PORT_Assert(alloc); + if (!alloc) { + /* no allocator, or buffer full */ + return NULL; + } + if (size > (alloc->len - alloc->used)) { + /* initial buffer full, let's use the arena */ + alloc->extra += size; + return PORT_ArenaAlloc(alloc->arena, size); + } + /* use the initial buffer */ + alloc->used += size; + return (char*)alloc->data + alloc->used - size; +} + +/* free hash table memory. + Individual PreAllocator elements cannot be freed, so this is a no-op. */ +static void PR_CALLBACK +PreFreeTable(void* pool, void* item) +{ +} + +/* allocate memory for hash table */ +static PLHashEntry* PR_CALLBACK +PreAllocEntry(void* pool, const void* key) +{ + return PreAllocTable(pool, sizeof(PLHashEntry)); +} + +/* free hash table entry. + Individual PreAllocator elements cannot be freed, so this is a no-op. */ +static void PR_CALLBACK +PreFreeEntry(void* pool, PLHashEntry* he, PRUintn flag) +{ +} + +/* methods required for PL hash table functions */ +static PLHashAllocOps preAllocOps = { PreAllocTable, PreFreeTable, + PreAllocEntry, PreFreeEntry }; + +/* destructor for PreAllocator object */ +void +PreAllocator_Destroy(PreAllocator* allocator) +{ + if (!allocator) { + return; + } + if (allocator->arena) { + PORT_FreeArena(allocator->arena, PR_TRUE); + } +} + +/* constructor for PreAllocator object */ +PreAllocator* +PreAllocator_Create(PRSize size) +{ + PLArenaPool* arena = NULL; + PreAllocator* prebuffer = NULL; + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) { + return NULL; + } + prebuffer = (PreAllocator*)PORT_ArenaZAlloc(arena, sizeof(PreAllocator)); + if (!prebuffer) { + PORT_FreeArena(arena, PR_TRUE); + return NULL; + } + prebuffer->arena = arena; + + if (size) { + prebuffer->len = size; + prebuffer->data = PORT_ArenaAlloc(arena, size); + if (!prebuffer->data) { + PORT_FreeArena(arena, PR_TRUE); + return NULL; + } + } + return prebuffer; +} + +/* global Named CRL cache object */ +static NamedCRLCache namedCRLCache = { NULL, NULL }; + +/* global CRL cache object */ +static CRLCache crlcache = { NULL, NULL }; + +/* initial state is off */ +static PRBool crlcache_initialized = PR_FALSE; + +PRTime CRLCache_Empty_TokenFetch_Interval = 60 * 1000000; /* how often + to query the tokens for CRL objects, in order to discover new objects, if + the cache does not contain any token CRLs . In microseconds */ + +PRTime CRLCache_TokenRefetch_Interval = 600 * 1000000; /* how often + to query the tokens for CRL objects, in order to discover new objects, if + the cache already contains token CRLs In microseconds */ + +PRTime CRLCache_ExistenceCheck_Interval = 60 * 1000000; /* how often to check + if a token CRL object still exists. In microseconds */ + +/* this function is called at NSS initialization time */ +SECStatus +InitCRLCache(void) +{ + if (PR_FALSE == crlcache_initialized) { + PORT_Assert(NULL == crlcache.lock); + PORT_Assert(NULL == crlcache.issuers); + PORT_Assert(NULL == namedCRLCache.lock); + PORT_Assert(NULL == namedCRLCache.entries); + if (crlcache.lock || crlcache.issuers || namedCRLCache.lock || + namedCRLCache.entries) { + /* CRL cache already partially initialized */ + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } +#ifdef GLOBAL_RWLOCK + crlcache.lock = NSSRWLock_New(NSS_RWLOCK_RANK_NONE, NULL); +#else + crlcache.lock = PR_NewLock(); +#endif + namedCRLCache.lock = PR_NewLock(); + crlcache.issuers = PL_NewHashTable(0, SECITEM_Hash, SECITEM_HashCompare, + PL_CompareValues, NULL, NULL); + namedCRLCache.entries = PL_NewHashTable( + 0, SECITEM_Hash, SECITEM_HashCompare, PL_CompareValues, NULL, NULL); + if (!crlcache.lock || !namedCRLCache.lock || !crlcache.issuers || + !namedCRLCache.entries) { + if (crlcache.lock) { +#ifdef GLOBAL_RWLOCK + NSSRWLock_Destroy(crlcache.lock); +#else + PR_DestroyLock(crlcache.lock); +#endif + crlcache.lock = NULL; + } + if (namedCRLCache.lock) { + PR_DestroyLock(namedCRLCache.lock); + namedCRLCache.lock = NULL; + } + if (crlcache.issuers) { + PL_HashTableDestroy(crlcache.issuers); + crlcache.issuers = NULL; + } + if (namedCRLCache.entries) { + PL_HashTableDestroy(namedCRLCache.entries); + namedCRLCache.entries = NULL; + } + + return SECFailure; + } + crlcache_initialized = PR_TRUE; + return SECSuccess; + } else { + PORT_Assert(crlcache.lock); + PORT_Assert(crlcache.issuers); + if ((NULL == crlcache.lock) || (NULL == crlcache.issuers)) { + /* CRL cache not fully initialized */ + return SECFailure; + } else { + /* CRL cache already initialized */ + return SECSuccess; + } + } +} + +/* destructor for CRL DPCache object */ +static SECStatus +DPCache_Destroy(CRLDPCache* cache) +{ + PRUint32 i = 0; + PORT_Assert(cache); + if (!cache) { + PORT_Assert(0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + if (cache->lock) { +#ifdef DPC_RWLOCK + NSSRWLock_Destroy(cache->lock); +#else + PR_DestroyLock(cache->lock); +#endif + } else { + PORT_Assert(0); + return SECFailure; + } + /* destroy all our CRL objects */ + for (i = 0; i < cache->ncrls; i++) { + if (!cache->crls || !cache->crls[i] || + SECSuccess != CachedCrl_Destroy(cache->crls[i])) { + return SECFailure; + } + } + /* free the array of CRLs */ + if (cache->crls) { + PORT_Free(cache->crls); + } + /* destroy the cert */ + if (cache->issuerDERCert) { + SECITEM_FreeItem(cache->issuerDERCert, PR_TRUE); + } + /* free the subject */ + if (cache->subject) { + SECITEM_FreeItem(cache->subject, PR_TRUE); + } + /* free the distribution points */ + if (cache->distributionPoint) { + SECITEM_FreeItem(cache->distributionPoint, PR_TRUE); + } + PORT_Free(cache); + return SECSuccess; +} + +/* destructor for CRL IssuerCache object */ +SECStatus +IssuerCache_Destroy(CRLIssuerCache* cache) +{ + PORT_Assert(cache); + if (!cache) { + PORT_Assert(0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } +#ifdef XCRL + if (cache->lock) { + NSSRWLock_Destroy(cache->lock); + } else { + PORT_Assert(0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + if (cache->issuer) { + CERT_DestroyCertificate(cache->issuer); + } +#endif + /* free the subject */ + if (cache->subject) { + SECITEM_FreeItem(cache->subject, PR_TRUE); + } + if (SECSuccess != DPCache_Destroy(cache->dpp)) { + PORT_Assert(0); + return SECFailure; + } + PORT_Free(cache); + return SECSuccess; +} + +/* create a named CRL entry object */ +static SECStatus +NamedCRLCacheEntry_Create(NamedCRLCacheEntry** returned) +{ + NamedCRLCacheEntry* entry = NULL; + if (!returned) { + PORT_Assert(0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + *returned = NULL; + entry = (NamedCRLCacheEntry*)PORT_ZAlloc(sizeof(NamedCRLCacheEntry)); + if (!entry) { + return SECFailure; + } + *returned = entry; + return SECSuccess; +} + +/* destroy a named CRL entry object */ +static SECStatus +NamedCRLCacheEntry_Destroy(NamedCRLCacheEntry* entry) +{ + if (!entry) { + PORT_Assert(0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + if (entry->crl) { + /* named CRL cache owns DER memory */ + SECITEM_ZfreeItem(entry->crl, PR_TRUE); + } + if (entry->canonicalizedName) { + SECITEM_FreeItem(entry->canonicalizedName, PR_TRUE); + } + PORT_Free(entry); + return SECSuccess; +} + +/* callback function used in hash table destructor */ +static PRIntn PR_CALLBACK +FreeIssuer(PLHashEntry* he, PRIntn i, void* arg) +{ + CRLIssuerCache* issuer = NULL; + SECStatus* rv = (SECStatus*)arg; + + PORT_Assert(he); + if (!he) { + return HT_ENUMERATE_NEXT; + } + issuer = (CRLIssuerCache*)he->value; + PORT_Assert(issuer); + if (issuer) { + if (SECSuccess != IssuerCache_Destroy(issuer)) { + PORT_Assert(rv); + if (rv) { + *rv = SECFailure; + } + return HT_ENUMERATE_NEXT; + } + } + return HT_ENUMERATE_NEXT; +} + +/* callback function used in hash table destructor */ +static PRIntn PR_CALLBACK +FreeNamedEntries(PLHashEntry* he, PRIntn i, void* arg) +{ + NamedCRLCacheEntry* entry = NULL; + SECStatus* rv = (SECStatus*)arg; + + PORT_Assert(he); + if (!he) { + return HT_ENUMERATE_NEXT; + } + entry = (NamedCRLCacheEntry*)he->value; + PORT_Assert(entry); + if (entry) { + if (SECSuccess != NamedCRLCacheEntry_Destroy(entry)) { + PORT_Assert(rv); + if (rv) { + *rv = SECFailure; + } + return HT_ENUMERATE_NEXT; + } + } + return HT_ENUMERATE_NEXT; +} + +/* needs to be called at NSS shutdown time + This will destroy the global CRL cache, including + - the hash table of issuer cache objects + - the issuer cache objects + - DPCache objects in issuer cache objects */ +SECStatus +ShutdownCRLCache(void) +{ + SECStatus rv = SECSuccess; + if (PR_FALSE == crlcache_initialized && !crlcache.lock && + !crlcache.issuers) { + /* CRL cache has already been shut down */ + return SECSuccess; + } + if (PR_TRUE == crlcache_initialized && + (!crlcache.lock || !crlcache.issuers || !namedCRLCache.lock || + !namedCRLCache.entries)) { + /* CRL cache has partially been shut down */ + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + /* empty the CRL cache */ + /* free the issuers */ + PL_HashTableEnumerateEntries(crlcache.issuers, &FreeIssuer, &rv); + /* free the hash table of issuers */ + PL_HashTableDestroy(crlcache.issuers); + crlcache.issuers = NULL; +/* free the global lock */ +#ifdef GLOBAL_RWLOCK + NSSRWLock_Destroy(crlcache.lock); +#else + PR_DestroyLock(crlcache.lock); +#endif + crlcache.lock = NULL; + + /* empty the named CRL cache. This must be done after freeing the CRL + * cache, since some CRLs in this cache are in the memory for the other */ + /* free the entries */ + PL_HashTableEnumerateEntries(namedCRLCache.entries, &FreeNamedEntries, &rv); + /* free the hash table of issuers */ + PL_HashTableDestroy(namedCRLCache.entries); + namedCRLCache.entries = NULL; + /* free the global lock */ + PR_DestroyLock(namedCRLCache.lock); + namedCRLCache.lock = NULL; + + crlcache_initialized = PR_FALSE; + return rv; +} + +/* add a new CRL object to the dynamic array of CRLs of the DPCache, and + returns the cached CRL object . Needs write access to DPCache. */ +static SECStatus +DPCache_AddCRL(CRLDPCache* cache, CachedCrl* newcrl, PRBool* added) +{ + CachedCrl** newcrls = NULL; + PRUint32 i = 0; + PORT_Assert(cache); + PORT_Assert(newcrl); + PORT_Assert(added); + if (!cache || !newcrl || !added) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + + *added = PR_FALSE; + /* before adding a new CRL, check if it is a duplicate */ + for (i = 0; i < cache->ncrls; i++) { + CachedCrl* existing = NULL; + SECStatus rv = SECSuccess; + PRBool dupe = PR_FALSE, updated = PR_FALSE; + if (!cache->crls) { + PORT_Assert(0); + return SECFailure; + } + existing = cache->crls[i]; + if (!existing) { + PORT_Assert(0); + return SECFailure; + } + rv = CachedCrl_Compare(existing, newcrl, &dupe, &updated); + if (SECSuccess != rv) { + PORT_Assert(0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + if (PR_TRUE == dupe) { + /* dupe */ + PORT_SetError(SEC_ERROR_CRL_ALREADY_EXISTS); + return SECSuccess; + } + if (PR_TRUE == updated) { + /* this token CRL is in the same slot and has the same object ID, + but different content. We need to remove the old object */ + if (SECSuccess != DPCache_RemoveCRL(cache, i)) { + PORT_Assert(0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return PR_FALSE; + } + } + } + + newcrls = (CachedCrl**)PORT_Realloc(cache->crls, (cache->ncrls + 1) * sizeof(CachedCrl*)); + if (!newcrls) { + return SECFailure; + } + cache->crls = newcrls; + cache->ncrls++; + cache->crls[cache->ncrls - 1] = newcrl; + *added = PR_TRUE; + return SECSuccess; +} + +/* remove CRL at offset specified */ +static SECStatus +DPCache_RemoveCRL(CRLDPCache* cache, PRUint32 offset) +{ + CachedCrl* acrl = NULL; + PORT_Assert(cache); + if (!cache || (!cache->crls) || (!(offset < cache->ncrls))) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + acrl = cache->crls[offset]; + PORT_Assert(acrl); + if (!acrl) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + cache->crls[offset] = cache->crls[cache->ncrls - 1]; + cache->crls[cache->ncrls - 1] = NULL; + cache->ncrls--; + if (cache->selected == acrl) { + cache->selected = NULL; + } + if (SECSuccess != CachedCrl_Destroy(acrl)) { + PORT_Assert(0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + return SECSuccess; +} + +/* check whether a CRL object stored in a PKCS#11 token still exists in + that token . This has to be efficient (the entire CRL value cannot be + transferred accross the token boundaries), so this is accomplished by + simply fetching the subject attribute and making sure it hasn't changed . + Note that technically, the CRL object could have been replaced with a new + PKCS#11 object of the same ID and subject (which actually happens in + softoken), but this function has no way of knowing that the object + value changed, since CKA_VALUE isn't checked. */ +static PRBool +TokenCRLStillExists(CERTSignedCrl* crl) +{ + NSSItem newsubject; + SECItem subject; + CK_ULONG crl_class; + PRStatus status; + PK11SlotInfo* slot = NULL; + nssCryptokiObject instance; + NSSArena* arena; + PRBool xstatus = PR_TRUE; + SECItem* oldSubject = NULL; + + PORT_Assert(crl); + if (!crl) { + return PR_FALSE; + } + slot = crl->slot; + PORT_Assert(crl->slot); + if (!slot) { + return PR_FALSE; + } + oldSubject = &crl->crl.derName; + PORT_Assert(oldSubject); + if (!oldSubject) { + return PR_FALSE; + } + + /* query subject and type attributes in order to determine if the + object has been deleted */ + + /* first, make an nssCryptokiObject */ + instance.handle = crl->pkcs11ID; + PORT_Assert(instance.handle); + if (!instance.handle) { + return PR_FALSE; + } + instance.token = PK11Slot_GetNSSToken(slot); + PORT_Assert(instance.token); + if (!instance.token) { + return PR_FALSE; + } + instance.isTokenObject = PR_TRUE; + instance.label = NULL; + + arena = NSSArena_Create(); + PORT_Assert(arena); + if (!arena) { + (void)nssToken_Destroy(instance.token); + return PR_FALSE; + } + + status = + nssCryptokiCRL_GetAttributes(&instance, NULL, /* XXX sessionOpt */ + arena, NULL, &newsubject, /* subject */ + &crl_class, /* class */ + NULL, NULL); + if (PR_SUCCESS == status) { + subject.data = newsubject.data; + subject.len = newsubject.size; + if (SECITEM_CompareItem(oldSubject, &subject) != SECEqual) { + xstatus = PR_FALSE; + } + if (CKO_NSS_CRL != crl_class) { + xstatus = PR_FALSE; + } + } else { + xstatus = PR_FALSE; + } + NSSArena_Destroy(arena); + (void)nssToken_Destroy(instance.token); + return xstatus; +} + +/* verify the signature of a CRL against its issuer at a given date */ +static SECStatus +CERT_VerifyCRL(CERTSignedCrl* crlobject, CERTCertificate* issuer, PRTime vfdate, + void* wincx) +{ + return CERT_VerifySignedData(&crlobject->signatureWrap, issuer, vfdate, + wincx); +} + +/* verify a CRL and update cache state */ +static SECStatus +CachedCrl_Verify(CRLDPCache* cache, CachedCrl* crlobject, PRTime vfdate, + void* wincx) +{ + /* Check if it is an invalid CRL + if we got a bad CRL, we want to cache it in order to avoid + subsequent fetches of this same identical bad CRL. We set + the cache to the invalid state to ensure that all certs on this + DP are considered to have unknown status from now on. The cache + object will remain in this state until the bad CRL object + is removed from the token it was fetched from. If the cause + of the failure is that we didn't have the issuer cert to + verify the signature, this state can be cleared when + the issuer certificate becomes available if that causes the + signature to verify */ + + if (!cache || !crlobject) { + PORT_Assert(0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + if (PR_TRUE == GetOpaqueCRLFields(crlobject->crl)->decodingError) { + crlobject->sigChecked = PR_TRUE; /* we can never verify a CRL + with bogus DER. Mark it checked so we won't try again */ + PORT_SetError(SEC_ERROR_BAD_DER); + return SECSuccess; + } else { + SECStatus signstatus = SECFailure; + if (cache->issuerDERCert) { + CERTCertificate* issuer = CERT_NewTempCertificate( + cache->dbHandle, cache->issuerDERCert, NULL, PR_FALSE, PR_TRUE); + + if (issuer) { + signstatus = + CERT_VerifyCRL(crlobject->crl, issuer, vfdate, wincx); + CERT_DestroyCertificate(issuer); + } + } + if (SECSuccess != signstatus) { + if (!cache->issuerDERCert) { + /* we tried to verify without an issuer cert . This is + because this CRL came through a call to SEC_FindCrlByName. + So, we don't cache this verification failure. We'll try + to verify the CRL again when a certificate from that issuer + becomes available */ + } else { + crlobject->sigChecked = PR_TRUE; + } + PORT_SetError(SEC_ERROR_CRL_BAD_SIGNATURE); + return SECSuccess; + } else { + crlobject->sigChecked = PR_TRUE; + crlobject->sigValid = PR_TRUE; + } + } + + return SECSuccess; +} + +/* fetch the CRLs for this DP from the PKCS#11 tokens */ +static SECStatus +DPCache_FetchFromTokens(CRLDPCache* cache, PRTime vfdate, void* wincx) +{ + SECStatus rv = SECSuccess; + CERTCrlHeadNode head; + if (!cache) { + PORT_Assert(0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + /* first, initialize list */ + memset(&head, 0, sizeof(head)); + head.arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + rv = pk11_RetrieveCrls(&head, cache->subject, wincx); + + /* if this function fails, something very wrong happened, such as an out + of memory error during CRL decoding. We don't want to proceed and must + mark the cache object invalid */ + if (SECFailure == rv) { + /* fetch failed, add error bit */ + cache->invalid |= CRL_CACHE_LAST_FETCH_FAILED; + } else { + /* fetch was successful, clear this error bit */ + cache->invalid &= (~CRL_CACHE_LAST_FETCH_FAILED); + } + + /* add any CRLs found to our array */ + if (SECSuccess == rv) { + CERTCrlNode* crlNode = NULL; + + for (crlNode = head.first; crlNode; crlNode = crlNode->next) { + CachedCrl* returned = NULL; + CERTSignedCrl* crlobject = crlNode->crl; + if (!crlobject) { + PORT_Assert(0); + continue; + } + rv = CachedCrl_Create(&returned, crlobject, CRL_OriginToken); + if (SECSuccess == rv) { + PRBool added = PR_FALSE; + rv = DPCache_AddCRL(cache, returned, &added); + if (PR_TRUE != added) { + rv = CachedCrl_Destroy(returned); + returned = NULL; + } else if (vfdate) { + rv = CachedCrl_Verify(cache, returned, vfdate, wincx); + } + } else { + /* not enough memory to add the CRL to the cache. mark it + invalid so we will try again . */ + cache->invalid |= CRL_CACHE_LAST_FETCH_FAILED; + } + if (SECFailure == rv) { + break; + } + } + } + + if (head.arena) { + CERTCrlNode* crlNode = NULL; + /* clean up the CRL list in case we got a partial one + during a failed fetch */ + for (crlNode = head.first; crlNode; crlNode = crlNode->next) { + if (crlNode->crl) { + SEC_DestroyCrl(crlNode->crl); /* free the CRL. Either it got + added to the cache and the refcount got bumped, or not, and + thus we need to free its RAM */ + } + } + PORT_FreeArena(head.arena, PR_FALSE); /* destroy CRL list */ + } + + return rv; +} + +static SECStatus +CachedCrl_GetEntry(CachedCrl* crl, const SECItem* sn, CERTCrlEntry** returned) +{ + CERTCrlEntry* acrlEntry; + + PORT_Assert(crl); + PORT_Assert(crl->entries); + PORT_Assert(sn); + PORT_Assert(returned); + if (!crl || !sn || !returned || !crl->entries) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + acrlEntry = PL_HashTableLookup(crl->entries, (void*)sn); + if (acrlEntry) { + *returned = acrlEntry; + } else { + *returned = NULL; + } + return SECSuccess; +} + +/* check if a particular SN is in the CRL cache and return its entry */ +dpcacheStatus +DPCache_Lookup(CRLDPCache* cache, const SECItem* sn, CERTCrlEntry** returned) +{ + SECStatus rv; + if (!cache || !sn || !returned) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + /* no cache or SN to look up, or no way to return entry */ + return dpcacheCallerError; + } + *returned = NULL; + if (0 != cache->invalid) { + /* the cache contains a bad CRL, or there was a CRL fetching error. */ + PORT_SetError(SEC_ERROR_CRL_INVALID); + return dpcacheInvalidCacheError; + } + if (!cache->selected) { + /* no CRL means no entry to return. This is OK, except for + * NIST policy */ + return dpcacheEmpty; + } + rv = CachedCrl_GetEntry(cache->selected, sn, returned); + if (SECSuccess != rv) { + return dpcacheLookupError; + } else { + if (*returned) { + return dpcacheFoundEntry; + } else { + return dpcacheNoEntry; + } + } +} + +#if defined(DPC_RWLOCK) + +#define DPCache_LockWrite() \ + { \ + if (readlocked) { \ + NSSRWLock_UnlockRead(cache->lock); \ + } \ + NSSRWLock_LockWrite(cache->lock); \ + } + +#define DPCache_UnlockWrite() \ + { \ + if (readlocked) { \ + NSSRWLock_LockRead(cache->lock); \ + } \ + NSSRWLock_UnlockWrite(cache->lock); \ + } + +#else + +/* with a global lock, we are always locked for read before we need write + access, so do nothing */ + +#define DPCache_LockWrite() \ + { \ + } + +#define DPCache_UnlockWrite() \ + { \ + } + +#endif + +/* update the content of the CRL cache, including fetching of CRLs, and + reprocessing with specified issuer and date . We are always holding + either the read or write lock on DPCache upon entry. */ +static SECStatus +DPCache_GetUpToDate(CRLDPCache* cache, CERTCertificate* issuer, + PRBool readlocked, PRTime vfdate, void* wincx) +{ + /* Update the CRLDPCache now. We don't cache token CRL lookup misses + yet, as we have no way of getting notified of new PKCS#11 object + creation that happens in a token */ + SECStatus rv = SECSuccess; + PRUint32 i = 0; + PRBool forcedrefresh = PR_FALSE; + PRBool dirty = PR_FALSE; /* whether something was changed in the + cache state during this update cycle */ + PRBool hastokenCRLs = PR_FALSE; + PRTime now = 0; + PRTime lastfetch = 0; + PRBool mustunlock = PR_FALSE; + + if (!cache) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + + /* first, make sure we have obtained all the CRLs we need. + We do an expensive token fetch in the following cases : + 1) cache is empty because no fetch was ever performed yet + 2) cache is explicitly set to refresh state + 3) cache is in invalid state because last fetch failed + 4) cache contains no token CRLs, and it's been more than one minute + since the last fetch + 5) cache contains token CRLs, and it's been more than 10 minutes since + the last fetch + */ + forcedrefresh = cache->refresh; + lastfetch = cache->lastfetch; + if (PR_TRUE != forcedrefresh && + (!(cache->invalid & CRL_CACHE_LAST_FETCH_FAILED))) { + now = PR_Now(); + hastokenCRLs = DPCache_HasTokenCRLs(cache); + } + if ((0 == lastfetch) || + + (PR_TRUE == forcedrefresh) || + + (cache->invalid & CRL_CACHE_LAST_FETCH_FAILED) || + + ((PR_FALSE == hastokenCRLs) && + ((now - cache->lastfetch > CRLCache_Empty_TokenFetch_Interval) || + (now < cache->lastfetch))) || + + ((PR_TRUE == hastokenCRLs) && + ((now - cache->lastfetch > CRLCache_TokenRefetch_Interval) || + (now < cache->lastfetch)))) { + /* the cache needs to be refreshed, and/or we had zero CRL for this + DP. Try to get one from PKCS#11 tokens */ + DPCache_LockWrite(); + /* check if another thread updated before us, and skip update if so */ + if (lastfetch == cache->lastfetch) { + /* we are the first */ + rv = DPCache_FetchFromTokens(cache, vfdate, wincx); + if (PR_TRUE == cache->refresh) { + cache->refresh = PR_FALSE; /* clear refresh state */ + } + dirty = PR_TRUE; + cache->lastfetch = PR_Now(); + } + DPCache_UnlockWrite(); + } + + /* now, make sure we have no extraneous CRLs (deleted token objects) + we'll do this inexpensive existence check either + 1) if there was a token object fetch + 2) every minute */ + if ((PR_TRUE != dirty) && (!now)) { + now = PR_Now(); + } + if ((PR_TRUE == dirty) || + ((now - cache->lastcheck > CRLCache_ExistenceCheck_Interval) || + (now < cache->lastcheck))) { + PRTime lastcheck = cache->lastcheck; + mustunlock = PR_FALSE; + /* check if all CRLs still exist */ + for (i = 0; (i < cache->ncrls); i++) { + CachedCrl* savcrl = cache->crls[i]; + if ((!savcrl) || (savcrl && CRL_OriginToken != savcrl->origin)) { + /* we only want to check token CRLs */ + continue; + } + if ((PR_TRUE != TokenCRLStillExists(savcrl->crl))) { + + /* this CRL is gone */ + if (PR_TRUE != mustunlock) { + DPCache_LockWrite(); + mustunlock = PR_TRUE; + } + /* first, we need to check if another thread did an update + before we did */ + if (lastcheck == cache->lastcheck) { + /* the CRL is gone. And we are the one to do the update */ + DPCache_RemoveCRL(cache, i); + dirty = PR_TRUE; + } + /* stay locked here intentionally so we do all the other + updates in this thread for the remaining CRLs */ + } + } + if (PR_TRUE == mustunlock) { + cache->lastcheck = PR_Now(); + DPCache_UnlockWrite(); + mustunlock = PR_FALSE; + } + } + + /* add issuer certificate if it was previously unavailable */ + if (issuer && (NULL == cache->issuerDERCert) && + (SECSuccess == CERT_CheckCertUsage(issuer, KU_CRL_SIGN))) { + /* if we didn't have a valid issuer cert yet, but we do now. add it */ + DPCache_LockWrite(); + if (!cache->issuerDERCert) { + dirty = PR_TRUE; + cache->dbHandle = issuer->dbhandle; + cache->issuerDERCert = SECITEM_DupItem(&issuer->derCert); + } + DPCache_UnlockWrite(); + } + + /* verify CRLs that couldn't be checked when inserted into the cache + because the issuer cert or a verification date was unavailable. + These are CRLs that were inserted into the cache through + SEC_FindCrlByName, or through manual insertion, rather than through a + certificate verification (CERT_CheckCRL) */ + + if (cache->issuerDERCert && vfdate) { + mustunlock = PR_FALSE; + /* re-process all unverified CRLs */ + for (i = 0; i < cache->ncrls; i++) { + CachedCrl* savcrl = cache->crls[i]; + if (!savcrl) { + continue; + } + if (PR_TRUE != savcrl->sigChecked) { + if (!mustunlock) { + DPCache_LockWrite(); + mustunlock = PR_TRUE; + } + /* first, we need to check if another thread updated + it before we did, and abort if it has been modified since + we acquired the lock. Make sure first that the CRL is still + in the array at the same position */ + if ((i < cache->ncrls) && (savcrl == cache->crls[i]) && + (PR_TRUE != savcrl->sigChecked)) { + /* the CRL is still there, unverified. Do it */ + CachedCrl_Verify(cache, savcrl, vfdate, wincx); + dirty = PR_TRUE; + } + /* stay locked here intentionally so we do all the other + updates in this thread for the remaining CRLs */ + } + if (mustunlock && !dirty) { + DPCache_UnlockWrite(); + mustunlock = PR_FALSE; + } + } + } + + if (dirty || cache->mustchoose) { + /* changes to the content of the CRL cache necessitate examining all + CRLs for selection of the most appropriate one to cache */ + if (!mustunlock) { + DPCache_LockWrite(); + mustunlock = PR_TRUE; + } + DPCache_SelectCRL(cache); + cache->mustchoose = PR_FALSE; + } + if (mustunlock) + DPCache_UnlockWrite(); + + return rv; +} + +/* callback for qsort to sort by thisUpdate */ +static int +SortCRLsByThisUpdate(const void* arg1, const void* arg2) +{ + PRTime timea, timeb; + SECStatus rv = SECSuccess; + CachedCrl *a, *b; + + a = *(CachedCrl**)arg1; + b = *(CachedCrl**)arg2; + + if (!a || !b) { + PORT_Assert(0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + rv = SECFailure; + } + + if (SECSuccess == rv) { + rv = DER_DecodeTimeChoice(&timea, &a->crl->crl.lastUpdate); + } + if (SECSuccess == rv) { + rv = DER_DecodeTimeChoice(&timeb, &b->crl->crl.lastUpdate); + } + if (SECSuccess == rv) { + if (timea > timeb) { + return 1; /* a is better than b */ + } + if (timea < timeb) { + return -1; /* a is not as good as b */ + } + } + + /* if they are equal, or if all else fails, use pointer differences */ + PORT_Assert(a != b); /* they should never be equal */ + return a > b ? 1 : -1; +} + +/* callback for qsort to sort a set of disparate CRLs, some of which are + invalid DER or failed signature check. + + Validated CRLs are differentiated by thisUpdate . + Validated CRLs are preferred over non-validated CRLs . + Proper DER CRLs are preferred over non-DER data . +*/ +static int +SortImperfectCRLs(const void* arg1, const void* arg2) +{ + CachedCrl *a, *b; + + a = *(CachedCrl**)arg1; + b = *(CachedCrl**)arg2; + + if (!a || !b) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + PORT_Assert(0); + } else { + PRBool aDecoded = PR_FALSE, bDecoded = PR_FALSE; + if ((PR_TRUE == a->sigValid) && (PR_TRUE == b->sigValid)) { + /* both CRLs have been validated, choose the latest one */ + return SortCRLsByThisUpdate(arg1, arg2); + } + if (PR_TRUE == a->sigValid) { + return 1; /* a is greater than b */ + } + if (PR_TRUE == b->sigValid) { + return -1; /* a is not as good as b */ + } + aDecoded = GetOpaqueCRLFields(a->crl)->decodingError; + bDecoded = GetOpaqueCRLFields(b->crl)->decodingError; + /* neither CRL had its signature check pass */ + if ((PR_FALSE == aDecoded) && (PR_FALSE == bDecoded)) { + /* both CRLs are proper DER, choose the latest one */ + return SortCRLsByThisUpdate(arg1, arg2); + } + if (PR_FALSE == aDecoded) { + return 1; /* a is better than b */ + } + if (PR_FALSE == bDecoded) { + return -1; /* a is not as good as b */ + } + /* both are invalid DER. sigh. */ + } + /* if they are equal, or if all else fails, use pointer differences */ + PORT_Assert(a != b); /* they should never be equal */ + return a > b ? 1 : -1; +} + +/* Pick best CRL to use . needs write access */ +static SECStatus +DPCache_SelectCRL(CRLDPCache* cache) +{ + PRUint32 i; + PRBool valid = PR_TRUE; + CachedCrl* selected = NULL; + + PORT_Assert(cache); + if (!cache) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + /* if any invalid CRL is present, then the CRL cache is + considered invalid, for security reasons */ + for (i = 0; i < cache->ncrls; i++) { + if (!cache->crls[i] || !cache->crls[i]->sigChecked || + !cache->crls[i]->sigValid) { + valid = PR_FALSE; + break; + } + } + if (PR_TRUE == valid) { + /* all CRLs are valid, clear this error */ + cache->invalid &= (~CRL_CACHE_INVALID_CRLS); + } else { + /* some CRLs are invalid, set this error */ + cache->invalid |= CRL_CACHE_INVALID_CRLS; + } + + if (cache->invalid) { + /* cache is in an invalid state, so reset it */ + if (cache->selected) { + cache->selected = NULL; + } + /* also sort the CRLs imperfectly */ + qsort(cache->crls, cache->ncrls, sizeof(CachedCrl*), SortImperfectCRLs); + return SECSuccess; + } + + if (cache->ncrls) { + /* all CRLs are good, sort them by thisUpdate */ + qsort(cache->crls, cache->ncrls, sizeof(CachedCrl*), SortCRLsByThisUpdate); + + /* pick the newest CRL */ + selected = cache->crls[cache->ncrls - 1]; + + /* and populate the cache */ + if (SECSuccess != CachedCrl_Populate(selected)) { + return SECFailure; + } + } + + cache->selected = selected; + + return SECSuccess; +} + +/* initialize a DPCache object */ +static SECStatus +DPCache_Create(CRLDPCache** returned, CERTCertificate* issuer, + const SECItem* subject, SECItem* dp) +{ + CRLDPCache* cache = NULL; + PORT_Assert(returned); + /* issuer and dp are allowed to be NULL */ + if (!returned || !subject) { + PORT_Assert(0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + *returned = NULL; + cache = PORT_ZAlloc(sizeof(CRLDPCache)); + if (!cache) { + return SECFailure; + } +#ifdef DPC_RWLOCK + cache->lock = NSSRWLock_New(NSS_RWLOCK_RANK_NONE, NULL); +#else + cache->lock = PR_NewLock(); +#endif + if (!cache->lock) { + PORT_Free(cache); + return SECFailure; + } + if (issuer) { + cache->dbHandle = issuer->dbhandle; + cache->issuerDERCert = SECITEM_DupItem(&issuer->derCert); + } + cache->distributionPoint = SECITEM_DupItem(dp); + cache->subject = SECITEM_DupItem(subject); + cache->lastfetch = 0; + cache->lastcheck = 0; + *returned = cache; + return SECSuccess; +} + +/* create an issuer cache object (per CA subject ) */ +static SECStatus +IssuerCache_Create(CRLIssuerCache** returned, CERTCertificate* issuer, + const SECItem* subject, const SECItem* dp) +{ + SECStatus rv = SECSuccess; + CRLIssuerCache* cache = NULL; + PORT_Assert(returned); + PORT_Assert(subject); + /* issuer and dp are allowed to be NULL */ + if (!returned || !subject) { + PORT_Assert(0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + *returned = NULL; + cache = (CRLIssuerCache*)PORT_ZAlloc(sizeof(CRLIssuerCache)); + if (!cache) { + return SECFailure; + } + cache->subject = SECITEM_DupItem(subject); +#ifdef XCRL + cache->lock = NSSRWLock_New(NSS_RWLOCK_RANK_NONE, NULL); + if (!cache->lock) { + rv = SECFailure; + } + if (SECSuccess == rv && issuer) { + cache->issuer = CERT_DupCertificate(issuer); + if (!cache->issuer) { + rv = SECFailure; + } + } +#endif + if (SECSuccess != rv) { + PORT_Assert(SECSuccess == IssuerCache_Destroy(cache)); + return SECFailure; + } + *returned = cache; + return SECSuccess; +} + +/* add a DPCache to the issuer cache */ +static SECStatus +IssuerCache_AddDP(CRLIssuerCache* cache, CERTCertificate* issuer, + const SECItem* subject, const SECItem* dp, + CRLDPCache** newdpc) +{ + /* now create the required DP cache object */ + if (!cache || !subject || !newdpc) { + PORT_Assert(0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + if (!dp) { + /* default distribution point */ + SECStatus rv = DPCache_Create(&cache->dpp, issuer, subject, NULL); + if (SECSuccess == rv) { + *newdpc = cache->dpp; + return SECSuccess; + } + } else { + /* we should never hit this until we support multiple DPs */ + PORT_Assert(dp); + /* XCRL allocate a new distribution point cache object, initialize it, + and add it to the hash table of DPs */ + } + return SECFailure; +} + +/* add an IssuerCache to the global hash table of issuers */ +static SECStatus +CRLCache_AddIssuer(CRLIssuerCache* issuer) +{ + PORT_Assert(issuer); + PORT_Assert(crlcache.issuers); + if (!issuer || !crlcache.issuers) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + if (NULL == PL_HashTableAdd(crlcache.issuers, (void*)issuer->subject, + (void*)issuer)) { + return SECFailure; + } + return SECSuccess; +} + +/* retrieve the issuer cache object for a given issuer subject */ +static SECStatus +CRLCache_GetIssuerCache(CRLCache* cache, const SECItem* subject, + CRLIssuerCache** returned) +{ + /* we need to look up the issuer in the hash table */ + SECStatus rv = SECSuccess; + PORT_Assert(cache); + PORT_Assert(subject); + PORT_Assert(returned); + PORT_Assert(crlcache.issuers); + if (!cache || !subject || !returned || !crlcache.issuers) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + rv = SECFailure; + } + + if (SECSuccess == rv) { + *returned = (CRLIssuerCache*)PL_HashTableLookup(crlcache.issuers, + (void*)subject); + } + + return rv; +} + +/* retrieve the full CRL object that best matches the content of a DPCache */ +static CERTSignedCrl* +GetBestCRL(CRLDPCache* cache, PRBool entries) +{ + CachedCrl* acrl = NULL; + + PORT_Assert(cache); + if (!cache) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return NULL; + } + + if (0 == cache->ncrls) { + /* empty cache*/ + PORT_SetError(SEC_ERROR_CRL_NOT_FOUND); + return NULL; + } + + /* if we have a valid full CRL selected, return it */ + if (cache->selected) { + return SEC_DupCrl(cache->selected->crl); + } + + /* otherwise, use latest valid DER CRL */ + acrl = cache->crls[cache->ncrls - 1]; + + if (acrl && (PR_FALSE == GetOpaqueCRLFields(acrl->crl)->decodingError)) { + SECStatus rv = SECSuccess; + if (PR_TRUE == entries) { + rv = CERT_CompleteCRLDecodeEntries(acrl->crl); + } + if (SECSuccess == rv) { + return SEC_DupCrl(acrl->crl); + } + } + + PORT_SetError(SEC_ERROR_CRL_NOT_FOUND); + return NULL; +} + +/* get a particular DPCache object from an IssuerCache */ +static CRLDPCache* +IssuerCache_GetDPCache(CRLIssuerCache* cache, const SECItem* dp) +{ + CRLDPCache* dpp = NULL; + PORT_Assert(cache); + /* XCRL for now we only support the "default" DP, ie. the + full CRL. So we can return the global one without locking. In + the future we will have a lock */ + PORT_Assert(NULL == dp); + if (!cache || dp) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return NULL; + } +#ifdef XCRL + NSSRWLock_LockRead(cache->lock); +#endif + dpp = cache->dpp; +#ifdef XCRL + NSSRWLock_UnlockRead(cache->lock); +#endif + return dpp; +} + +/* get a DPCache object for the given issuer subject and dp + Automatically creates the cache object if it doesn't exist yet. + */ +SECStatus +AcquireDPCache(CERTCertificate* issuer, const SECItem* subject, + const SECItem* dp, PRTime t, void* wincx, CRLDPCache** dpcache, + PRBool* writeLocked) +{ + SECStatus rv = SECSuccess; + CRLIssuerCache* issuercache = NULL; +#ifdef GLOBAL_RWLOCK + PRBool globalwrite = PR_FALSE; +#endif + PORT_Assert(crlcache.lock); + if (!crlcache.lock) { + /* CRL cache is not initialized */ + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } +#ifdef GLOBAL_RWLOCK + NSSRWLock_LockRead(crlcache.lock); +#else + PR_Lock(crlcache.lock); +#endif + rv = CRLCache_GetIssuerCache(&crlcache, subject, &issuercache); + if (SECSuccess != rv) { +#ifdef GLOBAL_RWLOCK + NSSRWLock_UnlockRead(crlcache.lock); +#else + PR_Unlock(crlcache.lock); +#endif + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + if (!issuercache) { + /* there is no cache for this issuer yet. This means this is the + first time we look up a cert from that issuer, and we need to + create the cache. */ + + rv = IssuerCache_Create(&issuercache, issuer, subject, dp); + if (SECSuccess == rv && !issuercache) { + PORT_Assert(issuercache); + rv = SECFailure; + } + + if (SECSuccess == rv) { + /* This is the first time we look up a cert of this issuer. + Create the DPCache for this DP . */ + rv = IssuerCache_AddDP(issuercache, issuer, subject, dp, dpcache); + } + + if (SECSuccess == rv) { + /* lock the DPCache for write to ensure the update happens in this + thread */ + *writeLocked = PR_TRUE; +#ifdef DPC_RWLOCK + NSSRWLock_LockWrite((*dpcache)->lock); +#else + PR_Lock((*dpcache)->lock); +#endif + } + + if (SECSuccess == rv) { +/* now add the new issuer cache to the global hash table of + issuers */ +#ifdef GLOBAL_RWLOCK + CRLIssuerCache* existing = NULL; + NSSRWLock_UnlockRead(crlcache.lock); + /* when using a r/w lock for the global cache, check if the issuer + already exists before adding to the hash table */ + NSSRWLock_LockWrite(crlcache.lock); + globalwrite = PR_TRUE; + rv = CRLCache_GetIssuerCache(&crlcache, subject, &existing); + if (!existing) { +#endif + rv = CRLCache_AddIssuer(issuercache); + if (SECSuccess != rv) { + /* failure */ + rv = SECFailure; + } +#ifdef GLOBAL_RWLOCK + } else { + /* somebody else updated before we did */ + IssuerCache_Destroy(issuercache); /* destroy the new object */ + issuercache = existing; /* use the existing one */ + *dpcache = IssuerCache_GetDPCache(issuercache, dp); + } +#endif + } + +/* now unlock the global cache. We only want to lock the issuer hash + table addition. Holding it longer would hurt scalability */ +#ifdef GLOBAL_RWLOCK + if (PR_TRUE == globalwrite) { + NSSRWLock_UnlockWrite(crlcache.lock); + globalwrite = PR_FALSE; + } else { + NSSRWLock_UnlockRead(crlcache.lock); + } +#else + PR_Unlock(crlcache.lock); +#endif + + /* if there was a failure adding an issuer cache object, destroy it */ + if (SECSuccess != rv && issuercache) { + if (PR_TRUE == *writeLocked) { +#ifdef DPC_RWLOCK + NSSRWLock_UnlockWrite((*dpcache)->lock); +#else + PR_Unlock((*dpcache)->lock); +#endif + } + IssuerCache_Destroy(issuercache); + issuercache = NULL; + } + + if (SECSuccess != rv) { + return SECFailure; + } + } else { +#ifdef GLOBAL_RWLOCK + NSSRWLock_UnlockRead(crlcache.lock); +#else + PR_Unlock(crlcache.lock); +#endif + *dpcache = IssuerCache_GetDPCache(issuercache, dp); + } + /* we now have a DPCache that we can use for lookups */ + /* lock it for read, unless we already locked for write */ + if (PR_FALSE == *writeLocked) { +#ifdef DPC_RWLOCK + NSSRWLock_LockRead((*dpcache)->lock); +#else + PR_Lock((*dpcache)->lock); +#endif + } + + if (SECSuccess == rv) { + /* currently there is always one and only one DPCache per issuer */ + PORT_Assert(*dpcache); + if (*dpcache) { + /* make sure the DP cache is up to date before using it */ + rv = DPCache_GetUpToDate(*dpcache, issuer, PR_FALSE == *writeLocked, + t, wincx); + } else { + rv = SECFailure; + } + } + return rv; +} + +/* unlock access to the DPCache */ +void +ReleaseDPCache(CRLDPCache* dpcache, PRBool writeLocked) +{ + if (!dpcache) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return; + } +#ifdef DPC_RWLOCK + if (PR_TRUE == writeLocked) { + NSSRWLock_UnlockWrite(dpcache->lock); + } else { + NSSRWLock_UnlockRead(dpcache->lock); + } +#else + PR_Unlock(dpcache->lock); +#endif +} + +SECStatus +cert_CheckCertRevocationStatus(CERTCertificate* cert, CERTCertificate* issuer, + const SECItem* dp, PRTime t, void* wincx, + CERTRevocationStatus* revStatus, + CERTCRLEntryReasonCode* revReason) +{ + PRBool lockedwrite = PR_FALSE; + SECStatus rv = SECSuccess; + CRLDPCache* dpcache = NULL; + CERTRevocationStatus status = certRevocationStatusRevoked; + CERTCRLEntryReasonCode reason = crlEntryReasonUnspecified; + CERTCrlEntry* entry = NULL; + dpcacheStatus ds; + + if (!cert || !issuer) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + + if (revStatus) { + *revStatus = status; + } + if (revReason) { + *revReason = reason; + } + + if (t && + secCertTimeValid != CERT_CheckCertValidTimes(issuer, t, PR_FALSE)) { + /* we won't be able to check the CRL's signature if the issuer cert + is expired as of the time we are verifying. This may cause a valid + CRL to be cached as bad. short-circuit to avoid this case. */ + PORT_SetError(SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE); + return SECFailure; + } + + rv = AcquireDPCache(issuer, &issuer->derSubject, dp, t, wincx, &dpcache, + &lockedwrite); + PORT_Assert(SECSuccess == rv); + if (SECSuccess != rv) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + /* now look up the certificate SN in the DP cache's CRL */ + ds = DPCache_Lookup(dpcache, &cert->serialNumber, &entry); + switch (ds) { + case dpcacheFoundEntry: + PORT_Assert(entry); + /* check the time if we have one */ + if (entry->revocationDate.data && entry->revocationDate.len) { + PRTime revocationDate = 0; + if (SECSuccess == + DER_DecodeTimeChoice(&revocationDate, + &entry->revocationDate)) { + /* we got a good revocation date, only consider the + certificate revoked if the time we are inquiring about + is past the revocation date */ + if (t >= revocationDate) { + rv = SECFailure; + } else { + status = certRevocationStatusValid; + } + } else { + /* invalid revocation date, consider the certificate + permanently revoked */ + rv = SECFailure; + } + } else { + /* no revocation date, certificate is permanently revoked */ + rv = SECFailure; + } + if (SECFailure == rv) { + (void)CERT_FindCRLEntryReasonExten(entry, &reason); + PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE); + } + break; + + case dpcacheEmpty: + /* useful for NIST policy */ + status = certRevocationStatusUnknown; + break; + + case dpcacheNoEntry: + status = certRevocationStatusValid; + break; + + case dpcacheInvalidCacheError: + /* treat it as unknown and let the caller decide based on + the policy */ + status = certRevocationStatusUnknown; + break; + + default: + /* leave status as revoked */ + break; + } + + ReleaseDPCache(dpcache, lockedwrite); + if (revStatus) { + *revStatus = status; + } + if (revReason) { + *revReason = reason; + } + return rv; +} + +/* check CRL revocation status of given certificate and issuer */ +SECStatus +CERT_CheckCRL(CERTCertificate* cert, CERTCertificate* issuer, const SECItem* dp, + PRTime t, void* wincx) +{ + return cert_CheckCertRevocationStatus(cert, issuer, dp, t, wincx, NULL, + NULL); +} + +/* retrieve full CRL object that best matches the cache status */ +CERTSignedCrl* +SEC_FindCrlByName(CERTCertDBHandle* handle, SECItem* crlKey, int type) +{ + CERTSignedCrl* acrl = NULL; + CRLDPCache* dpcache = NULL; + SECStatus rv = SECSuccess; + PRBool writeLocked = PR_FALSE; + + if (!crlKey) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + rv = AcquireDPCache(NULL, crlKey, NULL, 0, NULL, &dpcache, &writeLocked); + if (SECSuccess == rv) { + acrl = GetBestCRL(dpcache, PR_TRUE); /* decode entries, because + SEC_FindCrlByName always returned fully decoded CRLs in the past */ + ReleaseDPCache(dpcache, writeLocked); + } + return acrl; +} + +/* invalidate the CRL cache for a given issuer, which forces a refetch of + CRL objects from PKCS#11 tokens */ +void +CERT_CRLCacheRefreshIssuer(CERTCertDBHandle* dbhandle, SECItem* crlKey) +{ + CRLDPCache* cache = NULL; + SECStatus rv = SECSuccess; + PRBool writeLocked = PR_FALSE; + PRBool readlocked; + + (void)dbhandle; /* silence compiler warnings */ + + /* XCRL we will need to refresh all the DPs of the issuer in the future, + not just the default one */ + rv = AcquireDPCache(NULL, crlKey, NULL, 0, NULL, &cache, &writeLocked); + if (SECSuccess != rv) { + return; + } + /* we need to invalidate the DPCache here */ + readlocked = (writeLocked == PR_TRUE ? PR_FALSE : PR_TRUE); + DPCache_LockWrite(); + cache->refresh = PR_TRUE; + DPCache_UnlockWrite(); + ReleaseDPCache(cache, writeLocked); + return; +} + +/* add the specified RAM CRL object to the cache */ +SECStatus +CERT_CacheCRL(CERTCertDBHandle* dbhandle, SECItem* newdercrl) +{ + CRLDPCache* cache = NULL; + SECStatus rv = SECSuccess; + PRBool writeLocked = PR_FALSE; + PRBool readlocked; + CachedCrl* returned = NULL; + PRBool added = PR_FALSE; + CERTSignedCrl* newcrl = NULL; + int realerror = 0; + + if (!dbhandle || !newdercrl) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + /* first decode the DER CRL to make sure it's OK */ + newcrl = CERT_DecodeDERCrlWithFlags(NULL, newdercrl, SEC_CRL_TYPE, + CRL_DECODE_DONT_COPY_DER | + CRL_DECODE_SKIP_ENTRIES); + + if (!newcrl) { + return SECFailure; + } + + /* XXX check if it has IDP extension. If so, do not proceed and set error */ + + rv = AcquireDPCache(NULL, &newcrl->crl.derName, NULL, 0, NULL, &cache, + &writeLocked); + if (SECSuccess == rv) { + readlocked = (writeLocked == PR_TRUE ? PR_FALSE : PR_TRUE); + + rv = CachedCrl_Create(&returned, newcrl, CRL_OriginExplicit); + if (SECSuccess == rv && returned) { + DPCache_LockWrite(); + rv = DPCache_AddCRL(cache, returned, &added); + if (PR_TRUE != added) { + realerror = PORT_GetError(); + CachedCrl_Destroy(returned); + returned = NULL; + } + DPCache_UnlockWrite(); + } + + ReleaseDPCache(cache, writeLocked); + + if (!added) { + rv = SECFailure; + } + } + SEC_DestroyCrl(newcrl); /* free the CRL. Either it got added to the cache + and the refcount got bumped, or not, and thus we need to free its + RAM */ + if (realerror) { + PORT_SetError(realerror); + } + return rv; +} + +/* remove the specified RAM CRL object from the cache */ +SECStatus +CERT_UncacheCRL(CERTCertDBHandle* dbhandle, SECItem* olddercrl) +{ + CRLDPCache* cache = NULL; + SECStatus rv = SECSuccess; + PRBool writeLocked = PR_FALSE; + PRBool readlocked; + PRBool removed = PR_FALSE; + PRUint32 i; + CERTSignedCrl* oldcrl = NULL; + + if (!dbhandle || !olddercrl) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + /* first decode the DER CRL to make sure it's OK */ + oldcrl = CERT_DecodeDERCrlWithFlags(NULL, olddercrl, SEC_CRL_TYPE, + CRL_DECODE_DONT_COPY_DER | + CRL_DECODE_SKIP_ENTRIES); + + if (!oldcrl) { + /* if this DER CRL can't decode, it can't be in the cache */ + return SECFailure; + } + + rv = AcquireDPCache(NULL, &oldcrl->crl.derName, NULL, 0, NULL, &cache, + &writeLocked); + if (SECSuccess == rv) { + CachedCrl* returned = NULL; + + readlocked = (writeLocked == PR_TRUE ? PR_FALSE : PR_TRUE); + + rv = CachedCrl_Create(&returned, oldcrl, CRL_OriginExplicit); + if (SECSuccess == rv && returned) { + DPCache_LockWrite(); + for (i = 0; i < cache->ncrls; i++) { + PRBool dupe = PR_FALSE, updated = PR_FALSE; + rv = CachedCrl_Compare(returned, cache->crls[i], &dupe, + &updated); + if (SECSuccess != rv) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + break; + } + if (PR_TRUE == dupe) { + rv = DPCache_RemoveCRL(cache, i); /* got a match */ + if (SECSuccess == rv) { + cache->mustchoose = PR_TRUE; + removed = PR_TRUE; + } + break; + } + } + + DPCache_UnlockWrite(); + + if (SECSuccess != CachedCrl_Destroy(returned)) { + rv = SECFailure; + } + } + + ReleaseDPCache(cache, writeLocked); + } + if (SECSuccess != SEC_DestroyCrl(oldcrl)) { + /* need to do this because object is refcounted */ + rv = SECFailure; + } + if (SECSuccess == rv && PR_TRUE != removed) { + PORT_SetError(SEC_ERROR_CRL_NOT_FOUND); + } + return rv; +} + +SECStatus +cert_AcquireNamedCRLCache(NamedCRLCache** returned) +{ + PORT_Assert(returned); + if (!namedCRLCache.lock) { + PORT_Assert(0); + return SECFailure; + } + PR_Lock(namedCRLCache.lock); + *returned = &namedCRLCache; + return SECSuccess; +} + +/* This must be called only while cache is acquired, and the entry is only + * valid until cache is released. + */ +SECStatus +cert_FindCRLByGeneralName(NamedCRLCache* ncc, const SECItem* canonicalizedName, + NamedCRLCacheEntry** retEntry) +{ + if (!ncc || !canonicalizedName || !retEntry) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + *retEntry = (NamedCRLCacheEntry*)PL_HashTableLookup( + namedCRLCache.entries, (void*)canonicalizedName); + return SECSuccess; +} + +SECStatus +cert_ReleaseNamedCRLCache(NamedCRLCache* ncc) +{ + if (!ncc) { + return SECFailure; + } + if (!ncc->lock) { + PORT_Assert(0); + return SECFailure; + } + PR_Unlock(namedCRLCache.lock); + return SECSuccess; +} + +/* creates new named cache entry from CRL, and tries to add it to CRL cache */ +static SECStatus +addCRLToCache(CERTCertDBHandle* dbhandle, SECItem* crl, + const SECItem* canonicalizedName, NamedCRLCacheEntry** newEntry) +{ + SECStatus rv = SECSuccess; + NamedCRLCacheEntry* entry = NULL; + + /* create new named entry */ + if (SECSuccess != NamedCRLCacheEntry_Create(newEntry) || !*newEntry) { + /* no need to keep unused CRL around */ + SECITEM_ZfreeItem(crl, PR_TRUE); + return SECFailure; + } + entry = *newEntry; + entry->crl = crl; /* named CRL cache owns DER */ + entry->lastAttemptTime = PR_Now(); + entry->canonicalizedName = SECITEM_DupItem(canonicalizedName); + if (!entry->canonicalizedName) { + rv = NamedCRLCacheEntry_Destroy(entry); /* destroys CRL too */ + PORT_Assert(SECSuccess == rv); + return SECFailure; + } + /* now, attempt to insert CRL into CRL cache */ + if (SECSuccess == CERT_CacheCRL(dbhandle, entry->crl)) { + entry->inCRLCache = PR_TRUE; + entry->successfulInsertionTime = entry->lastAttemptTime; + } else { + switch (PR_GetError()) { + case SEC_ERROR_CRL_ALREADY_EXISTS: + entry->dupe = PR_TRUE; + break; + + case SEC_ERROR_BAD_DER: + entry->badDER = PR_TRUE; + break; + + /* all other reasons */ + default: + entry->unsupported = PR_TRUE; + break; + } + rv = SECFailure; + /* no need to keep unused CRL around */ + SECITEM_ZfreeItem(entry->crl, PR_TRUE); + entry->crl = NULL; + } + return rv; +} + +/* take ownership of CRL, and insert it into the named CRL cache + * and indexed CRL cache + */ +SECStatus +cert_CacheCRLByGeneralName(CERTCertDBHandle* dbhandle, SECItem* crl, + const SECItem* canonicalizedName) +{ + NamedCRLCacheEntry *oldEntry, *newEntry = NULL; + NamedCRLCache* ncc = NULL; + SECStatus rv = SECSuccess; + + PORT_Assert(namedCRLCache.lock); + PORT_Assert(namedCRLCache.entries); + + if (!crl || !canonicalizedName) { + PORT_Assert(0); + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + rv = cert_AcquireNamedCRLCache(&ncc); + PORT_Assert(SECSuccess == rv); + if (SECSuccess != rv) { + SECITEM_ZfreeItem(crl, PR_TRUE); + return SECFailure; + } + rv = cert_FindCRLByGeneralName(ncc, canonicalizedName, &oldEntry); + PORT_Assert(SECSuccess == rv); + if (SECSuccess != rv) { + (void)cert_ReleaseNamedCRLCache(ncc); + SECITEM_ZfreeItem(crl, PR_TRUE); + return SECFailure; + } + if (SECSuccess == + addCRLToCache(dbhandle, crl, canonicalizedName, &newEntry)) { + if (!oldEntry) { + /* add new good entry to the hash table */ + if (NULL == PL_HashTableAdd(namedCRLCache.entries, + (void*)newEntry->canonicalizedName, + (void*)newEntry)) { + PORT_Assert(0); + NamedCRLCacheEntry_Destroy(newEntry); + rv = SECFailure; + } + } else { + PRBool removed; + /* remove the old CRL from the cache if needed */ + if (oldEntry->inCRLCache) { + rv = CERT_UncacheCRL(dbhandle, oldEntry->crl); + PORT_Assert(SECSuccess == rv); + } + removed = PL_HashTableRemove(namedCRLCache.entries, + (void*)oldEntry->canonicalizedName); + PORT_Assert(removed); + if (!removed) { + rv = SECFailure; + /* leak old entry since we couldn't remove it from the hash + * table */ + } else { + PORT_CheckSuccess(NamedCRLCacheEntry_Destroy(oldEntry)); + } + if (NULL == PL_HashTableAdd(namedCRLCache.entries, + (void*)newEntry->canonicalizedName, + (void*)newEntry)) { + PORT_Assert(0); + rv = SECFailure; + } + } + } else { + /* error adding new CRL to cache */ + if (!oldEntry) { + /* no old cache entry, use the new one even though it's bad */ + if (NULL == PL_HashTableAdd(namedCRLCache.entries, + (void*)newEntry->canonicalizedName, + (void*)newEntry)) { + PORT_Assert(0); + rv = SECFailure; + } + } else { + if (oldEntry->inCRLCache) { + /* previous cache entry was good, keep it and update time */ + oldEntry->lastAttemptTime = newEntry->lastAttemptTime; + /* throw away new bad entry */ + rv = NamedCRLCacheEntry_Destroy(newEntry); + PORT_Assert(SECSuccess == rv); + } else { + /* previous cache entry was bad, just replace it */ + PRBool removed = PL_HashTableRemove( + namedCRLCache.entries, (void*)oldEntry->canonicalizedName); + PORT_Assert(removed); + if (!removed) { + /* leak old entry since we couldn't remove it from the hash + * table */ + rv = SECFailure; + } else { + PORT_CheckSuccess(NamedCRLCacheEntry_Destroy(oldEntry)); + } + if (NULL == PL_HashTableAdd(namedCRLCache.entries, + (void*)newEntry->canonicalizedName, + (void*)newEntry)) { + PORT_Assert(0); + rv = SECFailure; + } + } + } + } + PORT_CheckSuccess(cert_ReleaseNamedCRLCache(ncc)); + + return rv; +} + +static SECStatus +CachedCrl_Create(CachedCrl** returned, CERTSignedCrl* crl, CRLOrigin origin) +{ + CachedCrl* newcrl = NULL; + if (!returned) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + newcrl = PORT_ZAlloc(sizeof(CachedCrl)); + if (!newcrl) { + return SECFailure; + } + newcrl->crl = SEC_DupCrl(crl); + newcrl->origin = origin; + *returned = newcrl; + return SECSuccess; +} + +/* empty the cache content */ +static SECStatus +CachedCrl_Depopulate(CachedCrl* crl) +{ + if (!crl) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + /* destroy the hash table */ + if (crl->entries) { + PL_HashTableDestroy(crl->entries); + crl->entries = NULL; + } + + /* free the pre buffer */ + if (crl->prebuffer) { + PreAllocator_Destroy(crl->prebuffer); + crl->prebuffer = NULL; + } + return SECSuccess; +} + +static SECStatus +CachedCrl_Destroy(CachedCrl* crl) +{ + if (!crl) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + CachedCrl_Depopulate(crl); + SEC_DestroyCrl(crl->crl); + PORT_Free(crl); + return SECSuccess; +} + +/* create hash table of CRL entries */ +static SECStatus +CachedCrl_Populate(CachedCrl* crlobject) +{ + SECStatus rv = SECFailure; + CERTCrlEntry** crlEntry = NULL; + PRUint32 numEntries = 0; + + if (!crlobject) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + /* complete the entry decoding . XXX thread-safety of CRL object */ + rv = CERT_CompleteCRLDecodeEntries(crlobject->crl); + if (SECSuccess != rv) { + crlobject->unbuildable = PR_TRUE; /* don't try to build this again */ + return SECFailure; + } + + if (crlobject->entries && crlobject->prebuffer) { + /* cache is already built */ + return SECSuccess; + } + + /* build the hash table from the full CRL */ + /* count CRL entries so we can pre-allocate space for hash table entries */ + for (crlEntry = crlobject->crl->crl.entries; crlEntry && *crlEntry; + crlEntry++) { + numEntries++; + } + crlobject->prebuffer = + PreAllocator_Create(numEntries * sizeof(PLHashEntry)); + PORT_Assert(crlobject->prebuffer); + if (!crlobject->prebuffer) { + return SECFailure; + } + /* create a new hash table */ + crlobject->entries = + PL_NewHashTable(0, SECITEM_Hash, SECITEM_HashCompare, PL_CompareValues, + &preAllocOps, crlobject->prebuffer); + PORT_Assert(crlobject->entries); + if (!crlobject->entries) { + return SECFailure; + } + /* add all serial numbers to the hash table */ + for (crlEntry = crlobject->crl->crl.entries; crlEntry && *crlEntry; + crlEntry++) { + PL_HashTableAdd(crlobject->entries, &(*crlEntry)->serialNumber, + *crlEntry); + } + + return SECSuccess; +} + +/* returns true if there are CRLs from PKCS#11 slots */ +static PRBool +DPCache_HasTokenCRLs(CRLDPCache* cache) +{ + PRBool answer = PR_FALSE; + PRUint32 i; + for (i = 0; i < cache->ncrls; i++) { + if (cache->crls[i] && (CRL_OriginToken == cache->crls[i]->origin)) { + answer = PR_TRUE; + break; + } + } + return answer; +} + +/* are these CRLs the same, as far as the cache is concerned ? */ +/* are these CRLs the same token object but with different DER ? + This can happen if the DER CRL got updated in the token, but the PKCS#11 + object ID did not change. NSS softoken has the unfortunate property to + never change the object ID for CRL objects. */ +static SECStatus +CachedCrl_Compare(CachedCrl* a, CachedCrl* b, PRBool* isDupe, PRBool* isUpdated) +{ + PORT_Assert(a); + PORT_Assert(b); + PORT_Assert(isDupe); + PORT_Assert(isUpdated); + if (!a || !b || !isDupe || !isUpdated || !a->crl || !b->crl) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + + *isDupe = *isUpdated = PR_FALSE; + + if (a == b) { + /* dupe */ + *isDupe = PR_TRUE; + *isUpdated = PR_FALSE; + return SECSuccess; + } + if (b->origin != a->origin) { + /* CRLs of different origins are not considered dupes, + and can't be updated either */ + return SECSuccess; + } + if (CRL_OriginToken == b->origin) { + /* for token CRLs, slot and PKCS#11 object handle must match for CRL + to truly be a dupe */ + if ((b->crl->slot == a->crl->slot) && + (b->crl->pkcs11ID == a->crl->pkcs11ID)) { + /* ASN.1 DER needs to match for dupe check */ + /* could optimize by just checking a few fields like thisUpdate */ + if (SECEqual == + SECITEM_CompareItem(b->crl->derCrl, a->crl->derCrl)) { + *isDupe = PR_TRUE; + } else { + *isUpdated = PR_TRUE; + } + } + return SECSuccess; + } + if (CRL_OriginExplicit == b->origin) { + /* We need to make sure this is the same object that the user provided + to CERT_CacheCRL previously. That API takes a SECItem*, thus, we + just do a pointer comparison here. + */ + if (b->crl->derCrl == a->crl->derCrl) { + *isDupe = PR_TRUE; + } + } + return SECSuccess; +} diff --git a/security/nss/lib/certdb/exports.gyp b/security/nss/lib/certdb/exports.gyp new file mode 100644 index 0000000000..359c93041f --- /dev/null +++ b/security/nss/lib/certdb/exports.gyp @@ -0,0 +1,36 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +{ + 'includes': [ + '../../coreconf/config.gypi' + ], + 'targets': [ + { + 'target_name': 'lib_certdb_exports', + 'type': 'none', + 'copies': [ + { + 'files': [ + 'cert.h', + 'certdb.h', + 'certt.h' + ], + 'destination': '<(nss_public_dist_dir)/<(module)' + }, + { + 'files': [ + 'certi.h', + 'certxutl.h', + 'genname.h', + 'xconst.h' + ], + 'destination': '<(nss_private_dist_dir)/<(module)' + } + ] + } + ], + 'variables': { + 'module': 'nss' + } +} diff --git a/security/nss/lib/certdb/genname.c b/security/nss/lib/certdb/genname.c new file mode 100644 index 0000000000..764a5c1cc5 --- /dev/null +++ b/security/nss/lib/certdb/genname.c @@ -0,0 +1,2011 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "plarena.h" +#include "seccomon.h" +#include "secitem.h" +#include "secoidt.h" +#include "secasn1.h" +#include "secder.h" +#include "certt.h" +#include "cert.h" +#include "certi.h" +#include "xconst.h" +#include "secerr.h" +#include "secoid.h" +#include "prprf.h" +#include "genname.h" + +SEC_ASN1_MKSUB(SEC_AnyTemplate) +SEC_ASN1_MKSUB(SEC_IntegerTemplate) +SEC_ASN1_MKSUB(SEC_IA5StringTemplate) +SEC_ASN1_MKSUB(SEC_ObjectIDTemplate) +SEC_ASN1_MKSUB(SEC_OctetStringTemplate) + +static const SEC_ASN1Template CERTNameConstraintTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTNameConstraint) }, + { SEC_ASN1_ANY, offsetof(CERTNameConstraint, DERName) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, + offsetof(CERTNameConstraint, min), SEC_ASN1_SUB(SEC_IntegerTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 1, + offsetof(CERTNameConstraint, max), SEC_ASN1_SUB(SEC_IntegerTemplate) }, + { 0 } +}; + +const SEC_ASN1Template CERT_NameConstraintSubtreeSubTemplate[] = { + { SEC_ASN1_SEQUENCE_OF | SEC_ASN1_XTRN, 0, SEC_ASN1_SUB(SEC_AnyTemplate) } +}; + +static const SEC_ASN1Template CERTNameConstraintsTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTNameConstraints) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(CERTNameConstraints, DERPermited), + CERT_NameConstraintSubtreeSubTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(CERTNameConstraints, DERExcluded), + CERT_NameConstraintSubtreeSubTemplate }, + { 0 } +}; + +static const SEC_ASN1Template CERTOthNameTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(OtherName) }, + { SEC_ASN1_OBJECT_ID, offsetof(OtherName, oid) }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | + SEC_ASN1_XTRN | 0, + offsetof(OtherName, name), SEC_ASN1_SUB(SEC_AnyTemplate) }, + { 0 } +}; + +static const SEC_ASN1Template CERTOtherNameTemplate[] = { + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | 0, + offsetof(CERTGeneralName, name.OthName), CERTOthNameTemplate, + sizeof(CERTGeneralName) } +}; + +static const SEC_ASN1Template CERT_RFC822NameTemplate[] = { + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 1, + offsetof(CERTGeneralName, name.other), + SEC_ASN1_SUB(SEC_IA5StringTemplate), sizeof(CERTGeneralName) } +}; + +static const SEC_ASN1Template CERT_DNSNameTemplate[] = { + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 2, + offsetof(CERTGeneralName, name.other), + SEC_ASN1_SUB(SEC_IA5StringTemplate), sizeof(CERTGeneralName) } +}; + +static const SEC_ASN1Template CERT_X400AddressTemplate[] = { + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_XTRN | 3, + offsetof(CERTGeneralName, name.other), SEC_ASN1_SUB(SEC_AnyTemplate), + sizeof(CERTGeneralName) } +}; + +static const SEC_ASN1Template CERT_DirectoryNameTemplate[] = { + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | + SEC_ASN1_XTRN | 4, + offsetof(CERTGeneralName, derDirectoryName), + SEC_ASN1_SUB(SEC_AnyTemplate), sizeof(CERTGeneralName) } +}; + +static const SEC_ASN1Template CERT_EDIPartyNameTemplate[] = { + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_XTRN | 5, + offsetof(CERTGeneralName, name.other), SEC_ASN1_SUB(SEC_AnyTemplate), + sizeof(CERTGeneralName) } +}; + +static const SEC_ASN1Template CERT_URITemplate[] = { + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 6, + offsetof(CERTGeneralName, name.other), + SEC_ASN1_SUB(SEC_IA5StringTemplate), sizeof(CERTGeneralName) } +}; + +static const SEC_ASN1Template CERT_IPAddressTemplate[] = { + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 7, + offsetof(CERTGeneralName, name.other), + SEC_ASN1_SUB(SEC_OctetStringTemplate), sizeof(CERTGeneralName) } +}; + +static const SEC_ASN1Template CERT_RegisteredIDTemplate[] = { + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 8, + offsetof(CERTGeneralName, name.other), SEC_ASN1_SUB(SEC_ObjectIDTemplate), + sizeof(CERTGeneralName) } +}; + +const SEC_ASN1Template CERT_GeneralNamesTemplate[] = { + { SEC_ASN1_SEQUENCE_OF | SEC_ASN1_XTRN, 0, SEC_ASN1_SUB(SEC_AnyTemplate) } +}; + +static struct { + CERTGeneralNameType type; + char *name; +} typesArray[] = { { certOtherName, "other" }, + { certRFC822Name, "email" }, + { certRFC822Name, "rfc822" }, + { certDNSName, "dns" }, + { certX400Address, "x400" }, + { certX400Address, "x400addr" }, + { certDirectoryName, "directory" }, + { certDirectoryName, "dn" }, + { certEDIPartyName, "edi" }, + { certEDIPartyName, "ediparty" }, + { certURI, "uri" }, + { certIPAddress, "ip" }, + { certIPAddress, "ipaddr" }, + { certRegisterID, "registerid" } }; + +CERTGeneralNameType +CERT_GetGeneralNameTypeFromString(const char *string) +{ + int types_count = sizeof(typesArray) / sizeof(typesArray[0]); + int i; + + for (i = 0; i < types_count; i++) { + if (PORT_Strcasecmp(string, typesArray[i].name) == 0) { + return typesArray[i].type; + } + } + return 0; +} + +CERTGeneralName * +CERT_NewGeneralName(PLArenaPool *arena, CERTGeneralNameType type) +{ + CERTGeneralName *name = arena ? PORT_ArenaZNew(arena, CERTGeneralName) + : PORT_ZNew(CERTGeneralName); + if (name) { + name->type = type; + name->l.prev = name->l.next = &name->l; + } + return name; +} + +/* Copy content of one General Name to another. +** Caller has allocated destination general name. +** This function does not change the destinate's GeneralName's list linkage. +*/ +SECStatus +cert_CopyOneGeneralName(PLArenaPool *arena, CERTGeneralName *dest, + CERTGeneralName *src) +{ + SECStatus rv; + void *mark = NULL; + + PORT_Assert(dest != NULL); + dest->type = src->type; + + mark = PORT_ArenaMark(arena); + + switch (src->type) { + case certDirectoryName: + rv = SECITEM_CopyItem(arena, &dest->derDirectoryName, + &src->derDirectoryName); + if (rv == SECSuccess) + rv = CERT_CopyName(arena, &dest->name.directoryName, + &src->name.directoryName); + break; + + case certOtherName: + rv = SECITEM_CopyItem(arena, &dest->name.OthName.name, + &src->name.OthName.name); + if (rv == SECSuccess) + rv = SECITEM_CopyItem(arena, &dest->name.OthName.oid, + &src->name.OthName.oid); + break; + + default: + rv = SECITEM_CopyItem(arena, &dest->name.other, &src->name.other); + break; + } + if (rv != SECSuccess) { + PORT_ArenaRelease(arena, mark); + } else { + PORT_ArenaUnmark(arena, mark); + } + return rv; +} + +void +CERT_DestroyGeneralNameList(CERTGeneralNameList *list) +{ + PZLock *lock; + + if (list != NULL) { + lock = list->lock; + PZ_Lock(lock); + if (--list->refCount <= 0 && list->arena != NULL) { + PORT_FreeArena(list->arena, PR_FALSE); + PZ_Unlock(lock); + PZ_DestroyLock(lock); + } else { + PZ_Unlock(lock); + } + } + return; +} + +CERTGeneralNameList * +CERT_CreateGeneralNameList(CERTGeneralName *name) +{ + PLArenaPool *arena; + CERTGeneralNameList *list = NULL; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + goto done; + } + list = PORT_ArenaZNew(arena, CERTGeneralNameList); + if (!list) + goto loser; + if (name != NULL) { + SECStatus rv; + list->name = CERT_NewGeneralName(arena, (CERTGeneralNameType)0); + if (!list->name) + goto loser; + rv = CERT_CopyGeneralName(arena, list->name, name); + if (rv != SECSuccess) + goto loser; + } + list->lock = PZ_NewLock(nssILockList); + if (!list->lock) + goto loser; + list->arena = arena; + list->refCount = 1; +done: + return list; + +loser: + PORT_FreeArena(arena, PR_FALSE); + return NULL; +} + +CERTGeneralName * +CERT_GetNextGeneralName(CERTGeneralName *current) +{ + PRCList *next; + + next = current->l.next; + return (CERTGeneralName *)(((char *)next) - offsetof(CERTGeneralName, l)); +} + +CERTGeneralName * +CERT_GetPrevGeneralName(CERTGeneralName *current) +{ + PRCList *prev; + prev = current->l.prev; + return (CERTGeneralName *)(((char *)prev) - offsetof(CERTGeneralName, l)); +} + +CERTNameConstraint * +CERT_GetNextNameConstraint(CERTNameConstraint *current) +{ + PRCList *next; + + next = current->l.next; + return (CERTNameConstraint *)(((char *)next) - + offsetof(CERTNameConstraint, l)); +} + +CERTNameConstraint * +CERT_GetPrevNameConstraint(CERTNameConstraint *current) +{ + PRCList *prev; + prev = current->l.prev; + return (CERTNameConstraint *)(((char *)prev) - + offsetof(CERTNameConstraint, l)); +} + +SECItem * +CERT_EncodeGeneralName(CERTGeneralName *genName, SECItem *dest, + PLArenaPool *arena) +{ + + const SEC_ASN1Template *template; + + PORT_Assert(arena); + if (arena == NULL || !genName) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + /* TODO: mark arena */ + if (dest == NULL) { + dest = PORT_ArenaZNew(arena, SECItem); + if (!dest) + goto loser; + } + if (genName->type == certDirectoryName) { + if (genName->derDirectoryName.data == NULL) { + /* The field hasn't been encoded yet. */ + SECItem *pre_dest = SEC_ASN1EncodeItem( + arena, &(genName->derDirectoryName), + &(genName->name.directoryName), CERT_NameTemplate); + if (!pre_dest) + goto loser; + } + if (genName->derDirectoryName.data == NULL) { + goto loser; + } + } + switch (genName->type) { + case certURI: + template = CERT_URITemplate; + break; + case certRFC822Name: + template = CERT_RFC822NameTemplate; + break; + case certDNSName: + template = CERT_DNSNameTemplate; + break; + case certIPAddress: + template = CERT_IPAddressTemplate; + break; + case certOtherName: + template = CERTOtherNameTemplate; + break; + case certRegisterID: + template = CERT_RegisteredIDTemplate; + break; + /* for this type, we expect the value is already encoded */ + case certEDIPartyName: + template = CERT_EDIPartyNameTemplate; + break; + /* for this type, we expect the value is already encoded */ + case certX400Address: + template = CERT_X400AddressTemplate; + break; + case certDirectoryName: + template = CERT_DirectoryNameTemplate; + break; + default: + PORT_Assert(0); + goto loser; + } + dest = SEC_ASN1EncodeItem(arena, dest, genName, template); + if (!dest) { + goto loser; + } + /* TODO: unmark arena */ + return dest; +loser: + /* TODO: release arena back to mark */ + return NULL; +} + +SECItem ** +cert_EncodeGeneralNames(PLArenaPool *arena, CERTGeneralName *names) +{ + CERTGeneralName *current_name; + SECItem **items = NULL; + int count = 1; + int i; + PRCList *head; + + if (!names) { + return NULL; + } + + PORT_Assert(arena); + /* TODO: mark arena */ + current_name = names; + head = &(names->l); + while (current_name->l.next != head) { + current_name = CERT_GetNextGeneralName(current_name); + ++count; + } + current_name = CERT_GetNextGeneralName(current_name); + items = PORT_ArenaNewArray(arena, SECItem *, count + 1); + if (items == NULL) { + goto loser; + } + for (i = 0; i < count; i++) { + items[i] = CERT_EncodeGeneralName(current_name, (SECItem *)NULL, arena); + if (items[i] == NULL) { + goto loser; + } + current_name = CERT_GetNextGeneralName(current_name); + } + items[i] = NULL; + /* TODO: unmark arena */ + return items; +loser: + /* TODO: release arena to mark */ + return NULL; +} + +CERTGeneralName * +CERT_DecodeGeneralName(PLArenaPool *reqArena, SECItem *encodedName, + CERTGeneralName *genName) +{ + const SEC_ASN1Template *template; + CERTGeneralNameType genNameType; + SECStatus rv = SECSuccess; + SECItem *newEncodedName; + + if (!reqArena) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + /* make a copy for decoding so the data decoded with QuickDER doesn't + point to temporary memory */ + newEncodedName = SECITEM_ArenaDupItem(reqArena, encodedName); + if (!newEncodedName) { + return NULL; + } + /* TODO: mark arena */ + genNameType = (CERTGeneralNameType)((*(newEncodedName->data) & 0x0f) + 1); + if (genName == NULL) { + genName = CERT_NewGeneralName(reqArena, genNameType); + if (!genName) + goto loser; + } else { + genName->type = genNameType; + genName->l.prev = genName->l.next = &genName->l; + } + + switch (genNameType) { + case certURI: + template = CERT_URITemplate; + break; + case certRFC822Name: + template = CERT_RFC822NameTemplate; + break; + case certDNSName: + template = CERT_DNSNameTemplate; + break; + case certIPAddress: + template = CERT_IPAddressTemplate; + break; + case certOtherName: + template = CERTOtherNameTemplate; + break; + case certRegisterID: + template = CERT_RegisteredIDTemplate; + break; + case certEDIPartyName: + template = CERT_EDIPartyNameTemplate; + break; + case certX400Address: + template = CERT_X400AddressTemplate; + break; + case certDirectoryName: + template = CERT_DirectoryNameTemplate; + break; + default: + goto loser; + } + rv = SEC_QuickDERDecodeItem(reqArena, genName, template, newEncodedName); + if (rv != SECSuccess) + goto loser; + if (genNameType == certDirectoryName) { + rv = SEC_QuickDERDecodeItem(reqArena, &(genName->name.directoryName), + CERT_NameTemplate, + &(genName->derDirectoryName)); + if (rv != SECSuccess) + goto loser; + } + + /* TODO: unmark arena */ + return genName; +loser: + /* TODO: release arena to mark */ + return NULL; +} + +CERTGeneralName * +cert_DecodeGeneralNames(PLArenaPool *arena, SECItem **encodedGenName) +{ + PRCList *head = NULL; + PRCList *tail = NULL; + CERTGeneralName *currentName = NULL; + + PORT_Assert(arena); + if (!encodedGenName || !arena) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + /* TODO: mark arena */ + while (*encodedGenName != NULL) { + currentName = CERT_DecodeGeneralName(arena, *encodedGenName, NULL); + if (currentName == NULL) + break; + if (head == NULL) { + head = &(currentName->l); + tail = head; + } + currentName->l.next = head; + currentName->l.prev = tail; + tail = head->prev = tail->next = &(currentName->l); + encodedGenName++; + } + if (currentName) { + /* TODO: unmark arena */ + return CERT_GetNextGeneralName(currentName); + } + /* TODO: release arena to mark */ + return NULL; +} + +void +CERT_DestroyGeneralName(CERTGeneralName *name) +{ + cert_DestroyGeneralNames(name); +} + +SECStatus +cert_DestroyGeneralNames(CERTGeneralName *name) +{ + CERTGeneralName *first; + CERTGeneralName *next = NULL; + + first = name; + do { + next = CERT_GetNextGeneralName(name); + PORT_Free(name); + name = next; + } while (name != first); + return SECSuccess; +} + +static SECItem * +cert_EncodeNameConstraint(CERTNameConstraint *constraint, SECItem *dest, + PLArenaPool *arena) +{ + PORT_Assert(arena); + if (dest == NULL) { + dest = PORT_ArenaZNew(arena, SECItem); + if (dest == NULL) { + return NULL; + } + } + CERT_EncodeGeneralName(&(constraint->name), &(constraint->DERName), arena); + + dest = + SEC_ASN1EncodeItem(arena, dest, constraint, CERTNameConstraintTemplate); + return dest; +} + +SECStatus +cert_EncodeNameConstraintSubTree(CERTNameConstraint *constraints, + PLArenaPool *arena, SECItem ***dest, + PRBool permited) +{ + CERTNameConstraint *current_constraint = constraints; + SECItem **items = NULL; + int count = 0; + int i; + PRCList *head; + + PORT_Assert(arena); + /* TODO: mark arena */ + if (constraints != NULL) { + count = 1; + } + head = &constraints->l; + while (current_constraint->l.next != head) { + current_constraint = CERT_GetNextNameConstraint(current_constraint); + ++count; + } + current_constraint = CERT_GetNextNameConstraint(current_constraint); + items = PORT_ArenaZNewArray(arena, SECItem *, count + 1); + if (items == NULL) { + goto loser; + } + for (i = 0; i < count; i++) { + items[i] = cert_EncodeNameConstraint(current_constraint, + (SECItem *)NULL, arena); + if (items[i] == NULL) { + goto loser; + } + current_constraint = CERT_GetNextNameConstraint(current_constraint); + } + *dest = items; + if (*dest == NULL) { + goto loser; + } + /* TODO: unmark arena */ + return SECSuccess; +loser: + /* TODO: release arena to mark */ + return SECFailure; +} + +SECStatus +cert_EncodeNameConstraints(CERTNameConstraints *constraints, PLArenaPool *arena, + SECItem *dest) +{ + SECStatus rv = SECSuccess; + + PORT_Assert(arena); + /* TODO: mark arena */ + if (constraints->permited != NULL) { + rv = cert_EncodeNameConstraintSubTree( + constraints->permited, arena, &constraints->DERPermited, PR_TRUE); + if (rv == SECFailure) { + goto loser; + } + } + if (constraints->excluded != NULL) { + rv = cert_EncodeNameConstraintSubTree( + constraints->excluded, arena, &constraints->DERExcluded, PR_FALSE); + if (rv == SECFailure) { + goto loser; + } + } + dest = SEC_ASN1EncodeItem(arena, dest, constraints, + CERTNameConstraintsTemplate); + if (dest == NULL) { + goto loser; + } + /* TODO: unmark arena */ + return SECSuccess; +loser: + /* TODO: release arena to mark */ + return SECFailure; +} + +CERTNameConstraint * +cert_DecodeNameConstraint(PLArenaPool *reqArena, SECItem *encodedConstraint) +{ + CERTNameConstraint *constraint; + SECStatus rv = SECSuccess; + CERTGeneralName *temp; + SECItem *newEncodedConstraint; + + if (!reqArena) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + newEncodedConstraint = SECITEM_ArenaDupItem(reqArena, encodedConstraint); + if (!newEncodedConstraint) { + return NULL; + } + /* TODO: mark arena */ + constraint = PORT_ArenaZNew(reqArena, CERTNameConstraint); + if (!constraint) + goto loser; + rv = SEC_QuickDERDecodeItem( + reqArena, constraint, CERTNameConstraintTemplate, newEncodedConstraint); + if (rv != SECSuccess) { + goto loser; + } + temp = CERT_DecodeGeneralName(reqArena, &(constraint->DERName), + &(constraint->name)); + if (temp != &(constraint->name)) { + goto loser; + } + + /* ### sjlee: since the name constraint contains only one + * CERTGeneralName, the list within CERTGeneralName shouldn't + * point anywhere else. Otherwise, bad things will happen. + */ + constraint->name.l.prev = constraint->name.l.next = &(constraint->name.l); + /* TODO: unmark arena */ + return constraint; +loser: + /* TODO: release arena back to mark */ + return NULL; +} + +static CERTNameConstraint * +cert_DecodeNameConstraintSubTree(PLArenaPool *arena, SECItem **subTree, + PRBool permited) +{ + CERTNameConstraint *current = NULL; + CERTNameConstraint *first = NULL; + CERTNameConstraint *last = NULL; + int i = 0; + + PORT_Assert(arena); + /* TODO: mark arena */ + while (subTree[i] != NULL) { + current = cert_DecodeNameConstraint(arena, subTree[i]); + if (current == NULL) { + goto loser; + } + if (first == NULL) { + first = current; + } else { + current->l.prev = &(last->l); + last->l.next = &(current->l); + } + last = current; + i++; + } + if (first && last) { + first->l.prev = &(last->l); + last->l.next = &(first->l); + } + /* TODO: unmark arena */ + return first; +loser: + /* TODO: release arena back to mark */ + return NULL; +} + +CERTNameConstraints * +cert_DecodeNameConstraints(PLArenaPool *reqArena, + const SECItem *encodedConstraints) +{ + CERTNameConstraints *constraints; + SECStatus rv; + SECItem *newEncodedConstraints; + + if (!reqArena) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + PORT_Assert(encodedConstraints); + newEncodedConstraints = SECITEM_ArenaDupItem(reqArena, encodedConstraints); + + /* TODO: mark arena */ + constraints = PORT_ArenaZNew(reqArena, CERTNameConstraints); + if (constraints == NULL) { + goto loser; + } + rv = SEC_QuickDERDecodeItem(reqArena, constraints, + CERTNameConstraintsTemplate, + newEncodedConstraints); + if (rv != SECSuccess) { + goto loser; + } + if (constraints->DERPermited != NULL && + constraints->DERPermited[0] != NULL) { + constraints->permited = cert_DecodeNameConstraintSubTree( + reqArena, constraints->DERPermited, PR_TRUE); + if (constraints->permited == NULL) { + goto loser; + } + } + if (constraints->DERExcluded != NULL && + constraints->DERExcluded[0] != NULL) { + constraints->excluded = cert_DecodeNameConstraintSubTree( + reqArena, constraints->DERExcluded, PR_FALSE); + if (constraints->excluded == NULL) { + goto loser; + } + } + /* TODO: unmark arena */ + return constraints; +loser: + /* TODO: release arena back to mark */ + return NULL; +} + +/* Copy a chain of one or more general names to a destination chain. +** Caller has allocated at least the first destination GeneralName struct. +** Both source and destination chains are circular doubly-linked lists. +** The first source struct is copied to the first destination struct. +** If the source chain has more than one member, and the destination chain +** has only one member, then this function allocates new structs for all but +** the first copy from the arena and links them into the destination list. +** If the destination struct is part of a list with more than one member, +** then this function traverses both the source and destination lists, +** copying each source struct to the corresponding dest struct. +** In that case, the destination list MUST contain at least as many +** structs as the source list or some dest entries will be overwritten. +*/ +SECStatus +CERT_CopyGeneralName(PLArenaPool *arena, CERTGeneralName *dest, + CERTGeneralName *src) +{ + SECStatus rv; + CERTGeneralName *destHead = dest; + CERTGeneralName *srcHead = src; + + PORT_Assert(dest != NULL); + if (!dest) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + /* TODO: mark arena */ + do { + rv = cert_CopyOneGeneralName(arena, dest, src); + if (rv != SECSuccess) + goto loser; + src = CERT_GetNextGeneralName(src); + /* if there is only one general name, we shouldn't do this */ + if (src != srcHead) { + if (dest->l.next == &destHead->l) { + CERTGeneralName *temp; + temp = CERT_NewGeneralName(arena, (CERTGeneralNameType)0); + if (!temp) + goto loser; + temp->l.next = &destHead->l; + temp->l.prev = &dest->l; + destHead->l.prev = &temp->l; + dest->l.next = &temp->l; + dest = temp; + } else { + dest = CERT_GetNextGeneralName(dest); + } + } + } while (src != srcHead && rv == SECSuccess); + /* TODO: unmark arena */ + return rv; +loser: + /* TODO: release back to mark */ + return SECFailure; +} + +CERTGeneralNameList * +CERT_DupGeneralNameList(CERTGeneralNameList *list) +{ + if (list != NULL) { + PZ_Lock(list->lock); + list->refCount++; + PZ_Unlock(list->lock); + } + return list; +} + +/* Allocate space and copy CERTNameConstraint from src to dest */ +CERTNameConstraint * +CERT_CopyNameConstraint(PLArenaPool *arena, CERTNameConstraint *dest, + CERTNameConstraint *src) +{ + SECStatus rv; + + /* TODO: mark arena */ + if (dest == NULL) { + dest = PORT_ArenaZNew(arena, CERTNameConstraint); + if (!dest) + goto loser; + /* mark that it is not linked */ + dest->name.l.prev = dest->name.l.next = &(dest->name.l); + } + rv = CERT_CopyGeneralName(arena, &dest->name, &src->name); + if (rv != SECSuccess) { + goto loser; + } + rv = SECITEM_CopyItem(arena, &dest->DERName, &src->DERName); + if (rv != SECSuccess) { + goto loser; + } + rv = SECITEM_CopyItem(arena, &dest->min, &src->min); + if (rv != SECSuccess) { + goto loser; + } + rv = SECITEM_CopyItem(arena, &dest->max, &src->max); + if (rv != SECSuccess) { + goto loser; + } + dest->l.prev = dest->l.next = &dest->l; + /* TODO: unmark arena */ + return dest; +loser: + /* TODO: release arena to mark */ + return NULL; +} + +CERTGeneralName * +cert_CombineNamesLists(CERTGeneralName *list1, CERTGeneralName *list2) +{ + PRCList *begin1; + PRCList *begin2; + PRCList *end1; + PRCList *end2; + + if (list1 == NULL) { + return list2; + } else if (list2 == NULL) { + return list1; + } else { + begin1 = &list1->l; + begin2 = &list2->l; + end1 = list1->l.prev; + end2 = list2->l.prev; + end1->next = begin2; + end2->next = begin1; + begin1->prev = end2; + begin2->prev = end1; + return list1; + } +} + +CERTNameConstraint * +cert_CombineConstraintsLists(CERTNameConstraint *list1, + CERTNameConstraint *list2) +{ + PRCList *begin1; + PRCList *begin2; + PRCList *end1; + PRCList *end2; + + if (list1 == NULL) { + return list2; + } else if (list2 == NULL) { + return list1; + } else { + begin1 = &list1->l; + begin2 = &list2->l; + end1 = list1->l.prev; + end2 = list2->l.prev; + end1->next = begin2; + end2->next = begin1; + begin1->prev = end2; + begin2->prev = end1; + return list1; + } +} + +/* Add a CERTNameConstraint to the CERTNameConstraint list */ +CERTNameConstraint * +CERT_AddNameConstraint(CERTNameConstraint *list, CERTNameConstraint *constraint) +{ + PORT_Assert(constraint != NULL); + constraint->l.next = constraint->l.prev = &constraint->l; + list = cert_CombineConstraintsLists(list, constraint); + return list; +} + +SECStatus +CERT_GetNameConstraintByType(CERTNameConstraint *constraints, + CERTGeneralNameType type, + CERTNameConstraint **returnList, + PLArenaPool *arena) +{ + CERTNameConstraint *current = NULL; + void *mark = NULL; + + *returnList = NULL; + if (!constraints) + return SECSuccess; + + mark = PORT_ArenaMark(arena); + + current = constraints; + do { + PORT_Assert(current->name.type); + if (current->name.type == type) { + CERTNameConstraint *temp; + temp = CERT_CopyNameConstraint(arena, NULL, current); + if (temp == NULL) + goto loser; + *returnList = CERT_AddNameConstraint(*returnList, temp); + } + current = CERT_GetNextNameConstraint(current); + } while (current != constraints); + PORT_ArenaUnmark(arena, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(arena, mark); + return SECFailure; +} + +void * +CERT_GetGeneralNameByType(CERTGeneralName *genNames, CERTGeneralNameType type, + PRBool derFormat) +{ + CERTGeneralName *current; + + if (!genNames) + return NULL; + current = genNames; + + do { + if (current->type == type) { + switch (type) { + case certDNSName: + case certEDIPartyName: + case certIPAddress: + case certRegisterID: + case certRFC822Name: + case certX400Address: + case certURI: + return (void *)¤t->name.other; /* SECItem * */ + + case certOtherName: + return (void *)¤t->name.OthName; /* OthName * */ + + case certDirectoryName: + return derFormat + ? (void *)¤t + ->derDirectoryName /* SECItem * */ + : (void *)¤t->name + .directoryName; /* CERTName * */ + } + PORT_Assert(0); + return NULL; + } + current = CERT_GetNextGeneralName(current); + } while (current != genNames); + return NULL; +} + +int +CERT_GetNamesLength(CERTGeneralName *names) +{ + int length = 0; + CERTGeneralName *first; + + first = names; + if (names != NULL) { + do { + length++; + names = CERT_GetNextGeneralName(names); + } while (names != first); + } + return length; +} + +/* Creates new GeneralNames for any email addresses found in the +** input DN, and links them onto the list for the DN. +*/ +SECStatus +cert_ExtractDNEmailAddrs(CERTGeneralName *name, PLArenaPool *arena) +{ + CERTGeneralName *nameList = NULL; + const CERTRDN **nRDNs = (const CERTRDN **)(name->name.directoryName.rdns); + SECStatus rv = SECSuccess; + + PORT_Assert(name->type == certDirectoryName); + if (name->type != certDirectoryName) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + /* TODO: mark arena */ + while (nRDNs && *nRDNs) { /* loop over RDNs */ + const CERTRDN *nRDN = *nRDNs++; + CERTAVA **nAVAs = nRDN->avas; + while (nAVAs && *nAVAs) { /* loop over AVAs */ + int tag; + CERTAVA *nAVA = *nAVAs++; + tag = CERT_GetAVATag(nAVA); + if (tag == SEC_OID_PKCS9_EMAIL_ADDRESS || + tag == SEC_OID_RFC1274_MAIL) { /* email AVA */ + CERTGeneralName *newName = NULL; + SECItem *avaValue = CERT_DecodeAVAValue(&nAVA->value); + if (!avaValue) + goto loser; + rv = SECFailure; + newName = CERT_NewGeneralName(arena, certRFC822Name); + if (newName) { + rv = + SECITEM_CopyItem(arena, &newName->name.other, avaValue); + } + SECITEM_FreeItem(avaValue, PR_TRUE); + if (rv != SECSuccess) + goto loser; + nameList = cert_CombineNamesLists(nameList, newName); + } /* handle one email AVA */ + } /* loop over AVAs */ + } /* loop over RDNs */ + /* combine new names with old one. */ + (void)cert_CombineNamesLists(name, nameList); + /* TODO: unmark arena */ + return SECSuccess; + +loser: + /* TODO: release arena back to mark */ + return SECFailure; +} + +/* Extract all names except Subject Common Name from a cert +** in preparation for a name constraints test. +*/ +CERTGeneralName * +CERT_GetCertificateNames(CERTCertificate *cert, PLArenaPool *arena) +{ + return CERT_GetConstrainedCertificateNames(cert, arena, PR_FALSE); +} + +/* This function is called by CERT_VerifyCertChain to extract all +** names from a cert in preparation for a name constraints test. +*/ +CERTGeneralName * +CERT_GetConstrainedCertificateNames(const CERTCertificate *cert, + PLArenaPool *arena, + PRBool includeSubjectCommonName) +{ + CERTGeneralName *DN; + CERTGeneralName *SAN; + PRUint32 numDNSNames = 0; + SECStatus rv; + + if (!arena) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + /* TODO: mark arena */ + DN = CERT_NewGeneralName(arena, certDirectoryName); + if (DN == NULL) { + goto loser; + } + rv = CERT_CopyName(arena, &DN->name.directoryName, &cert->subject); + if (rv != SECSuccess) { + goto loser; + } + rv = SECITEM_CopyItem(arena, &DN->derDirectoryName, &cert->derSubject); + if (rv != SECSuccess) { + goto loser; + } + /* Extract email addresses from DN, construct CERTGeneralName structs + ** for them, add them to the name list + */ + rv = cert_ExtractDNEmailAddrs(DN, arena); + if (rv != SECSuccess) + goto loser; + + /* Now extract any GeneralNames from the subject name names extension. */ + SAN = cert_GetSubjectAltNameList(cert, arena); + if (SAN) { + numDNSNames = cert_CountDNSPatterns(SAN); + DN = cert_CombineNamesLists(DN, SAN); + } + if (!numDNSNames && includeSubjectCommonName) { + char *cn = CERT_GetCommonName(&cert->subject); + if (cn) { + CERTGeneralName *CN = CERT_NewGeneralName(arena, certDNSName); + if (CN) { + SECItem cnItem = { siBuffer, NULL, 0 }; + cnItem.data = (unsigned char *)cn; + cnItem.len = strlen(cn); + rv = SECITEM_CopyItem(arena, &CN->name.other, &cnItem); + if (rv == SECSuccess) { + DN = cert_CombineNamesLists(DN, CN); + } + } + PORT_Free(cn); + } + } + if (rv == SECSuccess) { + /* TODO: unmark arena */ + return DN; + } +loser: + /* TODO: release arena to mark */ + return NULL; +} + +/* Returns SECSuccess if name matches constraint per RFC 3280 rules for +** URI name constraints. SECFailure otherwise. +** If the constraint begins with a dot, it is a domain name, otherwise +** It is a host name. Examples: +** Constraint Name Result +** ------------ --------------- -------- +** foo.bar.com foo.bar.com matches +** foo.bar.com FoO.bAr.CoM matches +** foo.bar.com www.foo.bar.com no match +** foo.bar.com nofoo.bar.com no match +** .foo.bar.com www.foo.bar.com matches +** .foo.bar.com nofoo.bar.com no match +** .foo.bar.com foo.bar.com no match +** .foo.bar.com www..foo.bar.com no match +*/ +static SECStatus +compareURIN2C(const SECItem *name, const SECItem *constraint) +{ + int offset; + /* The spec is silent on intepreting zero-length constraints. + ** We interpret them as matching no URI names. + */ + if (!constraint->len) + return SECFailure; + if (constraint->data[0] != '.') { + /* constraint is a host name. */ + if (name->len != constraint->len || + PL_strncasecmp((char *)name->data, (char *)constraint->data, + constraint->len)) + return SECFailure; + return SECSuccess; + } + /* constraint is a domain name. */ + if (name->len < constraint->len) + return SECFailure; + offset = name->len - constraint->len; + if (PL_strncasecmp((char *)(name->data + offset), (char *)constraint->data, + constraint->len)) + return SECFailure; + if (!offset || + (name->data[offset - 1] == '.') + (constraint->data[0] == '.') == 1) + return SECSuccess; + return SECFailure; +} + +/* for DNSname constraints, RFC 3280 says, (section 4.2.1.11, page 38) +** +** DNS name restrictions are expressed as foo.bar.com. Any DNS name +** that can be constructed by simply adding to the left hand side of the +** name satisfies the name constraint. For example, www.foo.bar.com +** would satisfy the constraint but foo1.bar.com would not. +** +** But NIST's PKITS test suite requires that the constraint be treated +** as a domain name, and requires that any name added to the left hand +** side end in a dot ".". Sensible, but not strictly following the RFC. +** +** Constraint Name RFC 3280 NIST PKITS +** ------------ --------------- -------- ---------- +** foo.bar.com foo.bar.com matches matches +** foo.bar.com FoO.bAr.CoM matches matches +** foo.bar.com www.foo.bar.com matches matches +** foo.bar.com nofoo.bar.com MATCHES NO MATCH +** .foo.bar.com www.foo.bar.com matches matches? disallowed? +** .foo.bar.com foo.bar.com no match no match +** .foo.bar.com www..foo.bar.com matches probably not +** +** We will try to conform to NIST's PKITS tests, and the unstated +** rules they imply. +*/ +static SECStatus +compareDNSN2C(const SECItem *name, const SECItem *constraint) +{ + int offset; + /* The spec is silent on intepreting zero-length constraints. + ** We interpret them as matching all DNSnames. + */ + if (!constraint->len) + return SECSuccess; + if (name->len < constraint->len) + return SECFailure; + offset = name->len - constraint->len; + if (PL_strncasecmp((char *)(name->data + offset), (char *)constraint->data, + constraint->len)) + return SECFailure; + if (!offset || + (name->data[offset - 1] == '.') + (constraint->data[0] == '.') == 1) + return SECSuccess; + return SECFailure; +} + +/* Returns SECSuccess if name matches constraint per RFC 3280 rules for +** internet email addresses. SECFailure otherwise. +** If constraint contains a '@' then the two strings much match exactly. +** Else if constraint starts with a '.'. then it must match the right-most +** substring of the name, +** else constraint string must match entire name after the name's '@'. +** Empty constraint string matches all names. All comparisons case insensitive. +*/ +static SECStatus +compareRFC822N2C(const SECItem *name, const SECItem *constraint) +{ + int offset; + if (!constraint->len) + return SECSuccess; + if (name->len < constraint->len) + return SECFailure; + if (constraint->len == 1 && constraint->data[0] == '.') + return SECSuccess; + for (offset = constraint->len - 1; offset >= 0; --offset) { + if (constraint->data[offset] == '@') { + return (name->len == constraint->len && + !PL_strncasecmp((char *)name->data, + (char *)constraint->data, constraint->len)) + ? SECSuccess + : SECFailure; + } + } + offset = name->len - constraint->len; + if (PL_strncasecmp((char *)(name->data + offset), (char *)constraint->data, + constraint->len)) + return SECFailure; + if (constraint->data[0] == '.') + return SECSuccess; + if (offset > 0 && name->data[offset - 1] == '@') + return SECSuccess; + return SECFailure; +} + +/* name contains either a 4 byte IPv4 address or a 16 byte IPv6 address. +** constraint contains an address of the same length, and a subnet mask +** of the same length. Compare name's address to the constraint's +** address, subject to the mask. +** Return SECSuccess if they match, SECFailure if they don't. +*/ +static SECStatus +compareIPaddrN2C(const SECItem *name, const SECItem *constraint) +{ + int i; + if (name->len == 4 && constraint->len == 8) { /* ipv4 addr */ + for (i = 0; i < 4; i++) { + if ((name->data[i] ^ constraint->data[i]) & constraint->data[i + 4]) + goto loser; + } + return SECSuccess; + } + if (name->len == 16 && constraint->len == 32) { /* ipv6 addr */ + for (i = 0; i < 16; i++) { + if ((name->data[i] ^ constraint->data[i]) & + constraint->data[i + 16]) + goto loser; + } + return SECSuccess; + } +loser: + return SECFailure; +} + +/* start with a SECItem that points to a URI. Parse it lookingg for +** a hostname. Modify item->data and item->len to define the hostname, +** but do not modify and data at item->data. +** If anything goes wrong, the contents of *item are undefined. +*/ +static SECStatus +parseUriHostname(SECItem *item) +{ + int i; + PRBool found = PR_FALSE; + for (i = 0; (unsigned)(i + 2) < item->len; ++i) { + if (item->data[i] == ':' && item->data[i + 1] == '/' && + item->data[i + 2] == '/') { + i += 3; + item->data += i; + item->len -= i; + found = PR_TRUE; + break; + } + } + if (!found) + return SECFailure; + /* now look for a '/', which is an upper bound in the end of the name */ + for (i = 0; (unsigned)i < item->len; ++i) { + if (item->data[i] == '/') { + item->len = i; + break; + } + } + /* now look for a ':', which marks the end of the name */ + for (i = item->len; --i >= 0;) { + if (item->data[i] == ':') { + item->len = i; + break; + } + } + /* now look for an '@', which marks the beginning of the hostname */ + for (i = 0; (unsigned)i < item->len; ++i) { + if (item->data[i] == '@') { + ++i; + item->data += i; + item->len -= i; + break; + } + } + return item->len ? SECSuccess : SECFailure; +} + +/* This function takes one name, and a list of constraints. +** It searches the constraints looking for a match. +** It returns SECSuccess if the name satisfies the constraints, i.e., +** if excluded, then the name does not match any constraint, +** if permitted, then the name matches at least one constraint. +** It returns SECFailure if the name fails to satisfy the constraints, +** or if some code fails (e.g. out of memory, or invalid constraint) +*/ +SECStatus +cert_CompareNameWithConstraints(const CERTGeneralName *name, + const CERTNameConstraint *constraints, + PRBool excluded) +{ + SECStatus rv = SECSuccess; + SECStatus matched = SECFailure; + const CERTNameConstraint *current; + + PORT_Assert(constraints); /* caller should not call with NULL */ + if (!constraints) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + current = constraints; + do { + rv = SECSuccess; + matched = SECFailure; + PORT_Assert(name->type == current->name.type); + switch (name->type) { + + case certDNSName: + matched = + compareDNSN2C(&name->name.other, ¤t->name.name.other); + break; + + case certRFC822Name: + matched = compareRFC822N2C(&name->name.other, + ¤t->name.name.other); + break; + + case certURI: { + /* make a modifiable copy of the URI SECItem. */ + SECItem uri = name->name.other; + /* find the hostname in the URI */ + rv = parseUriHostname(&uri); + if (rv == SECSuccess) { + /* does our hostname meet the constraint? */ + matched = compareURIN2C(&uri, ¤t->name.name.other); + } + } break; + + case certDirectoryName: + /* Determine if the constraint directory name is a "prefix" + ** for the directory name being tested. + */ + { + /* status defaults to SECEqual, so that a constraint with + ** no AVAs will be a wildcard, matching all directory names. + */ + SECComparison status = SECEqual; + const CERTRDN **cRDNs = + (const CERTRDN **)current->name.name.directoryName.rdns; + const CERTRDN **nRDNs = + (const CERTRDN **)name->name.directoryName.rdns; + while (cRDNs && *cRDNs && nRDNs && *nRDNs) { + /* loop over name RDNs and constraint RDNs in lock step + */ + const CERTRDN *cRDN = *cRDNs++; + const CERTRDN *nRDN = *nRDNs++; + CERTAVA **cAVAs = cRDN->avas; + while (cAVAs && + *cAVAs) { /* loop over constraint AVAs */ + CERTAVA *cAVA = *cAVAs++; + CERTAVA **nAVAs = nRDN->avas; + while (nAVAs && *nAVAs) { /* loop over name AVAs */ + CERTAVA *nAVA = *nAVAs++; + status = CERT_CompareAVA(cAVA, nAVA); + if (status == SECEqual) + break; + } /* loop over name AVAs */ + if (status != SECEqual) + break; + } /* loop over constraint AVAs */ + if (status != SECEqual) + break; + } /* loop over name RDNs and constraint RDNs */ + matched = (status == SECEqual) ? SECSuccess : SECFailure; + break; + } + + case certIPAddress: /* type 8 */ + matched = compareIPaddrN2C(&name->name.other, + ¤t->name.name.other); + break; + + /* NSS does not know how to compare these "Other" type names with + ** their respective constraints. But it does know how to tell + ** if the constraint applies to the type of name (by comparing + ** the constraint OID to the name OID). NSS makes no use of "Other" + ** type names at all, so NSS errs on the side of leniency for these + ** types, provided that their OIDs match. So, when an "Other" + ** name constraint appears in an excluded subtree, it never causes + ** a name to fail. When an "Other" name constraint appears in a + ** permitted subtree, AND the constraint's OID matches the name's + ** OID, then name is treated as if it matches the constraint. + */ + case certOtherName: /* type 1 */ + matched = + (!excluded && name->type == current->name.type && + SECITEM_ItemsAreEqual(&name->name.OthName.oid, + ¤t->name.name.OthName.oid)) + ? SECSuccess + : SECFailure; + break; + + /* NSS does not know how to compare these types of names with their + ** respective constraints. But NSS makes no use of these types of + ** names at all, so it errs on the side of leniency for these types. + ** Constraints for these types of names never cause the name to + ** fail the constraints test. NSS behaves as if the name matched + ** for permitted constraints, and did not match for excluded ones. + */ + case certX400Address: /* type 4 */ + case certEDIPartyName: /* type 6 */ + case certRegisterID: /* type 9 */ + matched = excluded ? SECFailure : SECSuccess; + break; + + default: /* non-standard types are not supported */ + rv = SECFailure; + break; + } + if (matched == SECSuccess || rv != SECSuccess) + break; + current = CERT_GetNextNameConstraint((CERTNameConstraint *)current); + } while (current != constraints); + if (rv == SECSuccess) { + if (matched == SECSuccess) + rv = excluded ? SECFailure : SECSuccess; + else + rv = excluded ? SECSuccess : SECFailure; + return rv; + } + + return SECFailure; +} + +/* Add and link a CERTGeneralName to a CERTNameConstraint list. Most +** likely the CERTNameConstraint passed in is either the permitted +** list or the excluded list of a CERTNameConstraints. +*/ +SECStatus +CERT_AddNameConstraintByGeneralName(PLArenaPool *arena, + CERTNameConstraint **constraints, + CERTGeneralName *name) +{ + SECStatus rv; + CERTNameConstraint *current = NULL; + CERTNameConstraint *first = *constraints; + void *mark = NULL; + + mark = PORT_ArenaMark(arena); + + current = PORT_ArenaZNew(arena, CERTNameConstraint); + if (current == NULL) { + rv = SECFailure; + goto done; + } + + rv = cert_CopyOneGeneralName(arena, ¤t->name, name); + if (rv != SECSuccess) { + goto done; + } + + current->name.l.prev = current->name.l.next = &(current->name.l); + + if (first == NULL) { + *constraints = current; + PR_INIT_CLIST(¤t->l); + } else { + PR_INSERT_BEFORE(¤t->l, &first->l); + } + +done: + if (rv == SECFailure) { + PORT_ArenaRelease(arena, mark); + } else { + PORT_ArenaUnmark(arena, mark); + } + return rv; +} + +/* + * Here we define a list of name constraints to be imposed on + * certain certificates, most importantly root certificates. + * + * Each entry in the name constraints list is constructed with this + * macro. An entry contains two SECItems, which have names in + * specific forms to make the macro work: + * + * * ${CA}_SUBJECT_DN - The subject DN for which the constraints + * should be applied + * * ${CA}_NAME_CONSTRAINTS - The name constraints extension + * + * Entities subject to name constraints are identified by subject name + * so that we can cover all certificates for that entity, including, e.g., + * cross-certificates. We use subject rather than public key because + * calling methods often have easy access to that field (vs., say, a key ID), + * and in practice, subject names and public keys are usually in one-to-one + * correspondence anyway. + * + */ + +#define STRING_TO_SECITEM(str) \ + { \ + siBuffer, (unsigned char *)str, sizeof(str) - 1 \ + } + +#define NAME_CONSTRAINTS_ENTRY(CA) \ + { \ + STRING_TO_SECITEM(CA##_SUBJECT_DN) \ + , \ + STRING_TO_SECITEM(CA##_NAME_CONSTRAINTS) \ + } + +/* clang-format off */ + +/* Agence Nationale de la Securite des Systemes d'Information (ANSSI) */ + +#define ANSSI_SUBJECT_DN \ + "\x30\x81\x85" \ + "\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02" "FR" /* C */ \ + "\x31\x0F\x30\x0D\x06\x03\x55\x04\x08\x13\x06" "France" /* ST */ \ + "\x31\x0E\x30\x0C\x06\x03\x55\x04\x07\x13\x05" "Paris" /* L */ \ + "\x31\x10\x30\x0E\x06\x03\x55\x04\x0A\x13\x07" "PM/SGDN" /* O */ \ + "\x31\x0E\x30\x0C\x06\x03\x55\x04\x0B\x13\x05" "DCSSI" /* OU */ \ + "\x31\x0E\x30\x0C\x06\x03\x55\x04\x03\x13\x05" "IGC/A" /* CN */ \ + "\x31\x23\x30\x21\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x09\x01" \ + "\x16\x14" "igca@sgdn.pm.gouv.fr" /* emailAddress */ \ + +#define ANSSI_NAME_CONSTRAINTS \ + "\x30\x5D\xA0\x5B" \ + "\x30\x05\x82\x03" ".fr" \ + "\x30\x05\x82\x03" ".gp" \ + "\x30\x05\x82\x03" ".gf" \ + "\x30\x05\x82\x03" ".mq" \ + "\x30\x05\x82\x03" ".re" \ + "\x30\x05\x82\x03" ".yt" \ + "\x30\x05\x82\x03" ".pm" \ + "\x30\x05\x82\x03" ".bl" \ + "\x30\x05\x82\x03" ".mf" \ + "\x30\x05\x82\x03" ".wf" \ + "\x30\x05\x82\x03" ".pf" \ + "\x30\x05\x82\x03" ".nc" \ + "\x30\x05\x82\x03" ".tf" + +/* TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 */ + +#define TUBITAK1_SUBJECT_DN \ + "\x30\x81\xd2" \ + "\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02" \ + /* C */ "TR" \ + "\x31\x18\x30\x16\x06\x03\x55\x04\x07\x13\x0f" \ + /* L */ "Gebze - Kocaeli" \ + "\x31\x42\x30\x40\x06\x03\x55\x04\x0a\x13\x39" \ + /* O */ "Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK" \ + "\x31\x2d\x30\x2b\x06\x03\x55\x04\x0b\x13\x24" \ + /* OU */ "Kamu Sertifikasyon Merkezi - Kamu SM" \ + "\x31\x36\x30\x34\x06\x03\x55\x04\x03\x13\x2d" \ + /* CN */ "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1" + +#define TUBITAK1_NAME_CONSTRAINTS \ + "\x30\x09\xa0\x07" \ + "\x30\x05\x82\x03" ".tr" + +/* clang-format on */ + +static const SECItem builtInNameConstraints[][2] = { + NAME_CONSTRAINTS_ENTRY(ANSSI), + NAME_CONSTRAINTS_ENTRY(TUBITAK1) +}; + +SECStatus +CERT_GetImposedNameConstraints(const SECItem *derSubject, SECItem *extensions) +{ + size_t i; + + if (!extensions) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + for (i = 0; i < PR_ARRAY_SIZE(builtInNameConstraints); ++i) { + if (SECITEM_ItemsAreEqual(derSubject, &builtInNameConstraints[i][0])) { + return SECITEM_CopyItem(NULL, extensions, + &builtInNameConstraints[i][1]); + } + } + + PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND); + return SECFailure; +} + +/* + * Extract the name constraints extension from the CA cert. + * If the certificate contains no name constraints extension, but + * CERT_GetImposedNameConstraints returns a name constraints extension + * for the subject of the certificate, then that extension will be returned. + */ +SECStatus +CERT_FindNameConstraintsExten(PLArenaPool *arena, CERTCertificate *cert, + CERTNameConstraints **constraints) +{ + SECStatus rv = SECSuccess; + SECItem constraintsExtension; + void *mark = NULL; + + *constraints = NULL; + + rv = CERT_FindCertExtension(cert, SEC_OID_X509_NAME_CONSTRAINTS, + &constraintsExtension); + if (rv != SECSuccess) { + if (PORT_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND) { + return rv; + } + rv = CERT_GetImposedNameConstraints(&cert->derSubject, + &constraintsExtension); + if (rv != SECSuccess) { + if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND) { + return SECSuccess; + } + return rv; + } + } + + mark = PORT_ArenaMark(arena); + + *constraints = cert_DecodeNameConstraints(arena, &constraintsExtension); + if (*constraints == NULL) { /* decode failed */ + rv = SECFailure; + } + PORT_Free(constraintsExtension.data); + + if (rv == SECFailure) { + PORT_ArenaRelease(arena, mark); + } else { + PORT_ArenaUnmark(arena, mark); + } + + return rv; +} + +/* Verify name against all the constraints relevant to that type of +** the name. +*/ +SECStatus +CERT_CheckNameSpace(PLArenaPool *arena, const CERTNameConstraints *constraints, + const CERTGeneralName *currentName) +{ + CERTNameConstraint *matchingConstraints; + SECStatus rv = SECSuccess; + + if (constraints->excluded != NULL) { + rv = CERT_GetNameConstraintByType(constraints->excluded, + currentName->type, + &matchingConstraints, arena); + if (rv == SECSuccess && matchingConstraints != NULL) { + rv = cert_CompareNameWithConstraints(currentName, + matchingConstraints, PR_TRUE); + } + if (rv != SECSuccess) { + return (rv); + } + } + + if (constraints->permited != NULL) { + rv = CERT_GetNameConstraintByType(constraints->permited, + currentName->type, + &matchingConstraints, arena); + if (rv == SECSuccess && matchingConstraints != NULL) { + rv = cert_CompareNameWithConstraints(currentName, + matchingConstraints, PR_FALSE); + } + if (rv != SECSuccess) { + return (rv); + } + } + + return (SECSuccess); +} + +/* Extract the name constraints extension from the CA cert. +** Test each and every name in namesList against all the constraints +** relevant to that type of name. +** Returns NULL in pBadCert for success, if all names are acceptable. +** If some name is not acceptable, returns a pointer to the cert that +** contained that name. +*/ +SECStatus +CERT_CompareNameSpace(CERTCertificate *cert, CERTGeneralName *namesList, + CERTCertificate **certsList, PLArenaPool *reqArena, + CERTCertificate **pBadCert) +{ + SECStatus rv = SECSuccess; + CERTNameConstraints *constraints; + CERTGeneralName *currentName; + int count = 0; + CERTCertificate *badCert = NULL; + + /* If no names to check, then no names can be bad. */ + if (!namesList) + goto done; + rv = CERT_FindNameConstraintsExten(reqArena, cert, &constraints); + if (rv != SECSuccess) { + count = -1; + goto done; + } + + currentName = namesList; + do { + if (constraints) { + rv = CERT_CheckNameSpace(reqArena, constraints, currentName); + if (rv != SECSuccess) { + break; + } + } + currentName = CERT_GetNextGeneralName(currentName); + count++; + } while (currentName != namesList); + +done: + if (rv != SECSuccess) { + badCert = (count >= 0) ? certsList[count] : cert; + } + if (pBadCert) + *pBadCert = badCert; + + return rv; +} + +#if 0 +/* not exported from shared libs, not used. Turn on if we ever need it. */ +SECStatus +CERT_CompareGeneralName(CERTGeneralName *a, CERTGeneralName *b) +{ + CERTGeneralName *currentA; + CERTGeneralName *currentB; + PRBool found; + + currentA = a; + currentB = b; + if (a != NULL) { + do { + if (currentB == NULL) { + return SECFailure; + } + currentB = CERT_GetNextGeneralName(currentB); + currentA = CERT_GetNextGeneralName(currentA); + } while (currentA != a); + } + if (currentB != b) { + return SECFailure; + } + currentA = a; + do { + currentB = b; + found = PR_FALSE; + do { + if (currentB->type == currentA->type) { + switch (currentB->type) { + case certDNSName: + case certEDIPartyName: + case certIPAddress: + case certRegisterID: + case certRFC822Name: + case certX400Address: + case certURI: + if (SECITEM_CompareItem(¤tA->name.other, + ¤tB->name.other) + == SECEqual) { + found = PR_TRUE; + } + break; + case certOtherName: + if (SECITEM_CompareItem(¤tA->name.OthName.oid, + ¤tB->name.OthName.oid) + == SECEqual && + SECITEM_CompareItem(¤tA->name.OthName.name, + ¤tB->name.OthName.name) + == SECEqual) { + found = PR_TRUE; + } + break; + case certDirectoryName: + if (CERT_CompareName(¤tA->name.directoryName, + ¤tB->name.directoryName) + == SECEqual) { + found = PR_TRUE; + } + } + + } + currentB = CERT_GetNextGeneralName(currentB); + } while (currentB != b && found != PR_TRUE); + if (found != PR_TRUE) { + return SECFailure; + } + currentA = CERT_GetNextGeneralName(currentA); + } while (currentA != a); + return SECSuccess; +} + +SECStatus +CERT_CompareGeneralNameLists(CERTGeneralNameList *a, CERTGeneralNameList *b) +{ + SECStatus rv; + + if (a == b) { + return SECSuccess; + } + if (a != NULL && b != NULL) { + PZ_Lock(a->lock); + PZ_Lock(b->lock); + rv = CERT_CompareGeneralName(a->name, b->name); + PZ_Unlock(a->lock); + PZ_Unlock(b->lock); + } else { + rv = SECFailure; + } + return rv; +} +#endif + +#if 0 +/* This function is not exported from NSS shared libraries, and is not +** used inside of NSS. +** XXX it doesn't check for failed allocations. :-( +*/ +void * +CERT_GetGeneralNameFromListByType(CERTGeneralNameList *list, + CERTGeneralNameType type, + PLArenaPool *arena) +{ + CERTName *name = NULL; + SECItem *item = NULL; + OtherName *other = NULL; + OtherName *tmpOther = NULL; + void *data; + + PZ_Lock(list->lock); + data = CERT_GetGeneralNameByType(list->name, type, PR_FALSE); + if (data != NULL) { + switch (type) { + case certDNSName: + case certEDIPartyName: + case certIPAddress: + case certRegisterID: + case certRFC822Name: + case certX400Address: + case certURI: + if (arena != NULL) { + item = PORT_ArenaNew(arena, SECItem); + if (item != NULL) { +XXX SECITEM_CopyItem(arena, item, (SECItem *) data); + } + } else { + item = SECITEM_DupItem((SECItem *) data); + } + PZ_Unlock(list->lock); + return item; + case certOtherName: + other = (OtherName *) data; + if (arena != NULL) { + tmpOther = PORT_ArenaNew(arena, OtherName); + } else { + tmpOther = PORT_New(OtherName); + } + if (tmpOther != NULL) { +XXX SECITEM_CopyItem(arena, &tmpOther->oid, &other->oid); +XXX SECITEM_CopyItem(arena, &tmpOther->name, &other->name); + } + PZ_Unlock(list->lock); + return tmpOther; + case certDirectoryName: + if (arena) { + name = PORT_ArenaZNew(list->arena, CERTName); + if (name) { +XXX CERT_CopyName(arena, name, (CERTName *) data); + } + } + PZ_Unlock(list->lock); + return name; + } + } + PZ_Unlock(list->lock); + return NULL; +} +#endif + +#if 0 +/* This function is not exported from NSS shared libraries, and is not +** used inside of NSS. +** XXX it should NOT be a void function, since it does allocations +** that can fail. +*/ +void +CERT_AddGeneralNameToList(CERTGeneralNameList *list, + CERTGeneralNameType type, + void *data, SECItem *oid) +{ + CERTGeneralName *name; + + if (list != NULL && data != NULL) { + PZ_Lock(list->lock); + name = CERT_NewGeneralName(list->arena, type); + if (!name) + goto done; + switch (type) { + case certDNSName: + case certEDIPartyName: + case certIPAddress: + case certRegisterID: + case certRFC822Name: + case certX400Address: + case certURI: +XXX SECITEM_CopyItem(list->arena, &name->name.other, (SECItem *)data); + break; + case certOtherName: +XXX SECITEM_CopyItem(list->arena, &name->name.OthName.name, + (SECItem *) data); +XXX SECITEM_CopyItem(list->arena, &name->name.OthName.oid, + oid); + break; + case certDirectoryName: +XXX CERT_CopyName(list->arena, &name->name.directoryName, + (CERTName *) data); + break; + } + list->name = cert_CombineNamesLists(list->name, name); + list->len++; +done: + PZ_Unlock(list->lock); + } + return; +} +#endif diff --git a/security/nss/lib/certdb/genname.h b/security/nss/lib/certdb/genname.h new file mode 100644 index 0000000000..5824157108 --- /dev/null +++ b/security/nss/lib/certdb/genname.h @@ -0,0 +1,93 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _GENAME_H_ +#define _GENAME_H_ + +#include "plarena.h" +#include "seccomon.h" +#include "secoidt.h" +#include "secasn1.h" +#include "secder.h" +#include "certt.h" + +/************************************************************************/ +SEC_BEGIN_PROTOS + +extern const SEC_ASN1Template CERT_GeneralNamesTemplate[]; + +extern SECItem **cert_EncodeGeneralNames(PLArenaPool *arena, + CERTGeneralName *names); + +extern CERTGeneralName *cert_DecodeGeneralNames(PLArenaPool *arena, + SECItem **encodedGenName); + +extern SECStatus cert_DestroyGeneralNames(CERTGeneralName *name); + +extern SECStatus cert_EncodeNameConstraints(CERTNameConstraints *constraints, + PLArenaPool *arena, SECItem *dest); + +extern CERTNameConstraints *cert_DecodeNameConstraints( + PLArenaPool *arena, const SECItem *encodedConstraints); + +extern CERTGeneralName *cert_CombineNamesLists(CERTGeneralName *list1, + CERTGeneralName *list2); + +extern CERTNameConstraint *cert_CombineConstraintsLists( + CERTNameConstraint *list1, CERTNameConstraint *list2); + +/*********************************************************************/ +/* A thread safe implementation of General Names */ +/*********************************************************************/ + +/* Destroy a Single CERTGeneralName */ +void CERT_DestroyGeneralName(CERTGeneralName *name); + +SECStatus CERT_CompareGeneralName(CERTGeneralName *a, CERTGeneralName *b); + +SECStatus CERT_CopyGeneralName(PLArenaPool *arena, CERTGeneralName *dest, + CERTGeneralName *src); + +/* General Name Lists are a thread safe, reference counting layer to + * general names */ + +/* Destroys a CERTGeneralNameList */ +void CERT_DestroyGeneralNameList(CERTGeneralNameList *list); + +/* Creates a CERTGeneralNameList */ +CERTGeneralNameList *CERT_CreateGeneralNameList(CERTGeneralName *name); + +/* Compares two CERTGeneralNameList */ +SECStatus CERT_CompareGeneralNameLists(CERTGeneralNameList *a, + CERTGeneralNameList *b); + +/* returns a copy of the first name of the type requested */ +void *CERT_GetGeneralNameFromListByType(CERTGeneralNameList *list, + CERTGeneralNameType type, + PLArenaPool *arena); + +/* Adds a name to the tail of the list */ +void CERT_AddGeneralNameToList(CERTGeneralNameList *list, + CERTGeneralNameType type, void *data, + SECItem *oid); + +/* returns a duplicate of the CERTGeneralNameList */ +CERTGeneralNameList *CERT_DupGeneralNameList(CERTGeneralNameList *list); + +/* returns the number of CERTGeneralName objects in the doubly linked +** list of which *names is a member. +*/ +extern int CERT_GetNamesLength(CERTGeneralName *names); + +/************************************************************************/ + +SECStatus CERT_CompareNameSpace(CERTCertificate *cert, + CERTGeneralName *namesList, + CERTCertificate **certsList, + PLArenaPool *reqArena, + CERTCertificate **pBadCert); + +SEC_END_PROTOS + +#endif diff --git a/security/nss/lib/certdb/manifest.mn b/security/nss/lib/certdb/manifest.mn new file mode 100644 index 0000000000..4a8c5c40ad --- /dev/null +++ b/security/nss/lib/certdb/manifest.mn @@ -0,0 +1,41 @@ +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +CORE_DEPTH = ../.. + +EXPORTS = \ + cert.h \ + certt.h \ + certdb.h \ + $(NULL) + +PRIVATE_EXPORTS = \ + genname.h \ + xconst.h \ + certxutl.h \ + certi.h \ + $(NULL) + +MODULE = nss + +CSRCS = \ + alg1485.c \ + certdb.c \ + certv3.c \ + certxutl.c \ + crl.c \ + genname.c \ + stanpcertdb.c \ + polcyxtn.c \ + secname.c \ + xauthkid.c \ + xbsconst.c \ + xconst.c \ + $(NULL) + +LIBRARY_NAME = certdb +SHARED_LIBRARY = $(NULL) + +# This part of the code, including all sub-dirs, can be optimized for size +export ALLOW_OPT_CODE_SIZE = 1 diff --git a/security/nss/lib/certdb/polcyxtn.c b/security/nss/lib/certdb/polcyxtn.c new file mode 100644 index 0000000000..aae34e2433 --- /dev/null +++ b/security/nss/lib/certdb/polcyxtn.c @@ -0,0 +1,806 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Support for various policy related extensions + */ + +#include "seccomon.h" +#include "secport.h" +#include "secder.h" +#include "cert.h" +#include "secoid.h" +#include "secasn1.h" +#include "secerr.h" +#include "nspr.h" + +SEC_ASN1_MKSUB(SEC_IntegerTemplate) +SEC_ASN1_MKSUB(SEC_ObjectIDTemplate) + +const SEC_ASN1Template CERT_DisplayTextTypeTemplate[] = { + { SEC_ASN1_CHOICE, offsetof(SECItem, type), 0, sizeof(SECItem) }, + { SEC_ASN1_IA5_STRING, 0, 0, siAsciiString }, + { SEC_ASN1_VISIBLE_STRING, 0, 0, siVisibleString }, + { SEC_ASN1_BMP_STRING, 0, 0, siBMPString }, + { SEC_ASN1_UTF8_STRING, 0, 0, siUTF8String }, + { 0 } +}; + +const SEC_ASN1Template CERT_NoticeReferenceTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTNoticeReference) }, + { SEC_ASN1_INLINE, offsetof(CERTNoticeReference, organization), + CERT_DisplayTextTypeTemplate, 0 }, + { SEC_ASN1_SEQUENCE_OF | SEC_ASN1_XTRN, + offsetof(CERTNoticeReference, noticeNumbers), + SEC_ASN1_SUB(SEC_IntegerTemplate) }, + { 0 } +}; + +const SEC_ASN1Template CERT_UserNoticeTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTUserNotice) }, + { SEC_ASN1_INLINE | SEC_ASN1_OPTIONAL, + offsetof(CERTUserNotice, noticeReference), CERT_NoticeReferenceTemplate, + 0 }, + { SEC_ASN1_INLINE | SEC_ASN1_OPTIONAL, + offsetof(CERTUserNotice, displayText), CERT_DisplayTextTypeTemplate, 0 }, + { 0 } +}; + +const SEC_ASN1Template CERT_PolicyQualifierTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTPolicyQualifier) }, + { SEC_ASN1_OBJECT_ID, offsetof(CERTPolicyQualifier, qualifierID) }, + { SEC_ASN1_ANY, offsetof(CERTPolicyQualifier, qualifierValue) }, + { 0 } +}; + +const SEC_ASN1Template CERT_PolicyInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTPolicyInfo) }, + { SEC_ASN1_OBJECT_ID, offsetof(CERTPolicyInfo, policyID) }, + { SEC_ASN1_SEQUENCE_OF | SEC_ASN1_OPTIONAL, + offsetof(CERTPolicyInfo, policyQualifiers), + CERT_PolicyQualifierTemplate }, + { 0 } +}; + +const SEC_ASN1Template CERT_CertificatePoliciesTemplate[] = { + { SEC_ASN1_SEQUENCE_OF, offsetof(CERTCertificatePolicies, policyInfos), + CERT_PolicyInfoTemplate, sizeof(CERTCertificatePolicies) } +}; + +const SEC_ASN1Template CERT_PolicyMapTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTPolicyMap) }, + { SEC_ASN1_OBJECT_ID, offsetof(CERTPolicyMap, issuerDomainPolicy) }, + { SEC_ASN1_OBJECT_ID, offsetof(CERTPolicyMap, subjectDomainPolicy) }, + { 0 } +}; + +const SEC_ASN1Template CERT_PolicyMappingsTemplate[] = { + { SEC_ASN1_SEQUENCE_OF, offsetof(CERTCertificatePolicyMappings, policyMaps), + CERT_PolicyMapTemplate, sizeof(CERTPolicyMap) } +}; + +const SEC_ASN1Template CERT_PolicyConstraintsTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertificatePolicyConstraints) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, + offsetof(CERTCertificatePolicyConstraints, explicitPolicySkipCerts), + SEC_ASN1_SUB(SEC_IntegerTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 1, + offsetof(CERTCertificatePolicyConstraints, inhibitMappingSkipCerts), + SEC_ASN1_SUB(SEC_IntegerTemplate) }, + { 0 } +}; + +const SEC_ASN1Template CERT_InhibitAnyTemplate[] = { + { SEC_ASN1_INTEGER, + offsetof(CERTCertificateInhibitAny, inhibitAnySkipCerts), NULL, + sizeof(CERTCertificateInhibitAny) } +}; + +static void +breakLines(char *string) +{ + char *tmpstr; + char *lastspace = NULL; + int curlen = 0; + int c; + + tmpstr = string; + + while ((c = *tmpstr) != '\0') { + switch (c) { + case ' ': + lastspace = tmpstr; + break; + case '\n': + lastspace = NULL; + curlen = 0; + break; + } + + if ((curlen >= 55) && (lastspace != NULL)) { + *lastspace = '\n'; + curlen = (tmpstr - lastspace); + lastspace = NULL; + } + + curlen++; + tmpstr++; + } + + return; +} + +CERTCertificatePolicies * +CERT_DecodeCertificatePoliciesExtension(const SECItem *extnValue) +{ + PLArenaPool *arena = NULL; + SECStatus rv; + CERTCertificatePolicies *policies; + CERTPolicyInfo **policyInfos, *policyInfo; + CERTPolicyQualifier **policyQualifiers, *policyQualifier; + SECItem newExtnValue; + + /* make a new arena */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + + if (!arena) { + goto loser; + } + + /* allocate the certificate policies structure */ + policies = (CERTCertificatePolicies *)PORT_ArenaZAlloc( + arena, sizeof(CERTCertificatePolicies)); + + if (policies == NULL) { + goto loser; + } + + policies->arena = arena; + + /* copy the DER into the arena, since Quick DER returns data that points + into the DER input, which may get freed by the caller */ + rv = SECITEM_CopyItem(arena, &newExtnValue, extnValue); + if (rv != SECSuccess) { + goto loser; + } + + /* decode the policy info */ + rv = SEC_QuickDERDecodeItem( + arena, policies, CERT_CertificatePoliciesTemplate, &newExtnValue); + + if (rv != SECSuccess) { + goto loser; + } + + /* initialize the oid tags */ + policyInfos = policies->policyInfos; + while (*policyInfos != NULL) { + policyInfo = *policyInfos; + policyInfo->oid = SECOID_FindOIDTag(&policyInfo->policyID); + policyQualifiers = policyInfo->policyQualifiers; + while (policyQualifiers != NULL && *policyQualifiers != NULL) { + policyQualifier = *policyQualifiers; + policyQualifier->oid = + SECOID_FindOIDTag(&policyQualifier->qualifierID); + policyQualifiers++; + } + policyInfos++; + } + + return (policies); + +loser: + if (arena != NULL) { + PORT_FreeArena(arena, PR_FALSE); + } + + return (NULL); +} + +void +CERT_DestroyCertificatePoliciesExtension(CERTCertificatePolicies *policies) +{ + if (policies != NULL) { + PORT_FreeArena(policies->arena, PR_FALSE); + } + return; +} + +CERTCertificatePolicyMappings * +CERT_DecodePolicyMappingsExtension(SECItem *extnValue) +{ + PLArenaPool *arena = NULL; + SECStatus rv; + CERTCertificatePolicyMappings *mappings; + SECItem newExtnValue; + + /* make a new arena */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) { + goto loser; + } + + /* allocate the policy mappings structure */ + mappings = (CERTCertificatePolicyMappings *)PORT_ArenaZAlloc( + arena, sizeof(CERTCertificatePolicyMappings)); + if (mappings == NULL) { + goto loser; + } + mappings->arena = arena; + + /* copy the DER into the arena, since Quick DER returns data that points + into the DER input, which may get freed by the caller */ + rv = SECITEM_CopyItem(arena, &newExtnValue, extnValue); + if (rv != SECSuccess) { + goto loser; + } + + /* decode the policy mappings */ + rv = SEC_QuickDERDecodeItem(arena, mappings, CERT_PolicyMappingsTemplate, + &newExtnValue); + if (rv != SECSuccess) { + goto loser; + } + + return (mappings); + +loser: + if (arena != NULL) { + PORT_FreeArena(arena, PR_FALSE); + } + + return (NULL); +} + +SECStatus +CERT_DestroyPolicyMappingsExtension(CERTCertificatePolicyMappings *mappings) +{ + if (mappings != NULL) { + PORT_FreeArena(mappings->arena, PR_FALSE); + } + return SECSuccess; +} + +SECStatus +CERT_DecodePolicyConstraintsExtension( + CERTCertificatePolicyConstraints *decodedValue, const SECItem *encodedValue) +{ + CERTCertificatePolicyConstraints decodeContext; + PLArenaPool *arena = NULL; + SECStatus rv = SECSuccess; + + /* initialize so we can tell when an optional component is omitted */ + PORT_Memset(&decodeContext, 0, sizeof(decodeContext)); + + /* make a new arena */ + arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if (!arena) { + return SECFailure; + } + + do { + /* decode the policy constraints */ + rv = SEC_QuickDERDecodeItem(arena, &decodeContext, + CERT_PolicyConstraintsTemplate, + encodedValue); + + if (rv != SECSuccess) { + break; + } + + if (decodeContext.explicitPolicySkipCerts.len == 0) { + *(PRInt32 *)decodedValue->explicitPolicySkipCerts.data = -1; + } else { + *(PRInt32 *)decodedValue->explicitPolicySkipCerts.data = + DER_GetInteger(&decodeContext.explicitPolicySkipCerts); + } + + if (decodeContext.inhibitMappingSkipCerts.len == 0) { + *(PRInt32 *)decodedValue->inhibitMappingSkipCerts.data = -1; + } else { + *(PRInt32 *)decodedValue->inhibitMappingSkipCerts.data = + DER_GetInteger(&decodeContext.inhibitMappingSkipCerts); + } + + if ((*(PRInt32 *)decodedValue->explicitPolicySkipCerts.data == + PR_INT32_MIN) || + (*(PRInt32 *)decodedValue->explicitPolicySkipCerts.data == + PR_INT32_MAX) || + (*(PRInt32 *)decodedValue->inhibitMappingSkipCerts.data == + PR_INT32_MIN) || + (*(PRInt32 *)decodedValue->inhibitMappingSkipCerts.data == + PR_INT32_MAX)) { + rv = SECFailure; + } + + } while (0); + + PORT_FreeArena(arena, PR_FALSE); + return (rv); +} + +SECStatus +CERT_DecodeInhibitAnyExtension(CERTCertificateInhibitAny *decodedValue, + SECItem *encodedValue) +{ + CERTCertificateInhibitAny decodeContext; + PLArenaPool *arena = NULL; + SECStatus rv = SECSuccess; + + /* make a new arena */ + arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if (!arena) { + return SECFailure; + } + + do { + + /* decode the policy mappings */ + decodeContext.inhibitAnySkipCerts.type = siUnsignedInteger; + rv = SEC_QuickDERDecodeItem(arena, &decodeContext, + CERT_InhibitAnyTemplate, encodedValue); + + if (rv != SECSuccess) { + break; + } + + *(PRInt32 *)decodedValue->inhibitAnySkipCerts.data = + DER_GetInteger(&decodeContext.inhibitAnySkipCerts); + + } while (0); + + PORT_FreeArena(arena, PR_FALSE); + return (rv); +} + +CERTUserNotice * +CERT_DecodeUserNotice(SECItem *noticeItem) +{ + PLArenaPool *arena = NULL; + SECStatus rv; + CERTUserNotice *userNotice; + SECItem newNoticeItem; + + /* make a new arena */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + + if (!arena) { + goto loser; + } + + /* allocate the userNotice structure */ + userNotice = + (CERTUserNotice *)PORT_ArenaZAlloc(arena, sizeof(CERTUserNotice)); + + if (userNotice == NULL) { + goto loser; + } + + userNotice->arena = arena; + + /* copy the DER into the arena, since Quick DER returns data that points + into the DER input, which may get freed by the caller */ + rv = SECITEM_CopyItem(arena, &newNoticeItem, noticeItem); + if (rv != SECSuccess) { + goto loser; + } + + /* decode the user notice */ + rv = SEC_QuickDERDecodeItem(arena, userNotice, CERT_UserNoticeTemplate, + &newNoticeItem); + + if (rv != SECSuccess) { + goto loser; + } + + if (userNotice->derNoticeReference.data != NULL) { + + rv = SEC_QuickDERDecodeItem(arena, &userNotice->noticeReference, + CERT_NoticeReferenceTemplate, + &userNotice->derNoticeReference); + if (rv == SECFailure) { + goto loser; + } + } + + return (userNotice); + +loser: + if (arena != NULL) { + PORT_FreeArena(arena, PR_FALSE); + } + + return (NULL); +} + +void +CERT_DestroyUserNotice(CERTUserNotice *userNotice) +{ + if (userNotice != NULL) { + PORT_FreeArena(userNotice->arena, PR_FALSE); + } + return; +} + +static CERTPolicyStringCallback policyStringCB = NULL; +static void *policyStringCBArg = NULL; + +void +CERT_SetCAPolicyStringCallback(CERTPolicyStringCallback cb, void *cbarg) +{ + policyStringCB = cb; + policyStringCBArg = cbarg; + return; +} + +char * +stringFromUserNotice(SECItem *noticeItem) +{ + SECItem *org; + unsigned int len, headerlen; + char *stringbuf; + CERTUserNotice *userNotice; + char *policystr; + char *retstr = NULL; + SECItem *displayText; + SECItem **noticeNumbers; + unsigned int strnum; + + /* decode the user notice */ + userNotice = CERT_DecodeUserNotice(noticeItem); + if (userNotice == NULL) { + return (NULL); + } + + org = &userNotice->noticeReference.organization; + if ((org->len != 0) && (policyStringCB != NULL)) { + /* has a noticeReference */ + + /* extract the org string */ + len = org->len; + stringbuf = (char *)PORT_Alloc(len + 1); + if (stringbuf != NULL) { + PORT_Memcpy(stringbuf, org->data, len); + stringbuf[len] = '\0'; + + noticeNumbers = userNotice->noticeReference.noticeNumbers; + while (*noticeNumbers != NULL) { + /* XXX - only one byte integers right now*/ + strnum = (*noticeNumbers)->data[0]; + policystr = + (*policyStringCB)(stringbuf, strnum, policyStringCBArg); + if (policystr != NULL) { + if (retstr != NULL) { + retstr = PR_sprintf_append(retstr, "\n%s", policystr); + } else { + retstr = PR_sprintf_append(retstr, "%s", policystr); + } + + PORT_Free(policystr); + } + + noticeNumbers++; + } + + PORT_Free(stringbuf); + } + } + + if (retstr == NULL) { + if (userNotice->displayText.len != 0) { + displayText = &userNotice->displayText; + + if (displayText->len > 2) { + if (displayText->data[0] == SEC_ASN1_VISIBLE_STRING) { + headerlen = 2; + if (displayText->data[1] & 0x80) { + /* multibyte length */ + headerlen += (displayText->data[1] & 0x7f); + } + + len = displayText->len - headerlen; + retstr = (char *)PORT_Alloc(len + 1); + if (retstr != NULL) { + PORT_Memcpy(retstr, &displayText->data[headerlen], len); + retstr[len] = '\0'; + } + } + } + } + } + + CERT_DestroyUserNotice(userNotice); + + return (retstr); +} + +char * +CERT_GetCertCommentString(CERTCertificate *cert) +{ + char *retstring = NULL; + SECStatus rv; + SECItem policyItem; + CERTCertificatePolicies *policies = NULL; + CERTPolicyInfo **policyInfos; + CERTPolicyQualifier **policyQualifiers, *qualifier; + + policyItem.data = NULL; + + rv = CERT_FindCertExtension(cert, SEC_OID_X509_CERTIFICATE_POLICIES, + &policyItem); + if (rv != SECSuccess) { + goto nopolicy; + } + + policies = CERT_DecodeCertificatePoliciesExtension(&policyItem); + if (policies == NULL) { + goto nopolicy; + } + + policyInfos = policies->policyInfos; + /* search through policyInfos looking for the verisign policy */ + while (*policyInfos != NULL) { + if ((*policyInfos)->oid == SEC_OID_VERISIGN_USER_NOTICES) { + policyQualifiers = (*policyInfos)->policyQualifiers; + /* search through the policy qualifiers looking for user notice */ + while (policyQualifiers != NULL && *policyQualifiers != NULL) { + qualifier = *policyQualifiers; + if (qualifier->oid == SEC_OID_PKIX_USER_NOTICE_QUALIFIER) { + retstring = + stringFromUserNotice(&qualifier->qualifierValue); + break; + } + + policyQualifiers++; + } + break; + } + policyInfos++; + } + +nopolicy: + if (policyItem.data != NULL) { + PORT_Free(policyItem.data); + } + + if (policies != NULL) { + CERT_DestroyCertificatePoliciesExtension(policies); + } + + if (retstring == NULL) { + retstring = + CERT_FindNSStringExtension(cert, SEC_OID_NS_CERT_EXT_COMMENT); + } + + if (retstring != NULL) { + breakLines(retstring); + } + + return (retstring); +} + +const SEC_ASN1Template CERT_OidSeqTemplate[] = { + { SEC_ASN1_SEQUENCE_OF | SEC_ASN1_XTRN, offsetof(CERTOidSequence, oids), + SEC_ASN1_SUB(SEC_ObjectIDTemplate) } +}; + +CERTOidSequence * +CERT_DecodeOidSequence(const SECItem *seqItem) +{ + PLArenaPool *arena = NULL; + SECStatus rv; + CERTOidSequence *oidSeq; + SECItem newSeqItem; + + /* make a new arena */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + + if (!arena) { + goto loser; + } + + /* allocate the userNotice structure */ + oidSeq = + (CERTOidSequence *)PORT_ArenaZAlloc(arena, sizeof(CERTOidSequence)); + + if (oidSeq == NULL) { + goto loser; + } + + oidSeq->arena = arena; + + /* copy the DER into the arena, since Quick DER returns data that points + into the DER input, which may get freed by the caller */ + rv = SECITEM_CopyItem(arena, &newSeqItem, seqItem); + if (rv != SECSuccess) { + goto loser; + } + + /* decode the user notice */ + rv = + SEC_QuickDERDecodeItem(arena, oidSeq, CERT_OidSeqTemplate, &newSeqItem); + + if (rv != SECSuccess) { + goto loser; + } + + return (oidSeq); + +loser: + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + return (NULL); +} + +void +CERT_DestroyOidSequence(CERTOidSequence *oidSeq) +{ + if (oidSeq != NULL) { + PORT_FreeArena(oidSeq->arena, PR_FALSE); + } + return; +} + +PRBool +CERT_GovtApprovedBitSet(CERTCertificate *cert) +{ + SECStatus rv; + SECItem extItem; + CERTOidSequence *oidSeq = NULL; + PRBool ret; + SECItem **oids; + SECItem *oid; + SECOidTag oidTag; + + extItem.data = NULL; + rv = CERT_FindCertExtension(cert, SEC_OID_X509_EXT_KEY_USAGE, &extItem); + if (rv != SECSuccess) { + goto loser; + } + + oidSeq = CERT_DecodeOidSequence(&extItem); + if (oidSeq == NULL) { + goto loser; + } + + oids = oidSeq->oids; + while (oids != NULL && *oids != NULL) { + oid = *oids; + + oidTag = SECOID_FindOIDTag(oid); + + if (oidTag == SEC_OID_NS_KEY_USAGE_GOVT_APPROVED) { + goto success; + } + + oids++; + } + +loser: + ret = PR_FALSE; + goto done; +success: + ret = PR_TRUE; +done: + if (oidSeq != NULL) { + CERT_DestroyOidSequence(oidSeq); + } + if (extItem.data != NULL) { + PORT_Free(extItem.data); + } + return (ret); +} + +SECStatus +CERT_EncodePolicyConstraintsExtension(PLArenaPool *arena, + CERTCertificatePolicyConstraints *constr, + SECItem *dest) +{ + SECStatus rv = SECSuccess; + + PORT_Assert(constr != NULL && dest != NULL); + if (constr == NULL || dest == NULL) { + return SECFailure; + } + + if (SEC_ASN1EncodeItem(arena, dest, constr, + CERT_PolicyConstraintsTemplate) == NULL) { + rv = SECFailure; + } + return (rv); +} + +SECStatus +CERT_EncodePolicyMappingExtension(PLArenaPool *arena, + CERTCertificatePolicyMappings *mapping, + SECItem *dest) +{ + SECStatus rv = SECSuccess; + + PORT_Assert(mapping != NULL && dest != NULL); + if (mapping == NULL || dest == NULL) { + return SECFailure; + } + + if (SEC_ASN1EncodeItem(arena, dest, mapping, CERT_PolicyMappingsTemplate) == + NULL) { + rv = SECFailure; + } + return (rv); +} + +SECStatus +CERT_EncodeCertPoliciesExtension(PLArenaPool *arena, CERTPolicyInfo **info, + SECItem *dest) +{ + SECStatus rv = SECSuccess; + + PORT_Assert(info != NULL && dest != NULL); + if (info == NULL || dest == NULL) { + return SECFailure; + } + + if (SEC_ASN1EncodeItem(arena, dest, info, + CERT_CertificatePoliciesTemplate) == NULL) { + rv = SECFailure; + } + return (rv); +} + +SECStatus +CERT_EncodeUserNotice(PLArenaPool *arena, CERTUserNotice *notice, SECItem *dest) +{ + SECStatus rv = SECSuccess; + + PORT_Assert(notice != NULL && dest != NULL); + if (notice == NULL || dest == NULL) { + return SECFailure; + } + + if (SEC_ASN1EncodeItem(arena, dest, notice, CERT_UserNoticeTemplate) == + NULL) { + rv = SECFailure; + } + + return (rv); +} + +SECStatus +CERT_EncodeNoticeReference(PLArenaPool *arena, CERTNoticeReference *reference, + SECItem *dest) +{ + SECStatus rv = SECSuccess; + + PORT_Assert(reference != NULL && dest != NULL); + if (reference == NULL || dest == NULL) { + return SECFailure; + } + + if (SEC_ASN1EncodeItem(arena, dest, reference, + CERT_NoticeReferenceTemplate) == NULL) { + rv = SECFailure; + } + + return (rv); +} + +SECStatus +CERT_EncodeInhibitAnyExtension(PLArenaPool *arena, + CERTCertificateInhibitAny *certInhibitAny, + SECItem *dest) +{ + SECStatus rv = SECSuccess; + + PORT_Assert(certInhibitAny != NULL && dest != NULL); + if (certInhibitAny == NULL || dest == NULL) { + return SECFailure; + } + + if (SEC_ASN1EncodeItem(arena, dest, certInhibitAny, + CERT_InhibitAnyTemplate) == NULL) { + rv = SECFailure; + } + return (rv); +} diff --git a/security/nss/lib/certdb/secname.c b/security/nss/lib/certdb/secname.c new file mode 100644 index 0000000000..654dfdf3f0 --- /dev/null +++ b/security/nss/lib/certdb/secname.c @@ -0,0 +1,701 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "cert.h" +#include "secoid.h" +#include "secder.h" /* XXX remove this when remove the DERTemplates */ +#include "secasn1.h" +#include "secitem.h" +#include <stdarg.h> +#include "secerr.h" +#include "certi.h" + +static const SEC_ASN1Template cert_AVATemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTAVA) }, + { SEC_ASN1_OBJECT_ID, offsetof(CERTAVA, type) }, + { SEC_ASN1_ANY, offsetof(CERTAVA, value) }, + { 0 } +}; + +const SEC_ASN1Template CERT_RDNTemplate[] = { + { SEC_ASN1_SET_OF, offsetof(CERTRDN, avas), cert_AVATemplate, + sizeof(CERTRDN) } +}; + +static int +CountArray(void **array) +{ + int count = 0; + if (array) { + while (*array++) { + count++; + } + } + return count; +} + +static void ** +AddToArray(PLArenaPool *arena, void **array, void *element) +{ + unsigned count; + void **ap; + + /* Count up number of slots already in use in the array */ + count = 0; + ap = array; + if (ap) { + while (*ap++) { + count++; + } + } + + if (array) { + array = + (void **)PORT_ArenaGrow(arena, array, (count + 1) * sizeof(void *), + (count + 2) * sizeof(void *)); + } else { + array = (void **)PORT_ArenaAlloc(arena, (count + 2) * sizeof(void *)); + } + if (array) { + array[count] = element; + array[count + 1] = 0; + } + return array; +} + +SECOidTag +CERT_GetAVATag(CERTAVA *ava) +{ + SECOidData *oid; + if (!ava->type.data) + return (SECOidTag)-1; + + oid = SECOID_FindOID(&ava->type); + + if (oid) { + return (oid->offset); + } + return (SECOidTag)-1; +} + +static SECStatus +SetupAVAType(PLArenaPool *arena, SECOidTag type, SECItem *it, unsigned *maxLenp) +{ + unsigned char *oid; + unsigned oidLen; + unsigned char *cp; + int maxLen; + SECOidData *oidrec; + + oidrec = SECOID_FindOIDByTag(type); + if (oidrec == NULL) + return SECFailure; + + oid = oidrec->oid.data; + oidLen = oidrec->oid.len; + + maxLen = cert_AVAOidTagToMaxLen(type); + if (maxLen < 0) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + it->data = cp = (unsigned char *)PORT_ArenaAlloc(arena, oidLen); + if (cp == NULL) { + return SECFailure; + } + it->len = oidLen; + PORT_Memcpy(cp, oid, oidLen); + *maxLenp = (unsigned)maxLen; + return SECSuccess; +} + +static SECStatus +SetupAVAValue(PLArenaPool *arena, int valueType, const SECItem *in, + SECItem *out, unsigned maxLen) +{ + PRUint8 *value, *cp, *ucs4Val; + unsigned valueLen, valueLenLen, total; + unsigned ucs4Len = 0, ucs4MaxLen; + + value = in->data; + valueLen = in->len; + switch (valueType) { + case SEC_ASN1_PRINTABLE_STRING: + case SEC_ASN1_IA5_STRING: + case SEC_ASN1_T61_STRING: + case SEC_ASN1_UTF8_STRING: /* no conversion required */ + break; + case SEC_ASN1_UNIVERSAL_STRING: + ucs4MaxLen = valueLen * 6; + ucs4Val = (PRUint8 *)PORT_ArenaZAlloc(arena, ucs4MaxLen); + if (!ucs4Val || + !PORT_UCS4_UTF8Conversion(PR_TRUE, value, valueLen, ucs4Val, + ucs4MaxLen, &ucs4Len)) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + value = ucs4Val; + valueLen = ucs4Len; + maxLen *= 4; + break; + default: + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (valueLen > maxLen) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + valueLenLen = DER_LengthLength(valueLen); + total = 1 + valueLenLen + valueLen; + cp = (PRUint8 *)PORT_ArenaAlloc(arena, total); + if (!cp) { + return SECFailure; + } + out->data = cp; + out->len = total; + cp = (PRUint8 *)DER_StoreHeader(cp, valueType, valueLen); + PORT_Memcpy(cp, value, valueLen); + return SECSuccess; +} + +CERTAVA * +CERT_CreateAVAFromRaw(PLArenaPool *pool, const SECItem *OID, + const SECItem *value) +{ + CERTAVA *ava; + int rv; + + ava = PORT_ArenaZNew(pool, CERTAVA); + if (ava) { + rv = SECITEM_CopyItem(pool, &ava->type, OID); + if (rv) + return NULL; + + rv = SECITEM_CopyItem(pool, &ava->value, value); + if (rv) + return NULL; + } + return ava; +} + +CERTAVA * +CERT_CreateAVAFromSECItem(PLArenaPool *arena, SECOidTag kind, int valueType, + SECItem *value) +{ + CERTAVA *ava; + int rv; + unsigned maxLen; + + ava = (CERTAVA *)PORT_ArenaZAlloc(arena, sizeof(CERTAVA)); + if (ava) { + rv = SetupAVAType(arena, kind, &ava->type, &maxLen); + if (rv) { + /* Illegal AVA type */ + return NULL; + } + rv = SetupAVAValue(arena, valueType, value, &ava->value, maxLen); + if (rv) { + /* Illegal value type */ + return NULL; + } + } + return ava; +} + +CERTAVA * +CERT_CreateAVA(PLArenaPool *arena, SECOidTag kind, int valueType, char *value) +{ + SECItem item = { siBuffer, NULL, 0 }; + + item.data = (PRUint8 *)value; + item.len = PORT_Strlen(value); + + return CERT_CreateAVAFromSECItem(arena, kind, valueType, &item); +} + +CERTAVA * +CERT_CopyAVA(PLArenaPool *arena, CERTAVA *from) +{ + CERTAVA *ava; + int rv; + + ava = (CERTAVA *)PORT_ArenaZAlloc(arena, sizeof(CERTAVA)); + if (ava) { + rv = SECITEM_CopyItem(arena, &ava->type, &from->type); + if (rv) + goto loser; + rv = SECITEM_CopyItem(arena, &ava->value, &from->value); + if (rv) + goto loser; + } + return ava; + +loser: + return 0; +} + +CERTRDN * +CERT_CreateRDN(PLArenaPool *arena, CERTAVA *ava0, ...) +{ + CERTAVA *ava; + CERTRDN *rdn; + va_list ap; + unsigned count; + CERTAVA **avap; + + rdn = (CERTRDN *)PORT_ArenaAlloc(arena, sizeof(CERTRDN)); + if (rdn) { + /* Count number of avas going into the rdn */ + count = 0; + if (ava0) { + count++; + va_start(ap, ava0); + while ((ava = va_arg(ap, CERTAVA *)) != 0) { + count++; + } + va_end(ap); + } + + /* Now fill in the pointers */ + rdn->avas = avap = + (CERTAVA **)PORT_ArenaAlloc(arena, (count + 1) * sizeof(CERTAVA *)); + if (!avap) { + return 0; + } + if (ava0) { + *avap++ = ava0; + va_start(ap, ava0); + while ((ava = va_arg(ap, CERTAVA *)) != 0) { + *avap++ = ava; + } + va_end(ap); + } + *avap++ = 0; + } + return rdn; +} + +SECStatus +CERT_AddAVA(PLArenaPool *arena, CERTRDN *rdn, CERTAVA *ava) +{ + rdn->avas = (CERTAVA **)AddToArray(arena, (void **)rdn->avas, ava); + return rdn->avas ? SECSuccess : SECFailure; +} + +SECStatus +CERT_CopyRDN(PLArenaPool *arena, CERTRDN *to, CERTRDN *from) +{ + CERTAVA **avas, *fava, *tava; + SECStatus rv = SECSuccess; + + /* Copy each ava from from */ + avas = from->avas; + if (avas) { + if (avas[0] == NULL) { + rv = CERT_AddAVA(arena, to, NULL); + return rv; + } + while ((fava = *avas++) != 0) { + tava = CERT_CopyAVA(arena, fava); + if (!tava) { + rv = SECFailure; + break; + } + rv = CERT_AddAVA(arena, to, tava); + if (rv != SECSuccess) + break; + } + } + return rv; +} + +/************************************************************************/ + +const SEC_ASN1Template CERT_NameTemplate[] = { + { SEC_ASN1_SEQUENCE_OF, offsetof(CERTName, rdns), CERT_RDNTemplate, + sizeof(CERTName) } +}; + +SEC_ASN1_CHOOSER_IMPLEMENT(CERT_NameTemplate) + +CERTName * +CERT_CreateName(CERTRDN *rdn0, ...) +{ + CERTRDN *rdn; + CERTName *name; + va_list ap; + unsigned count; + CERTRDN **rdnp; + PLArenaPool *arena; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) { + return (0); + } + + name = (CERTName *)PORT_ArenaAlloc(arena, sizeof(CERTName)); + if (name) { + name->arena = arena; + + /* Count number of RDNs going into the Name */ + if (!rdn0) { + count = 0; + } else { + count = 1; + va_start(ap, rdn0); + while ((rdn = va_arg(ap, CERTRDN *)) != 0) { + count++; + } + va_end(ap); + } + + /* Allocate space (including space for terminal null ptr) */ + name->rdns = rdnp = + (CERTRDN **)PORT_ArenaAlloc(arena, (count + 1) * sizeof(CERTRDN *)); + if (!name->rdns) { + goto loser; + } + + /* Now fill in the pointers */ + if (count > 0) { + *rdnp++ = rdn0; + va_start(ap, rdn0); + while ((rdn = va_arg(ap, CERTRDN *)) != 0) { + *rdnp++ = rdn; + } + va_end(ap); + } + + /* null terminate the list */ + *rdnp++ = 0; + } + return name; + +loser: + PORT_FreeArena(arena, PR_FALSE); + return (0); +} + +void +CERT_DestroyName(CERTName *name) +{ + if (name) { + PLArenaPool *arena = name->arena; + name->rdns = NULL; + name->arena = NULL; + if (arena) + PORT_FreeArena(arena, PR_FALSE); + } +} + +SECStatus +CERT_AddRDN(CERTName *name, CERTRDN *rdn) +{ + name->rdns = (CERTRDN **)AddToArray(name->arena, (void **)name->rdns, rdn); + return name->rdns ? SECSuccess : SECFailure; +} + +SECStatus +CERT_CopyName(PLArenaPool *arena, CERTName *to, const CERTName *from) +{ + CERTRDN **rdns, *frdn, *trdn; + SECStatus rv = SECSuccess; + + if (!to || !from) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + CERT_DestroyName(to); + to->arena = arena; + + /* Copy each rdn from from */ + rdns = from->rdns; + if (rdns) { + if (rdns[0] == NULL) { + rv = CERT_AddRDN(to, NULL); + return rv; + } + while ((frdn = *rdns++) != NULL) { + trdn = CERT_CreateRDN(arena, NULL); + if (!trdn) { + rv = SECFailure; + break; + } + rv = CERT_CopyRDN(arena, trdn, frdn); + if (rv != SECSuccess) + break; + rv = CERT_AddRDN(to, trdn); + if (rv != SECSuccess) + break; + } + } + return rv; +} + +/************************************************************************/ + +static void +canonicalize(SECItem *foo) +{ + int ch, lastch, len, src, dest; + + /* strip trailing whitespace. */ + len = foo->len; + while (len > 0 && ((ch = foo->data[len - 1]) == ' ' || ch == '\t' || + ch == '\r' || ch == '\n')) { + len--; + } + + src = 0; + /* strip leading whitespace. */ + while (src < len && ((ch = foo->data[src]) == ' ' || ch == '\t' || + ch == '\r' || ch == '\n')) { + src++; + } + dest = 0; + lastch = ' '; + while (src < len) { + ch = foo->data[src++]; + if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') { + ch = ' '; + if (ch == lastch) + continue; + } else if (ch >= 'A' && ch <= 'Z') { + ch |= 0x20; /* downshift */ + } + foo->data[dest++] = lastch = ch; + } + foo->len = dest; +} + +/* SECItems a and b contain DER-encoded printable strings. */ +SECComparison +CERT_CompareDERPrintableStrings(const SECItem *a, const SECItem *b) +{ + SECComparison rv = SECLessThan; + SECItem *aVal = CERT_DecodeAVAValue(a); + SECItem *bVal = CERT_DecodeAVAValue(b); + + if (aVal && aVal->len && aVal->data && bVal && bVal->len && bVal->data) { + canonicalize(aVal); + canonicalize(bVal); + rv = SECITEM_CompareItem(aVal, bVal); + } + SECITEM_FreeItem(aVal, PR_TRUE); + SECITEM_FreeItem(bVal, PR_TRUE); + return rv; +} + +SECComparison +CERT_CompareAVA(const CERTAVA *a, const CERTAVA *b) +{ + SECComparison rv; + + rv = SECITEM_CompareItem(&a->type, &b->type); + if (SECEqual != rv) + return rv; /* Attribute types don't match. */ + /* Let's be optimistic. Maybe the values will just compare equal. */ + rv = SECITEM_CompareItem(&a->value, &b->value); + if (SECEqual == rv) + return rv; /* values compared exactly. */ + if (a->value.len && a->value.data && b->value.len && b->value.data) { + /* Here, the values did not match. + ** If the values had different encodings, convert them to the same + ** encoding and compare that way. + */ + if (a->value.data[0] != b->value.data[0]) { + /* encodings differ. Convert both to UTF-8 and compare. */ + SECItem *aVal = CERT_DecodeAVAValue(&a->value); + SECItem *bVal = CERT_DecodeAVAValue(&b->value); + if (aVal && aVal->len && aVal->data && bVal && bVal->len && + bVal->data) { + rv = SECITEM_CompareItem(aVal, bVal); + } + SECITEM_FreeItem(aVal, PR_TRUE); + SECITEM_FreeItem(bVal, PR_TRUE); + } else if (a->value.data[0] == 0x13) { /* both are printable strings. */ + /* printable strings */ + rv = CERT_CompareDERPrintableStrings(&a->value, &b->value); + } + } + return rv; +} + +SECComparison +CERT_CompareRDN(const CERTRDN *a, const CERTRDN *b) +{ + CERTAVA **aavas, *aava; + CERTAVA **bavas, *bava; + int ac, bc; + SECComparison rv = SECEqual; + + aavas = a->avas; + bavas = b->avas; + + /* + ** Make sure array of ava's are the same length. If not, then we are + ** not equal + */ + ac = CountArray((void **)aavas); + bc = CountArray((void **)bavas); + if (ac < bc) + return SECLessThan; + if (ac > bc) + return SECGreaterThan; + + while (NULL != (aava = *aavas++)) { + for (bavas = b->avas; NULL != (bava = *bavas++);) { + rv = SECITEM_CompareItem(&aava->type, &bava->type); + if (SECEqual == rv) { + rv = CERT_CompareAVA(aava, bava); + if (SECEqual != rv) + return rv; + break; + } + } + if (!bava) /* didn't find a match */ + return SECGreaterThan; + } + return rv; +} + +SECComparison +CERT_CompareName(const CERTName *a, const CERTName *b) +{ + CERTRDN **ardns; + CERTRDN **brdns; + int ac, bc; + SECComparison rv = SECEqual; + + ardns = a->rdns; + brdns = b->rdns; + + /* + ** Make sure array of rdn's are the same length. If not, then we are + ** not equal + */ + ac = CountArray((void **)ardns); + bc = CountArray((void **)brdns); + if (ac < bc) + return SECLessThan; + if (ac > bc) + return SECGreaterThan; + + while (rv == SECEqual && *ardns) { + rv = CERT_CompareRDN(*ardns++, *brdns++); + } + return rv; +} + +/* Moved from certhtml.c */ +SECItem * +CERT_DecodeAVAValue(const SECItem *derAVAValue) +{ + SECItem *retItem; + const SEC_ASN1Template *theTemplate = NULL; + enum { conv_none, + conv_ucs4, + conv_ucs2, + conv_iso88591 } convert = conv_none; + SECItem avaValue = { siBuffer, 0 }; + PORTCheapArenaPool tmpArena; + + if (!derAVAValue || !derAVAValue->len || !derAVAValue->data) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + switch (derAVAValue->data[0]) { + case SEC_ASN1_UNIVERSAL_STRING: + convert = conv_ucs4; + theTemplate = SEC_ASN1_GET(SEC_UniversalStringTemplate); + break; + case SEC_ASN1_IA5_STRING: + theTemplate = SEC_ASN1_GET(SEC_IA5StringTemplate); + break; + case SEC_ASN1_PRINTABLE_STRING: + theTemplate = SEC_ASN1_GET(SEC_PrintableStringTemplate); + break; + case SEC_ASN1_T61_STRING: + /* + * Per common practice, we're not decoding actual T.61, but instead + * treating T61-labeled strings as containing ISO-8859-1. + */ + convert = conv_iso88591; + theTemplate = SEC_ASN1_GET(SEC_T61StringTemplate); + break; + case SEC_ASN1_BMP_STRING: + convert = conv_ucs2; + theTemplate = SEC_ASN1_GET(SEC_BMPStringTemplate); + break; + case SEC_ASN1_UTF8_STRING: + /* No conversion needed ! */ + theTemplate = SEC_ASN1_GET(SEC_UTF8StringTemplate); + break; + default: + PORT_SetError(SEC_ERROR_INVALID_AVA); + return NULL; + } + + PORT_Memset(&avaValue, 0, sizeof(SECItem)); + PORT_InitCheapArena(&tmpArena, DER_DEFAULT_CHUNKSIZE); + if (SEC_QuickDERDecodeItem(&tmpArena.arena, &avaValue, theTemplate, + derAVAValue) != SECSuccess) { + PORT_DestroyCheapArena(&tmpArena); + return NULL; + } + + if (convert != conv_none) { + unsigned int utf8ValLen = avaValue.len * 3; + unsigned char *utf8Val = + (unsigned char *)PORT_ArenaZAlloc(&tmpArena.arena, utf8ValLen); + + switch (convert) { + case conv_ucs4: + if (avaValue.len % 4 != 0 || + !PORT_UCS4_UTF8Conversion(PR_FALSE, avaValue.data, + avaValue.len, utf8Val, utf8ValLen, + &utf8ValLen)) { + PORT_DestroyCheapArena(&tmpArena); + PORT_SetError(SEC_ERROR_INVALID_AVA); + return NULL; + } + break; + case conv_ucs2: + if (avaValue.len % 2 != 0 || + !PORT_UCS2_UTF8Conversion(PR_FALSE, avaValue.data, + avaValue.len, utf8Val, utf8ValLen, + &utf8ValLen)) { + PORT_DestroyCheapArena(&tmpArena); + PORT_SetError(SEC_ERROR_INVALID_AVA); + return NULL; + } + break; + case conv_iso88591: + if (!PORT_ISO88591_UTF8Conversion(avaValue.data, avaValue.len, + utf8Val, utf8ValLen, + &utf8ValLen)) { + PORT_DestroyCheapArena(&tmpArena); + PORT_SetError(SEC_ERROR_INVALID_AVA); + return NULL; + } + break; + case conv_none: + PORT_Assert(0); /* not reached */ + break; + } + + avaValue.data = utf8Val; + avaValue.len = utf8ValLen; + } + + retItem = SECITEM_DupItem(&avaValue); + PORT_DestroyCheapArena(&tmpArena); + return retItem; +} diff --git a/security/nss/lib/certdb/stanpcertdb.c b/security/nss/lib/certdb/stanpcertdb.c new file mode 100644 index 0000000000..e255df1053 --- /dev/null +++ b/security/nss/lib/certdb/stanpcertdb.c @@ -0,0 +1,1137 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "prtime.h" + +#include "cert.h" +#include "certi.h" +#include "certdb.h" +#include "secitem.h" +#include "secder.h" + +/* Call to PK11_FreeSlot below */ + +#include "secasn1.h" +#include "secerr.h" +#include "nssilock.h" +#include "prmon.h" +#include "base64.h" +#include "sechash.h" +#include "plhash.h" +#include "pk11func.h" /* sigh */ + +#include "nsspki.h" +#include "pki.h" +#include "pkim.h" +#include "pki3hack.h" +#include "ckhelper.h" +#include "base.h" +#include "pkistore.h" +#include "dev3hack.h" +#include "dev.h" +#include "secmodi.h" + +extern void CERT_MaybeLockCertTempPerm(const CERTCertificate *cert); +extern void CERT_MaybeUnlockCertTempPerm(const CERTCertificate *cert); + +PRBool +SEC_CertNicknameConflict(const char *nickname, const SECItem *derSubject, + CERTCertDBHandle *handle) +{ + CERTCertificate *cert; + PRBool conflict = PR_FALSE; + + cert = CERT_FindCertByNickname(handle, nickname); + + if (!cert) { + return conflict; + } + + conflict = !SECITEM_ItemsAreEqual(derSubject, &cert->derSubject); + CERT_DestroyCertificate(cert); + return conflict; +} + +SECStatus +SEC_DeletePermCertificate(CERTCertificate *cert) +{ + PRStatus nssrv; + NSSTrustDomain *td = STAN_GetDefaultTrustDomain(); + NSSCertificate *c = STAN_GetNSSCertificate(cert); + CERTCertTrust *certTrust; + + if (c == NULL) { + /* error code is set */ + return SECFailure; + } + + certTrust = nssTrust_GetCERTCertTrustForCert(c, cert); + if (certTrust) { + NSSTrust *nssTrust = nssTrustDomain_FindTrustForCertificate(td, c); + if (nssTrust) { + nssrv = STAN_DeleteCertTrustMatchingSlot(c); + if (nssrv != PR_SUCCESS) { + CERT_MapStanError(); + } + /* This call always returns PR_SUCCESS! */ + (void)nssTrust_Destroy(nssTrust); + } + } + + /* get rid of the token instances */ + nssrv = NSSCertificate_DeleteStoredObject(c, NULL); + + /* get rid of the cache entry */ + nssTrustDomain_LockCertCache(td); + nssTrustDomain_RemoveCertFromCacheLOCKED(td, c); + nssTrustDomain_UnlockCertCache(td); + + return (nssrv == PR_SUCCESS) ? SECSuccess : SECFailure; +} + +SECStatus +CERT_GetCertTrust(const CERTCertificate *cert, CERTCertTrust *trust) +{ + SECStatus rv; + CERT_LockCertTrust(cert); + if (!cert || cert->trust == NULL) { + rv = SECFailure; + } else { + *trust = *cert->trust; + rv = SECSuccess; + } + CERT_UnlockCertTrust(cert); + return (rv); +} + +extern const NSSError NSS_ERROR_NO_ERROR; +extern const NSSError NSS_ERROR_INTERNAL_ERROR; +extern const NSSError NSS_ERROR_NO_MEMORY; +extern const NSSError NSS_ERROR_INVALID_POINTER; +extern const NSSError NSS_ERROR_INVALID_ARENA; +extern const NSSError NSS_ERROR_INVALID_ARENA_MARK; +extern const NSSError NSS_ERROR_DUPLICATE_POINTER; +extern const NSSError NSS_ERROR_POINTER_NOT_REGISTERED; +extern const NSSError NSS_ERROR_TRACKER_NOT_EMPTY; +extern const NSSError NSS_ERROR_TRACKER_NOT_INITIALIZED; +extern const NSSError NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD; +extern const NSSError NSS_ERROR_VALUE_TOO_LARGE; +extern const NSSError NSS_ERROR_UNSUPPORTED_TYPE; +extern const NSSError NSS_ERROR_BUFFER_TOO_SHORT; +extern const NSSError NSS_ERROR_INVALID_ATOB_CONTEXT; +extern const NSSError NSS_ERROR_INVALID_BASE64; +extern const NSSError NSS_ERROR_INVALID_BTOA_CONTEXT; +extern const NSSError NSS_ERROR_INVALID_ITEM; +extern const NSSError NSS_ERROR_INVALID_STRING; +extern const NSSError NSS_ERROR_INVALID_ASN1ENCODER; +extern const NSSError NSS_ERROR_INVALID_ASN1DECODER; +extern const NSSError NSS_ERROR_INVALID_BER; +extern const NSSError NSS_ERROR_INVALID_ATAV; +extern const NSSError NSS_ERROR_INVALID_ARGUMENT; +extern const NSSError NSS_ERROR_INVALID_UTF8; +extern const NSSError NSS_ERROR_INVALID_NSSOID; +extern const NSSError NSS_ERROR_UNKNOWN_ATTRIBUTE; +extern const NSSError NSS_ERROR_NOT_FOUND; +extern const NSSError NSS_ERROR_INVALID_PASSWORD; +extern const NSSError NSS_ERROR_USER_CANCELED; +extern const NSSError NSS_ERROR_MAXIMUM_FOUND; +extern const NSSError NSS_ERROR_CERTIFICATE_ISSUER_NOT_FOUND; +extern const NSSError NSS_ERROR_CERTIFICATE_IN_CACHE; +extern const NSSError NSS_ERROR_HASH_COLLISION; +extern const NSSError NSS_ERROR_DEVICE_ERROR; +extern const NSSError NSS_ERROR_INVALID_CERTIFICATE; +extern const NSSError NSS_ERROR_BUSY; +extern const NSSError NSS_ERROR_ALREADY_INITIALIZED; +extern const NSSError NSS_ERROR_PKCS11; + +/* Look at the stan error stack and map it to NSS 3 errors */ +#define STAN_MAP_ERROR(x, y) \ + else if (error == (x)) { secError = y; } + +/* + * map Stan errors into NSS errors + * This function examines the stan error stack and automatically sets + * PORT_SetError(); to the appropriate SEC_ERROR value. + */ +void +CERT_MapStanError() +{ + PRInt32 *errorStack; + NSSError error, prevError; + int secError; + int i; + + errorStack = NSS_GetErrorStack(); + if (errorStack == 0) { + PORT_SetError(0); + return; + } + error = prevError = CKR_GENERAL_ERROR; + /* get the 'top 2' error codes from the stack */ + for (i = 0; errorStack[i]; i++) { + prevError = error; + error = errorStack[i]; + } + if (error == NSS_ERROR_PKCS11) { + /* map it */ + secError = PK11_MapError(prevError); + } + STAN_MAP_ERROR(NSS_ERROR_NO_ERROR, 0) + STAN_MAP_ERROR(NSS_ERROR_NO_MEMORY, SEC_ERROR_NO_MEMORY) + STAN_MAP_ERROR(NSS_ERROR_INVALID_BASE64, SEC_ERROR_BAD_DATA) + STAN_MAP_ERROR(NSS_ERROR_INVALID_BER, SEC_ERROR_BAD_DER) + STAN_MAP_ERROR(NSS_ERROR_INVALID_ATAV, SEC_ERROR_INVALID_AVA) + STAN_MAP_ERROR(NSS_ERROR_INVALID_PASSWORD, SEC_ERROR_BAD_PASSWORD) + STAN_MAP_ERROR(NSS_ERROR_BUSY, SEC_ERROR_BUSY) + STAN_MAP_ERROR(NSS_ERROR_DEVICE_ERROR, SEC_ERROR_IO) + STAN_MAP_ERROR(NSS_ERROR_CERTIFICATE_ISSUER_NOT_FOUND, + SEC_ERROR_UNKNOWN_ISSUER) + STAN_MAP_ERROR(NSS_ERROR_INVALID_CERTIFICATE, SEC_ERROR_CERT_NOT_VALID) + STAN_MAP_ERROR(NSS_ERROR_INVALID_UTF8, SEC_ERROR_BAD_DATA) + STAN_MAP_ERROR(NSS_ERROR_INVALID_NSSOID, SEC_ERROR_BAD_DATA) + + /* these are library failure for lack of a better error code */ + STAN_MAP_ERROR(NSS_ERROR_NOT_FOUND, SEC_ERROR_LIBRARY_FAILURE) + STAN_MAP_ERROR(NSS_ERROR_CERTIFICATE_IN_CACHE, SEC_ERROR_LIBRARY_FAILURE) + STAN_MAP_ERROR(NSS_ERROR_MAXIMUM_FOUND, SEC_ERROR_LIBRARY_FAILURE) + STAN_MAP_ERROR(NSS_ERROR_USER_CANCELED, SEC_ERROR_LIBRARY_FAILURE) + STAN_MAP_ERROR(NSS_ERROR_TRACKER_NOT_INITIALIZED, SEC_ERROR_LIBRARY_FAILURE) + STAN_MAP_ERROR(NSS_ERROR_ALREADY_INITIALIZED, SEC_ERROR_LIBRARY_FAILURE) + STAN_MAP_ERROR(NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD, + SEC_ERROR_LIBRARY_FAILURE) + STAN_MAP_ERROR(NSS_ERROR_HASH_COLLISION, SEC_ERROR_LIBRARY_FAILURE) + + STAN_MAP_ERROR(NSS_ERROR_INTERNAL_ERROR, SEC_ERROR_LIBRARY_FAILURE) + + /* these are all invalid arguments */ + STAN_MAP_ERROR(NSS_ERROR_INVALID_ARGUMENT, SEC_ERROR_INVALID_ARGS) + STAN_MAP_ERROR(NSS_ERROR_INVALID_POINTER, SEC_ERROR_INVALID_ARGS) + STAN_MAP_ERROR(NSS_ERROR_INVALID_ARENA, SEC_ERROR_INVALID_ARGS) + STAN_MAP_ERROR(NSS_ERROR_INVALID_ARENA_MARK, SEC_ERROR_INVALID_ARGS) + STAN_MAP_ERROR(NSS_ERROR_DUPLICATE_POINTER, SEC_ERROR_INVALID_ARGS) + STAN_MAP_ERROR(NSS_ERROR_POINTER_NOT_REGISTERED, SEC_ERROR_INVALID_ARGS) + STAN_MAP_ERROR(NSS_ERROR_TRACKER_NOT_EMPTY, SEC_ERROR_INVALID_ARGS) + STAN_MAP_ERROR(NSS_ERROR_VALUE_TOO_LARGE, SEC_ERROR_INVALID_ARGS) + STAN_MAP_ERROR(NSS_ERROR_UNSUPPORTED_TYPE, SEC_ERROR_INVALID_ARGS) + STAN_MAP_ERROR(NSS_ERROR_BUFFER_TOO_SHORT, SEC_ERROR_INVALID_ARGS) + STAN_MAP_ERROR(NSS_ERROR_INVALID_ATOB_CONTEXT, SEC_ERROR_INVALID_ARGS) + STAN_MAP_ERROR(NSS_ERROR_INVALID_BTOA_CONTEXT, SEC_ERROR_INVALID_ARGS) + STAN_MAP_ERROR(NSS_ERROR_INVALID_ITEM, SEC_ERROR_INVALID_ARGS) + STAN_MAP_ERROR(NSS_ERROR_INVALID_STRING, SEC_ERROR_INVALID_ARGS) + STAN_MAP_ERROR(NSS_ERROR_INVALID_ASN1ENCODER, SEC_ERROR_INVALID_ARGS) + STAN_MAP_ERROR(NSS_ERROR_INVALID_ASN1DECODER, SEC_ERROR_INVALID_ARGS) + STAN_MAP_ERROR(NSS_ERROR_UNKNOWN_ATTRIBUTE, SEC_ERROR_INVALID_ARGS) + else { secError = SEC_ERROR_LIBRARY_FAILURE; } + PORT_SetError(secError); +} + +SECStatus +CERT_ChangeCertTrust(CERTCertDBHandle *handle, CERTCertificate *cert, + CERTCertTrust *trust) +{ + SECStatus rv = SECSuccess; + PRStatus ret; + + ret = STAN_ChangeCertTrust(cert, trust); + if (ret != PR_SUCCESS) { + rv = SECFailure; + CERT_MapStanError(); + } + return rv; +} + +extern const NSSError NSS_ERROR_INVALID_CERTIFICATE; + +SECStatus +__CERT_AddTempCertToPerm(CERTCertificate *cert, char *nickname, + CERTCertTrust *trust) +{ + NSSUTF8 *stanNick; + PK11SlotInfo *slot; + NSSToken *internal; + NSSCryptoContext *context; + nssCryptokiObject *permInstance; + NSSCertificate *c = STAN_GetNSSCertificate(cert); + nssCertificateStoreTrace lockTrace = { NULL, NULL, PR_FALSE, PR_FALSE }; + nssCertificateStoreTrace unlockTrace = { NULL, NULL, PR_FALSE, PR_FALSE }; + SECStatus rv; + PRStatus ret; + + if (c == NULL) { + CERT_MapStanError(); + return SECFailure; + } + + context = c->object.cryptoContext; + if (!context) { + PORT_SetError(SEC_ERROR_ADDING_CERT); + return SECFailure; /* wasn't a temp cert */ + } + stanNick = nssCertificate_GetNickname(c, NULL); + if (stanNick && nickname && strcmp(nickname, stanNick) != 0) { + /* different: take the new nickname */ + cert->nickname = NULL; + nss_ZFreeIf(stanNick); + stanNick = NULL; + } + if (!stanNick && nickname) { + /* Either there was no nickname yet, or we have a new nickname */ + stanNick = nssUTF8_Duplicate((NSSUTF8 *)nickname, NULL); + } /* else: old stanNick is identical to new nickname */ + /* Delete the temp instance */ + nssCertificateStore_Lock(context->certStore, &lockTrace); + nssCertificateStore_RemoveCertLOCKED(context->certStore, c); + nssCertificateStore_Unlock(context->certStore, &lockTrace, &unlockTrace); + c->object.cryptoContext = NULL; + + /* if the id has not been set explicitly yet, create one from the public + * key. */ + if (c->id.data == NULL) { + SECItem *keyID = pk11_mkcertKeyID(cert); + if (keyID) { + nssItem_Create(c->object.arena, &c->id, keyID->len, keyID->data); + SECITEM_FreeItem(keyID, PR_TRUE); + } + /* if any of these failed, continue with our null c->id */ + } + + /* Import the perm instance onto the internal token */ + slot = PK11_GetInternalKeySlot(); + internal = PK11Slot_GetNSSToken(slot); + if (!internal) { + PK11_FreeSlot(slot); + PORT_SetError(SEC_ERROR_NO_TOKEN); + return SECFailure; + } + permInstance = nssToken_ImportCertificate( + internal, NULL, NSSCertificateType_PKIX, &c->id, stanNick, &c->encoding, + &c->issuer, &c->subject, &c->serial, cert->emailAddr, PR_TRUE); + (void)nssToken_Destroy(internal); + nss_ZFreeIf(stanNick); + stanNick = NULL; + PK11_FreeSlot(slot); + if (!permInstance) { + if (NSS_GetError() == NSS_ERROR_INVALID_CERTIFICATE) { + PORT_SetError(SEC_ERROR_REUSED_ISSUER_AND_SERIAL); + } + return SECFailure; + } + nssPKIObject_AddInstance(&c->object, permInstance); + nssTrustDomain_AddCertsToCache(STAN_GetDefaultTrustDomain(), &c, 1); + /* reset the CERTCertificate fields */ + CERT_LockCertTempPerm(cert); + cert->nssCertificate = NULL; + CERT_UnlockCertTempPerm(cert); + cert = STAN_GetCERTCertificateOrRelease(c); /* should return same pointer */ + if (!cert) { + CERT_MapStanError(); + return SECFailure; + } + CERT_LockCertTempPerm(cert); + cert->istemp = PR_FALSE; + cert->isperm = PR_TRUE; + CERT_UnlockCertTempPerm(cert); + if (!trust) { + return SECSuccess; + } + ret = STAN_ChangeCertTrust(cert, trust); + rv = SECSuccess; + if (ret != PR_SUCCESS) { + rv = SECFailure; + CERT_MapStanError(); + } + return rv; +} + +SECStatus +CERT_AddTempCertToPerm(CERTCertificate *cert, char *nickname, + CERTCertTrust *trust) +{ + return __CERT_AddTempCertToPerm(cert, nickname, trust); +} + +CERTCertificate * +CERT_NewTempCertificate(CERTCertDBHandle *handle, SECItem *derCert, + char *nickname, PRBool isperm, PRBool copyDER) +{ + NSSCertificate *c; + CERTCertificate *cc; + NSSCertificate *tempCert = NULL; + nssPKIObject *pkio; + NSSCryptoContext *gCC = STAN_GetDefaultCryptoContext(); + NSSTrustDomain *gTD = STAN_GetDefaultTrustDomain(); + if (!isperm) { + NSSDER encoding; + NSSITEM_FROM_SECITEM(&encoding, derCert); + /* First, see if it is already a temp cert */ + c = NSSCryptoContext_FindCertificateByEncodedCertificate(gCC, + &encoding); + if (!c && handle) { + /* Then, see if it is already a perm cert */ + c = NSSTrustDomain_FindCertificateByEncodedCertificate(handle, + &encoding); + } + if (c) { + /* actually, that search ends up going by issuer/serial, + * so it is still possible to return a cert with the same + * issuer/serial but a different encoding, and we're + * going to reject that + */ + if (!nssItem_Equal(&c->encoding, &encoding, NULL)) { + nssCertificate_Destroy(c); + PORT_SetError(SEC_ERROR_REUSED_ISSUER_AND_SERIAL); + cc = NULL; + } else { + cc = STAN_GetCERTCertificateOrRelease(c); + if (cc == NULL) { + CERT_MapStanError(); + } + } + return cc; + } + } + pkio = nssPKIObject_Create(NULL, NULL, gTD, gCC, nssPKIMonitor); + if (!pkio) { + CERT_MapStanError(); + return NULL; + } + c = nss_ZNEW(pkio->arena, NSSCertificate); + if (!c) { + CERT_MapStanError(); + nssPKIObject_Destroy(pkio); + return NULL; + } + c->object = *pkio; + if (copyDER) { + nssItem_Create(c->object.arena, &c->encoding, derCert->len, + derCert->data); + } else { + NSSITEM_FROM_SECITEM(&c->encoding, derCert); + } + /* Forces a decoding of the cert in order to obtain the parts used + * below + */ + /* 'c' is not adopted here, if we fail loser frees what has been + * allocated so far for 'c' */ + cc = STAN_GetCERTCertificate(c); + if (!cc) { + CERT_MapStanError(); + goto loser; + } + nssItem_Create(c->object.arena, &c->issuer, cc->derIssuer.len, + cc->derIssuer.data); + nssItem_Create(c->object.arena, &c->subject, cc->derSubject.len, + cc->derSubject.data); + /* CERTCertificate stores serial numbers decoded. I need the DER + * here. sigh. + */ + SECItem derSerial = { 0 }; + CERT_SerialNumberFromDERCert(&cc->derCert, &derSerial); + if (!derSerial.data) + goto loser; + nssItem_Create(c->object.arena, &c->serial, derSerial.len, + derSerial.data); + PORT_Free(derSerial.data); + + if (nickname) { + c->object.tempName = + nssUTF8_Create(c->object.arena, nssStringType_UTF8String, + (NSSUTF8 *)nickname, PORT_Strlen(nickname)); + } + if (cc->emailAddr && cc->emailAddr[0]) { + c->email = nssUTF8_Create( + c->object.arena, nssStringType_PrintableString, + (NSSUTF8 *)cc->emailAddr, PORT_Strlen(cc->emailAddr)); + } + + tempCert = NSSCryptoContext_FindOrImportCertificate(gCC, c); + if (!tempCert) { + CERT_MapStanError(); + goto loser; + } + /* destroy our copy */ + NSSCertificate_Destroy(c); + /* and use the stored entry */ + c = tempCert; + cc = STAN_GetCERTCertificateOrRelease(c); + if (!cc) { + /* STAN_GetCERTCertificateOrRelease destroys c on failure. */ + CERT_MapStanError(); + return NULL; + } + + CERT_LockCertTempPerm(cc); + cc->istemp = PR_TRUE; + cc->isperm = PR_FALSE; + CERT_UnlockCertTempPerm(cc); + return cc; +loser: + /* Perhaps this should be nssCertificate_Destroy(c) */ + nssPKIObject_Destroy(&c->object); + return NULL; +} + +/* This symbol is exported for backward compatibility. */ +CERTCertificate * +__CERT_NewTempCertificate(CERTCertDBHandle *handle, SECItem *derCert, + char *nickname, PRBool isperm, PRBool copyDER) +{ + return CERT_NewTempCertificate(handle, derCert, nickname, isperm, copyDER); +} + +static CERTCertificate * +common_FindCertByIssuerAndSN(CERTCertDBHandle *handle, + CERTIssuerAndSN *issuerAndSN, + void *wincx) +{ + PK11SlotInfo *slot; + CERTCertificate *cert; + + cert = PK11_FindCertByIssuerAndSN(&slot, issuerAndSN, wincx); + if (cert && slot) { + PK11_FreeSlot(slot); + } + + return cert; +} + +/* maybe all the wincx's should be some const for internal token login? */ +CERTCertificate * +CERT_FindCertByIssuerAndSN(CERTCertDBHandle *handle, + CERTIssuerAndSN *issuerAndSN) +{ + return common_FindCertByIssuerAndSN(handle, issuerAndSN, NULL); +} + +/* maybe all the wincx's should be some const for internal token login? */ +CERTCertificate * +CERT_FindCertByIssuerAndSNCX(CERTCertDBHandle *handle, + CERTIssuerAndSN *issuerAndSN, + void *wincx) +{ + return common_FindCertByIssuerAndSN(handle, issuerAndSN, wincx); +} + +static NSSCertificate * +get_best_temp_or_perm(NSSCertificate *ct, NSSCertificate *cp) +{ + NSSUsage usage; + NSSCertificate *arr[3]; + if (!ct) { + return nssCertificate_AddRef(cp); + } else if (!cp) { + return nssCertificate_AddRef(ct); + } + arr[0] = ct; + arr[1] = cp; + arr[2] = NULL; + usage.anyUsage = PR_TRUE; + return nssCertificateArray_FindBestCertificate(arr, NULL, &usage, NULL); +} + +CERTCertificate * +CERT_FindCertByName(CERTCertDBHandle *handle, SECItem *name) +{ + NSSCertificate *cp, *ct, *c; + NSSDER subject; + NSSUsage usage; + NSSCryptoContext *cc; + NSSITEM_FROM_SECITEM(&subject, name); + usage.anyUsage = PR_TRUE; + cc = STAN_GetDefaultCryptoContext(); + ct = NSSCryptoContext_FindBestCertificateBySubject(cc, &subject, NULL, + &usage, NULL); + cp = NSSTrustDomain_FindBestCertificateBySubject(handle, &subject, NULL, + &usage, NULL); + c = get_best_temp_or_perm(ct, cp); + if (ct) { + CERT_DestroyCertificate(STAN_GetCERTCertificateOrRelease(ct)); + } + if (cp) { + CERT_DestroyCertificate(STAN_GetCERTCertificateOrRelease(cp)); + } + return c ? STAN_GetCERTCertificateOrRelease(c) : NULL; +} + +CERTCertificate * +CERT_FindCertByKeyID(CERTCertDBHandle *handle, SECItem *name, SECItem *keyID) +{ + CERTCertList *list; + CERTCertificate *cert = NULL; + CERTCertListNode *node; + + list = CERT_CreateSubjectCertList(NULL, handle, name, 0, PR_FALSE); + if (list == NULL) + return NULL; + + node = CERT_LIST_HEAD(list); + while (!CERT_LIST_END(node, list)) { + if (node->cert && + SECITEM_ItemsAreEqual(&node->cert->subjectKeyID, keyID)) { + cert = CERT_DupCertificate(node->cert); + goto done; + } + node = CERT_LIST_NEXT(node); + } + PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER); + +done: + CERT_DestroyCertList(list); + return cert; +} + +CERTCertificate * +CERT_FindCertByNickname(CERTCertDBHandle *handle, const char *nickname) +{ + NSSCryptoContext *cc; + NSSCertificate *c, *ct; + CERTCertificate *cert; + NSSUsage usage; + usage.anyUsage = PR_TRUE; + cc = STAN_GetDefaultCryptoContext(); + ct = NSSCryptoContext_FindBestCertificateByNickname(cc, nickname, NULL, + &usage, NULL); + cert = PK11_FindCertFromNickname(nickname, NULL); + c = NULL; + if (cert) { + c = get_best_temp_or_perm(ct, STAN_GetNSSCertificate(cert)); + CERT_DestroyCertificate(cert); + if (ct) { + CERT_DestroyCertificate(STAN_GetCERTCertificateOrRelease(ct)); + } + } else { + c = ct; + } + return c ? STAN_GetCERTCertificateOrRelease(c) : NULL; +} + +CERTCertificate * +CERT_FindCertByDERCert(CERTCertDBHandle *handle, SECItem *derCert) +{ + NSSCryptoContext *cc; + NSSCertificate *c; + NSSDER encoding; + NSSITEM_FROM_SECITEM(&encoding, derCert); + cc = STAN_GetDefaultCryptoContext(); + c = NSSCryptoContext_FindCertificateByEncodedCertificate(cc, &encoding); + if (!c) { + c = NSSTrustDomain_FindCertificateByEncodedCertificate(handle, + &encoding); + if (!c) + return NULL; + } + return STAN_GetCERTCertificateOrRelease(c); +} + +static CERTCertificate * +common_FindCertByNicknameOrEmailAddrForUsage(CERTCertDBHandle *handle, + const char *name, PRBool anyUsage, + SECCertUsage lookingForUsage, + void *wincx) +{ + NSSCryptoContext *cc; + NSSCertificate *c, *ct; + CERTCertificate *cert = NULL; + NSSUsage usage; + CERTCertList *certlist; + + if (NULL == name) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + usage.anyUsage = anyUsage; + + if (!anyUsage) { + usage.nss3lookingForCA = PR_FALSE; + usage.nss3usage = lookingForUsage; + } + + cc = STAN_GetDefaultCryptoContext(); + ct = NSSCryptoContext_FindBestCertificateByNickname(cc, name, NULL, &usage, + NULL); + if (!ct && PORT_Strchr(name, '@') != NULL) { + char *lowercaseName = CERT_FixupEmailAddr(name); + if (lowercaseName) { + ct = NSSCryptoContext_FindBestCertificateByEmail( + cc, lowercaseName, NULL, &usage, NULL); + PORT_Free(lowercaseName); + } + } + + if (anyUsage) { + cert = PK11_FindCertFromNickname(name, wincx); + } else { + if (ct) { + /* Does ct really have the required usage? */ + nssDecodedCert *dc; + dc = nssCertificate_GetDecoding(ct); + if (!dc->matchUsage(dc, &usage)) { + CERT_DestroyCertificate(STAN_GetCERTCertificateOrRelease(ct)); + ct = NULL; + } + } + + certlist = PK11_FindCertsFromNickname(name, wincx); + if (certlist) { + SECStatus rv = + CERT_FilterCertListByUsage(certlist, lookingForUsage, PR_FALSE); + if (SECSuccess == rv && !CERT_LIST_EMPTY(certlist)) { + cert = CERT_DupCertificate(CERT_LIST_HEAD(certlist)->cert); + } + CERT_DestroyCertList(certlist); + } + } + + if (cert) { + c = get_best_temp_or_perm(ct, STAN_GetNSSCertificate(cert)); + CERT_DestroyCertificate(cert); + if (ct) { + CERT_DestroyCertificate(STAN_GetCERTCertificateOrRelease(ct)); + } + } else { + c = ct; + } + return c ? STAN_GetCERTCertificateOrRelease(c) : NULL; +} + +CERTCertificate * +CERT_FindCertByNicknameOrEmailAddr(CERTCertDBHandle *handle, const char *name) +{ + return common_FindCertByNicknameOrEmailAddrForUsage(handle, name, PR_TRUE, + 0, NULL); +} + +CERTCertificate * +CERT_FindCertByNicknameOrEmailAddrCX(CERTCertDBHandle *handle, const char *name, + void *wincx) +{ + return common_FindCertByNicknameOrEmailAddrForUsage(handle, name, PR_TRUE, + 0, wincx); +} + +CERTCertificate * +CERT_FindCertByNicknameOrEmailAddrForUsage(CERTCertDBHandle *handle, + const char *name, + SECCertUsage lookingForUsage) +{ + return common_FindCertByNicknameOrEmailAddrForUsage(handle, name, PR_FALSE, + lookingForUsage, NULL); +} + +CERTCertificate * +CERT_FindCertByNicknameOrEmailAddrForUsageCX(CERTCertDBHandle *handle, + const char *name, + SECCertUsage lookingForUsage, + void *wincx) +{ + return common_FindCertByNicknameOrEmailAddrForUsage(handle, name, PR_FALSE, + lookingForUsage, wincx); +} + +static void +add_to_subject_list(CERTCertList *certList, CERTCertificate *cert, + PRBool validOnly, PRTime sorttime) +{ + SECStatus secrv; + if (!validOnly || + CERT_CheckCertValidTimes(cert, sorttime, PR_FALSE) == + secCertTimeValid) { + secrv = CERT_AddCertToListSorted(certList, cert, CERT_SortCBValidity, + (void *)&sorttime); + if (secrv != SECSuccess) { + CERT_DestroyCertificate(cert); + } + } else { + CERT_DestroyCertificate(cert); + } +} + +CERTCertList * +CERT_CreateSubjectCertList(CERTCertList *certList, CERTCertDBHandle *handle, + const SECItem *name, PRTime sorttime, + PRBool validOnly) +{ + NSSCryptoContext *cc; + NSSCertificate **tSubjectCerts, **pSubjectCerts; + NSSCertificate **ci; + CERTCertificate *cert; + NSSDER subject; + PRBool myList = PR_FALSE; + cc = STAN_GetDefaultCryptoContext(); + NSSITEM_FROM_SECITEM(&subject, name); + /* Collect both temp and perm certs for the subject */ + tSubjectCerts = + NSSCryptoContext_FindCertificatesBySubject(cc, &subject, NULL, 0, NULL); + pSubjectCerts = NSSTrustDomain_FindCertificatesBySubject(handle, &subject, + NULL, 0, NULL); + if (!tSubjectCerts && !pSubjectCerts) { + return NULL; + } + if (certList == NULL) { + certList = CERT_NewCertList(); + myList = PR_TRUE; + if (!certList) + goto loser; + } + /* Iterate over the matching temp certs. Add them to the list */ + ci = tSubjectCerts; + while (ci && *ci) { + cert = STAN_GetCERTCertificateOrRelease(*ci); + /* *ci may be invalid at this point, don't reference it again */ + if (cert) { + /* NOTE: add_to_subject_list adopts the incoming cert. */ + add_to_subject_list(certList, cert, validOnly, sorttime); + } + ci++; + } + /* Iterate over the matching perm certs. Add them to the list */ + ci = pSubjectCerts; + while (ci && *ci) { + cert = STAN_GetCERTCertificateOrRelease(*ci); + /* *ci may be invalid at this point, don't reference it again */ + if (cert) { + /* NOTE: add_to_subject_list adopts the incoming cert. */ + add_to_subject_list(certList, cert, validOnly, sorttime); + } + ci++; + } + /* all the references have been adopted or freed at this point, just + * free the arrays now */ + nss_ZFreeIf(tSubjectCerts); + nss_ZFreeIf(pSubjectCerts); + return certList; +loser: + /* need to free the references in tSubjectCerts and pSubjectCerts! */ + nssCertificateArray_Destroy(tSubjectCerts); + nssCertificateArray_Destroy(pSubjectCerts); + if (myList && certList != NULL) { + CERT_DestroyCertList(certList); + } + return NULL; +} + +void +CERT_DestroyCertificate(CERTCertificate *cert) +{ + if (cert) { + /* don't use STAN_GetNSSCertificate because we don't want to + * go to the trouble of translating the CERTCertificate into + * an NSSCertificate just to destroy it. If it hasn't been done + * yet, don't do it at all + * + * cert->nssCertificate contains its own locks and refcount, but as it + * may be NULL, the pointer itself must be guarded by some other lock. + * Rather than creating a new global lock for only this purpose, share + * an existing global lock that happens to be taken near the write in + * fill_CERTCertificateFields(). The longer-term goal is to refactor + * all these global locks to be certificate-scoped. */ + CERT_MaybeLockCertTempPerm(cert); + NSSCertificate *tmp = cert->nssCertificate; + CERT_MaybeUnlockCertTempPerm(cert); + if (tmp) { + /* delete the NSSCertificate */ + NSSCertificate_Destroy(tmp); + } else if (cert->arena) { + PORT_FreeArena(cert->arena, PR_FALSE); + } + } + return; +} + +int +CERT_GetDBContentVersion(CERTCertDBHandle *handle) +{ + /* should read the DB content version from the pkcs #11 device */ + return 0; +} + +SECStatus +certdb_SaveSingleProfile(CERTCertificate *cert, const char *emailAddr, + SECItem *emailProfile, SECItem *profileTime) +{ + PRTime oldtime; + PRTime newtime; + SECStatus rv = SECFailure; + PRBool saveit; + SECItem oldprof, oldproftime; + SECItem *oldProfile = NULL; + SECItem *oldProfileTime = NULL; + PK11SlotInfo *slot = NULL; + NSSCertificate *c; + NSSCryptoContext *cc; + nssSMIMEProfile *stanProfile = NULL; + PRBool freeOldProfile = PR_FALSE; + + c = STAN_GetNSSCertificate(cert); + if (!c) + return SECFailure; + cc = c->object.cryptoContext; + if (cc != NULL) { + stanProfile = nssCryptoContext_FindSMIMEProfileForCertificate(cc, c); + if (stanProfile) { + PORT_Assert(stanProfile->profileData); + SECITEM_FROM_NSSITEM(&oldprof, stanProfile->profileData); + oldProfile = &oldprof; + SECITEM_FROM_NSSITEM(&oldproftime, stanProfile->profileTime); + oldProfileTime = &oldproftime; + } + } else { + oldProfile = PK11_FindSMimeProfile(&slot, (char *)emailAddr, + &cert->derSubject, &oldProfileTime); + freeOldProfile = PR_TRUE; + } + + saveit = PR_FALSE; + + /* both profileTime and emailProfile have to exist or not exist */ + if (emailProfile == NULL) { + profileTime = NULL; + } else if (profileTime == NULL) { + emailProfile = NULL; + } + + if (oldProfileTime == NULL) { + saveit = PR_TRUE; + } else { + /* there was already a profile for this email addr */ + if (profileTime) { + /* we have an old and new profile - save whichever is more recent*/ + if (oldProfileTime->len == 0) { + /* always replace if old entry doesn't have a time */ + oldtime = LL_MININT; + } else { + rv = DER_UTCTimeToTime(&oldtime, oldProfileTime); + if (rv != SECSuccess) { + goto loser; + } + } + + rv = DER_UTCTimeToTime(&newtime, profileTime); + if (rv != SECSuccess) { + goto loser; + } + + if (LL_CMP(newtime, >, oldtime)) { + /* this is a newer profile, save it and cert */ + saveit = PR_TRUE; + } + } else { + saveit = PR_TRUE; + } + } + + if (saveit) { + if (cc) { + if (stanProfile && profileTime && emailProfile) { + /* stanProfile is already stored in the crypto context, + * overwrite the data + */ + NSSArena *arena = stanProfile->object.arena; + stanProfile->profileTime = nssItem_Create( + arena, NULL, profileTime->len, profileTime->data); + stanProfile->profileData = nssItem_Create( + arena, NULL, emailProfile->len, emailProfile->data); + } else if (profileTime && emailProfile) { + PRStatus nssrv; + NSSItem profTime, profData; + NSSITEM_FROM_SECITEM(&profTime, profileTime); + NSSITEM_FROM_SECITEM(&profData, emailProfile); + stanProfile = nssSMIMEProfile_Create(c, &profTime, &profData); + if (!stanProfile) + goto loser; + nssrv = nssCryptoContext_ImportSMIMEProfile(cc, stanProfile); + rv = (nssrv == PR_SUCCESS) ? SECSuccess : SECFailure; + } + } else { + rv = PK11_SaveSMimeProfile(slot, (char *)emailAddr, + &cert->derSubject, emailProfile, + profileTime); + } + } else { + rv = SECSuccess; + } + +loser: + if (oldProfile && freeOldProfile) { + SECITEM_FreeItem(oldProfile, PR_TRUE); + } + if (oldProfileTime && freeOldProfile) { + SECITEM_FreeItem(oldProfileTime, PR_TRUE); + } + if (stanProfile) { + nssSMIMEProfile_Destroy(stanProfile); + } + if (slot) { + PK11_FreeSlot(slot); + } + + return (rv); +} + +/* + * + * Manage S/MIME profiles + * + */ + +SECStatus +CERT_SaveSMimeProfile(CERTCertificate *cert, SECItem *emailProfile, + SECItem *profileTime) +{ + const char *emailAddr; + SECStatus rv; + PRBool isperm = PR_FALSE; + + if (!cert) { + return SECFailure; + } + + if (cert->slot && !PK11_IsInternal(cert->slot)) { + /* this cert comes from an external source, we need to add it + to the cert db before creating an S/MIME profile */ + PK11SlotInfo *internalslot = PK11_GetInternalKeySlot(); + if (!internalslot) { + return SECFailure; + } + rv = PK11_ImportCert(internalslot, cert, CK_INVALID_HANDLE, NULL, + PR_FALSE); + + PK11_FreeSlot(internalslot); + if (rv != SECSuccess) { + return SECFailure; + } + } + + rv = CERT_GetCertIsPerm(cert, &isperm); + if (rv != SECSuccess) { + return SECFailure; + } + if (cert->slot && isperm && CERT_IsUserCert(cert) && + (!emailProfile || !emailProfile->len)) { + /* Don't clobber emailProfile for user certs. */ + return SECSuccess; + } + + for (emailAddr = CERT_GetFirstEmailAddress(cert); emailAddr != NULL; + emailAddr = CERT_GetNextEmailAddress(cert, emailAddr)) { + rv = certdb_SaveSingleProfile(cert, emailAddr, emailProfile, + profileTime); + if (rv != SECSuccess) { + return SECFailure; + } + } + return SECSuccess; +} + +SECItem * +CERT_FindSMimeProfile(CERTCertificate *cert) +{ + PK11SlotInfo *slot = NULL; + NSSCertificate *c; + NSSCryptoContext *cc; + SECItem *rvItem = NULL; + + if (!cert || !cert->emailAddr || !cert->emailAddr[0]) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + c = STAN_GetNSSCertificate(cert); + if (!c) + return NULL; + cc = c->object.cryptoContext; + if (cc != NULL) { + nssSMIMEProfile *stanProfile; + stanProfile = nssCryptoContext_FindSMIMEProfileForCertificate(cc, c); + if (stanProfile) { + rvItem = + SECITEM_AllocItem(NULL, NULL, stanProfile->profileData->size); + if (rvItem) { + rvItem->data = stanProfile->profileData->data; + } + nssSMIMEProfile_Destroy(stanProfile); + } + return rvItem; + } + rvItem = + PK11_FindSMimeProfile(&slot, cert->emailAddr, &cert->derSubject, NULL); + if (slot) { + PK11_FreeSlot(slot); + } + return rvItem; +} + +SECStatus +CERT_GetCertIsPerm(const CERTCertificate *cert, PRBool *isperm) +{ + if (cert == NULL) { + return SECFailure; + } + + CERT_LockCertTempPerm(cert); + *isperm = cert->isperm; + CERT_UnlockCertTempPerm(cert); + return SECSuccess; +} + +SECStatus +CERT_GetCertIsTemp(const CERTCertificate *cert, PRBool *istemp) +{ + if (cert == NULL) { + return SECFailure; + } + + CERT_LockCertTempPerm(cert); + *istemp = cert->istemp; + CERT_UnlockCertTempPerm(cert); + return SECSuccess; +} + +/* + * deprecated functions that are now just stubs. + */ +/* + * Close the database + */ +void +__CERT_ClosePermCertDB(CERTCertDBHandle *handle) +{ + PORT_Assert("CERT_ClosePermCertDB is Deprecated" == NULL); + return; +} + +SECStatus +CERT_OpenCertDBFilename(CERTCertDBHandle *handle, char *certdbname, + PRBool readOnly) +{ + PORT_Assert("CERT_OpenCertDBFilename is Deprecated" == NULL); + PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); + return SECFailure; +} + +SECItem * +SECKEY_HashPassword(char *pw, SECItem *salt) +{ + PORT_Assert("SECKEY_HashPassword is Deprecated" == NULL); + PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); + return NULL; +} + +SECStatus +__CERT_TraversePermCertsForSubject(CERTCertDBHandle *handle, + SECItem *derSubject, void *cb, void *cbarg) +{ + PORT_Assert("CERT_TraversePermCertsForSubject is Deprecated" == NULL); + PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); + return SECFailure; +} + +SECStatus +__CERT_TraversePermCertsForNickname(CERTCertDBHandle *handle, char *nickname, + void *cb, void *cbarg) +{ + PORT_Assert("CERT_TraversePermCertsForNickname is Deprecated" == NULL); + PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); + return SECFailure; +} diff --git a/security/nss/lib/certdb/xauthkid.c b/security/nss/lib/certdb/xauthkid.c new file mode 100644 index 0000000000..c7ef046db0 --- /dev/null +++ b/security/nss/lib/certdb/xauthkid.c @@ -0,0 +1,128 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * X.509 v3 Subject Key Usage Extension + * + */ + +#include "prtypes.h" +#include "seccomon.h" +#include "secdert.h" +#include "secoidt.h" +#include "secasn1t.h" +#include "secasn1.h" +#include "secport.h" +#include "certt.h" +#include "genname.h" +#include "secerr.h" + +SEC_ASN1_MKSUB(SEC_IntegerTemplate) +SEC_ASN1_MKSUB(SEC_OctetStringTemplate) + +const SEC_ASN1Template CERTAuthKeyIDTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTAuthKeyID) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, + offsetof(CERTAuthKeyID, keyID), SEC_ASN1_SUB(SEC_OctetStringTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(CERTAuthKeyID, DERAuthCertIssuer), CERT_GeneralNamesTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 2, + offsetof(CERTAuthKeyID, authCertSerialNumber), + SEC_ASN1_SUB(SEC_IntegerTemplate) }, + { 0 } +}; + +SECStatus +CERT_EncodeAuthKeyID(PLArenaPool *arena, CERTAuthKeyID *value, + SECItem *encodedValue) +{ + SECStatus rv = SECFailure; + + PORT_Assert(value); + PORT_Assert(arena); + PORT_Assert(value->DERAuthCertIssuer == NULL); + PORT_Assert(encodedValue); + + do { + + /* If both of the authCertIssuer and the serial number exist, encode + the name first. Otherwise, it is an error if one exist and the other + is not. + */ + if (value->authCertIssuer) { + if (!value->authCertSerialNumber.data) { + PORT_SetError(SEC_ERROR_EXTENSION_VALUE_INVALID); + break; + } + + value->DERAuthCertIssuer = + cert_EncodeGeneralNames(arena, value->authCertIssuer); + if (!value->DERAuthCertIssuer) { + PORT_SetError(SEC_ERROR_EXTENSION_VALUE_INVALID); + break; + } + } else if (value->authCertSerialNumber.data) { + PORT_SetError(SEC_ERROR_EXTENSION_VALUE_INVALID); + break; + } + + if (SEC_ASN1EncodeItem(arena, encodedValue, value, + CERTAuthKeyIDTemplate) == NULL) + break; + rv = SECSuccess; + + } while (0); + return (rv); +} + +CERTAuthKeyID * +CERT_DecodeAuthKeyID(PLArenaPool *arena, const SECItem *encodedValue) +{ + CERTAuthKeyID *value = NULL; + SECStatus rv = SECFailure; + void *mark; + SECItem newEncodedValue; + + PORT_Assert(arena); + + do { + mark = PORT_ArenaMark(arena); + value = (CERTAuthKeyID *)PORT_ArenaZAlloc(arena, sizeof(*value)); + if (value == NULL) + break; + value->DERAuthCertIssuer = NULL; + /* copy the DER into the arena, since Quick DER returns data that points + into the DER input, which may get freed by the caller */ + rv = SECITEM_CopyItem(arena, &newEncodedValue, encodedValue); + if (rv != SECSuccess) { + break; + } + + rv = SEC_QuickDERDecodeItem(arena, value, CERTAuthKeyIDTemplate, + &newEncodedValue); + if (rv != SECSuccess) + break; + + value->authCertIssuer = + cert_DecodeGeneralNames(arena, value->DERAuthCertIssuer); + if (value->authCertIssuer == NULL) + break; + + /* what if the general name contains other format but not URI ? + hl + */ + if ((value->authCertSerialNumber.data && !value->authCertIssuer) || + (!value->authCertSerialNumber.data && value->authCertIssuer)) { + PORT_SetError(SEC_ERROR_EXTENSION_VALUE_INVALID); + break; + } + } while (0); + + if (rv != SECSuccess) { + PORT_ArenaRelease(arena, mark); + return ((CERTAuthKeyID *)NULL); + } + PORT_ArenaUnmark(arena, mark); + return (value); +} diff --git a/security/nss/lib/certdb/xbsconst.c b/security/nss/lib/certdb/xbsconst.c new file mode 100644 index 0000000000..df70dbbefb --- /dev/null +++ b/security/nss/lib/certdb/xbsconst.c @@ -0,0 +1,146 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * X.509 v3 Basic Constraints Extension + */ + +#include "prtypes.h" +#include <limits.h> /* for LONG_MAX */ +#include "seccomon.h" +#include "secdert.h" +#include "secoidt.h" +#include "secasn1t.h" +#include "secasn1.h" +#include "certt.h" +#include "secder.h" +#include "prprf.h" +#include "secerr.h" + +typedef struct EncodedContext { + SECItem isCA; + SECItem pathLenConstraint; + SECItem encodedValue; + PLArenaPool *arena; +} EncodedContext; + +static const SEC_ASN1Template CERTBasicConstraintsTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(EncodedContext) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_BOOLEAN, /* XXX DER_DEFAULT */ + offsetof(EncodedContext, isCA) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_INTEGER, + offsetof(EncodedContext, pathLenConstraint) }, + { 0 } +}; + +static unsigned char hexTrue = 0xff; +static unsigned char hexFalse = 0x00; + +#define GEN_BREAK(status) \ + rv = status; \ + break; + +SECStatus +CERT_EncodeBasicConstraintValue(PLArenaPool *arena, CERTBasicConstraints *value, + SECItem *encodedValue) +{ + EncodedContext encodeContext; + PLArenaPool *our_pool = NULL; + SECStatus rv = SECSuccess; + + do { + PORT_Memset(&encodeContext, 0, sizeof(encodeContext)); + if (!value->isCA && value->pathLenConstraint >= 0) { + PORT_SetError(SEC_ERROR_EXTENSION_VALUE_INVALID); + GEN_BREAK(SECFailure); + } + + encodeContext.arena = arena; + if (value->isCA == PR_TRUE) { + encodeContext.isCA.data = &hexTrue; + encodeContext.isCA.len = 1; + } + + /* If the pathLenConstraint is less than 0, then it should be + * omitted from the encoding. + */ + if (value->isCA && value->pathLenConstraint >= 0) { + our_pool = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if (our_pool == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + GEN_BREAK(SECFailure); + } + if (SEC_ASN1EncodeUnsignedInteger( + our_pool, &encodeContext.pathLenConstraint, + (unsigned long)value->pathLenConstraint) == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + GEN_BREAK(SECFailure); + } + } + if (SEC_ASN1EncodeItem(arena, encodedValue, &encodeContext, + CERTBasicConstraintsTemplate) == NULL) { + GEN_BREAK(SECFailure); + } + } while (0); + if (our_pool) + PORT_FreeArena(our_pool, PR_FALSE); + return (rv); +} + +SECStatus +CERT_DecodeBasicConstraintValue(CERTBasicConstraints *value, + const SECItem *encodedValue) +{ + EncodedContext decodeContext; + PORTCheapArenaPool tmpArena; + SECStatus rv = SECSuccess; + + do { + PORT_Memset(&decodeContext, 0, sizeof(decodeContext)); + /* initialize the value just in case we got "0x30 00", or when the + pathLenConstraint is omitted. + */ + decodeContext.isCA.data = &hexFalse; + decodeContext.isCA.len = 1; + + PORT_InitCheapArena(&tmpArena, SEC_ASN1_DEFAULT_ARENA_SIZE); + + rv = SEC_QuickDERDecodeItem(&tmpArena.arena, &decodeContext, + CERTBasicConstraintsTemplate, encodedValue); + if (rv == SECFailure) + break; + + value->isCA = decodeContext.isCA.data + ? (PRBool)(decodeContext.isCA.data[0] != 0) + : PR_FALSE; + if (decodeContext.pathLenConstraint.data == NULL) { + /* if the pathLenConstraint is not encoded, and the current setting + is CA, then the pathLenConstraint should be set to a negative + number + for unlimited certificate path. + */ + if (value->isCA) { + value->pathLenConstraint = CERT_UNLIMITED_PATH_CONSTRAINT; + } else { + value->pathLenConstraint = 0; + } + } else if (value->isCA) { + long len = DER_GetInteger(&decodeContext.pathLenConstraint); + if (len < 0 || len == LONG_MAX) { + PORT_SetError(SEC_ERROR_BAD_DER); + GEN_BREAK(SECFailure); + } + value->pathLenConstraint = len; + } else { + /* here we get an error where the subject is not a CA, but + the pathLenConstraint is set */ + PORT_SetError(SEC_ERROR_BAD_DER); + GEN_BREAK(SECFailure); + break; + } + } while (0); + + PORT_DestroyCheapArena(&tmpArena); + return (rv); +} diff --git a/security/nss/lib/certdb/xconst.c b/security/nss/lib/certdb/xconst.c new file mode 100644 index 0000000000..9a5634a906 --- /dev/null +++ b/security/nss/lib/certdb/xconst.c @@ -0,0 +1,269 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * X.509 Extension Encoding + */ + +#include "prtypes.h" +#include "seccomon.h" +#include "secdert.h" +#include "secoidt.h" +#include "secasn1t.h" +#include "secasn1.h" +#include "cert.h" +#include "secder.h" +#include "prprf.h" +#include "xconst.h" +#include "genname.h" +#include "secasn1.h" +#include "secerr.h" + +static const SEC_ASN1Template CERTSubjectKeyIDTemplate[] = { + { SEC_ASN1_OCTET_STRING } +}; + +static const SEC_ASN1Template CERTIA5TypeTemplate[] = { + { SEC_ASN1_IA5_STRING } +}; + +SEC_ASN1_MKSUB(SEC_GeneralizedTimeTemplate) + +static const SEC_ASN1Template CERTPrivateKeyUsagePeriodTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTPrivKeyUsagePeriod) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, + offsetof(CERTPrivKeyUsagePeriod, notBefore), + SEC_ASN1_SUB(SEC_GeneralizedTimeTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 1, + offsetof(CERTPrivKeyUsagePeriod, notAfter), + SEC_ASN1_SUB(SEC_GeneralizedTimeTemplate) }, + { 0 } +}; + +const SEC_ASN1Template CERTAltNameTemplate[] = { + { SEC_ASN1_CONSTRUCTED, offsetof(CERTAltNameEncodedContext, encodedGenName), + CERT_GeneralNamesTemplate } +}; + +const SEC_ASN1Template CERTAuthInfoAccessItemTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTAuthInfoAccess) }, + { SEC_ASN1_OBJECT_ID, offsetof(CERTAuthInfoAccess, method) }, + { SEC_ASN1_ANY, offsetof(CERTAuthInfoAccess, derLocation) }, + { 0 } +}; + +const SEC_ASN1Template CERTAuthInfoAccessTemplate[] = { + { SEC_ASN1_SEQUENCE_OF, 0, CERTAuthInfoAccessItemTemplate } +}; + +SECStatus +CERT_EncodeSubjectKeyID(PLArenaPool *arena, const SECItem *srcString, + SECItem *encodedValue) +{ + SECStatus rv = SECSuccess; + + if (!srcString) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + if (SEC_ASN1EncodeItem(arena, encodedValue, srcString, + CERTSubjectKeyIDTemplate) == NULL) { + rv = SECFailure; + } + + return (rv); +} + +SECStatus +CERT_EncodePrivateKeyUsagePeriod(PLArenaPool *arena, + CERTPrivKeyUsagePeriod *pkup, + SECItem *encodedValue) +{ + SECStatus rv = SECSuccess; + + if (SEC_ASN1EncodeItem(arena, encodedValue, pkup, + CERTPrivateKeyUsagePeriodTemplate) == NULL) { + rv = SECFailure; + } + return (rv); +} + +CERTPrivKeyUsagePeriod * +CERT_DecodePrivKeyUsagePeriodExtension(PLArenaPool *arena, SECItem *extnValue) +{ + SECStatus rv; + CERTPrivKeyUsagePeriod *pPeriod; + SECItem newExtnValue; + + /* allocate the certificate policies structure */ + pPeriod = PORT_ArenaZNew(arena, CERTPrivKeyUsagePeriod); + if (pPeriod == NULL) { + goto loser; + } + + pPeriod->arena = arena; + + /* copy the DER into the arena, since Quick DER returns data that points + into the DER input, which may get freed by the caller */ + rv = SECITEM_CopyItem(arena, &newExtnValue, extnValue); + if (rv != SECSuccess) { + goto loser; + } + + rv = SEC_QuickDERDecodeItem( + arena, pPeriod, CERTPrivateKeyUsagePeriodTemplate, &newExtnValue); + if (rv != SECSuccess) { + goto loser; + } + return pPeriod; + +loser: + return NULL; +} + +SECStatus +CERT_EncodeIA5TypeExtension(PLArenaPool *arena, char *value, + SECItem *encodedValue) +{ + SECItem encodeContext; + SECStatus rv = SECSuccess; + + PORT_Memset(&encodeContext, 0, sizeof(encodeContext)); + + if (value != NULL) { + encodeContext.data = (unsigned char *)value; + encodeContext.len = strlen(value); + } + if (SEC_ASN1EncodeItem(arena, encodedValue, &encodeContext, + CERTIA5TypeTemplate) == NULL) { + rv = SECFailure; + } + + return (rv); +} + +SECStatus +CERT_EncodeAltNameExtension(PLArenaPool *arena, CERTGeneralName *value, + SECItem *encodedValue) +{ + SECItem **encodedGenName; + SECStatus rv = SECSuccess; + + encodedGenName = cert_EncodeGeneralNames(arena, value); + if (SEC_ASN1EncodeItem(arena, encodedValue, &encodedGenName, + CERT_GeneralNamesTemplate) == NULL) { + rv = SECFailure; + } + + return rv; +} + +CERTGeneralName * +CERT_DecodeAltNameExtension(PLArenaPool *reqArena, SECItem *EncodedAltName) +{ + SECStatus rv = SECSuccess; + CERTAltNameEncodedContext encodedContext; + SECItem *newEncodedAltName; + + if (!reqArena) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + newEncodedAltName = SECITEM_ArenaDupItem(reqArena, EncodedAltName); + if (!newEncodedAltName) { + return NULL; + } + + encodedContext.encodedGenName = NULL; + PORT_Memset(&encodedContext, 0, sizeof(CERTAltNameEncodedContext)); + rv = SEC_QuickDERDecodeItem(reqArena, &encodedContext, + CERT_GeneralNamesTemplate, newEncodedAltName); + if (rv == SECFailure) { + goto loser; + } + if (encodedContext.encodedGenName && encodedContext.encodedGenName[0]) + return cert_DecodeGeneralNames(reqArena, encodedContext.encodedGenName); + /* Extension contained an empty GeneralNames sequence */ + /* Treat as extension not found */ + PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND); +loser: + return NULL; +} + +SECStatus +CERT_EncodeNameConstraintsExtension(PLArenaPool *arena, + CERTNameConstraints *value, + SECItem *encodedValue) +{ + SECStatus rv = SECSuccess; + + rv = cert_EncodeNameConstraints(value, arena, encodedValue); + return rv; +} + +CERTNameConstraints * +CERT_DecodeNameConstraintsExtension(PLArenaPool *arena, + const SECItem *encodedConstraints) +{ + return cert_DecodeNameConstraints(arena, encodedConstraints); +} + +CERTAuthInfoAccess ** +CERT_DecodeAuthInfoAccessExtension(PLArenaPool *reqArena, + const SECItem *encodedExtension) +{ + CERTAuthInfoAccess **info = NULL; + SECStatus rv; + int i; + SECItem *newEncodedExtension; + + if (!reqArena) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + newEncodedExtension = SECITEM_ArenaDupItem(reqArena, encodedExtension); + if (!newEncodedExtension) { + return NULL; + } + + rv = SEC_QuickDERDecodeItem(reqArena, &info, CERTAuthInfoAccessTemplate, + newEncodedExtension); + if (rv != SECSuccess || info == NULL) { + return NULL; + } + + for (i = 0; info[i] != NULL; i++) { + info[i]->location = + CERT_DecodeGeneralName(reqArena, &(info[i]->derLocation), NULL); + } + return info; +} + +SECStatus +CERT_EncodeInfoAccessExtension(PLArenaPool *arena, CERTAuthInfoAccess **info, + SECItem *dest) +{ + SECItem *dummy; + int i; + + PORT_Assert(info != NULL); + PORT_Assert(dest != NULL); + if (info == NULL || dest == NULL) { + return SECFailure; + } + + for (i = 0; info[i] != NULL; i++) { + if (CERT_EncodeGeneralName(info[i]->location, &(info[i]->derLocation), + arena) == NULL) + /* Note that this may leave some of the locations filled in. */ + return SECFailure; + } + dummy = SEC_ASN1EncodeItem(arena, dest, &info, CERTAuthInfoAccessTemplate); + if (dummy == NULL) { + return SECFailure; + } + return SECSuccess; +} diff --git a/security/nss/lib/certdb/xconst.h b/security/nss/lib/certdb/xconst.h new file mode 100644 index 0000000000..8cf2e826e0 --- /dev/null +++ b/security/nss/lib/certdb/xconst.h @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _XCONST_H_ +#define _XCONST_H_ + +#include "certt.h" + +typedef struct CERTAltNameEncodedContextStr { + SECItem **encodedGenName; +} CERTAltNameEncodedContext; + +SEC_BEGIN_PROTOS + +extern SECStatus CERT_EncodePrivateKeyUsagePeriod(PLArenaPool *arena, + CERTPrivKeyUsagePeriod *pkup, + SECItem *encodedValue); + +extern SECStatus CERT_EncodeNameConstraintsExtension(PLArenaPool *arena, + CERTNameConstraints *value, + SECItem *encodedValue); + +extern SECStatus CERT_EncodeIA5TypeExtension(PLArenaPool *arena, char *value, + SECItem *encodedValue); + +SECStatus cert_EncodeAuthInfoAccessExtension(PLArenaPool *arena, + CERTAuthInfoAccess **info, + SECItem *dest); +SEC_END_PROTOS +#endif |