/* 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 CERT_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_DS},

#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},
};

#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) == '?'))

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;
}

static SECStatus
scanVal(char **pbp, char *endptr, char *valBuf, int valBufSize)  
{
    char *bp, *valBufp;
    int vallen;
    PRBool isQuoted;
    
    PORT_Assert(valBufSize > 0);
    
    /* skip optional leading space */
    skipSpace(pbp, endptr);
    if(*pbp == endptr) {
	/* nothing left */
	return SECFailure;
    }
    
    bp = *pbp;
    
    /* quoted? */
    if (*bp == C_DOUBLE_QUOTE) {
	isQuoted = PR_TRUE;
	/* skip over it */
	bp++;
    } else {
	isQuoted = PR_FALSE;
    }
    
    valBufp = valBuf;
    vallen = 0;
    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 SECFailure;
	    }
	} 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 SECFailure;
	}
	*valBufp++ = *bp++;
    }
    
    /* stip trailing spaces from unquoted values */
    if (!isQuoted) {
	if (valBufp > valBuf) {
	    valBufp--;
	    while ((valBufp > valBuf) && OPTIONAL_SPACE(*valBufp)) {
		valBufp--;
	    }
	    valBufp++;
	}
    }
    
    if (isQuoted) {
	/* insist that we stopped on a double quote */
	if (*bp != C_DOUBLE_QUOTE) {
	    *pbp = bp;
	    return SECFailure;
	}
	/* skip over the quote and skip optional space */
	bp++;
	skipSpace(&bp, endptr);
    }
    
    *pbp = bp;
    
    if (valBufp == valBuf) {
	/* empty value -- not allowed */
	return SECFailure;
    }
    
    /* null-terminate valBuf -- guaranteed at least one space left */
    *valBufp++ = 0;
    
    return SECSuccess;
}

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
};

/* 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;
}


CERTAVA *
CERT_ParseRFC1485AVA(PRArenaPool *arena, char **pbp, char *endptr,
		    PRBool singleAVA) 
{
    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 };

    char tagBuf[32];
    char valBuf[384];

    PORT_Assert(arena);
    if (scanTag(pbp, endptr, tagBuf, sizeof(tagBuf)) == SECFailure ||
	scanVal(pbp, endptr, valBuf, sizeof(valBuf)) == SECFailure) {
	goto loser;
    }

    /* insist that if we haven't finished we've stopped on a separator */
    bp = *pbp;
    if (bp < endptr) {
	if (singleAVA || (*bp != ',' && *bp != ';')) {
	    *pbp = bp;
	    goto loser;
	}
	/* ok, skip over separator */
	bp++;
    }
    *pbp = bp;

    /* 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 */
	SECItem  derVal = { 0, NULL, 0};
	valLen = PORT_Strlen(valBuf+1);
	rv = hexToBin(arena, &derVal, valBuf + 1, valLen);
	if (rv)
	    goto loser;
	a = CERT_CreateAVAFromRaw(arena, &derOid, &derVal);
    } else {
	if (kind == SEC_OID_UNKNOWN)
	    goto loser;
	valLen = PORT_Strlen(valBuf);
	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;
	}

	a = CERT_CreateAVA(arena, kind, vt, (char *)valBuf);
    }
    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;

    name = CERT_CreateName(NULL);
    if (name == NULL) {
	return NULL;
    }
    
    e = buf + len;
    bp = buf;
    while (bp < e) {
	ava = CERT_ParseRFC1485AVA(name->arena, &bp, e, PR_FALSE);
	if (ava == 0) goto loser;
	rdn = CERT_CreateRDN(name->arena, ava, (CERTAVA *)0);
	if (rdn == 0) goto loser;
	rv = CERT_AddRDN(name, rdn);
	if (rv) goto loser;
	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;
}

static int
cert_RFC1485_GetRequiredLen(const char *src, int srclen, PRBool *pNeedsQuoting)
{
    int i, reqLen=0;
    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 (!needsQuoting && (SPECIAL_CHAR(c) ||
	    (OPTIONAL_SPACE(c) && OPTIONAL_SPACE(lastC)))) {
	    /* entirety will need quoting */
	    needsQuoting = PR_TRUE;
	}
	if (c == C_DOUBLE_QUOTE || c == C_BACKSLASH) {
	    /* this char will need escaping */
	    reqLen++;
	}
	lastC = c;
    }
    /* if it begins or ends in optional space it needs quoting */
    if (!needsQuoting && srclen > 0 && 
	(OPTIONAL_SPACE(src[srclen-1]) || OPTIONAL_SPACE(src[0]))) {
	needsQuoting = PR_TRUE;
    }

    if (needsQuoting) 
    	reqLen += 2;
    if (pNeedsQuoting)
    	*pNeedsQuoting = needsQuoting;

    return reqLen;
}

