4811968: ASN.1 (X509Certificate) implementations don't handle large OID components
authorweijun
Wed, 08 Apr 2009 13:54:34 +0800
changeset 2584 a89e7cabf6fd
parent 2449 509945aa3d2a
child 2585 ff4e9215390a
4811968: ASN.1 (X509Certificate) implementations don't handle large OID components Reviewed-by: xuelei
jdk/src/share/classes/sun/security/util/ObjectIdentifier.java
jdk/test/sun/security/util/Oid/OidFormat.java
jdk/test/sun/security/util/Oid/S11N.sh
jdk/test/sun/security/util/Oid/SerialTest.java
--- a/jdk/src/share/classes/sun/security/util/ObjectIdentifier.java	Mon Apr 06 18:52:03 2009 -0700
+++ b/jdk/src/share/classes/sun/security/util/ObjectIdentifier.java	Wed Apr 08 13:54:34 2009 +0800
@@ -26,7 +26,8 @@
 package sun.security.util;
 
 import java.io.*;
-
+import java.math.BigInteger;
+import java.util.Arrays;
 
 /**
  * Represent an ISO Object Identifier.
@@ -44,105 +45,186 @@
  * hierarchy, and other organizations can easily acquire the ability
  * to assign such unique identifiers.
  *
- *
  * @author David Brownell
  * @author Amit Kapoor
  * @author Hemma Prafullchandra
  */
