| /* |
| * Copyright (c) 1996, 2016, Oracle and/or its affiliates. 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. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package sun.security.util; |
| |
| import java.io.*; |
| import java.math.BigInteger; |
| import java.util.Arrays; |
| |
| /** |
| * Represent an ISO Object Identifier. |
| * |
| * <P>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. |
| * |
| * <P>So for example, JavaSoft has the sole authority to assign the |
| * meaning to identifiers below the 1.3.6.1.4.1.42.2.17 node in the |
| * hierarchy, and other organizations can easily acquire the ability |
| * to assign such unique identifiers. |
| * |
| * @author David Brownell |
| * @author Amit Kapoor |
| * @author Hemma Prafullchandra |
| */ |
| |
| public final |
| class ObjectIdentifier implements Serializable |
| { |
| /** |
| * 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; |
| |
| /** |
| * 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? |
| private transient 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 end = 0; |
| |
| 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; |
| } |
| |
| 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; |
| 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); |
| } |
| } |
| |
| /** |
| * Constructor, from an array of integers. |
| * Validity check included. |
| */ |
| public ObjectIdentifier(int[] values) throws IOException |
| { |
| 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); |
| } |
| |
| /** |
| * 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 }. |
| * |
| * <P><STRONG>NOTE:</STRONG> 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 ( |
| "ObjectIdentifier() -- data isn't an object ID" |
| + " (tag = " + type_id + ")" |
| ); |
| |
| int len = in.getDefiniteLength(); |
| if (len > in.available()) { |
| throw new IOException("ObjectIdentifier() -- length exceeds" + |
| "data available. Length: " + len + ", Available: " + |
| in.available()); |
| } |
| encoding = new byte[len]; |
| in.getBytes(encoding); |
| check(encoding); |
| } |
| |
| /* |
| * Constructor, from the rest of a DER input buffer; |
| * the tag and length have been removed/verified |
| * Validity check NOT included. |
| */ |
| ObjectIdentifier (DerInputBuffer buf) throws IOException |
| { |
| DerInputStream in = new DerInputStream(buf); |
| encoding = new byte[in.available()]; |
| in.getBytes(encoding); |
| check(encoding); |
| } |
| |
| private void init(int[] components, int length) { |
| int pos = 0; |
| byte[] tmp = new byte[length*5+1]; // +1 for empty input |
| |
| 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); |
| } |
| |
| for (int i=2; i<length; i++) { |
| pos += pack7Oid(components[i], tmp, pos); |
| } |
| encoding = new byte[pos]; |
| System.arraycopy(tmp, 0, encoding, 0, pos); |
| } |
| |
| /** |
| * 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 |
| { |
| out.write (DerValue.tag_ObjectId, encoding); |
| } |
| |
| /** |
| * Compares this identifier with another, for equality. |
| * |
| * @return true iff the names are identical. |
| */ |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj instanceof ObjectIdentifier == false) { |
| return false; |
| } |
| ObjectIdentifier other = (ObjectIdentifier)obj; |
| 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 Arrays.copyOf(result, which); |
| } |
| |
| /** |
| * 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. |
| */ |
| @Override |
| public String toString() { |
| String s = stringForm; |
| if (s == null) { |
| int length = encoding.length; |
| StringBuilder sb = new StringBuilder(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; |
| } |
| } |
| s = sb.toString(); |
| stringForm = s; |
| } |
| return s; |
| } |
| |
| /** |
| * 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 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 |
| */ |
| |
| /** |
| * 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.TWO) > 0) { |
| 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 "); |
| } |
| } |
| } |