1 package org.bouncycastle.asn1; 2 3 import java.io.ByteArrayOutputStream; 4 import java.io.IOException; 5 import java.math.BigInteger; 6 import java.util.concurrent.ConcurrentHashMap; 7 import java.util.concurrent.ConcurrentMap; 8 9 import org.bouncycastle.util.Arrays; 10 11 /** 12 * Class representing the ASN.1 OBJECT IDENTIFIER type. 13 */ 14 public class ASN1ObjectIdentifier 15 extends ASN1Primitive 16 { 17 private final String identifier; 18 19 private byte[] body; 20 21 /** 22 * Return an OID from the passed in object 23 * 24 * @param obj an ASN1ObjectIdentifier or an object that can be converted into one. 25 * @return an ASN1ObjectIdentifier instance, or null. 26 * @throws IllegalArgumentException if the object cannot be converted. 27 */ getInstance( Object obj)28 public static ASN1ObjectIdentifier getInstance( 29 Object obj) 30 { 31 if (obj == null || obj instanceof ASN1ObjectIdentifier) 32 { 33 return (ASN1ObjectIdentifier)obj; 34 } 35 36 if (obj instanceof ASN1Encodable) 37 { 38 ASN1Primitive primitive = ((ASN1Encodable)obj).toASN1Primitive(); 39 40 if (primitive instanceof ASN1ObjectIdentifier) 41 { 42 return (ASN1ObjectIdentifier)primitive; 43 } 44 } 45 46 if (obj instanceof byte[]) 47 { 48 byte[] enc = (byte[])obj; 49 try 50 { 51 return (ASN1ObjectIdentifier)fromByteArray(enc); 52 } 53 catch (IOException e) 54 { 55 throw new IllegalArgumentException("failed to construct object identifier from byte[]: " + e.getMessage()); 56 } 57 } 58 59 throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); 60 } 61 62 /** 63 * Return an OBJECT IDENTIFIER from a tagged object. 64 * 65 * @param obj the tagged object holding the object we want 66 * @param explicit true if the object is meant to be explicitly 67 * tagged false otherwise. 68 * @return an ASN1ObjectIdentifier instance, or null. 69 * @throws IllegalArgumentException if the tagged object cannot 70 * be converted. 71 */ getInstance( ASN1TaggedObject obj, boolean explicit)72 public static ASN1ObjectIdentifier getInstance( 73 ASN1TaggedObject obj, 74 boolean explicit) 75 { 76 ASN1Primitive o = obj.getObject(); 77 78 if (explicit || o instanceof ASN1ObjectIdentifier) 79 { 80 return getInstance(o); 81 } 82 else 83 { 84 return ASN1ObjectIdentifier.fromOctetString(ASN1OctetString.getInstance(o).getOctets()); 85 } 86 } 87 88 private static final long LONG_LIMIT = (Long.MAX_VALUE >> 7) - 0x7f; 89 ASN1ObjectIdentifier( byte[] bytes)90 ASN1ObjectIdentifier( 91 byte[] bytes) 92 { 93 StringBuffer objId = new StringBuffer(); 94 long value = 0; 95 BigInteger bigValue = null; 96 boolean first = true; 97 98 for (int i = 0; i != bytes.length; i++) 99 { 100 int b = bytes[i] & 0xff; 101 102 if (value <= LONG_LIMIT) 103 { 104 value += (b & 0x7f); 105 if ((b & 0x80) == 0) // end of number reached 106 { 107 if (first) 108 { 109 if (value < 40) 110 { 111 objId.append('0'); 112 } 113 else if (value < 80) 114 { 115 objId.append('1'); 116 value -= 40; 117 } 118 else 119 { 120 objId.append('2'); 121 value -= 80; 122 } 123 first = false; 124 } 125 126 objId.append('.'); 127 objId.append(value); 128 value = 0; 129 } 130 else 131 { 132 value <<= 7; 133 } 134 } 135 else 136 { 137 if (bigValue == null) 138 { 139 bigValue = BigInteger.valueOf(value); 140 } 141 bigValue = bigValue.or(BigInteger.valueOf(b & 0x7f)); 142 if ((b & 0x80) == 0) 143 { 144 if (first) 145 { 146 objId.append('2'); 147 bigValue = bigValue.subtract(BigInteger.valueOf(80)); 148 first = false; 149 } 150 151 objId.append('.'); 152 objId.append(bigValue); 153 bigValue = null; 154 value = 0; 155 } 156 else 157 { 158 bigValue = bigValue.shiftLeft(7); 159 } 160 } 161 } 162 163 // Android-changed: Intern the identifier so there aren't hundreds of duplicates in practice. 164 this.identifier = objId.toString().intern(); 165 this.body = Arrays.clone(bytes); 166 } 167 168 /** 169 * Create an OID based on the passed in String. 170 * 171 * @param identifier a string representation of an OID. 172 */ ASN1ObjectIdentifier( String identifier)173 public ASN1ObjectIdentifier( 174 String identifier) 175 { 176 if (identifier == null) 177 { 178 throw new NullPointerException("'identifier' cannot be null"); 179 } 180 if (!isValidIdentifier(identifier)) 181 { 182 throw new IllegalArgumentException("string " + identifier + " not an OID"); 183 } 184 185 // Android-changed: Intern the identifier so there aren't hundreds of duplicates in practice. 186 this.identifier = identifier.intern(); 187 } 188 189 /** 190 * Create an OID that creates a branch under the current one. 191 * 192 * @param branchID node numbers for the new branch. 193 * @return the OID for the new created branch. 194 */ ASN1ObjectIdentifier(ASN1ObjectIdentifier oid, String branchID)195 ASN1ObjectIdentifier(ASN1ObjectIdentifier oid, String branchID) 196 { 197 if (!isValidBranchID(branchID, 0)) 198 { 199 throw new IllegalArgumentException("string " + branchID + " not a valid OID branch"); 200 } 201 202 this.identifier = oid.getId() + "." + branchID; 203 } 204 205 /** 206 * Return the OID as a string. 207 * 208 * @return the string representation of the OID carried by this object. 209 */ getId()210 public String getId() 211 { 212 return identifier; 213 } 214 215 /** 216 * Return an OID that creates a branch under the current one. 217 * 218 * @param branchID node numbers for the new branch. 219 * @return the OID for the new created branch. 220 */ branch(String branchID)221 public ASN1ObjectIdentifier branch(String branchID) 222 { 223 return new ASN1ObjectIdentifier(this, branchID); 224 } 225 226 /** 227 * Return true if this oid is an extension of the passed in branch - stem. 228 * 229 * @param stem the arc or branch that is a possible parent. 230 * @return true if the branch is on the passed in stem, false otherwise. 231 */ on(ASN1ObjectIdentifier stem)232 public boolean on(ASN1ObjectIdentifier stem) 233 { 234 String id = getId(), stemId = stem.getId(); 235 return id.length() > stemId.length() && id.charAt(stemId.length()) == '.' && id.startsWith(stemId); 236 } 237 writeField( ByteArrayOutputStream out, long fieldValue)238 private void writeField( 239 ByteArrayOutputStream out, 240 long fieldValue) 241 { 242 byte[] result = new byte[9]; 243 int pos = 8; 244 result[pos] = (byte)((int)fieldValue & 0x7f); 245 while (fieldValue >= (1L << 7)) 246 { 247 fieldValue >>= 7; 248 result[--pos] = (byte)((int)fieldValue & 0x7f | 0x80); 249 } 250 out.write(result, pos, 9 - pos); 251 } 252 writeField( ByteArrayOutputStream out, BigInteger fieldValue)253 private void writeField( 254 ByteArrayOutputStream out, 255 BigInteger fieldValue) 256 { 257 int byteCount = (fieldValue.bitLength() + 6) / 7; 258 if (byteCount == 0) 259 { 260 out.write(0); 261 } 262 else 263 { 264 BigInteger tmpValue = fieldValue; 265 byte[] tmp = new byte[byteCount]; 266 for (int i = byteCount - 1; i >= 0; i--) 267 { 268 tmp[i] = (byte)((tmpValue.intValue() & 0x7f) | 0x80); 269 tmpValue = tmpValue.shiftRight(7); 270 } 271 tmp[byteCount - 1] &= 0x7f; 272 out.write(tmp, 0, tmp.length); 273 } 274 } 275 doOutput(ByteArrayOutputStream aOut)276 private void doOutput(ByteArrayOutputStream aOut) 277 { 278 OIDTokenizer tok = new OIDTokenizer(identifier); 279 int first = Integer.parseInt(tok.nextToken()) * 40; 280 281 String secondToken = tok.nextToken(); 282 if (secondToken.length() <= 18) 283 { 284 writeField(aOut, first + Long.parseLong(secondToken)); 285 } 286 else 287 { 288 writeField(aOut, new BigInteger(secondToken).add(BigInteger.valueOf(first))); 289 } 290 291 while (tok.hasMoreTokens()) 292 { 293 String token = tok.nextToken(); 294 if (token.length() <= 18) 295 { 296 writeField(aOut, Long.parseLong(token)); 297 } 298 else 299 { 300 writeField(aOut, new BigInteger(token)); 301 } 302 } 303 } 304 getBody()305 private synchronized byte[] getBody() 306 { 307 if (body == null) 308 { 309 ByteArrayOutputStream bOut = new ByteArrayOutputStream(); 310 311 doOutput(bOut); 312 313 body = bOut.toByteArray(); 314 } 315 316 return body; 317 } 318 isConstructed()319 boolean isConstructed() 320 { 321 return false; 322 } 323 encodedLength()324 int encodedLength() 325 throws IOException 326 { 327 int length = getBody().length; 328 329 return 1 + StreamUtil.calculateBodyLength(length) + length; 330 } 331 encode(ASN1OutputStream out, boolean withTag)332 void encode(ASN1OutputStream out, boolean withTag) throws IOException 333 { 334 out.writeEncoded(withTag, BERTags.OBJECT_IDENTIFIER, getBody()); 335 } 336 hashCode()337 public int hashCode() 338 { 339 return identifier.hashCode(); 340 } 341 asn1Equals( ASN1Primitive o)342 boolean asn1Equals( 343 ASN1Primitive o) 344 { 345 if (o == this) 346 { 347 return true; 348 } 349 350 if (!(o instanceof ASN1ObjectIdentifier)) 351 { 352 return false; 353 } 354 355 return identifier.equals(((ASN1ObjectIdentifier)o).identifier); 356 } 357 toString()358 public String toString() 359 { 360 return getId(); 361 } 362 isValidBranchID( String branchID, int start)363 private static boolean isValidBranchID( 364 String branchID, int start) 365 { 366 int digitCount = 0; 367 368 int pos = branchID.length(); 369 while (--pos >= start) 370 { 371 char ch = branchID.charAt(pos); 372 373 if (ch == '.') 374 { 375 if (0 == digitCount 376 || (digitCount > 1 && branchID.charAt(pos + 1) == '0')) 377 { 378 return false; 379 } 380 381 digitCount = 0; 382 } 383 else if ('0' <= ch && ch <= '9') 384 { 385 ++digitCount; 386 } 387 else 388 { 389 return false; 390 } 391 } 392 393 if (0 == digitCount 394 || (digitCount > 1 && branchID.charAt(pos + 1) == '0')) 395 { 396 return false; 397 } 398 399 return true; 400 } 401 isValidIdentifier( String identifier)402 private static boolean isValidIdentifier( 403 String identifier) 404 { 405 if (identifier.length() < 3 || identifier.charAt(1) != '.') 406 { 407 return false; 408 } 409 410 char first = identifier.charAt(0); 411 if (first < '0' || first > '2') 412 { 413 return false; 414 } 415 416 return isValidBranchID(identifier, 2); 417 } 418 419 /** 420 * Intern will return a reference to a pooled version of this object, unless it 421 * is not present in which case intern will add it. 422 * <p> 423 * The pool is also used by the ASN.1 parsers to limit the number of duplicated OID 424 * objects in circulation. 425 * </p> 426 * 427 * @return a reference to the identifier in the pool. 428 */ intern()429 public ASN1ObjectIdentifier intern() 430 { 431 final OidHandle hdl = new OidHandle(getBody()); 432 ASN1ObjectIdentifier oid = pool.get(hdl); 433 if (oid == null) 434 { 435 oid = pool.putIfAbsent(hdl, this); 436 if (oid == null) 437 { 438 oid = this; 439 } 440 } 441 return oid; 442 } 443 444 private static final ConcurrentMap<OidHandle, ASN1ObjectIdentifier> pool = new ConcurrentHashMap<OidHandle, ASN1ObjectIdentifier>(); 445 446 private static class OidHandle 447 { 448 private final int key; 449 private final byte[] enc; 450 OidHandle(byte[] enc)451 OidHandle(byte[] enc) 452 { 453 this.key = Arrays.hashCode(enc); 454 this.enc = enc; 455 } 456 hashCode()457 public int hashCode() 458 { 459 return key; 460 } 461 equals(Object o)462 public boolean equals(Object o) 463 { 464 if (o instanceof OidHandle) 465 { 466 return Arrays.areEqual(enc, ((OidHandle)o).enc); 467 } 468 469 return false; 470 } 471 } 472 fromOctetString(byte[] enc)473 static ASN1ObjectIdentifier fromOctetString(byte[] enc) 474 { 475 final OidHandle hdl = new OidHandle(enc); 476 ASN1ObjectIdentifier oid = pool.get(hdl); 477 if (oid == null) 478 { 479 return new ASN1ObjectIdentifier(enc); 480 } 481 return oid; 482 } 483 } 484