+
 final public
 class ObjectIdentifier implements Serializable
 {
-    /** use serialVersionUID from JDK 1.1. for interoperability */
+    /**
+     * We use the DER value (no tag, no length) as the internal format
+     * @serial
+     */
+    private byte[] encoding = null;
+
+    private transient volatile String stringForm;
+
+    /*
+     * IMPORTANT NOTES FOR CODE CHANGES (bug 4811968) IN JDK 1.7.0
+     * ===========================================================
+     *
+     * (Almost) serialization compatibility with old versions:
+     *
+     * serialVersionUID is unchanged. Old field "component" is changed to
+     * type Object so that "poison" (unknown object type for old versions)
+     * can be put inside if there are huge components that cannot be saved
+     * as integers.
+     *
+     * New version use the new filed "encoding" only.
+     *
+     * Below are all 4 cases in a serialization/deserialization process:
+     *
+     * 1. old -> old: Not covered here
+     * 2. old -> new: There's no "encoding" field, new readObject() reads
+     *    "components" and "componentLen" instead and inits correctly.
+     * 3. new -> new: "encoding" field exists, new readObject() uses it
+     *    (ignoring the other 2 fields) and inits correctly.
+     * 4. new -> old: old readObject() only recognizes "components" and
+     *    "componentLen" fields. If no huge components are involved, they
+     *    are serialized as legal values and old object can init correctly.
+     *    Otherwise, old object cannot recognize the form (component not int[])
+     *    and throw a ClassNotFoundException at deserialization time.
+     *
+     * Therfore, for the first 3 cases, exact compatibility is preserved. In
+     * the 4th case, non-huge OID is still supportable in old versions, while
+     * huge OID is not.
+     */
     private static final long serialVersionUID = 8697030238860181294L;
-    private static final int maxFirstComponent = 2;
-    private static final int maxSecondComponent = 39;
 
     /**
-     * Constructs an object identifier from a string.  This string
-     * should be of the form 1.23.34.45.56 etc.
+     * Changed to Object
+     * @serial
+     */
+    private Object      components   = null;          // path from root
+    /**
+     * @serial
+     */
+    private int         componentLen = -1;            // how much is used.
+
+    // Is the components field calculated?
+    transient private boolean   componentsCalculated = false;
+
+    private void readObject(ObjectInputStream is)
+            throws IOException, ClassNotFoundException {
+        is.defaultReadObject();
+
+        if (encoding == null) {  // from an old version
+            init((int[])components, componentLen);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream os)
+            throws IOException {
+        if (!componentsCalculated) {
+            int[] comps = toIntArray();
+            if (comps != null) {    // every one understands this
+                components = comps;
+                componentLen = comps.length;
+            } else {
+                components = HugeOidNotSupportedByOldJDK.theOne;
+            }
+            componentsCalculated = true;
+        }
+        os.defaultWriteObject();
+    }
+
+    static class HugeOidNotSupportedByOldJDK implements Serializable {
+        private static final long serialVersionUID = 1L;
+        static HugeOidNotSupportedByOldJDK theOne = new HugeOidNotSupportedByOldJDK();
+    }
+
+    /**
+     * Constructs, from a string.  This string should be of the form 1.23.56.
+     * Validity check included.
      */
     public ObjectIdentifier (String oid) throws IOException
     {
         int ch = '.';
-        int     start = 0;
+        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];
+        int pos = 0;
+        byte[] tmp = new byte[oid.length()];
+        int first = 0, second;
+        int count = 0;
+
+        try {
+            String comp = null;
+            do {
+                int length = 0; // length of one section
+                end = oid.indexOf(ch,start);
+                if (end == -1) {
+                    comp = oid.substring(start);
+                    length = oid.length() - start;
+                } else {
+                    comp = oid.substring(start,end);
+                    length = end - start;
+                }
 
-        start = 0;
-        int i = 0;
-        String comp = null;
-        try {
-            while ((end = oid.indexOf(ch,start)) != -1) {
-                comp = oid.substring(start,end);
-                components[i++] = Integer.valueOf(comp).intValue();
+                if (length > 9) {
+                    BigInteger bignum = new BigInteger(comp);
+                    if (count == 0) {
+                        checkFirstComponent(bignum);
+                        first = bignum.intValue();
+                    } else {
+                        if (count == 1) {
+                            checkSecondComponent(first, bignum);
+                            bignum = bignum.add(BigInteger.valueOf(40*first));
+                        } else {
+                            checkOtherComponent(count, bignum);
+                        }
+                        pos += pack7Oid(bignum, tmp, pos);
+                    }
+                } else {
+                    int num = Integer.parseInt(comp);
+                    if (count == 0) {
+                        checkFirstComponent(num);
+                        first = num;
+                    } else {
+                        if (count == 1) {
+                            checkSecondComponent(first, num);
+                            num += 40 * first;
+                        } else {
+                            checkOtherComponent(count, num);
+                        }
+                        pos += pack7Oid(num, tmp, pos);
+                    }
+                }
                 start = end + 1;
-            }
-            comp = oid.substring(start);
-            components[i] = Integer.valueOf(comp).intValue();
+                count++;
+            } while (end != -1);
+
+            checkCount(count);
+            encoding = new byte[pos];
+            System.arraycopy(tmp, 0, encoding, 0, pos);
+            this.stringForm = oid;
+        } catch (IOException ioe) { // already detected by checkXXX
+            throw ioe;
         } catch (Exception e) {
             throw new IOException("ObjectIdentifier() -- Invalid format: "
                     + e.toString(), e);
         }
-        checkValidOid(components, componentLen);
-        this.stringForm = oid;
     }
 
     /**
-     * Check if the values make a legal OID. There must be at least 2
-     * components and they must be all non-negative. The first component
-     * should be 0,1 or 2. When the first component is 0 or 1, the
-     * second component should be less than or equal to 39
-     *
-     * @param values the components that will make the OID
-     * @param len the number of components to check. Note that the allocation
-     *        size of <code>values</code> may be longer than <code>len</code>.
-     *        In this case, only the first <code>len</code> items are checked.
-     * @exception IOException if this is not a legal OID
-     */
-    private void checkValidOid(int[] values, int len) throws IOException {
-        if (values == null || len < 2) {
-            throw new IOException("ObjectIdentifier() -- " +
-                    "Must be at least two oid components ");
-        }
-
-        for (int i=0; i<len; i++) {
-            if (values[i] < 0) {
-                throw new IOException("ObjectIdentifier() -- " +
-                        "oid component #" + (i+1) + " must be non-negative ");
-            }
-        }
-
-        if (values[0] > maxFirstComponent) {
-            throw new IOException("ObjectIdentifier() -- " +
-                    "First oid component is invalid ");
-        }
-
-        if (values[0] < 2 && values[1] > maxSecondComponent) {
-            throw new IOException("ObjectIdentifier() -- " +
-                    "Second oid component is invalid ");
-        }
-    }
-    /**
-     * Constructs an object ID from an array of integers.  This
-     * is used to construct constant object IDs.
+     * Constructor, from an array of integers.
+     * Validity check included.
      */
     public ObjectIdentifier (int values []) throws IOException
     {
-        checkValidOid(values, values.length);
-        components = values.clone();
-        componentLen = values.length;
+        checkCount(values.length);
+        checkFirstComponent(values[0]);
+        checkSecondComponent(values[0], values[1]);
+        for (int i=2; i<values.length; i++)
+            checkOtherComponent(i, values[i]);
+        init(values, values.length);
     }
 
     /**
-     * Constructs an object ID from an ASN.1 encoded input stream.
+     * Constructor, from an ASN.1 encoded input stream.
+     * Validity check NOT included.
      * The encoding of the ID in the stream uses "DER", a BER/1 subset.
      * In this case, that means a triple { typeId, length, data }.
      *
@@ -152,8 +234,7 @@
      * @param in DER-encoded data holding an object ID
      * @exception IOException indicates a decoding error
      */
-    public ObjectIdentifier (DerInputStream in)
-        throws IOException
+    public ObjectIdentifier (DerInputStream in) throws IOException
     {
         byte    type_id;
         int     bufferEnd;
@@ -174,215 +255,67 @@
                 + " (tag = " +  type_id + ")"
                 );
 
-        bufferEnd = in.available () - in.getLength () - 1;
-        if (bufferEnd < 0)
-            throw new IOException (
-                "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);
-    }
-
-    /**
-     * Private constructor for use by newInternal(). Dummy argument
-     * to avoid clash with the public constructor.
-     */
-    private ObjectIdentifier(int[] components, boolean dummy) {
-        this.components = components;
-        this.componentLen = components.length;
-    }
-
-    /**
-     * Create a new ObjectIdentifier for internal use. The values are
-     * neither checked nor cloned.
-     */
-    public static ObjectIdentifier newInternal(int[] values) {
-        return new ObjectIdentifier(values, true);
+        encoding = new byte[in.getLength()];
+        in.getBytes(encoding);
+        check(encoding);
     }
 
     /*
-     * Helper function -- get the OID from a stream, after tag and
-     * length are verified.
+     * Constructor, from the rest of a DER input buffer;
+     * the tag and length have been removed/verified
+     * Validity check NOT included.
      */
-    private void initFromEncoding (DerInputStream in, int bufferEnd)
-        throws IOException
+    ObjectIdentifier (DerInputBuffer buf) 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 (component < 0) {
-                throw new IOException(
-                    "ObjectIdentifier() -- " +
-                    "component values must be nonnegative");
-            }
-            if (first_subid) {
-                int     X, Y;
+        DerInputStream in = new DerInputStream(buf);
+        encoding = new byte[in.available()];
+        in.getBytes(encoding);
+        check(encoding);
+    }
 
-                /*
-                 * 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;
+    private void init(int[] components, int length) {
+        int pos = 0;
+        byte[] tmp = new byte[length*5+1];  // +1 for empty input
 
-            } 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;
-            }
+        if (components[1] < Integer.MAX_VALUE - components[0]*40)
+            pos += pack7Oid(components[0]*40+components[1], tmp, pos);
+        else {
+            BigInteger big = BigInteger.valueOf(components[1]);
+            big = big.add(BigInteger.valueOf(components[0]*40));
+            pos += pack7Oid(big, tmp, pos);
         }
 
-        checkValidOid(components, componentLen);
+        for (int i=2; i<length; i++) {
+            pos += pack7Oid(components[i], tmp, pos);
+        }
+        encoding = new byte[pos];
+        System.arraycopy(tmp, 0, encoding, 0, pos);
+    }
 
-        /*
-         * 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 (
-                    "ObjectIdentifier() -- malformed input data");
+    /**
+     * This method is kept for compatibility reasons. The new implementation
+     * does the check and conversion. All around the JDK, the method is called
+     * in static blocks to initialize pre-defined ObjectIdentifieies. No
+     * obvious performance hurt will be made after this change.
+     *
+     * Old doc: Create a new ObjectIdentifier for internal use. The values are
+     * neither checked nor cloned.
+     */
+    public static ObjectIdentifier newInternal(int[] values) {
+        try {
+            return new ObjectIdentifier(values);
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+            // Should not happen, internal calls always uses legal values.
         }
     }
 
-
     /*
      * n.b. the only public interface is DerOutputStream.putOID()
      */
     void encode (DerOutputStream out) throws IOException
     {
-        DerOutputStream bytes = new DerOutputStream ();
-        int i;
-
-        // According to ISO X.660, when the 1st component is 0 or 1, the 2nd
-        // component is restricted to be less than or equal to 39, thus make
-        // it small enough to be encoded into one single byte.
-        if (components[0] < 2) {
-            bytes.write ((components [0] * 40) + components [1]);
-        } else {
-            putComponent(bytes, (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 ("ObjectIdentifier() -- 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;
-        // TODO: val must be <128*128*128*128 here, otherwise, 4 bytes is not
-        // enough to hold it. Will address this later.
-        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 <em>other</em> 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;
+        out.write (DerValue.tag_ObjectId, encoding);
     }
 
     /**
@@ -398,6 +331,7 @@
      *
      * @return true iff the names are identical.
      */
+    @Override
     public boolean equals(Object obj) {
         if (this == obj) {
             return true;
@@ -406,23 +340,71 @@
             return false;
         }
         ObjectIdentifier other = (ObjectIdentifier)obj;
-        if (componentLen != other.componentLen) {
-            return false;
-        }
-        for (int i = 0; i < componentLen; i++) {
-            if (components[i] != other.components[i]) {
-                return false;
+        return Arrays.equals(encoding, other.encoding);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(encoding);
+    }
+
+    /**
+     * Private helper method for serialization. To be compatible with old
+     * versions of JDK.
+     * @return components in an int array, if all the components are less than
+     *         Integer.MAX_VALUE. Otherwise, null.
+     */
+    private int[] toIntArray() {
+        int length = encoding.length;
+        int[] result = new int[20];
+        int which = 0;
+        int fromPos = 0;
+        for (int i = 0; i < length; i++) {
+            if ((encoding[i] & 0x80) == 0) {
+                // one section [fromPos..i]
+                if (i - fromPos + 1 > 4) {
+                    BigInteger big = new BigInteger(pack(encoding, fromPos, i-fromPos+1, 7, 8));
+                    if (fromPos == 0) {
+                        result[which++] = 2;
+                        BigInteger second = big.subtract(BigInteger.valueOf(80));
+                        if (second.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) == 1) {
+                            return null;
+                        } else {
+                            result[which++] = second.intValue();
+                        }
+                    } else {
+                        if (big.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) == 1) {
+                            return null;
+                        } else {
+                            result[which++] = big.intValue();
+                        }
+                    }
+                } else {
+                    int retval = 0;
+                    for (int j = fromPos; j <= i; j++) {
+                        retval <<= 7;
+                        byte tmp = encoding[j];
+                        retval |= (tmp & 0x07f);
+                    }
+                    if (fromPos == 0) {
+                        if (retval < 80) {
+                            result[which++] = retval / 40;
+                            result[which++] = retval % 40;
+                        } else {
+                            result[which++] = 2;
+                            result[which++] = retval - 80;
+                        }
+                    } else {
+                        result[which++] = retval;
+                    }
+                }
+                fromPos = i+1;
+            }
+            if (which >= result.length) {
+                result = Arrays.copyOf(result, which + 10);
             }
         }
-        return true;
-    }
-
-    public int hashCode() {
-        int h = componentLen;
-        for (int i = 0; i < componentLen; i++) {
-            h += components[i] * 37;
-        }
-        return h;
+        return Arrays.copyOf(result, which);
     }
 
     /**
@@ -431,15 +413,52 @@
      * user-friendly descriptive strings, since those strings
      * will not be understood everywhere.
      */
+    @Override
     public String toString() {
         String s = stringForm;
         if (s == null) {
-            StringBuffer sb = new StringBuffer(componentLen * 4);
-            for (int i = 0; i < componentLen; i++) {
-                if (i != 0) {
-                    sb.append('.');
+            int length = encoding.length;
+            StringBuffer sb = new StringBuffer(length * 4);
+
+            int fromPos = 0;
+            for (int i = 0; i < length; i++) {
+                if ((encoding[i] & 0x80) == 0) {
+                    // one section [fromPos..i]
+                    if (fromPos != 0) {  // not the first segment
+                        sb.append('.');
+                    }
+                    if (i - fromPos + 1 > 4) { // maybe big integer
+                        BigInteger big = new BigInteger(pack(encoding, fromPos, i-fromPos+1, 7, 8));
+                        if (fromPos == 0) {
+                            // first section encoded with more than 4 bytes,
+                            // must be 2.something
+                            sb.append("2.");
+                            sb.append(big.subtract(BigInteger.valueOf(80)));
+                        } else {
+                            sb.append(big);
+                        }
+                    } else { // small integer
+                        int retval = 0;
+                        for (int j = fromPos; j <= i; j++) {
+                            retval <<= 7;
+                            byte tmp = encoding[j];
+                            retval |= (tmp & 0x07f);
+                        }
+                        if (fromPos == 0) {
+                            if (retval < 80) {
+                                sb.append(retval/40);
+                                sb.append('.');
+                                sb.append(retval%40);
+                            } else {
+                                sb.append("2.");
+                                sb.append(retval - 80);
+                            }
+                        } else {
+                            sb.append(retval);
+                        }
+                    }
+                    fromPos = i+1;
                 }
-                sb.append(components[i]);
             }
             s = sb.toString();
             stringForm = s;
@@ -447,15 +466,197 @@
         return s;
     }
 
-    /*
-     * 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.
+    /**
+     * Repack all bits from input to output. On the both sides, only a portion
+     * (from the least significant bit) of the 8 bits in a byte is used. This
+     * number is defined as the number of useful bits (NUB) for the array. All the
+     * used bits from the input byte array and repacked into the output in the
+     * exactly same order. The output bits are aligned so that the final bit of
+     * the input (the least significant bit in the last byte), when repacked as
+     * the final bit of the output, is still at the least significant position.
+     * Zeroes will be padded on the left side of the first output byte if
+     * necessary. All unused bits in the output are also zeroed.
+     *
+     * For example: if the input is 01001100 with NUB 8, the output which
+     * has a NUB 6 will look like:
+     *      00000001 00001100
+     * The first 2 bits of the output bytes are unused bits. The other bits
+     * turn out to be 000001 001100. While the 8 bits on the right are from
+     * the input, the left 4 zeroes are padded to fill the 6 bits space.
+     *
+     * @param in        the input byte array
+     * @param ioffset   start point inside <code>in</code>
+     * @param ilength   number of bytes to repack
+     * @param iw        NUB for input
+     * @param ow        NUB for output
+     * @return          the repacked bytes
+     */
+    private static byte[] pack(byte[] in, int ioffset, int ilength, int iw, int ow) {
+        assert (iw > 0 && iw <= 8): "input NUB must be between 1 and 8";
+        assert (ow > 0 && ow <= 8): "output NUB must be between 1 and 8";
+
+        if (iw == ow) {
+            return in.clone();
+        }
+
+        int bits = ilength * iw;    // number of all used bits
+        byte[] out = new byte[(bits+ow-1)/ow];
+
+        // starting from the 0th bit in the input
+        int ipos = 0;
+
+        // the number of padding 0's needed in the output, skip them
+        int opos = (bits+ow-1)/ow*ow-bits;
+
+        while(ipos < bits) {
+            int count = iw - ipos%iw;   // unpacked bits in current input byte
+            if (count > ow - opos%ow) { // free space available in output byte
+                count = ow - opos%ow;   // choose the smaller number
+            }
+            // and move them!
+            out[opos/ow] |=                         // paste!
+                (((in[ioffset+ipos/iw]+256)         // locate the byte (+256 so that it's never negative)
+                    >> (iw-ipos%iw-count))          // move to the end of a byte
+                        & ((1 << (count))-1))       // zero out all other bits
+                            << (ow-opos%ow-count);  // move to the output position
+            ipos += count;  // advance
+            opos += count;  // advance
+        }
+        return out;
+    }
+
+    /**
+     * Repack from NUB 8 to a NUB 7 OID sub-identifier, remove all
+     * unnecessary 0 headings, set the first bit of all non-tail
+     * output bytes to 1 (as ITU-T Rec. X.690 8.19.2 says), and
+     * paste it into an existing byte array.
+     * @param out the existing array to be pasted into
+     * @param ooffset the starting position to paste
+     * @return the number of bytes pasted
+     */
+    private static int pack7Oid(byte[] in, int ioffset, int ilength, byte[] out, int ooffset) {
+        byte[] pack = pack(in, ioffset, ilength, 8, 7);
+        int firstNonZero = pack.length-1;   // paste at least one byte
+        for (int i=pack.length-2; i>=0; i--) {
+            if (pack[i] != 0) {
+                firstNonZero = i;
+            }
+            pack[i] |= 0x80;
+        }
+        System.arraycopy(pack, firstNonZero, out, ooffset, pack.length-firstNonZero);
+        return pack.length-firstNonZero;
+    }
+
+    /**
+     * Repack from NUB 7 to NUB 8, remove all unnecessary 0
+     * headings, and paste it into an existing byte array.
+     * @param out the existing array to be pasted into
+     * @param ooffset the starting position to paste
+     * @return the number of bytes pasted
      */
-    private int         components [];                  // path from root
-    private int         componentLen;                   // how much is used.
+    private static int pack8(byte[] in, int ioffset, int ilength, byte[] out, int ooffset) {
+        byte[] pack = pack(in, ioffset, ilength, 7, 8);
+        int firstNonZero = pack.length-1;   // paste at least one byte
+        for (int i=pack.length-2; i>=0; i--) {
+            if (pack[i] != 0) {
+                firstNonZero = i;
+            }
+        }
+        System.arraycopy(pack, firstNonZero, out, ooffset, pack.length-firstNonZero);
+        return pack.length-firstNonZero;
+    }
+
+    /**
+     * Pack the int into a OID sub-identifier DER encoding
+     */
+    private static int pack7Oid(int input, byte[] out, int ooffset) {
+        byte[] b = new byte[4];
+        b[0] = (byte)(input >> 24);
+        b[1] = (byte)(input >> 16);
+        b[2] = (byte)(input >> 8);
+        b[3] = (byte)(input);
+        return pack7Oid(b, 0, 4, out, ooffset);
+    }
+
+    /**
+     * Pack the BigInteger into a OID subidentifier DER encoding
+     */
+    private static int pack7Oid(BigInteger input, byte[] out, int ooffset) {
+        byte[] b = input.toByteArray();
+        return pack7Oid(b, 0, b.length, out, ooffset);
+    }
+
+    /**
+     * Private methods to check validity of OID. They must be --
+     * 1. at least 2 components
+     * 2. all components must be non-negative
+     * 3. the first must be 0, 1 or 2
+     * 4. if the first is 0 or 1, the second must be <40
+     */
 
-    private transient volatile String stringForm;
-
-    private static final int allocationQuantum = 5;     // >= 2
+    /**
+     * Check the DER encoding. Since DER encoding defines that the integer bits
+     * are unsigned, so there's no need to check the MSB.
+     */
+    private static void check(byte[] encoding) throws IOException {
+        int length = encoding.length;
+        if (length < 1 ||      // too short
+                (encoding[length - 1] & 0x80) != 0) {  // not ended
+            throw new IOException("ObjectIdentifier() -- " +
+                    "Invalid DER encoding, not ended");
+        }
+        for (int i=0; i<length; i++) {
+            // 0x80 at the beginning of a subidentifier
+            if (encoding[i] == (byte)0x80 &&
+                    (i==0 || (encoding[i-1] & 0x80) == 0)) {
+                throw new IOException("ObjectIdentifier() -- " +
+                        "Invalid DER encoding, useless extra octet detected");
+            }
+        }
+    }
+    private static void checkCount(int count) throws IOException {
+        if (count < 2) {
+            throw new IOException("ObjectIdentifier() -- " +
+                    "Must be at least two oid components ");
+        }
+    }
+    private static void checkFirstComponent(int first) throws IOException {
+        if (first < 0 || first > 2) {
+            throw new IOException("ObjectIdentifier() -- " +
+                    "First oid component is invalid ");
+        }
+    }
+    private static void checkFirstComponent(BigInteger first) throws IOException {
+        if (first.signum() == -1 ||
+                first.compareTo(BigInteger.valueOf(2)) == 1) {
+            throw new IOException("ObjectIdentifier() -- " +
+                    "First oid component is invalid ");
+        }
+    }
+    private static void checkSecondComponent(int first, int second) throws IOException {
+        if (second < 0 || first != 2 && second > 39) {
+            throw new IOException("ObjectIdentifier() -- " +
+                    "Second oid component is invalid ");
+        }
+    }
+    private static void checkSecondComponent(int first, BigInteger second) throws IOException {
+        if (second.signum() == -1 ||
+                first != 2 &&
+                second.compareTo(BigInteger.valueOf(39)) == 1) {
+            throw new IOException("ObjectIdentifier() -- " +
+                    "Second oid component is invalid ");
+        }
+    }
+    private static void checkOtherComponent(int i, int num) throws IOException {
+        if (num < 0) {
+            throw new IOException("ObjectIdentifier() -- " +
+                    "oid component #" + (i+1) + " must be non-negative ");
+        }
+    }
+    private static void checkOtherComponent(int i, BigInteger num) throws IOException {
+        if (num.signum() == -1) {
+            throw new IOException("ObjectIdentifier() -- " +
+                    "oid component #" + (i+1) + " must be non-negative ");
+        }
+    }
 }
--- a/jdk/test/sun/security/util/Oid/OidFormat.java	Mon Apr 06 18:52:03 2009 -0700
+++ b/jdk/test/sun/security/util/Oid/OidFormat.java	Wed Apr 08 13:54:34 2009 +0800
@@ -63,9 +63,22 @@
             "1.2.3", "1.2.3445",
             "1.3.6.1.4.1.42.2.17",
             // 4811968: ASN.1 cannot handle huge OID components
-            //"2.16.764.1.3101555394.1.0.100.2.1",
-            //"1.2.2147483647.4",
-            //"1.2.268435456.4",
+            "2.16.764.1.3101555394.1.0.100.2.1",
+            "2.2726957624935694386592435",  // as huge as possible
+            "1.2.777777777777777777",
+            "1.2.888888888888888888.111111111111111.2222222222222.33333333333333333.44444444444444",
+            "1.2." +
+                "1111111111111111111111111111111111111111111111111111111111111." +
+                "2222222222222222222222222222222222222222222222222222222222222222." +
+                "333333333333333333333333333333333333333333333333333333333333333." +
+                "4444444444444444444444444444444444444444444444444444444." +
+                "55555555555555555555555555555555555555555555555555555555555555555555555." +
+                "666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666." +
+                "77777777777777777777777777777777777777777777777777777777777777777777777777." +
+                "8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888." +
+                "9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999",
+            "1.2.2147483647.4",
+            "1.2.268435456.4",
         };
 
         for (String s: goodOids) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/util/Oid/S11N.sh	Wed Apr 08 13:54:34 2009 +0800
@@ -0,0 +1,164 @@
+#
+# Copyright 2004-2005 Sun Microsystems, Inc.  All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.
+#
+# This code 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
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+# @test
+# @bug 4811968
+# @summary Serialization compatibility with old versions
+# @author Weijun Wang
+#
+# set a few environment variables so that the shell-script can run stand-alone
+# in the source directory
+
+if [ "${TESTSRC}" = "" ] ; then
+  TESTSRC="."
+fi
+if [ "${TESTCLASSES}" = "" ] ; then
+  TESTCLASSES="."
+fi
+if [ "${TESTJAVA}" = "" ] ; then
+  echo "TESTJAVA not set.  Test cannot execute."
+  echo "FAILED!!!"
+  exit 1
+fi
+
+# set platform-dependent variables
+PF=""
+
+OS=`uname -s`
+case "$OS" in
+  SunOS )
+    FS="/"
+    ARCH=`isainfo`
+    case "$ARCH" in
+      sparc* )
+        PF="solaris-sparc"
+        ;;
+      i[3-6]86 )
+        PF="solaris-i586"
+        ;;
+      amd64* )
+        PF="solaris-amd64"
+        ;;
+      * )
+        echo "Unsupported System: Solaris ${ARCH}"
+        exit 0;
+        ;;
+    esac
+    ;;
+  Linux )
+    ARCH=`uname -m`
+    FS="/"
+    case "$ARCH" in
+      i[3-6]86 )
+        PF="linux-i586"
+        ;;
+      amd64* )
+        PF="linux-amd64"
+        ;;
+      * )
+        echo "Unsupported System: Linux ${ARCH}"
+        exit 0;
+        ;;
+    esac
+    ;;
+  Windows* )
+    FS="\\"
+    PF="windows-i586"
+
+    # 'uname -m' does not give us enough information -
+    #  should rely on $PROCESSOR_IDENTIFIER (as is done in Defs-windows.gmk),
+    #  but JTREG does not pass this env variable when executing a shell script.
+    #
+    #  execute test program - rely on it to exit if platform unsupported
+
+    ;;
+  * )
+    echo "Unsupported System: ${OS}"
+    exit 0;
+    ;;
+esac
+
+# the test code
+
+${TESTJAVA}${FS}bin${FS}javac -source 1.3 -target 1.3 -d . ${TESTSRC}${FS}SerialTest.java || exit 10
+
+OLDJAVA="
+    /java/re/j2se/1.6.0/latest/binaries/${PF}
+    /java/re/j2se/1.5.0/latest/binaries/${PF}
+    /java/re/j2se/1.4.2/latest/binaries/${PF}
+"
+
+SMALL="
+    0.0
+    1.1
+    2.2
+    1.2.3456
+    1.2.2147483647.4
+    1.2.268435456.4
+"
+
+HUGE="
+    2.16.764.1.3101555394.1.0.100.2.1
+    1.2.2147483648.4
+    2.3.4444444444444444444444
+    1.2.888888888888888888.111111111111111.2222222222222.33333333333333333.44444444444444
+"
+
+for oid in ${SMALL}; do
+    echo ${oid}
+    # new ->
+    ${TESTJAVA}${FS}bin${FS}java SerialTest out ${oid} > tmp.oid.serial || exit 1
+    # -> new
+    ${TESTJAVA}${FS}bin${FS}java SerialTest in ${oid} < tmp.oid.serial || exit 2
+    for oldj in ${OLDJAVA}; do
+        if [ -d ${oldj} ]; then
+            echo ${oldj}
+            # -> old
+            ${oldj}${FS}bin${FS}java SerialTest in ${oid} < tmp.oid.serial || exit 3
+            # old ->
+            ${oldj}${FS}bin${FS}java SerialTest out ${oid} > tmp.oid.serial.old || exit 4
+            # -> new
+            ${TESTJAVA}${FS}bin${FS}java SerialTest in ${oid} < tmp.oid.serial.old || exit 5
+        fi
+    done
+done
+
+for oid in ${HUGE}; do
+    echo ${oid}
+    # new ->
+    ${TESTJAVA}${FS}bin${FS}java SerialTest out ${oid} > tmp.oid.serial || exit 1
+    # -> new
+    ${TESTJAVA}${FS}bin${FS}java SerialTest in ${oid} < tmp.oid.serial || exit 2
+    for oldj in ${OLDJAVA}; do
+        if [ -d ${oldj} ]; then
+            echo ${oldj}
+            # -> old
+            ${oldj}${FS}bin${FS}java SerialTest badin < tmp.oid.serial || exit 3
+        fi
+    done
+done
+
+rm -f tmp.oid.serial
+rm -f tmp.oid.serial.old
+rm -f SerialTest.class
+
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/util/Oid/SerialTest.java	Wed Apr 08 13:54:34 2009 +0800
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2004 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * read S11.sh
+ */
+import java.io.*;
+import sun.security.util.*;
+
+/**
+ * Test OID serialization between versions
+ *
+ * java SerialTest out oid  // write a OID into System.out
+ * java SerialTest in oid   // read from System.in and compare it with oid
+ * java SerialTest badin    // make sure *cannot* read from System.in
+ */
+class SerialTest {
+    public static void main(String[] args) throws Exception {
+        if (args[0].equals("out"))
+            out(args[1]);
+        else if (args[0].equals("in"))
+            in(args[1]);
+        else
+            badin();
+    }
+
+    static void in(String oid) throws Exception {
+        ObjectIdentifier o = (ObjectIdentifier) (new ObjectInputStream(System.in).readObject());
+        if (!o.toString().equals(oid))
+            throw new Exception("Read Fail " + o + ", not " + oid);
+    }
+
+    static void badin() throws Exception {
+        boolean pass = true;
+        try {
+            new ObjectInputStream(System.in).readObject();
+        } catch (Exception e) {
+            pass = false;
+        }
+        if (pass) throw new Exception("Should fail but not");
+    }
+
+    static void out(String oid) throws Exception {
+        new ObjectOutputStream(System.out).writeObject(new ObjectIdentifier(oid));
+    }
+}