|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.util; |
|
|
|
import java.io.*; |
|
import java.math.BigInteger; |
|
import java.util.Arrays; |
|
import java.util.concurrent.ConcurrentHashMap; |
|
|
|
/** |
|
* 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 { |
|
/* |
|
* The maximum encoded OID length, excluding the ASN.1 encoding tag and |
|
* length. |
|
* |
|
* In theory, there is no maximum size for OIDs. However, there are some |
|
* limitation in practice. |
|
* |
|
* RFC 5280 mandates support for OIDs that have arc elements with values |
|
* that are less than 2^28 (that is, they MUST be between 0 and |
|
* 268,435,455, inclusive), and implementations MUST be able to handle |
|
* OIDs with up to 20 elements (inclusive). Per RFC 5280, an encoded |
|
* OID should be less than 80 bytes for safe interoperability. |
|
* |
|
* This class could be used for protocols other than X.509 certificates. |
|
* To be safe, a relatively large but still reasonable value is chosen |
|
* as the restriction in JDK. |
|
*/ |
|
private static final int MAXIMUM_OID_SIZE = 4096; |
|
|
|
|
|
|
|
|
|
*/ |
|
private byte[] encoding = null; |
|
|
|
private transient volatile String stringForm; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@java.io.Serial |
|
private static final long serialVersionUID = 8697030238860181294L; |
|
|
|
|
|
|
|
|
|
*/ |
|
@SuppressWarnings("serial") |
|
private Object components = null; |
|
|
|
/** |
|
* @serial |
|
*/ |
|
private int componentLen = -1; |
|
|
|
|
|
private transient boolean componentsCalculated = false; |
|
|
|
@java.io.Serial |
|
private void readObject(ObjectInputStream is) |
|
throws IOException, ClassNotFoundException { |
|
is.defaultReadObject(); |
|
|
|
if (encoding == null) { |
|
int[] comp = (int[])components; |
|
if (componentLen > comp.length) { |
|
componentLen = comp.length; |
|
} |
|
|
|
// Check the estimated size before it is too late. The check |
|
|
|
checkOidSize(componentLen); |
|
init(comp, componentLen); |
|
} else { |
|
checkOidSize(encoding.length); |
|
check(encoding); |
|
} |
|
} |
|
|
|
@java.io.Serial |
|
private void writeObject(ObjectOutputStream os) |
|
throws IOException { |
|
if (!componentsCalculated) { |
|
int[] comps = toIntArray(); |
|
if (comps != null) { |
|
components = comps; |
|
componentLen = comps.length; |
|
} else { |
|
components = HugeOidNotSupportedByOldJDK.theOne; |
|
} |
|
componentsCalculated = true; |
|
} |
|
os.defaultWriteObject(); |
|
} |
|
|
|
static class HugeOidNotSupportedByOldJDK implements Serializable { |
|
@java.io.Serial |
|
private static final long serialVersionUID = 1L; |
|
static HugeOidNotSupportedByOldJDK theOne = |
|
new HugeOidNotSupportedByOldJDK(); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private 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; |
|
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++; |
|
|
|
checkOidSize(pos); |
|
} while (end != -1); |
|
|
|
checkCount(count); |
|
encoding = new byte[pos]; |
|
System.arraycopy(tmp, 0, encoding, 0, pos); |
|
this.stringForm = oid; |
|
} catch (IOException ioe) { |
|
throw ioe; |
|
} catch (Exception e) { |
|
throw new IOException("ObjectIdentifier() -- Invalid format: " |
|
+ e.toString(), e); |
|
} |
|
} |
|
|
|
|
|
ObjectIdentifier(byte[] encoding) throws IOException { |
|
checkOidSize(encoding.length); |
|
check(encoding); |
|
this.encoding = encoding; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public ObjectIdentifier(DerInputStream in) throws IOException { |
|
encoding = in.getDerValue().getOID().encoding; |
|
} |
|
|
|
private void init(int[] components, int length) throws IOException { |
|
int pos = 0; |
|
byte[] tmp = new byte[length * 5 + 1]; |
|
|
|
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); |
|
|
|
checkOidSize(pos); |
|
} |
|
|
|
encoding = new byte[pos]; |
|
System.arraycopy(tmp, 0, encoding, 0, pos); |
|
} |
|
|
|
|
|
private static ConcurrentHashMap<String,ObjectIdentifier> oidTable = |
|
new ConcurrentHashMap<>(); |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static ObjectIdentifier of(String oidStr) throws IOException { |
|
|
|
ObjectIdentifier oid = oidTable.get(oidStr); |
|
if (oid == null) { |
|
oid = new ObjectIdentifier(oidStr); |
|
oidTable.put(oidStr, oid); |
|
} |
|
return oid; |
|
} |
|
|
|
|
|
|
|
*/ |
|
public static ObjectIdentifier of(KnownOIDs o) { |
|
|
|
String oidStr = o.value(); |
|
ObjectIdentifier oid = oidTable.get(oidStr); |
|
if (oid == null) { |
|
try { |
|
oid = new ObjectIdentifier(oidStr); |
|
} catch (IOException ioe) { |
|
|
|
throw new RuntimeException(ioe); |
|
} |
|
oidTable.put(oidStr, oid); |
|
} |
|
return oid; |
|
} |
|
|
|
|
|
|
|
*/ |
|
void encode(DerOutputStream out) throws IOException { |
|
out.write (DerValue.tag_ObjectId, encoding); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@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 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) { |
|
|
|
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); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@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) { |
|
|
|
if (fromPos != 0) { |
|
sb.append('.'); |
|
} |
|
if (i - fromPos + 1 > 4) { |
|
BigInteger big = new BigInteger( |
|
pack(encoding, fromPos, i-fromPos+1, 7, 8)); |
|
if (fromPos == 0) { |
|
// first section encoded with more than 4 bytes, |
|
|
|
sb.append("2."); |
|
sb.append(big.subtract(BigInteger.valueOf(80))); |
|
} else { |
|
sb.append(big); |
|
} |
|
} 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) { |
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
byte[] out = new byte[(bits+ow-1)/ow]; |
|
|
|
|
|
int ipos = 0; |
|
|
|
|
|
int opos = (bits+ow-1)/ow*ow-bits; |
|
|
|
while(ipos < bits) { |
|
int count = iw - ipos%iw; |
|
if (count > ow - opos%ow) { // free space available in output byte |
|
count = ow - opos%ow; |
|
} |
|
|
|
|
|
out[opos/ow] |= |
|
(((in[ioffset+ipos/iw]+256) |
|
>> (iw-ipos%iw-count)) & |
|
((1 << (count))-1)) |
|
<< (ow-opos%ow-count); |
|
ipos += count; |
|
opos += count; |
|
} |
|
return out; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
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; |
|
} |
|
|
|
|
|
|
|
*/ |
|
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); |
|
} |
|
|
|
|
|
|
|
*/ |
|
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 static void check(byte[] encoding) throws IOException { |
|
int length = encoding.length; |
|
if (length < 1 || |
|
(encoding[length - 1] & 0x80) != 0) { |
|
throw new IOException("ObjectIdentifier() -- " + |
|
"Invalid DER encoding, not ended"); |
|
} |
|
for (int i=0; i<length; i++) { |
|
|
|
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 "); |
|
} |
|
} |
|
|
|
private static void checkOidSize(int oidLength) throws IOException { |
|
if (oidLength > MAXIMUM_OID_SIZE) { |
|
throw new IOException( |
|
"ObjectIdentifier encoded length exceeds " + |
|
"the restriction in JDK (OId length(>=): " + oidLength + |
|
", Restriction: " + MAXIMUM_OID_SIZE + ")"); |
|
} |
|
} |
|
} |