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