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