diff options
Diffstat (limited to 'security/nss/lib/certdb/alg1485.c')
-rw-r--r-- | security/nss/lib/certdb/alg1485.c | 1590 |
1 files changed, 1590 insertions, 0 deletions
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)); +} |