// --- BEGIN COPYRIGHT BLOCK --- // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; version 2 of the License. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // // (C) 2007 Red Hat, Inc. // All rights reserved. // --- END COPYRIGHT BLOCK --- package netscape.security.util; import java.io.IOException; import java.io.Serializable; import java.util.StringTokenizer; /** * Represent an ISO Object Identifier. * *

* Object Identifiers are arbitrary length hierarchical identifiers. The individual components are numbers, and they * define paths from the root of an ISO-managed identifier space. You will sometimes see a string name used instead of * (or in addition to) the numerical id. These are synonyms for the numerical IDs, but are not widely used since most * sites do not know all the requisite strings, while all sites can parse the numeric forms. * *

* So for example, JavaSoft has the sole authority to assign the meaning to identifiers below the 1.3.6.1.4.42.2.17 node * in the hierarchy, and other organizations can easily acquire the ability to assign such unique identifiers. * * @version 1.23 * * @author David Brownell * @author Amit Kapoor * @author Hemma Prafullchandra */ final public class ObjectIdentifier implements Serializable { /** use serialVersionUID from JDK 1.1. for interoperability */ private static final long serialVersionUID = 8697030238860181294L; /** * Constructs an object identifier from a string. This string * should be of the form 1.23.34.45.56 etc. */ public ObjectIdentifier(String oid) { if (oid == null) return; int ch = '.'; int start = 0; int end = 0; // Calculate length of oid componentLen = 0; while ((end = oid.indexOf(ch, start)) != -1) { start = end + 1; componentLen += 1; } componentLen += 1; components = new int[componentLen]; start = 0; int i = 0; String comp = null; while ((end = oid.indexOf(ch, start)) != -1) { comp = oid.substring(start, end); components[i++] = Integer.valueOf(comp).intValue(); start = end + 1; } comp = oid.substring(start); components[i] = Integer.valueOf(comp).intValue(); } /** * Constructs an object ID from an array of integers. This * is used to construct constant object IDs. */ public ObjectIdentifier(int values[]) { try { components = (int[]) values.clone(); componentLen = values.length; } catch (Throwable t) { System.out.println("X509.ObjectIdentifier(), no cloning!"); } } /** * Constructs an object ID from an ASN.1 encoded input stream. * The encoding of the ID in the stream uses "DER", a BER/1 subset. * In this case, that means a triple { typeId, length, data }. * *

* NOTE: When an exception is thrown, the input stream has not been returned to its "initial" * state. * * @param in DER-encoded data holding an object ID * @exception IOException indicates a decoding error */ public ObjectIdentifier(DerInputStream in) throws IOException { byte type_id; int bufferEnd; /* * Object IDs are a "universal" type, and their tag needs only * one byte of encoding. Verify that the tag of this datum * is that of an object ID. * * Then get and check the length of the ID's encoding. We set * up so that we can use in.available() to check for the end of * this value in the data stream. */ type_id = (byte) in.getByte(); if (type_id != DerValue.tag_ObjectId) throw new IOException( "X509.ObjectIdentifier() -- data isn't an object ID" + " (tag = " + type_id + ")"); bufferEnd = in.available() - in.getLength() - 1; if (bufferEnd < 0) throw new IOException( "X509.ObjectIdentifier() -- not enough data"); initFromEncoding(in, bufferEnd); } /* * Build the OID from the rest of a DER input buffer; the tag * and length have been removed/verified */ ObjectIdentifier(DerInputBuffer buf) throws IOException { initFromEncoding(new DerInputStream(buf), 0); } /* * Helper function -- get the OID from a stream, after tag and * length are verified. */ private void initFromEncoding(DerInputStream in, int bufferEnd) throws IOException { /* * Now get the components ("sub IDs") one at a time. We fill a * temporary buffer, resizing it as needed. */ int component; boolean first_subid = true; for (components = new int[allocationQuantum], componentLen = 0; in.available() > bufferEnd;) { component = getComponent(in); if (first_subid) { int X, Y; /* * The ISO root has three children (0, 1, 2) and those nodes * aren't allowed to assign IDs larger than 39. These rules * are memorialized by some special casing in the BER encoding * of object IDs ... or maybe it's vice versa. * * NOTE: the allocation quantum is large enough that we know * we don't have to reallocate here! */ if (component < 40) X = 0; else if (component < 80) X = 1; else X = 2; Y = component - (X * 40); components[0] = X; components[1] = Y; componentLen = 2; first_subid = false; } else { /* * Other components are encoded less exotically. The only * potential trouble is the need to grow the array. */ if (componentLen >= components.length) { int tmp_components[]; tmp_components = new int[components.length + allocationQuantum]; System.arraycopy(components, 0, tmp_components, 0, components.length); components = tmp_components; } components[componentLen++] = component; } } /* * Final sanity check -- if we didn't use exactly the number of bytes * specified, something's quite wrong. */ if (in.available() != bufferEnd) { throw new IOException( "X509.ObjectIdentifier() -- malformed input data"); } } /* * n.b. the only public interface is DerOutputStream.putOID() */ void encode(DerOutputStream out) throws IOException { DerOutputStream bytes = new DerOutputStream(); int i; bytes.write((components[0] * 40) + components[1]); for (i = 2; i < componentLen; i++) putComponent(bytes, components[i]); /* * Now that we've constructed the component, encode * it in the stream we were given. */ out.write(DerValue.tag_ObjectId, bytes); } /* * Tricky OID component parsing technique ... note that one bit * per octet is lost, this returns at most 28 bits of component. * Also, notice this parses in big-endian format. */ private static int getComponent(DerInputStream in) throws IOException { int retval, i, tmp; for (i = 0, retval = 0; i < 4; i++) { retval <<= 7; tmp = in.getByte(); retval |= (tmp & 0x07f); if ((tmp & 0x080) == 0) return retval; } throw new IOException("X509.OID, component value too big"); } /* * Reverse of the above routine. Notice it needs to emit in * big-endian form, so it buffers the output until it's ready. * (Minimum length encoding is a DER requirement.) */ private static void putComponent(DerOutputStream out, int val) throws IOException { int i; byte buf[] = new byte[4]; for (i = 0; i < 4; i++) { buf[i] = (byte) (val & 0x07f); val >>>= 7; if (val == 0) break; } for (; i > 0; --i) out.write(buf[i] | 0x080); out.write(buf[0]); } // XXX this API should probably facilitate the JDK sort utility /** * Compares this identifier with another, for sorting purposes. * An identifier does not precede itself. * * @param other identifer that may precede this one. * @return true iff other precedes this one * in a particular sorting order. */ public boolean precedes(ObjectIdentifier other) { int i; // shorter IDs go first if (other == this || componentLen < other.componentLen) return false; if (other.componentLen < componentLen) return true; // for each component, the lesser component goes first for (i = 0; i < componentLen; i++) { if (other.components[i] < components[i]) return true; } // identical IDs don't precede each other return false; } public boolean equals(Object other) { if (other instanceof ObjectIdentifier) return equals((ObjectIdentifier) other); else return false; } /** * Compares this identifier with another, for equality. * * @return true iff the names are identical. */ public boolean equals(ObjectIdentifier other) { int i; if (other == this) return true; if (componentLen != other.componentLen) return false; for (i = 0; i < componentLen; i++) { if (components[i] != other.components[i]) return false; } return true; } public int hashCode() { int h = 0; int oflow = 0; for (int i = 0; i < componentLen; i++) { oflow = (h & 0xff800000) >> 23; h <<= 9; h += components[i]; h ^= oflow; } return h; } /** * Returns a string form of the object ID. The format is the * conventional "dot" notation for such IDs, without any * user-friendly descriptive strings, since those strings * will not be understood everywhere. */ public String toString() { String retval; int i; for (i = 0, retval = ""; i < componentLen; i++) { if (i != 0) retval += "."; retval += components[i]; } return retval; } /* * To simplify, we assume no individual component of an object ID is * larger than 32 bits. Then we represent the path from the root as * an array that's (usually) only filled at the beginning. */ private int components[]; // path from root private int componentLen; // how much is used. private static final int allocationQuantum = 5; // >= 2 /** * Netscape Enhancement: * This function implements a object identifier factory. It * should help reduces in-memory Object Identifier object. * This function also provide additional checking on the OID. * A valid OID should start with 0, 1, or 2. * * Notes: * This function never returns null. IOException is raised * in error conditions. */ public static java.util.Hashtable mOIDs = new java.util.Hashtable(); public static ObjectIdentifier getObjectIdentifier(String oid) throws IOException { int value; if (oid == null) throw new IOException("empty object identifier"); oid = oid.trim(); ObjectIdentifier thisOID = (ObjectIdentifier) mOIDs.get(oid); if (thisOID != null) return thisOID; StringTokenizer token = new StringTokenizer(oid, "."); value = new Integer(token.nextToken()).intValue(); /* First token should be 0, 1, 2 */ if (value >= 0 && value <= 2) { value = new Integer(token.nextToken()).intValue(); /* Second token should be 0 <= && >= 39 */ if (value >= 0 && value <= 39) { thisOID = new ObjectIdentifier(oid); if (thisOID.toString().equals(oid)) { mOIDs.put(oid, thisOID); return thisOID; } throw new IOException("invalid oid " + oid); } else throw new IOException("invalid oid " + oid); } else throw new IOException("invalid oid " + oid); } public static ObjectIdentifier getObjectIdentifier(int values[]) throws IOException { String retval; int i; for (i = 0, retval = ""; i < values.length; i++) { if (i != 0) retval += "."; retval += values[i]; } return getObjectIdentifier(retval); } }