SECStatus
CERT_RFC1485_EscapeAndQuote(char *dst, int dstlen, char *src, int srclen)
{
    int i, reqLen=0;
    char *d = dst;
    PRBool needsQuoting = PR_FALSE;

    /* space for terminal null */
    reqLen = cert_RFC1485_GetRequiredLen(src, srclen, &needsQuoting) + 1;
    if (reqLen > dstlen) {
	PORT_SetError(SEC_ERROR_OUTPUT_LEN);
	return SECFailure;
    }

    d = dst;
    if (needsQuoting) *d++ = C_DOUBLE_QUOTE;
    for (i = 0; i < srclen; i++) {
	char c = src[i];
	if (c == C_DOUBLE_QUOTE || c == C_BACKSLASH) {
	    /* escape it */
	    *d++ = C_BACKSLASH;
	}
	*d++ = c;
    }
    if (needsQuoting) *d++ = C_DOUBLE_QUOTE;
    *d++ = 0;
    return SECSuccess;
}

/* 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 *end;
    PRUint8 *d;
    PRUint8 *e;
    char *a         = NULL;
    char *b;

#define MAX_OID_LEN 1024 /* bytes */

    if (oid->len > MAX_OID_LEN) {
    	PORT_SetError(SEC_ERROR_INPUT_LEN);
	return NULL;
    }

    /* d will point to the next sequence of bytes to decode */
    d = (PRUint8 *)oid->data;
    /* end points to one past the legitimate data */
    end = &d[ oid->len ];

    /*
     * Check for our pseudo-encoded single-digit OIDs
     */
    if( (*d == 0x80) && (2 == oid->len) ) {
	/* Funky encoding.  The second byte is the number */
	a = PR_smprintf("%lu", (PRUint32)d[1]);
	if( (char *)NULL == a ) {
	    PORT_SetError(SEC_ERROR_NO_MEMORY);
	    return (char *)NULL;
	}
	return a;
    }

    for( ; d < end; d = &e[1] ) {
    
	for( e = d; e < end; e++ ) {
	    if( 0 == (*e & 0x80) ) {
		break;
	    }
	}
    
	if( ((e-d) > 4) || (((e-d) == 4) && (*d & 0x70)) ) {
	    /* More than a 32-bit number */
	} else {
	    PRUint32 n = 0;
      
	    switch( e-d ) {
	    case 4:
		n |= ((PRUint32)(e[-4] & 0x0f)) << 28;
	    case 3:
		n |= ((PRUint32)(e[-3] & 0x7f)) << 21;
	    case 2:
		n |= ((PRUint32)(e[-2] & 0x7f)) << 14;
	    case 1:
		n |= ((PRUint32)(e[-1] & 0x7f)) <<  7;
	    case 0:
		n |= ((PRUint32)(e[-0] & 0x7f))      ;
	    }
      
	    if( (char *)NULL == a ) {
		/* This is the first number.. decompose it */
		PRUint32 one = PR_MIN(n/40, 2); /* never > 2 */
		PRUint32 two = n - one * 40;
        
		a = PR_smprintf("OID.%lu.%lu", one, two);
		if( (char *)NULL == a ) {
		    PORT_SetError(SEC_ERROR_NO_MEMORY);
		    return (char *)NULL;
		}
	    } else {
		b = PR_smprintf("%s.%lu", a, n);
		if( (char *)NULL == b ) {
		    PR_smprintf_free(a);
		    PORT_SetError(SEC_ERROR_NO_MEMORY);
		    return (char *)NULL;
		}
        
		PR_smprintf_free(a);
		a = b;
	    }
	}
    }

    return a;
}

/* 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)
{
    const NameToKind *pn2k   = name2kinds;
    SECItem     *avaValue    = NULL;
    char        *unknownTag  = NULL;
    char        *encodedAVA  = NULL;
    PRBool       useHex      = PR_FALSE;  /* use =#hexXXXX form */
    SECOidTag    endKind;
    SECStatus    rv;
    unsigned int len;
    int          nameLen, valueLen;
    NameToKind   n2k         = { NULL, 32767, SEC_OID_UNKNOWN, SEC_ASN1_DS };
    char         tmpBuf[384];

