1package org.bouncycastle.asn1; 2 3import java.io.ByteArrayOutputStream; 4import java.io.IOException; 5import java.math.BigInteger; 6import java.util.concurrent.ConcurrentHashMap; 7import java.util.concurrent.ConcurrentMap; 8 9import org.bouncycastle.util.Arrays; 10 11/** 12 * Class representing the ASN.1 OBJECT IDENTIFIER type. 13 */ 14public 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 */ 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 */ 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 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 // Android-changed: Intern the identifier so there aren't hundreds of duplicates in practice. 159 this.identifier = objId.toString().intern(); 160 this.body = Arrays.clone(bytes); 161 } 162 163 /** 164 * Create an OID based on the passed in String. 165 * 166 * @param identifier a string representation of an OID. 167 */ 168 public ASN1ObjectIdentifier( 169 String identifier) 170 { 171 if (identifier == null) 172 { 173 throw new IllegalArgumentException("'identifier' cannot be null"); 174 } 175 if (!isValidIdentifier(identifier)) 176 { 177 throw new IllegalArgumentException("string " + identifier + " not an OID"); 178 } 179 180 // Android-changed: Intern the identifier so there aren't hundreds of duplicates in practice. 181 this.identifier = identifier.intern(); 182 } 183 184 /** 185 * Create an OID that creates a branch under the current one. 186 * 187 * @param branchID node numbers for the new branch. 188 * @return the OID for the new created branch. 189 */ 190 ASN1ObjectIdentifier(ASN1ObjectIdentifier oid, String branchID) 191 { 192 if (!isValidBranchID(branchID, 0)) 193 { 194 throw new IllegalArgumentException("string " + branchID + " not a valid OID branch"); 195 } 196 197 this.identifier = oid.getId() + "." + branchID; 198 } 199 200 /** 201 * Return the OID as a string. 202 * 203 * @return the string representation of the OID carried by this object. 204 */ 205 public String getId() 206 { 207 return identifier; 208 } 209 210 /** 211 * Return an OID that creates a branch under the current one. 212 * 213 * @param branchID node numbers for the new branch. 214 * @return the OID for the new created branch. 215 */ 216 public ASN1ObjectIdentifier branch(String branchID) 217 { 218 return new ASN1ObjectIdentifier(this, branchID); 219 } 220 221 /** 222 * Return true if this oid is an extension of the passed in branch, stem. 223 * 224 * @param stem the arc or branch that is a possible parent. 225 * @return true if the branch is on the passed in stem, false otherwise. 226 */ 227 public boolean on(ASN1ObjectIdentifier stem) 228 { 229 String id = getId(), stemId = stem.getId(); 230 return id.length() > stemId.length() && id.charAt(stemId.length()) == '.' && id.startsWith(stemId); 231 } 232 233 private void writeField( 234 ByteArrayOutputStream out, 235 long fieldValue) 236 { 237 byte[] result = new byte[9]; 238 int pos = 8; 239 result[pos] = (byte)((int)fieldValue & 0x7f); 240 while (fieldValue >= (1L << 7)) 241 { 242 fieldValue >>= 7; 243 result[--pos] = (byte)((int)fieldValue & 0x7f | 0x80); 244 } 245 out.write(result, pos, 9 - pos); 246 } 247 248 private void writeField( 249 ByteArrayOutputStream out, 250 BigInteger fieldValue) 251 { 252 int byteCount = (fieldValue.bitLength() + 6) / 7; 253 if (byteCount == 0) 254 { 255 out.write(0); 256 } 257 else 258 { 259 BigInteger tmpValue = fieldValue; 260 byte[] tmp = new byte[byteCount]; 261 for (int i = byteCount - 1; i >= 0; i--) 262 { 263 tmp[i] = (byte)((tmpValue.intValue() & 0x7f) | 0x80); 264 tmpValue = tmpValue.shiftRight(7); 265 } 266 tmp[byteCount - 1] &= 0x7f; 267 out.write(tmp, 0, tmp.length); 268 } 269 } 270 271 private void doOutput(ByteArrayOutputStream aOut) 272 { 273 OIDTokenizer tok = new OIDTokenizer(identifier); 274 int first = Integer.parseInt(tok.nextToken()) * 40; 275 276 String secondToken = tok.nextToken(); 277 if (secondToken.length() <= 18) 278 { 279 writeField(aOut, first + Long.parseLong(secondToken)); 280 } 281 else 282 { 283 writeField(aOut, new BigInteger(secondToken).add(BigInteger.valueOf(first))); 284 } 285 286 while (tok.hasMoreTokens()) 287 { 288 String token = tok.nextToken(); 289 if (token.length() <= 18) 290 { 291 writeField(aOut, Long.parseLong(token)); 292 } 293 else 294 { 295 writeField(aOut, new BigInteger(token)); 296 } 297 } 298 } 299 300 private synchronized byte[] getBody() 301 { 302 if (body == null) 303 { 304 ByteArrayOutputStream bOut = new ByteArrayOutputStream(); 305 306 doOutput(bOut); 307 308 body = bOut.toByteArray(); 309 } 310 311 return body; 312 } 313 314 boolean isConstructed() 315 { 316 return false; 317 } 318 319 int encodedLength() 320 throws IOException 321 { 322 int length = getBody().length; 323 324 return 1 + StreamUtil.calculateBodyLength(length) + length; 325 } 326 327 void encode( 328 ASN1OutputStream out) 329 throws IOException 330 { 331 byte[] enc = getBody(); 332 333 out.write(BERTags.OBJECT_IDENTIFIER); 334 out.writeLength(enc.length); 335 out.write(enc); 336 } 337 338 public int hashCode() 339 { 340 return identifier.hashCode(); 341 } 342 343 boolean asn1Equals( 344 ASN1Primitive o) 345 { 346 if (o == this) 347 { 348 return true; 349 } 350 351 if (!(o instanceof ASN1ObjectIdentifier)) 352 { 353 return false; 354 } 355 356 return identifier.equals(((ASN1ObjectIdentifier)o).identifier); 357 } 358 359 public String toString() 360 { 361 return getId(); 362 } 363 364 private static boolean isValidBranchID( 365 String branchID, int start) 366 { 367 boolean periodAllowed = false; 368 369 int pos = branchID.length(); 370 while (--pos >= start) 371 { 372 char ch = branchID.charAt(pos); 373 374 // TODO Leading zeroes? 375 if ('0' <= ch && ch <= '9') 376 { 377 periodAllowed = true; 378 continue; 379 } 380 381 if (ch == '.') 382 { 383 if (!periodAllowed) 384 { 385 return false; 386 } 387 388 periodAllowed = false; 389 continue; 390 } 391 392 return false; 393 } 394 395 return periodAllowed; 396 } 397 398 private static boolean isValidIdentifier( 399 String identifier) 400 { 401 if (identifier.length() < 3 || identifier.charAt(1) != '.') 402 { 403 return false; 404 } 405 406 char first = identifier.charAt(0); 407 if (first < '0' || first > '2') 408 { 409 return false; 410 } 411 412 return isValidBranchID(identifier, 2); 413 } 414 415 /** 416 * Intern will return a reference to a pooled version of this object, unless it 417 * is not present in which case intern will add it. 418 * <p> 419 * The pool is also used by the ASN.1 parsers to limit the number of duplicated OID 420 * objects in circulation. 421 * </p> 422 * 423 * @return a reference to the identifier in the pool. 424 */ 425 public ASN1ObjectIdentifier intern() 426 { 427 final OidHandle hdl = new OidHandle(getBody()); 428 ASN1ObjectIdentifier oid = pool.get(hdl); 429 if (oid == null) 430 { 431 oid = pool.putIfAbsent(hdl, this); 432 if (oid == null) 433 { 434 oid = this; 435 } 436 } 437 return oid; 438 } 439 440 private static final ConcurrentMap<OidHandle, ASN1ObjectIdentifier> pool = new ConcurrentHashMap<OidHandle, ASN1ObjectIdentifier>(); 441 442 private static class OidHandle 443 { 444 private final int key; 445 private final byte[] enc; 446 447 OidHandle(byte[] enc) 448 { 449 this.key = Arrays.hashCode(enc); 450 this.enc = enc; 451 } 452 453 public int hashCode() 454 { 455 return key; 456 } 457 458 public boolean equals(Object o) 459 { 460 if (o instanceof OidHandle) 461 { 462 return Arrays.areEqual(enc, ((OidHandle)o).enc); 463 } 464 465 return false; 466 } 467 } 468 469 static ASN1ObjectIdentifier fromOctetString(byte[] enc) 470 { 471 final OidHandle hdl = new OidHandle(enc); 472 ASN1ObjectIdentifier oid = pool.get(hdl); 473 if (oid == null) 474 { 475 return new ASN1ObjectIdentifier(enc); 476 } 477 return oid; 478 } 479} 480