Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved.
      3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      4  *
      5  * This code is free software; you can redistribute it and/or modify it
      6  * under the terms of the GNU General Public License version 2 only, as
      7  * published by the Free Software Foundation.  Oracle designates this
      8  * particular file as subject to the "Classpath" exception as provided
      9  * by Oracle in the LICENSE file that accompanied this code.
     10  *
     11  * This code is distributed in the hope that it will be useful, but WITHOUT
     12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     14  * version 2 for more details (a copy is included in the LICENSE file that
     15  * accompanied this code).
     16  *
     17  * You should have received a copy of the GNU General Public License version
     18  * 2 along with this work; if not, write to the Free Software Foundation,
     19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     20  *
     21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     22  * or visit www.oracle.com if you need additional information or have any
     23  * questions.
     24  */
     25 
     26 package sun.security.util;
     27 
     28 import java.io.*;
     29 import java.math.BigInteger;
     30 import java.util.Arrays;
     31 
     32 /**
     33  * Represent an ISO Object Identifier.
     34  *
     35  * <P>Object Identifiers are arbitrary length hierarchical identifiers.
     36  * The individual components are numbers, and they define paths from the
     37  * root of an ISO-managed identifier space.  You will sometimes see a
     38  * string name used instead of (or in addition to) the numerical id.
     39  * These are synonyms for the numerical IDs, but are not widely used
     40  * since most sites do not know all the requisite strings, while all
     41  * sites can parse the numeric forms.
     42  *
     43  * <P>So for example, JavaSoft has the sole authority to assign the
     44  * meaning to identifiers below the 1.3.6.1.4.1.42.2.17 node in the
     45  * hierarchy, and other organizations can easily acquire the ability
     46  * to assign such unique identifiers.
     47  *
     48  * @author David Brownell
     49  * @author Amit Kapoor
     50  * @author Hemma Prafullchandra
     51  */
     52 
     53 final public
     54 class ObjectIdentifier implements Serializable
     55 {
     56     /**
     57      * We use the DER value (no tag, no length) as the internal format
     58      * @serial
     59      */
     60     private byte[] encoding = null;
     61 
     62     private transient volatile String stringForm;
     63 
     64     /*
     65      * IMPORTANT NOTES FOR CODE CHANGES (bug 4811968) IN JDK 1.7.0
     66      * ===========================================================
     67      *
     68      * (Almost) serialization compatibility with old versions:
     69      *
     70      * serialVersionUID is unchanged. Old field "component" is changed to
     71      * type Object so that "poison" (unknown object type for old versions)
     72      * can be put inside if there are huge components that cannot be saved
     73      * as integers.
     74      *
     75      * New version use the new filed "encoding" only.
     76      *
     77      * Below are all 4 cases in a serialization/deserialization process:
     78      *
     79      * 1. old -> old: Not covered here
     80      * 2. old -> new: There's no "encoding" field, new readObject() reads
     81      *    "components" and "componentLen" instead and inits correctly.
     82      * 3. new -> new: "encoding" field exists, new readObject() uses it
     83      *    (ignoring the other 2 fields) and inits correctly.
     84      * 4. new -> old: old readObject() only recognizes "components" and
     85      *    "componentLen" fields. If no huge components are involved, they
     86      *    are serialized as legal values and old object can init correctly.
     87      *    Otherwise, old object cannot recognize the form (component not int[])
     88      *    and throw a ClassNotFoundException at deserialization time.
     89      *
     90      * Therfore, for the first 3 cases, exact compatibility is preserved. In
     91      * the 4th case, non-huge OID is still supportable in old versions, while
     92      * huge OID is not.
     93      */
     94     private static final long serialVersionUID = 8697030238860181294L;
     95 
     96     /**
     97      * Changed to Object
     98      * @serial
     99      */
    100     private Object      components   = null;          // path from root
    101     /**
    102      * @serial
    103      */
    104     private int         componentLen = -1;            // how much is used.
    105 
    106     // Is the components field calculated?
    107     transient private boolean   componentsCalculated = false;
    108 
    109     private void readObject(ObjectInputStream is)
    110             throws IOException, ClassNotFoundException {
    111         is.defaultReadObject();
    112 
    113         if (encoding == null) {  // from an old version
    114             init((int[])components, componentLen);
    115         }
    116     }
    117 
    118     private void writeObject(ObjectOutputStream os)
    119             throws IOException {
    120         if (!componentsCalculated) {
    121             int[] comps = toIntArray();
    122             if (comps != null) {    // every one understands this
    123                 components = comps;
    124                 componentLen = comps.length;
    125             } else {
    126                 components = HugeOidNotSupportedByOldJDK.theOne;
    127             }
    128             componentsCalculated = true;
    129         }
    130         os.defaultWriteObject();
    131     }
    132 
    133     static class HugeOidNotSupportedByOldJDK implements Serializable {
    134         private static final long serialVersionUID = 1L;
    135         static HugeOidNotSupportedByOldJDK theOne = new HugeOidNotSupportedByOldJDK();
    136     }
    137 
    138     /**
    139      * Constructs, from a string.  This string should be of the form 1.23.56.
    140      * Validity check included.
    141      */
    142     public ObjectIdentifier (String oid) throws IOException
    143     {
    144         int ch = '.';
    145         int start = 0;
    146         int end = 0;
    147 
    148         int pos = 0;
    149         byte[] tmp = new byte[oid.length()];
    150         int first = 0, second;
    151         int count = 0;
    152 
    153         try {
    154             String comp = null;
    155             do {
    156                 int length = 0; // length of one section
    157                 end = oid.indexOf(ch,start);
    158                 if (end == -1) {
    159                     comp = oid.substring(start);
    160                     length = oid.length() - start;
    161                 } else {
    162                     comp = oid.substring(start,end);
    163                     length = end - start;
    164                 }
    165 
    166                 if (length > 9) {
    167                     BigInteger bignum = new BigInteger(comp);
    168                     if (count == 0) {
    169                         checkFirstComponent(bignum);
    170                         first = bignum.intValue();
    171                     } else {
    172                         if (count == 1) {
    173                             checkSecondComponent(first, bignum);
    174                             bignum = bignum.add(BigInteger.valueOf(40*first));
    175                         } else {
    176                             checkOtherComponent(count, bignum);
    177                         }
    178                         pos += pack7Oid(bignum, tmp, pos);
    179                     }
    180                 } else {
    181                     int num = Integer.parseInt(comp);
    182                     if (count == 0) {
    183                         checkFirstComponent(num);
    184                         first = num;
    185                     } else {
    186                         if (count == 1) {
    187                             checkSecondComponent(first, num);
    188                             num += 40 * first;
    189                         } else {
    190                             checkOtherComponent(count, num);
    191                         }
    192                         pos += pack7Oid(num, tmp, pos);
    193                     }
    194                 }
    195                 start = end + 1;
    196                 count++;
    197             } while (end != -1);
    198 
    199             checkCount(count);
    200             encoding = new byte[pos];
    201             System.arraycopy(tmp, 0, encoding, 0, pos);
    202             this.stringForm = oid;
    203         } catch (IOException ioe) { // already detected by checkXXX
    204             throw ioe;
    205         } catch (Exception e) {
    206             throw new IOException("ObjectIdentifier() -- Invalid format: "
    207                     + e.toString(), e);
    208         }
    209     }
    210 
    211     /**
    212      * Constructor, from an array of integers.
    213      * Validity check included.
    214      */
    215     public ObjectIdentifier (int values []) throws IOException
    216     {
    217         checkCount(values.length);
    218         checkFirstComponent(values[0]);
    219         checkSecondComponent(values[0], values[1]);
    220         for (int i=2; i<values.length; i++)
    221             checkOtherComponent(i, values[i]);
    222         init(values, values.length);
    223     }
    224 
    225     /**
    226      * Constructor, from an ASN.1 encoded input stream.
    227      * Validity check NOT included.
    228      * The encoding of the ID in the stream uses "DER", a BER/1 subset.
    229      * In this case, that means a triple { typeId, length, data }.
    230      *
    231      * <P><STRONG>NOTE:</STRONG>  When an exception is thrown, the
    232      * input stream has not been returned to its "initial" state.
    233      *
    234      * @param in DER-encoded data holding an object ID
    235      * @exception IOException indicates a decoding error
    236      */
    237     public ObjectIdentifier (DerInputStream in) throws IOException
    238     {
    239         byte    type_id;
    240         int     bufferEnd;
    241 
    242         /*
    243          * Object IDs are a "universal" type, and their tag needs only
    244          * one byte of encoding.  Verify that the tag of this datum
    245          * is that of an object ID.
    246          *
    247          * Then get and check the length of the ID's encoding.  We set
    248          * up so that we can use in.available() to check for the end of
    249          * this value in the data stream.
    250          */
    251         type_id = (byte) in.getByte ();
    252         if (type_id != DerValue.tag_ObjectId)
    253             throw new IOException (
    254                 "ObjectIdentifier() -- data isn't an object ID"
    255                 + " (tag = " +  type_id + ")"
    256                 );
    257 
    258         int len = in.getLength();
    259         if (len > in.available()) {
    260             throw new IOException("ObjectIdentifier() -- length exceeds" +
    261                     "data available.  Length: " + len + ", Available: " +
    262                     in.available());
    263         }
    264         encoding = new byte[len];
    265         in.getBytes(encoding);
    266         check(encoding);
    267     }
    268 
    269     /*
    270      * Constructor, from the rest of a DER input buffer;
    271      * the tag and length have been removed/verified
    272      * Validity check NOT included.
    273      */
    274     ObjectIdentifier (DerInputBuffer buf) throws IOException
    275     {
    276         DerInputStream in = new DerInputStream(buf);
    277         encoding = new byte[in.available()];
    278         in.getBytes(encoding);
    279         check(encoding);
    280     }
    281 
    282     private void init(int[] components, int length) {
    283         int pos = 0;
    284         byte[] tmp = new byte[length*5+1];  // +1 for empty input
    285 
    286         if (components[1] < Integer.MAX_VALUE - components[0]*40)
    287             pos += pack7Oid(components[0]*40+components[1], tmp, pos);
    288         else {
    289             BigInteger big = BigInteger.valueOf(components[1]);
    290             big = big.add(BigInteger.valueOf(components[0]*40));
    291             pos += pack7Oid(big, tmp, pos);
    292         }
    293 
    294         for (int i=2; i<length; i++) {
    295             pos += pack7Oid(components[i], tmp, pos);
    296         }
    297         encoding = new byte[pos];
    298         System.arraycopy(tmp, 0, encoding, 0, pos);
    299     }
    300 
    301     /**
    302      * This method is kept for compatibility reasons. The new implementation
    303      * does the check and conversion. All around the JDK, the method is called
    304      * in static blocks to initialize pre-defined ObjectIdentifieies. No
    305      * obvious performance hurt will be made after this change.
    306      *
    307      * Old doc: Create a new ObjectIdentifier for internal use. The values are
    308      * neither checked nor cloned.
    309      */
    310     public static ObjectIdentifier newInternal(int[] values) {
    311         try {
    312             return new ObjectIdentifier(values);
    313         } catch (IOException ex) {
    314             throw new RuntimeException(ex);
    315             // Should not happen, internal calls always uses legal values.
    316         }
    317     }
    318 
    319     /*
    320      * n.b. the only public interface is DerOutputStream.putOID()
    321      */
    322     void encode (DerOutputStream out) throws IOException
    323     {
    324         out.write (DerValue.tag_ObjectId, encoding);
    325     }
    326 
    327     /**
    328      * @deprecated Use equals((Object)oid)
    329      */
    330     @Deprecated
    331     public boolean equals(ObjectIdentifier other) {
    332         return equals((Object)other);
    333     }
    334 
    335     /**
    336      * Compares this identifier with another, for equality.
    337      *
    338      * @return true iff the names are identical.
    339      */
    340     @Override
    341     public boolean equals(Object obj) {
    342         if (this == obj) {
    343             return true;
    344         }
    345         if (obj instanceof ObjectIdentifier == false) {
    346             return false;
    347         }
    348         ObjectIdentifier other = (ObjectIdentifier)obj;
    349         return Arrays.equals(encoding, other.encoding);
    350     }
    351 
    352     @Override
    353     public int hashCode() {
    354         return Arrays.hashCode(encoding);
    355     }
    356 
    357     /**
    358      * Private helper method for serialization. To be compatible with old
    359      * versions of JDK.
    360      * @return components in an int array, if all the components are less than
    361      *         Integer.MAX_VALUE. Otherwise, null.
    362      */
    363     // Android-changed: s/private/public: Needed to keep sort order of RDN from prev impl
    364     public int[] toIntArray() {
    365         int length = encoding.length;
    366         int[] result = new int[20];
    367         int which = 0;
    368         int fromPos = 0;
    369         for (int i = 0; i < length; i++) {
    370             if ((encoding[i] & 0x80) == 0) {
    371                 // one section [fromPos..i]
    372                 if (i - fromPos + 1 > 4) {
    373                     BigInteger big = new BigInteger(pack(encoding, fromPos, i-fromPos+1, 7, 8));
    374                     if (fromPos == 0) {
    375                         result[which++] = 2;
    376                         BigInteger second = big.subtract(BigInteger.valueOf(80));
    377                         if (second.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) == 1) {
    378                             return null;
    379                         } else {
    380                             result[which++] = second.intValue();
    381                         }
    382                     } else {
    383                         if (big.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) == 1) {
    384                             return null;
    385                         } else {
    386                             result[which++] = big.intValue();
    387                         }
    388                     }
    389                 } else {
    390                     int retval = 0;
    391                     for (int j = fromPos; j <= i; j++) {
    392                         retval <<= 7;
    393                         byte tmp = encoding[j];
    394                         retval |= (tmp & 0x07f);
    395                     }
    396                     if (fromPos == 0) {
    397                         if (retval < 80) {
    398                             result[which++] = retval / 40;
    399                             result[which++] = retval % 40;
    400                         } else {
    401                             result[which++] = 2;
    402                             result[which++] = retval - 80;
    403                         }
    404                     } else {
    405                         result[which++] = retval;
    406                     }
    407                 }
    408                 fromPos = i+1;
    409             }
    410             if (which >= result.length) {
    411                 result = Arrays.copyOf(result, which + 10);
    412             }
    413         }
    414         return Arrays.copyOf(result, which);
    415     }
    416 
    417     /**
    418      * Returns a string form of the object ID.  The format is the
    419      * conventional "dot" notation for such IDs, without any
    420      * user-friendly descriptive strings, since those strings
    421      * will not be understood everywhere.
    422      */
    423     @Override
    424     public String toString() {
    425         String s = stringForm;
    426         if (s == null) {
    427             int length = encoding.length;
    428             StringBuffer sb = new StringBuffer(length * 4);
    429 
    430             int fromPos = 0;
    431             for (int i = 0; i < length; i++) {
    432                 if ((encoding[i] & 0x80) == 0) {
    433                     // one section [fromPos..i]
    434                     if (fromPos != 0) {  // not the first segment
    435                         sb.append('.');
    436                     }
    437                     if (i - fromPos + 1 > 4) { // maybe big integer
    438                         BigInteger big = new BigInteger(pack(encoding, fromPos, i-fromPos+1, 7, 8));
    439                         if (fromPos == 0) {
    440                             // first section encoded with more than 4 bytes,
    441                             // must be 2.something
    442                             sb.append("2.");
    443                             sb.append(big.subtract(BigInteger.valueOf(80)));
    444                         } else {
    445                             sb.append(big);
    446                         }
    447                     } else { // small integer
    448                         int retval = 0;
    449                         for (int j = fromPos; j <= i; j++) {
    450                             retval <<= 7;
    451                             byte tmp = encoding[j];
    452                             retval |= (tmp & 0x07f);
    453                         }
    454                         if (fromPos == 0) {
    455                             if (retval < 80) {
    456                                 sb.append(retval/40);
    457                                 sb.append('.');
    458                                 sb.append(retval%40);
    459                             } else {
    460                                 sb.append("2.");
    461                                 sb.append(retval - 80);
    462                             }
    463                         } else {
    464                             sb.append(retval);
    465                         }
    466                     }
    467                     fromPos = i+1;
    468                 }
    469             }
    470             s = sb.toString();
    471             stringForm = s;
    472         }
    473         return s;
    474     }
    475 
    476     /**
    477      * Repack all bits from input to output. On the both sides, only a portion
    478      * (from the least significant bit) of the 8 bits in a byte is used. This
    479      * number is defined as the number of useful bits (NUB) for the array. All the
    480      * used bits from the input byte array and repacked into the output in the
    481      * exactly same order. The output bits are aligned so that the final bit of
    482      * the input (the least significant bit in the last byte), when repacked as
    483      * the final bit of the output, is still at the least significant position.
    484      * Zeroes will be padded on the left side of the first output byte if
    485      * necessary. All unused bits in the output are also zeroed.
    486      *
    487      * For example: if the input is 01001100 with NUB 8, the output which
    488      * has a NUB 6 will look like:
    489      *      00000001 00001100
    490      * The first 2 bits of the output bytes are unused bits. The other bits
    491      * turn out to be 000001 001100. While the 8 bits on the right are from
    492      * the input, the left 4 zeroes are padded to fill the 6 bits space.
    493      *
    494      * @param in        the input byte array
    495      * @param ioffset   start point inside <code>in</code>
    496      * @param ilength   number of bytes to repack
    497      * @param iw        NUB for input
    498      * @param ow        NUB for output
    499      * @return          the repacked bytes
    500      */
    501     private static byte[] pack(byte[] in, int ioffset, int ilength, int iw, int ow) {
    502         assert (iw > 0 && iw <= 8): "input NUB must be between 1 and 8";
    503         assert (ow > 0 && ow <= 8): "output NUB must be between 1 and 8";
    504 
    505         if (iw == ow) {
    506             return in.clone();
    507         }
    508 
    509         int bits = ilength * iw;    // number of all used bits
    510         byte[] out = new byte[(bits+ow-1)/ow];
    511 
    512         // starting from the 0th bit in the input
    513         int ipos = 0;
    514 
    515         // the number of padding 0's needed in the output, skip them
    516         int opos = (bits+ow-1)/ow*ow-bits;
    517 
    518         while(ipos < bits) {
    519             int count = iw - ipos%iw;   // unpacked bits in current input byte
    520             if (count > ow - opos%ow) { // free space available in output byte
    521                 count = ow - opos%ow;   // choose the smaller number
    522             }
    523             // and move them!
    524             out[opos/ow] |=                         // paste!
    525                 (((in[ioffset+ipos/iw]+256)         // locate the byte (+256 so that it's never negative)
    526                     >> (iw-ipos%iw-count))          // move to the end of a byte
    527                         & ((1 << (count))-1))       // zero out all other bits
    528                             << (ow-opos%ow-count);  // move to the output position
    529             ipos += count;  // advance
    530             opos += count;  // advance
    531         }
    532         return out;
    533     }
    534 
    535     /**
    536      * Repack from NUB 8 to a NUB 7 OID sub-identifier, remove all
    537      * unnecessary 0 headings, set the first bit of all non-tail
    538      * output bytes to 1 (as ITU-T Rec. X.690 8.19.2 says), and
    539      * paste it into an existing byte array.
    540      * @param out the existing array to be pasted into
    541      * @param ooffset the starting position to paste
    542      * @return the number of bytes pasted
    543      */
    544     private static int pack7Oid(byte[] in, int ioffset, int ilength, byte[] out, int ooffset) {
    545         byte[] pack = pack(in, ioffset, ilength, 8, 7);
    546         int firstNonZero = pack.length-1;   // paste at least one byte
    547         for (int i=pack.length-2; i>=0; i--) {
    548             if (pack[i] != 0) {
    549                 firstNonZero = i;
    550             }
    551             pack[i] |= 0x80;
    552         }
    553         System.arraycopy(pack, firstNonZero, out, ooffset, pack.length-firstNonZero);
    554         return pack.length-firstNonZero;
    555     }
    556 
    557     /**
    558      * Repack from NUB 7 to NUB 8, remove all unnecessary 0
    559      * headings, and paste it into an existing byte array.
    560      * @param out the existing array to be pasted into
    561      * @param ooffset the starting position to paste
    562      * @return the number of bytes pasted
    563      */
    564     private static int pack8(byte[] in, int ioffset, int ilength, byte[] out, int ooffset) {
    565         byte[] pack = pack(in, ioffset, ilength, 7, 8);
    566         int firstNonZero = pack.length-1;   // paste at least one byte
    567         for (int i=pack.length-2; i>=0; i--) {
    568             if (pack[i] != 0) {
    569                 firstNonZero = i;
    570             }
    571         }
    572         System.arraycopy(pack, firstNonZero, out, ooffset, pack.length-firstNonZero);
    573         return pack.length-firstNonZero;
    574     }
    575 
    576     /**
    577      * Pack the int into a OID sub-identifier DER encoding
    578      */
    579     private static int pack7Oid(int input, byte[] out, int ooffset) {
    580         byte[] b = new byte[4];
    581         b[0] = (byte)(input >> 24);
    582         b[1] = (byte)(input >> 16);
    583         b[2] = (byte)(input >> 8);
    584         b[3] = (byte)(input);
    585         return pack7Oid(b, 0, 4, out, ooffset);
    586     }
    587 
    588     /**
    589      * Pack the BigInteger into a OID subidentifier DER encoding
    590      */
    591     private static int pack7Oid(BigInteger input, byte[] out, int ooffset) {
    592         byte[] b = input.toByteArray();
    593         return pack7Oid(b, 0, b.length, out, ooffset);
    594     }
    595 
    596     /**
    597      * Private methods to check validity of OID. They must be --
    598      * 1. at least 2 components
    599      * 2. all components must be non-negative
    600      * 3. the first must be 0, 1 or 2
    601      * 4. if the first is 0 or 1, the second must be <40
    602      */
    603 
    604     /**
    605      * Check the DER encoding. Since DER encoding defines that the integer bits
    606      * are unsigned, so there's no need to check the MSB.
    607      */
    608     private static void check(byte[] encoding) throws IOException {
    609         int length = encoding.length;
    610         if (length < 1 ||      // too short
    611                 (encoding[length - 1] & 0x80) != 0) {  // not ended
    612             throw new IOException("ObjectIdentifier() -- " +
    613                     "Invalid DER encoding, not ended");
    614         }
    615         for (int i=0; i<length; i++) {
    616             // 0x80 at the beginning of a subidentifier
    617             if (encoding[i] == (byte)0x80 &&
    618                     (i==0 || (encoding[i-1] & 0x80) == 0)) {
    619                 throw new IOException("ObjectIdentifier() -- " +
    620                         "Invalid DER encoding, useless extra octet detected");
    621             }
    622         }
    623     }
    624     private static void checkCount(int count) throws IOException {
    625         if (count < 2) {
    626             throw new IOException("ObjectIdentifier() -- " +
    627                     "Must be at least two oid components ");
    628         }
    629     }
    630     private static void checkFirstComponent(int first) throws IOException {
    631         if (first < 0 || first > 2) {
    632             throw new IOException("ObjectIdentifier() -- " +
    633                     "First oid component is invalid ");
    634         }
    635     }
    636     private static void checkFirstComponent(BigInteger first) throws IOException {
    637         if (first.signum() == -1 ||
    638                 first.compareTo(BigInteger.valueOf(2)) == 1) {
    639             throw new IOException("ObjectIdentifier() -- " +
    640                     "First oid component is invalid ");
    641         }
    642     }
    643     private static void checkSecondComponent(int first, int second) throws IOException {
    644         if (second < 0 || first != 2 && second > 39) {
    645             throw new IOException("ObjectIdentifier() -- " +
    646                     "Second oid component is invalid ");
    647         }
    648     }
    649     private static void checkSecondComponent(int first, BigInteger second) throws IOException {
    650         if (second.signum() == -1 ||
    651                 first != 2 &&
    652                 second.compareTo(BigInteger.valueOf(39)) == 1) {
    653             throw new IOException("ObjectIdentifier() -- " +
    654                     "Second oid component is invalid ");
    655         }
    656     }
    657     private static void checkOtherComponent(int i, int num) throws IOException {
    658         if (num < 0) {
    659             throw new IOException("ObjectIdentifier() -- " +
    660                     "oid component #" + (i+1) + " must be non-negative ");
    661         }
    662     }
    663     private static void checkOtherComponent(int i, BigInteger num) throws IOException {
    664         if (num.signum() == -1) {
    665             throw new IOException("ObjectIdentifier() -- " +
    666                     "oid component #" + (i+1) + " must be non-negative ");
    667         }
    668     }
    669 }
    670