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