/* ***** 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 ***** */ #ifdef DEBUG static const char CVS_ID[] = "@(#) $RCSfile: oid.c,v $ $Revision: 1.6 $ $Date: 2005/01/20 02:25:49 $"; #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 PZLock *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 PLHashNumber PR_CALLBACK 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 PRIntn PR_CALLBACK 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_memequal(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 PRStatus PR_CALLBACK 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 = PZ_NewLock(nssILockOID); if( (PZLock *)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( (PZLock *)NULL != oid_hash_lock ) { PZ_DestroyLock(oid_hash_lock); oid_hash_lock = (PZLock *)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? */ PZ_Lock(oid_hash_lock); rv = (NSSOID *)PL_HashTableLookup(oid_hash_table, berOid); (void)PZ_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 = ""; rv->expl = "(OID registered at runtime)"; #endif /* DEBUG */ PZ_Lock(oid_hash_lock); e = PL_HashTableAdd(oid_hash_table, &rv->data, rv); (void)PZ_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 ) { PZ_Lock(oid_hash_lock); (void)PL_HashTableRemove(oid_hash_table, &rv->data); (void)PZ_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? */ PZ_Lock(oid_hash_lock); rv = (NSSOID *)PL_HashTableLookup(oid_hash_table, &candidate->data); (void)PZ_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 = ""; rv->expl = "(OID registered at runtime)"; #endif /* DEBUG */ PZ_Lock(oid_hash_lock); e = PL_HashTableAdd(oid_hash_table, &rv->data, rv); (void)PZ_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 ) { PZ_Lock(oid_hash_lock); (void)PL_HashTableRemove(oid_hash_table, &rv->data); (void)PZ_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;edata.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 */