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