/* alg1485.c - implementation of RFCs 1485, 1779 and 2253. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Netscape security libraries. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1994-2000 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #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 /* 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", 64, SEC_OID_AVA_COMMON_NAME, SEC_ASN1_DS}, { "ST", 128, SEC_OID_AVA_STATE_OR_PROVINCE, SEC_ASN1_DS}, { "O", 64, SEC_OID_AVA_ORGANIZATION_NAME, SEC_ASN1_DS}, { "OU", 64, 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}, #if 0 /* removed. Not yet in any IETF draft or RFC. */ { "pseudonym", 64, SEC_OID_AVA_PSEUDONYM, SEC_ASN1_DS}, #endif { 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) == '?')) /* 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(char **pbp, char *endptr) { char *bp = *pbp; while (bp < endptr && OPTIONAL_SPACE(*bp)) { bp++; } *pbp = bp; } static SECStatus scanTag(char **pbp, char *endptr, char *tagBuf, int tagBufSize) { char *bp, *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(char **pbp, char *endptr, char *valBuf, int valBufSize) { char *bp, *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) { PRInt16 bin = (x2b[(PRUint8)src[0]] << 4) | x2b[(PRUint8)src[1]]; if (bin < 0) 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(PRArenaPool *arena, char **pbp, char *endptr) { CERTAVA *a; const NameToKind *n2k; char *bp; int vt = -1; int valLen; 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[384]; 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)) { rv = SEC_StringToOID(arena, &derOid, tagBuf, strlen(tagBuf)); } 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_UNKNOWN) goto loser; 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; 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(char *buf, int len) { SECStatus rv; CERTName *name; 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(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 preceeding 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; } /* 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; } } 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; ilen; 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 384 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]; rv = escapeAndQuote(bigTmpBuf, sizeof bigTmpBuf, (char *)avaValue->data, 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; PRArenaPool *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(PRArenaPool *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(PRArenaPool *arena, 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(PRArenaPool *arena, 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; PRArenaPool *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; PRArenaPool * tmpArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); PRUint32 maxLen = 0; PRInt32 finalLen = 0; SECStatus rv; SECItem subAltName; if (!tmpArena) return addrBuf; 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, maxLen + 1); if (!addrBuf) goto loser; rawEmailAddr = CERT_GetNameElement(tmpArena, &cert->subject, SEC_OID_PKCS9_EMAIL_ADDRESS); pBuf = appendStringToBuf(pBuf, rawEmailAddr, &maxLen); rawEmailAddr = CERT_GetNameElement(tmpArena, &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, &subAltName))) { CERTGeneralName *current = nameList; do { if (current->type == certDirectoryName) { rawEmailAddr = CERT_GetNameElement(tmpArena, ¤t->name.directoryName, SEC_OID_PKCS9_EMAIL_ADDRESS); pBuf = appendStringToBuf(pBuf, rawEmailAddr, &maxLen); rawEmailAddr = CERT_GetNameElement(tmpArena, ¤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: if (tmpArena) PORT_FreeArena(tmpArena, PR_FALSE); 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(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(CERTName *name) { return(CERT_GetLastNameElement(NULL, name, SEC_OID_AVA_COMMON_NAME)); } char * CERT_GetCountryName(CERTName *name) { return(CERT_GetNameElement(NULL, name, SEC_OID_AVA_COUNTRY_NAME)); } char * CERT_GetLocalityName(CERTName *name) { return(CERT_GetNameElement(NULL, name, SEC_OID_AVA_LOCALITY)); } char * CERT_GetStateName(CERTName *name) { return(CERT_GetNameElement(NULL, name, SEC_OID_AVA_STATE_OR_PROVINCE)); } char * CERT_GetOrgName(CERTName *name) { return(CERT_GetNameElement(NULL, name, SEC_OID_AVA_ORGANIZATION_NAME)); } char * CERT_GetDomainComponentName(CERTName *name) { return(CERT_GetNameElement(NULL, name, SEC_OID_AVA_DC)); } char * CERT_GetOrgUnitName(CERTName *name) { return(CERT_GetNameElement(NULL, name, SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME)); } char * CERT_GetDnQualifier(CERTName *name) { return(CERT_GetNameElement(NULL, name, SEC_OID_AVA_DN_QUALIFIER)); } char * CERT_GetCertUid(CERTName *name) { return(CERT_GetNameElement(NULL, name, SEC_OID_RFC1274_UID)); }