#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;
	}
    }

    if (strict == CERT_N2A_READABLE) {
    	if (maxBytes > sizeof(tmpBuf) - 4)
	    maxBytes = sizeof(tmpBuf) - 4;
	/* Check value length.  Must be room for "..." */
	if (avaValue->len > maxBytes + 3) {
	    /* avaValue is a UTF8 string, freshly allocated and returned to us 
	    ** by CERT_DecodeAVAValue or get_hex_string just above, so we can
	    ** modify it here.  See if we're in the middle of a multi-byte
	    ** UTF8 character.
	    */
	    len = maxBytes;
	    while (((avaValue->data[len] & 0xc0) == 0x80) && len > 0) {
	       len--;
	    }
	    /* add elipsis to signify truncation. */
	    avaValue->data[len++] = '.'; 
	    avaValue->data[len++] = '.';
	    avaValue->data[len++] = '.';
	    avaValue->data[len]   = 0;
	    avaValue->len = len;
	}
    }

    nameLen  = strlen(tagName);
    valueLen = (useHex ? avaValue->len : 
            cert_RFC1485_GetRequiredLen(avaValue->data, avaValue->len, NULL));
    len = nameLen + valueLen + 2; /* Add 2 for '=' and trailing NUL */

    if (len <= sizeof(tmpBuf)) {
    	encodedAVA = tmpBuf;
    } else if (strict == CERT_N2A_READABLE) {
	PORT_SetError(SEC_ERROR_OUTPUT_LEN);
    } else {
	encodedAVA = PORT_Alloc(len);
    }
    if (!encodedAVA) {
	SECITEM_FreeItem(avaValue, PR_TRUE);
	if (unknownTag) 
	    PR_smprintf_free(unknownTag);
	return SECFailure;
    }
    memcpy(encodedAVA, tagName, nameLen);
    if (unknownTag) 
    	PR_smprintf_free(unknownTag);
    encodedAVA[nameLen++] = '=';
    
    /* escape and quote as necessary - don't quote hex strings */
    if (useHex) {
	memcpy(encodedAVA + nameLen, (char *)avaValue->data, avaValue->len);
	encodedAVA[nameLen + avaValue->len] = '\0';
	rv = SECSuccess;
    } else 
	rv = CERT_RFC1485_EscapeAndQuote(encodedAVA + nameLen, len - nameLen, 
		    		        (char *)avaValue->data, avaValue->len);
    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 buf */
	    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);
}

/* 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;
    CERTRDN *rdn;
    char *buf = 0;
    
    rdns = name->rdns;
    while (rdns && (rdn = *rdns++) != 0) {
	CERTAVA** avas = rdn->avas;
	CERTAVA*  ava;
	while (avas && (ava = *avas++) != 0) {
	    int tag = CERT_GetAVATag(ava);
	    if ( tag == wantedTag ) {
		SECItem *decodeItem = CERT_DecodeAVAValue(&ava->value);
		if(!decodeItem) {
		    return NULL;
		}
		if (arena) {
		    buf = (char *)PORT_ArenaZAlloc(arena,decodeItem->len + 1);
		} else {
		    buf = (char *)PORT_ZAlloc(decodeItem->len + 1);
		}
		if ( buf ) {
		    PORT_Memcpy(buf, decodeItem->data, decodeItem->len);
		    buf[decodeItem->len] = 0;
		}
		SECITEM_FreeItem(decodeItem, PR_TRUE);
		goto done;
	    }
	}
    }
    
  done:
    return buf;
}

/* 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;
    CERTRDN *rdn;
    CERTAVA * lastAva = NULL;
    char *buf = 0;
    
    rdns = name->rdns;
    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;
	    }
	}
    }

    if (lastAva) {
	SECItem *decodeItem = CERT_DecodeAVAValue(&lastAva->value);
	if(!decodeItem) {
	    return NULL;
	}
	if (arena) {
	    buf = (char *)PORT_ArenaZAlloc(arena,decodeItem->len + 1);
	} else {
	    buf = (char *)PORT_ZAlloc(decodeItem->len + 1);
	}
	if ( buf ) {
	    PORT_Memcpy(buf, decodeItem->data, decodeItem->len);
	    buf[decodeItem->len] = 0;
	}
	SECITEM_FreeItem(decodeItem, PR_TRUE);
    }    
    return buf;
}

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;
}

static char *
appendItemToBuf(char *dest, SECItem *src, PRUint32 *pRemaining)
{
    if (dest && src && src->data && src->len && src->data[0] && 
        *pRemaining > src->len + 1 ) {
	PRUint32 len = src->len;
	PRUint32 i;
	for (i = 0; i < len && src->data[i] ; ++i)
	    dest[i] = tolower(src->data[i]);
	dest[len] = 0;
	dest        += len + 1;
	*pRemaining -= len + 1;
    }
    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,
			                       &current->name.directoryName, 
					       SEC_OID_PKCS9_EMAIL_ADDRESS);
		    pBuf = appendStringToBuf(pBuf, rawEmailAddr, &maxLen);

		    rawEmailAddr = CERT_GetNameElement(tmpArena,
					      &current->name.directoryName, 
					      SEC_OID_RFC1274_MAIL);
		    pBuf = appendStringToBuf(pBuf, rawEmailAddr, &maxLen);
		} else if (current->type == certRFC822Name) {
		    pBuf = appendItemToBuf(pBuf, &current->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));
}

