diff options
Diffstat (limited to 'security/nss/lib/pki1/oid.c')
-rw-r--r-- | security/nss/lib/pki1/oid.c | 1615 |
1 files changed, 1615 insertions, 0 deletions
diff --git a/security/nss/lib/pki1/oid.c b/security/nss/lib/pki1/oid.c new file mode 100644 index 000000000..c4028a803 --- /dev/null +++ b/security/nss/lib/pki1/oid.c @@ -0,0 +1,1615 @@ +/* + * 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 Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +#ifdef DEBUG +static const char CVS_ID[] = "@(#) $RCSfile$ $Revision$ $Date$ $Name$"; +#endif /* DEBUG */ + +/* + * oid.c + * + * This file contains the implementation of the basic OID routines. + */ + +#ifndef BASE_H +#include "base.h" +#endif /* BASE_H */ + +#ifndef PKI1_H +#include "pki1.h" +#endif /* PKI1_H */ + +#include "plhash.h" +#include "plstr.h" + +/* + * NSSOID + * + * The public "methods" regarding this "object" are: + * + * NSSOID_CreateFromBER -- constructor + * NSSOID_CreateFromUTF8 -- constructor + * (there is no explicit destructor) + * + * NSSOID_GetDEREncoding + * NSSOID_GetUTF8Encoding + + * The non-public "methods" regarding this "object" are: + * + * nssOID_CreateFromBER -- constructor + * nssOID_CreateFromUTF8 -- constructor + * (there is no explicit destructor) + * + * nssOID_GetDEREncoding + * nssOID_GetUTF8Encoding + * + * In debug builds, the following non-public calls are also available: + * + * nssOID_verifyPointer + * nssOID_getExplanation + * nssOID_getTaggedUTF8 + */ + +const NSSOID *NSS_OID_UNKNOWN = (NSSOID *)NULL; + +/* + * First, the public "wrappers" + */ + +/* + * NSSOID_CreateFromBER + * + * This routine creates an NSSOID by decoding a BER- or DER-encoded + * OID. It may return NULL upon error, in which case it + * will have created an error stack. + * + * The error may be one of the following values: + * NSS_ERROR_INVALID_BER + * NSS_ERROR_NO_MEMORY + * + * Return value: + * NULL upon error + * An NSSOID upon success + */ + +NSS_EXTERN NSSOID * +NSSOID_CreateFromBER +( + NSSBER *berOid +) +{ + nss_ClearErrorStack(); + +#ifdef DEBUG + /* + * NSSBERs can be created by the user, + * so no pointer-tracking can be checked. + */ + + if( (NSSBER *)NULL == berOid ) { + nss_SetError(NSS_ERROR_INVALID_BER); + return (NSSOID *)NULL; + } + + if( (void *)NULL == berOid->data ) { + nss_SetError(NSS_ERROR_INVALID_BER); + return (NSSOID *)NULL; + } +#endif /* DEBUG */ + + return nssOID_CreateFromBER(berOid); +} + +/* + * NSSOID_CreateFromUTF8 + * + * This routine creates an NSSOID by decoding a UTF8 string + * representation of an OID in dotted-number format. The string may + * optionally begin with an octothorpe. It may return NULL + * upon error, in which case it will have created an error stack. + * + * The error may be one of the following values: + * NSS_ERROR_INVALID_UTF8 + * NSS_ERROR_NO_MEMORY + * + * Return value: + * NULL upon error + * An NSSOID upon success + */ + +NSS_EXTERN NSSOID * +NSSOID_CreateFromUTF8 +( + NSSUTF8 *stringOid +) +{ + nss_ClearErrorStack(); + +#ifdef DEBUG + /* + * NSSUTF8s can be created by the user, + * so no pointer-tracking can be checked. + */ + + if( (NSSUTF8 *)NULL == stringOid ) { + nss_SetError(NSS_ERROR_INVALID_UTF8); + return (NSSOID *)NULL; + } +#endif /* DEBUG */ + + return nssOID_CreateFromUTF8(stringOid); +} + +/* + * NSSOID_GetDEREncoding + * + * This routine returns the DER encoding of the specified NSSOID. + * If the optional arena argument is non-null, the memory used will + * be obtained from that arena; otherwise, the memory will be obtained + * from the heap. This routine may return return null upon error, in + * which case it will have created an error stack. + * + * The error may be one of the following values: + * NSS_ERROR_INVALID_NSSOID + * NSS_ERROR_NO_MEMORY + * + * Return value: + * NULL upon error + * The DER encoding of this NSSOID + */ + +NSS_EXTERN NSSDER * +NSSOID_GetDEREncoding +( + const NSSOID *oid, + NSSDER *rvOpt, + NSSArena *arenaOpt +) +{ + nss_ClearErrorStack(); + +#ifdef DEBUG + if( PR_SUCCESS != nssOID_verifyPointer(oid) ) { + return (NSSDER *)NULL; + } + + if( (NSSArena *)NULL != arenaOpt ) { + if( PR_SUCCESS != nssArena_verifyPointer(arenaOpt) ) { + return (NSSDER *)NULL; + } + } +#endif /* DEBUG */ + + return nssOID_GetDEREncoding(oid, rvOpt, arenaOpt); +} + +/* + * NSSOID_GetUTF8Encoding + * + * This routine returns a UTF8 string containing the dotted-number + * encoding of the specified NSSOID. If the optional arena argument + * is non-null, the memory used will be obtained from that arena; + * otherwise, the memory will be obtained from the heap. This routine + * may return null upon error, in which case it will have created an + * error stack. + * + * The error may be one of the following values: + * NSS_ERROR_INVALID_NSSOID + * NSS_ERROR_NO_MEMORY + * + * Return value: + * NULL upon error + * A pointer to a UTF8 string containing the dotted-digit encoding of + * this NSSOID + */ + +NSS_EXTERN NSSUTF8 * +NSSOID_GetUTF8Encoding +( + const NSSOID *oid, + NSSArena *arenaOpt +) +{ + nss_ClearErrorStack(); + +#ifdef DEBUG + if( PR_SUCCESS != nssOID_verifyPointer(oid) ) { + return (NSSUTF8 *)NULL; + } + + if( (NSSArena *)NULL != arenaOpt ) { + if( PR_SUCCESS != nssArena_verifyPointer(arenaOpt) ) { + return (NSSUTF8 *)NULL; + } + } +#endif /* DEBUG */ + + return nssOID_GetUTF8Encoding(oid, arenaOpt); +} + +/* + * Next, some internal bookkeeping; including the OID "tag" table + * and the debug-version pointer tracker. + */ + +/* + * For implementation reasons (so NSSOIDs can be compared with ==), + * we hash all NSSOIDs. This is the hash table. + */ + +static PLHashTable *oid_hash_table; + +/* + * And this is its lock. + */ + +static PRLock *oid_hash_lock; + +/* + * This is the hash function. We simply XOR the encoded form with + * itself in sizeof(PLHashNumber)-byte chunks. Improving this + * routine is left as an excercise for the more mathematically + * inclined student. + */ + +static PR_CALLBACK PLHashNumber +oid_hash +( + const void *key +) +{ + const NSSItem *item = (const NSSItem *)key; + PLHashNumber rv = 0; + + PRUint8 *data = (PRUint8 *)item->data; + PRUint32 i; + PRUint8 *rvc = (PRUint8 *)&rv; + + for( i = 0; i < item->size; i++ ) { + rvc[ i % sizeof(rv) ] ^= *data; + data++; + } + + return rv; +} + +/* + * This is the key-compare function. It simply does a lexical + * comparison on the encoded OID form. This does not result in + * quite the same ordering as the "sequence of numbers" order, + * but heck it's only used internally by the hash table anyway. + */ + +static PR_CALLBACK PRIntn +oid_hash_compare +( + const void *k1, + const void *k2 +) +{ + PRIntn rv; + + const NSSItem *i1 = (const NSSItem *)k1; + const NSSItem *i2 = (const NSSItem *)k2; + + PRUint32 size = (i1->size < i2->size) ? i1->size : i2->size; + + rv = (PRIntn)nsslibc_memcmp(i1->data, i2->data, size, (PRStatus *)NULL); + if( 0 == rv ) { + rv = i1->size - i2->size; + } + + return !rv; +} + +/* + * The pointer-tracking code + */ + +#ifdef DEBUG +extern const NSSError NSS_ERROR_INTERNAL_ERROR; + +static nssPointerTracker oid_pointer_tracker; + +static PRStatus +oid_add_pointer +( + const NSSOID *oid +) +{ + PRStatus rv; + + rv = nssPointerTracker_initialize(&oid_pointer_tracker); + if( PR_SUCCESS != rv ) { + return rv; + } + + rv = nssPointerTracker_add(&oid_pointer_tracker, oid); + if( PR_SUCCESS != rv ) { + NSSError e = NSS_GetError(); + if( NSS_ERROR_NO_MEMORY != e ) { + nss_SetError(NSS_ERROR_INTERNAL_ERROR); + } + + return rv; + } + + return PR_SUCCESS; +} + +#if defined(CAN_DELETE_OIDS) +/* + * We actually don't define NSSOID deletion, since we keep OIDs + * in a hash table for easy comparison. Were we to, this is + * what the pointer-removal function would look like. + */ + +static PRStatus +oid_remove_pointer +( + const NSSOID *oid +) +{ + PRStatus rv; + + rv = nssPointerTracker_remove(&oid_pointer_tracker, oid); + if( PR_SUCCESS != rv ) { + nss_SetError(NSS_ERROR_INTERNAL_ERROR); + } + + return rv; +} +#endif /* CAN_DELETE_OIDS */ + +#endif /* DEBUG */ + +/* + * All dynamically-added OIDs get their memory from one statically- + * declared arena here, merely so that any cleanup code will have + * an easier time of it. + */ + +static NSSArena *oid_arena; + +/* + * This is the call-once function which initializes the hashtable. + * It creates it, then prepopulates it with all of the builtin OIDs. + * It also creates the aforementioned NSSArena. + */ + +static PR_CALLBACK PRStatus +oid_once_func +( + void +) +{ + PRUint32 i; + + /* Initialize the arena */ + oid_arena = nssArena_Create(); + if( (NSSArena *)NULL == oid_arena ) { + goto loser; + } + + /* Create the hash table lock */ + oid_hash_lock = PR_NewLock(); + if( (PRLock *)NULL == oid_hash_lock ) { + nss_SetError(NSS_ERROR_NO_MEMORY); + goto loser; + } + + /* Create the hash table */ + oid_hash_table = PL_NewHashTable(0, oid_hash, oid_hash_compare, + PL_CompareValues, + (PLHashAllocOps *)0, + (void *)0); + if( (PLHashTable *)NULL == oid_hash_table ) { + nss_SetError(NSS_ERROR_NO_MEMORY); + goto loser; + } + + /* And populate it with all the builtins */ + for( i = 0; i < nss_builtin_oid_count; i++ ) { + NSSOID *oid = (NSSOID *)&nss_builtin_oids[i]; + PLHashEntry *e = PL_HashTableAdd(oid_hash_table, &oid->data, oid); + if( (PLHashEntry *)NULL == e ) { + nss_SetError(NSS_ERROR_NO_MEMORY); + goto loser; + } + +#ifdef DEBUG + if( PR_SUCCESS != oid_add_pointer(oid) ) { + goto loser; + } +#endif /* DEBUG */ + } + + return PR_SUCCESS; + + loser: + if( (PLHashTable *)NULL != oid_hash_table ) { + PL_HashTableDestroy(oid_hash_table); + oid_hash_table = (PLHashTable *)NULL; + } + + if( (PRLock *)NULL != oid_hash_lock ) { + PR_DestroyLock(oid_hash_lock); + oid_hash_lock = (PRLock *)NULL; + } + + if( (NSSArena *)NULL != oid_arena ) { + (void)nssArena_Destroy(oid_arena); + oid_arena = (NSSArena *)NULL; + } + + return PR_FAILURE; +} + +/* + * This is NSPR's once-block. + */ + +static PRCallOnceType oid_call_once; + +/* + * And this is our multiply-callable internal init routine, which + * will call-once our call-once function. + */ + +static PRStatus +oid_init +( + void +) +{ + return PR_CallOnce(&oid_call_once, oid_once_func); +} + +#ifdef DEBUG + +/* + * nssOID_verifyPointer + * + * This method is only present in debug builds. + * + * If the specified pointer is a valid pointer to an NSSOID object, + * this routine will return PR_SUCCESS. Otherwise, it will put an + * error on the error stack and return PR_FAILURE. + * + * The error may be one of the following values: + * NSS_ERROR_INVALID_NSSOID + * NSS_ERROR_NO_MEMORY + * + * Return value: + * PR_SUCCESS if the pointer is valid + * PR_FAILURE if it isn't + */ + +NSS_EXTERN PRStatus +nssOID_verifyPointer +( + const NSSOID *oid +) +{ + PRStatus rv; + + rv = oid_init(); + if( PR_SUCCESS != rv ) { + return PR_FAILURE; + } + + rv = nssPointerTracker_initialize(&oid_pointer_tracker); + if( PR_SUCCESS != rv ) { + return PR_FAILURE; + } + + rv = nssPointerTracker_verify(&oid_pointer_tracker, oid); + if( PR_SUCCESS != rv ) { + nss_SetError(NSS_ERROR_INVALID_NSSOID); + return PR_FAILURE; + } + + return PR_SUCCESS; +} +#endif /* DEBUG */ + +/* + * oid_sanity_check_ber + * + * This routine merely applies some sanity-checking to the BER-encoded + * OID. + */ + +static PRStatus +oid_sanity_check_ber +( + NSSBER *berOid +) +{ + PRUint32 i; + PRUint8 *data = (PRUint8 *)berOid->data; + + /* + * The size must be longer than zero bytes. + */ + + if( berOid->size <= 0 ) { + return PR_FAILURE; + } + + /* + * In general, we can't preclude any number from showing up + * someday. We could probably guess that top-level numbers + * won't get very big (beyond the current ccitt(0), iso(1), + * or joint-ccitt-iso(2)). However, keep in mind that the + * encoding rules wrap the first two numbers together, as + * + * (first * 40) + second + * + * Also, it is noted in the specs that this implies that the + * second number won't go above forty. + * + * 128 encodes 3.8, which seems pretty safe for now. Let's + * check that the first byte is less than that. + * + * XXX This is a "soft check" -- we may want to exclude it. + */ + + if( data[0] >= 0x80 ) { + return PR_FAILURE; + } + + /* + * In a normalised format, leading 0x80s will never show up. + * This means that no 0x80 will be preceeded by the final + * byte of a sequence, which would naturaly be less than 0x80. + * Our internal encoding for the single-digit OIDs uses 0x80, + * but the only places we use them (loading the builtin table, + * and adding a UTF8-encoded OID) bypass this check. + */ + + for( i = 1; i < berOid->size; i++ ) { + if( (0x80 == data[i]) && (data[i-1] < 0x80) ) { + return PR_FAILURE; + } + } + + /* + * The high bit of each octet indicates that following octets + * are included in the current number. Thus the last byte can't + * have the high bit set. + */ + + if( data[ berOid->size-1 ] >= 0x80 ) { + return PR_FAILURE; + } + + /* + * Other than that, any byte sequence is legit. + */ + return PR_SUCCESS; +} + +/* + * nssOID_CreateFromBER + * + * This routine creates an NSSOID by decoding a BER- or DER-encoded + * OID. It may return NULL upon error, in which case it + * will have set an error on the error stack. + * + * The error may be one of the following values: + * NSS_ERROR_INVALID_BER + * NSS_ERROR_NO_MEMORY + * + * Return value: + * NULL upon error + * An NSSOID upon success + */ + +NSS_EXTERN NSSOID * +nssOID_CreateFromBER +( + NSSBER *berOid +) +{ + NSSOID *rv; + PLHashEntry *e; + + if( PR_SUCCESS != oid_init() ) { + return (NSSOID *)NULL; + } + + if( PR_SUCCESS != oid_sanity_check_ber(berOid) ) { + nss_SetError(NSS_ERROR_INVALID_BER); + return (NSSOID *)NULL; + } + + /* + * Does it exist? + */ + PR_Lock(oid_hash_lock); + rv = (NSSOID *)PL_HashTableLookup(oid_hash_table, berOid); + (void)PR_Unlock(oid_hash_lock); + if( (NSSOID *)NULL != rv ) { + /* Found it! */ + return rv; + } + + /* + * Doesn't exist-- create it. + */ + rv = nss_ZNEW(oid_arena, NSSOID); + if( (NSSOID *)NULL == rv ) { + return (NSSOID *)NULL; + } + + rv->data.data = nss_ZAlloc(oid_arena, berOid->size); + if( (void *)NULL == rv->data.data ) { + return (NSSOID *)NULL; + } + + rv->data.size = berOid->size; + nsslibc_memcpy(rv->data.data, berOid->data, berOid->size); + +#ifdef DEBUG + rv->tag = "<runtime>"; + rv->expl = "(OID registered at runtime)"; +#endif /* DEBUG */ + + PR_Lock(oid_hash_lock); + e = PL_HashTableAdd(oid_hash_table, &rv->data, rv); + (void)PR_Unlock(oid_hash_lock); + if( (PLHashEntry *)NULL == e ) { + nss_ZFreeIf(rv->data.data); + nss_ZFreeIf(rv); + nss_SetError(NSS_ERROR_NO_MEMORY); + return (NSSOID *)NULL; + } + +#ifdef DEBUG + { + PRStatus st; + st = oid_add_pointer(rv); + if( PR_SUCCESS != st ) { + PR_Lock(oid_hash_lock); + (void)PL_HashTableRemove(oid_hash_table, &rv->data); + (void)PR_Unlock(oid_hash_lock); + (void)nss_ZFreeIf(rv->data.data); + (void)nss_ZFreeIf(rv); + return (NSSOID *)NULL; + } + } +#endif /* DEBUG */ + + return rv; +} + +/* + * oid_sanity_check_utf8 + * + * This routine merely applies some sanity-checking to the + * UTF8-encoded OID. + */ + +static PRStatus +oid_sanity_check_utf8 +( + NSSUTF8 *s +) +{ + /* + * It may begin with an octothorpe, which we skip. + */ + + if( '#' == *s ) { + s++; + } + + /* + * It begins with a number + */ + + if( (*s < '0') || (*s > '9') ) { + return PR_FAILURE; + } + + /* + * First number is only one digit long + * + * XXX This is a "soft check" -- we may want to exclude it + */ + + if( (s[1] != '.') && (s[1] != '\0') ) { + return PR_FAILURE; + } + + /* + * Every character is either a digit or a period + */ + + for( ; '\0' != *s; s++ ) { + if( ('.' != *s) && ((*s < '0') || (*s > '9')) ) { + return PR_FAILURE; + } + + /* No two consecutive periods */ + if( ('.' == *s) && ('.' == s[1]) ) { + return PR_FAILURE; + } + } + + /* + * The last character isn't a period + */ + + if( '.' == *--s ) { + return PR_FAILURE; + } + + return PR_SUCCESS; +} + +static PRUint32 +oid_encode_number +( + PRUint32 n, + PRUint8 *dp, + PRUint32 nb +) +{ + PRUint32 a[5]; + PRUint32 i; + PRUint32 rv; + + a[0] = (n >> 28) & 0x7f; + a[1] = (n >> 21) & 0x7f; + a[2] = (n >> 14) & 0x7f; + a[3] = (n >> 7) & 0x7f; + a[4] = n & 0x7f; + + for( i = 0; i < 5; i++ ) { + if( 0 != a[i] ) { + break; + } + } + + if( 5 == i ) { + i--; + } + + rv = 5-i; + if( rv > nb ) { + return rv; + } + + for( ; i < 4; i++ ) { + *dp = 0x80 | a[i]; + dp++; + } + + *dp = a[4]; + + return rv; +} + +/* + * oid_encode_huge + * + * This routine will convert a huge decimal number into the DER + * encoding for oid numbers. It is not limited to numbers that will + * fit into some wordsize, like oid_encode_number. But it's not + * necessarily very fast, either. This is here in case some joker + * throws us an ASCII oid like 1.2.3.99999999999999999999999999. + */ + +static PRUint32 +oid_encode_huge +( + NSSUTF8 *s, + NSSUTF8 *e, + PRUint8 *dp, + PRUint32 nb +) +{ + PRUint32 slen = (e-s); + PRUint32 blen = (slen+1)/2; + PRUint8 *st = (PRUint8 *)NULL; + PRUint8 *bd = (PRUint8 *)NULL; + PRUint32 i; + PRUint32 bitno; + PRUint8 *last; + PRUint8 *first; + PRUint32 byteno; + PRUint8 mask; + + /* We'll be munging the data, so duplicate it */ + st = (PRUint8 *)nss_ZAlloc((NSSArena *)NULL, slen); + if( (PRUint8 *)NULL == st ) { + return 0; + } + + /* Don't know ahead of time exactly how long we'll need */ + bd = (PRUint8 *)nss_ZAlloc((NSSArena *)NULL, blen); + if( (PRUint8 *)NULL == bd ) { + (void)nss_ZFreeIf(st); + return 0; + } + + /* Copy the original, and convert ASCII to numbers */ + for( i = 0; i < slen; i++ ) { + st[i] = (PRUint8)(s[i] - '0'); + } + + last = &st[slen-1]; + first = &st[0]; + + /* + * The way we create the binary version is by looking at it + * bit by bit. Start with the least significant bit. If the + * number is odd, set that bit. Halve the number (with integer + * division), and go to the next least significant bit. Keep + * going until the number goes to zero. + */ + for( bitno = 0; ; bitno++ ) { + PRUint8 *d; + + byteno = bitno/7; + mask = (PRUint8)(1 << (bitno%7)); + + /* Skip leading zeroes */ + for( ; first < last; first ++ ) { + if( 0 != *first ) { + break; + } + } + + /* Down to one number and it's a zero? Done. */ + if( (first == last) && (0 == *last) ) { + break; + } + + /* Last digit is odd? Set the bit */ + if( *last & 1 ) { + bd[ byteno ] |= mask; + } + + + /* + * Divide the number in half. This is just a matter + * of going from the least significant digit upwards, + * halving each one. If any digit is odd (other than + * the last, which has already been handled), add five + * to the digit to its right. + */ + *last /= 2; + + for( d = &last[-1]; d >= first; d-- ) { + if( *d & 1 ) { + d[1] += 5; + } + + *d /= 2; + } + } + + /* Is there room to write the encoded data? */ + if( (byteno+1) > nb ) { + return (byteno+1); + } + + /* Trim any leading zero that crept in there */ + for( ; byteno > 0; byteno-- ) { + if( 0 != bd[ byteno ] ) { + break; + } + } + + /* Copy all but the last, marking the "continue" bit */ + for( i = 0; i < byteno; i++ ) { + dp[i] = bd[ byteno-i ] | 0x80; + } + /* And the last with the "continue" bit clear */ + dp[byteno] = bd[0]; + + (void)nss_ZFreeIf(bd); + (void)nss_ZFreeIf(st); + return (byteno+1); +} + +/* + * oid_encode_string + * + * This routine converts a dotted-number OID into a DER-encoded + * one. It assumes we've already sanity-checked the string. + */ + +extern const NSSError NSS_ERROR_INTERNAL_ERROR; + +static NSSOID * +oid_encode_string +( + NSSUTF8 *s +) +{ + PRUint32 nn = 0; /* number of numbers */ + PRUint32 nb = 0; /* number of bytes (estimated) */ + NSSUTF8 *t; + PRUint32 nd = 0; /* number of digits */ + NSSOID *rv; + PRUint8 *dp; + PRUint32 a, b; + PRUint32 inc; + + /* Dump any octothorpe */ + if( '#' == *s ) { + s++; + } + + /* Count up the bytes needed */ + for( t = s; '\0' != *t; t++ ) { + if( '.' == *t ) { + nb += (nd+1)/2; /* errs on the big side */ + nd = 0; + nn++; + } else { + nd++; + } + } + nb += (nd+1)/2; + nn++; + + if( 1 == nn ) { + /* + * We have our own "denormalised" encoding for these, + * which is only used internally. + */ + nb++; + } + + /* + * Allocate. Note that we don't use the oid_arena here.. this is + * because there really isn't a "free()" for stuff allocated out of + * arenas (at least with the current implementation), so this would + * keep using up memory each time a UTF8-encoded OID were added. + * If need be (if this is the first time this oid has been seen), + * we'll copy it. + */ + rv = nss_ZNEW((NSSArena *)NULL, NSSOID); + if( (NSSOID *)NULL == rv ) { + return (NSSOID *)NULL; + } + + rv->data.data = nss_ZAlloc((NSSArena *)NULL, nb); + if( (void *)NULL == rv->data.data ) { + (void)nss_ZFreeIf(rv); + return (NSSOID *)NULL; + } + + dp = (PRUint8 *)rv->data.data; + + a = atoi(s); + + if( 1 == nn ) { + dp[0] = '\x80'; + inc = oid_encode_number(a, &dp[1], nb-1); + if( inc >= nb ) { + goto loser; + } + } else { + for( t = s; '.' != *t; t++ ) { + ; + } + + t++; + b = atoi(t); + inc = oid_encode_number(a*40+b, dp, nb); + if( inc > nb ) { + goto loser; + } + dp += inc; + nb -= inc; + nn -= 2; + + while( nn-- > 0 ) { + NSSUTF8 *u; + + for( ; '.' != *t; t++ ) { + ; + } + + t++; + + for( u = t; ('\0' != *u) && ('.' != *u); u++ ) { + ; + } + + if( (u-t > 9) ) { + /* In the billions. Rats. */ + inc = oid_encode_huge(t, u, dp, nb); + } else { + b = atoi(t); + inc = oid_encode_number(b, dp, nb); + } + + if( inc > nb ) { + goto loser; + } + dp += inc; + nb -= inc; + } + } + + return rv; + + loser: + nss_SetError(NSS_ERROR_INTERNAL_ERROR); + return (NSSOID *)NULL; +} + +/* + * nssOID_CreateFromUTF8 + * + * This routine creates an NSSOID by decoding a UTF8 string + * representation of an OID in dotted-number format. The string may + * optionally begin with an octothorpe. It may return NULL + * upon error, in which case it will have set an error on the error + * stack. + * + * The error may be one of the following values: + * NSS_ERROR_INVALID_STRING + * NSS_ERROR_NO_MEMORY + * + * Return value: + * NULL upon error + * An NSSOID upon success + */ + +NSS_EXTERN NSSOID * +nssOID_CreateFromUTF8 +( + NSSUTF8 *stringOid +) +{ + NSSOID *rv = (NSSOID *)NULL; + NSSOID *candidate = (NSSOID *)NULL; + PLHashEntry *e; + + if( PR_SUCCESS != oid_init() ) { + return (NSSOID *)NULL; + } + + if( PR_SUCCESS != oid_sanity_check_utf8(stringOid) ) { + nss_SetError(NSS_ERROR_INVALID_STRING); + return (NSSOID *)NULL; + } + + candidate = oid_encode_string(stringOid); + if( (NSSOID *)NULL == candidate ) { + /* Internal error only */ + return rv; + } + + /* + * Does it exist? + */ + PR_Lock(oid_hash_lock); + rv = (NSSOID *)PL_HashTableLookup(oid_hash_table, &candidate->data); + (void)PR_Unlock(oid_hash_lock); + if( (NSSOID *)NULL != rv ) { + /* Already exists. Delete my copy and return the original. */ + (void)nss_ZFreeIf(candidate->data.data); + (void)nss_ZFreeIf(candidate); + return rv; + } + + /* + * Nope. Add it. Remember to allocate it out of the oid arena. + */ + + rv = nss_ZNEW(oid_arena, NSSOID); + if( (NSSOID *)NULL == rv ) { + goto loser; + } + + rv->data.data = nss_ZAlloc(oid_arena, candidate->data.size); + if( (void *)NULL == rv->data.data ) { + goto loser; + } + + rv->data.size = candidate->data.size; + nsslibc_memcpy(rv->data.data, candidate->data.data, rv->data.size); + + (void)nss_ZFreeIf(candidate->data.data); + (void)nss_ZFreeIf(candidate); + +#ifdef DEBUG + rv->tag = "<runtime>"; + rv->expl = "(OID registered at runtime)"; +#endif /* DEBUG */ + + PR_Lock(oid_hash_lock); + e = PL_HashTableAdd(oid_hash_table, &rv->data, rv); + (void)PR_Unlock(oid_hash_lock); + if( (PLHashEntry *)NULL == e ) { + nss_SetError(NSS_ERROR_NO_MEMORY); + goto loser; + } + +#ifdef DEBUG + { + PRStatus st; + st = oid_add_pointer(rv); + if( PR_SUCCESS != st ) { + PR_Lock(oid_hash_lock); + (void)PL_HashTableRemove(oid_hash_table, &rv->data); + (void)PR_Unlock(oid_hash_lock); + goto loser; + } + } +#endif /* DEBUG */ + + return rv; + + loser: + if( (NSSOID *)NULL != candidate ) { + (void)nss_ZFreeIf(candidate->data.data); + } + (void)nss_ZFreeIf(candidate); + + if( (NSSOID *)NULL != rv ) { + (void)nss_ZFreeIf(rv->data.data); + } + (void)nss_ZFreeIf(rv); + + return (NSSOID *)NULL; +} + +/* + * nssOID_GetDEREncoding + * + * This routine returns the DER encoding of the specified NSSOID. + * If the optional arena argument is non-null, the memory used will + * be obtained from that arena; otherwise, the memory will be obtained + * from the heap. This routine may return return null upon error, in + * which case it will have set an error on the error stack. + * + * The error may be one of the following values: + * NSS_ERROR_INVALID_OID + * NSS_ERROR_NO_MEMORY + * + * Return value: + * NULL upon error + * The DER encoding of this NSSOID + */ + +NSS_EXTERN NSSDER * +nssOID_GetDEREncoding +( + const NSSOID *oid, + NSSDER *rvOpt, + NSSArena *arenaOpt +) +{ + const NSSItem *it; + NSSDER *rv; + + if( PR_SUCCESS != oid_init() ) { + return (NSSDER *)NULL; + } + +#ifdef NSSDEBUG + if( PR_SUCCESS != nssOID_verifyPointer(oid) ) { + return (NSSDER *)NULL; + } + + if( (NSSArena *)NULL != arenaOpt ) { + if( PR_SUCCESS != nssArena_verifyPointer(arenaOpt) ) { + return (NSSDER *)NULL; + } + } +#endif /* NSSDEBUG */ + + it = &oid->data; + + if( (NSSDER *)NULL == rvOpt ) { + rv = nss_ZNEW(arenaOpt, NSSDER); + if( (NSSDER *)NULL == rv ) { + return (NSSDER *)NULL; + } + } else { + rv = rvOpt; + } + + rv->data = nss_ZAlloc(arenaOpt, it->size); + if( (void *)NULL == rv->data ) { + if( rv != rvOpt ) { + (void)nss_ZFreeIf(rv); + } + return (NSSDER *)NULL; + } + + rv->size = it->size; + nsslibc_memcpy(rv->data, it->data, it->size); + + return rv; +} + +/* + * nssOID_GetUTF8Encoding + * + * This routine returns a UTF8 string containing the dotted-number + * encoding of the specified NSSOID. If the optional arena argument + * is non-null, the memory used will be obtained from that arena; + * otherwise, the memory will be obtained from the heap. This routine + * may return null upon error, in which case it will have set an error + * on the error stack. + * + * The error may be one of the following values: + * NSS_ERROR_INVALID_OID + * NSS_ERROR_NO_MEMORY + * + * Return value: + * NULL upon error + * A pointer to a UTF8 string containing the dotted-digit encoding of + * this NSSOID + */ + +NSS_EXTERN NSSUTF8 * +nssOID_GetUTF8Encoding +( + const NSSOID *oid, + NSSArena *arenaOpt +) +{ + NSSUTF8 *rv; + PRUint8 *end; + PRUint8 *d; + PRUint8 *e; + char *a; + char *b; + PRUint32 len; + + if( PR_SUCCESS != oid_init() ) { + return (NSSUTF8 *)NULL; + } + +#ifdef NSSDEBUG + if( PR_SUCCESS != nssOID_verifyPointer(oid) ) { + return (NSSUTF8 *)NULL; + } + + if( (NSSArena *)NULL != arenaOpt ) { + if( PR_SUCCESS != nssArena_verifyPointer(arenaOpt) ) { + return (NSSUTF8 *)NULL; + } + } +#endif /* NSSDEBUG */ + + a = (char *)NULL; + + /* d will point to the next sequence of bytes to decode */ + d = (PRUint8 *)oid->data.data; + /* end points to one past the legitimate data */ + end = &d[ oid->data.size ]; + +#ifdef NSSDEBUG + /* + * Guarantee that the for(e=d;e<end;e++) loop below will + * terminate. Our BER sanity-checking code above will prevent + * such a BER from being registered, so the only other way one + * might show up is if our dotted-decimal encoder above screws + * up or our generated list is wrong. So I'll wrap it with + * #ifdef NSSDEBUG and #endif. + */ + if( end[-1] & 0x80 ) { + nss_SetError(NSS_ERROR_INTERNAL_ERROR); + return (NSSUTF8 *)NULL; + } +#endif /* NSSDEBUG */ + + /* + * Check for our pseudo-encoded single-digit OIDs + */ + if( (*d == 0x80) && (2 == oid->data.size) ) { + /* Funky encoding. The second byte is the number */ + a = PR_smprintf("%lu", (PRUint32)d[1]); + if( (char *)NULL == a ) { + nss_SetError(NSS_ERROR_NO_MEMORY); + return (NSSUTF8 *)NULL; + } + goto done; + } + + 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 = (n/40), two = (n%40); + + a = PR_smprintf("%lu.%lu", one, two); + if( (char *)NULL == a ) { + nss_SetError(NSS_ERROR_NO_MEMORY); + return (NSSUTF8 *)NULL; + } + } else { + b = PR_smprintf("%s.%lu", a, n); + if( (char *)NULL == b ) { + PR_smprintf_free(a); + nss_SetError(NSS_ERROR_NO_MEMORY); + return (NSSUTF8 *)NULL; + } + + PR_smprintf_free(a); + a = b; + } + } + } + + done: + /* + * Even if arenaOpt is NULL, we have to copy the data so that + * it'll be freed with the right version of free: ours, not + * PR_smprintf_free's. + */ + len = PL_strlen(a); + rv = (NSSUTF8 *)nss_ZAlloc(arenaOpt, len); + if( (NSSUTF8 *)NULL == rv ) { + PR_smprintf_free(a); + return (NSSUTF8 *)NULL; + } + + nsslibc_memcpy(rv, a, len); + PR_smprintf_free(a); + + return rv; +} + +/* + * nssOID_getExplanation + * + * This method is only present in debug builds. + * + * This routine will return a static pointer to a UTF8-encoded string + * describing (in English) the specified OID. The memory pointed to + * by the return value is not owned by the caller, and should not be + * freed or modified. Note that explanations are only provided for + * the OIDs built into the NSS library; there is no way to specify an + * explanation for dynamically created OIDs. This routine is intended + * only for use in debugging tools such as "derdump." This routine + * may return null upon error, in which case it will have placed an + * error on the error stack. + * + * The error may be one of the following values: + * NSS_ERROR_INVALID_NSSOID + * + * Return value: + * NULL upon error + * A static pointer to a readonly, non-caller-owned UTF8-encoded + * string explaining the specified OID. + */ + +#ifdef DEBUG +NSS_EXTERN const NSSUTF8 * +nssOID_getExplanation +( + NSSOID *oid +) +{ + if( PR_SUCCESS != oid_init() ) { + return (const NSSUTF8 *)NULL; + } + +#ifdef NSSDEBUG + if( PR_SUCCESS != nssOID_verifyPointer(oid) ) { + return (NSSUTF8 *)NULL; + } +#endif /* NSSDEBUG */ + + return oid->expl; +} + +extern const NSSError NSS_ERROR_INVALID_NSSOID; +#endif /* DEBUG */ + +/* + * nssOID_getTaggedUTF8 + * + * This method is only present in debug builds. + * + * This routine will return a pointer to a caller-owned UTF8-encoded + * string containing a tagged encoding of the specified OID. Note + * that OID (component) tags are only provided for the OIDs built + * into the NSS library; there is no way to specify tags for + * dynamically created OIDs. This routine is intended for use in + * debugging tools such as "derdump." If the optional arena argument + * is non-null, the memory used will be obtained from that arena; + * otherwise, the memory will be obtained from the heap. This routine + * may return return null upon error, in which case it will have set + * an error on the error stack. + * + * The error may be one of the following values + * NSS_ERROR_INVALID_NSSOID + * NSS_ERROR_NO_MEMORY + * + * Return value: + * NULL upon error + * A pointer to a UTF8 string containing the tagged encoding of + * this NSSOID + */ + +#ifdef DEBUG +NSS_EXTERN NSSUTF8 * +nssOID_getTaggedUTF8 +( + NSSOID *oid, + NSSArena *arenaOpt +) +{ + NSSUTF8 *rv; + char *raw; + char *c; + char *a = (char *)NULL; + char *b; + PRBool done = PR_FALSE; + PRUint32 len; + + if( PR_SUCCESS != oid_init() ) { + return (NSSUTF8 *)NULL; + } + +#ifdef NSSDEBUG + if( PR_SUCCESS != nssOID_verifyPointer(oid) ) { + return (NSSUTF8 *)NULL; + } + + if( (NSSArena *)NULL != arenaOpt ) { + if( PR_SUCCESS != nssArena_verifyPointer(arenaOpt) ) { + return (NSSUTF8 *)NULL; + } + } +#endif /* NSSDEBUG */ + + a = PR_smprintf("{"); + if( (char *)NULL == a ) { + nss_SetError(NSS_ERROR_NO_MEMORY); + return (NSSUTF8 *)NULL; + } + + /* + * What I'm doing here is getting the text version of the OID, + * e.g. 1.2.12.92, then looking up each set of leading numbers + * as oids.. e.g. "1," then "1.2," then "1.2.12," etc. Each of + * those will have the leaf tag, and I just build up the string. + * I never said this was the most efficient way of doing it, + * but hey it's a debug-build thing, and I'm getting really tired + * of writing this stupid low-level PKI code. + */ + + /* I know it's all ASCII, so I can use char */ + raw = (char *)nssOID_GetUTF8Encoding(oid, (NSSArena *)NULL); + if( (char *)NULL == raw ) { + return (NSSUTF8 *)NULL; + } + + for( c = raw; !done; c++ ) { + NSSOID *lead; + char *lastdot; + + for( ; '.' != *c; c++ ) { + if( '\0' == *c ) { + done = PR_TRUE; + break; + } + } + + *c = '\0'; + lead = nssOID_CreateFromUTF8((NSSUTF8 *)raw); + if( (NSSOID *)NULL == lead ) { + PR_smprintf_free(a); + nss_ZFreeIf(raw); + nss_SetError(NSS_ERROR_NO_MEMORY); + return (NSSUTF8 *)NULL; + } + + lastdot = PL_strrchr(raw, '.'); + if( (char *)NULL == lastdot ) { + lastdot = raw; + } + + b = PR_smprintf("%s %s(%s) ", a, lead->tag, &lastdot[1]); + if( (char *)NULL == b ) { + PR_smprintf_free(a); + nss_ZFreeIf(raw); + /* drop the OID reference on the floor */ + nss_SetError(NSS_ERROR_NO_MEMORY); + return (NSSUTF8 *)NULL; + } + + PR_smprintf_free(a); + a = b; + + if( !done ) { + *c = '.'; + } + } + + nss_ZFreeIf(raw); + + b = PR_smprintf("%s }", a); + if( (char *)NULL == b ) { + PR_smprintf_free(a); + nss_SetError(NSS_ERROR_NO_MEMORY); + return (NSSUTF8 *)NULL; + } + + len = PL_strlen(b); + + rv = (NSSUTF8 *)nss_ZAlloc(arenaOpt, len+1); + if( (NSSUTF8 *)NULL == rv ) { + PR_smprintf_free(b); + return (NSSUTF8 *)NULL; + } + + nsslibc_memcpy(rv, b, len); + PR_smprintf_free(b); + + return rv; +} + +extern const NSSError NSS_ERROR_INVALID_NSSOID; +extern const NSSError NSS_ERROR_NO_MEMORY; +#endif /* DEBUG */ |