001package org.apache.commons.ssl.org.bouncycastle.asn1; 002 003import java.io.ByteArrayOutputStream; 004import java.io.IOException; 005import java.math.BigInteger; 006 007import org.bouncycastle.util.Arrays; 008 009/** 010 * Class representing the ASN.1 OBJECT IDENTIFIER type. 011 */ 012public class ASN1ObjectIdentifier 013 extends ASN1Primitive 014{ 015 String identifier; 016 017 private byte[] body; 018 019 /** 020 * return an OID from the passed in object 021 * @param obj an ASN1ObjectIdentifier or an object that can be converted into one. 022 * @throws IllegalArgumentException if the object cannot be converted. 023 * @return an ASN1ObjectIdentifier instance, or null. 024 */ 025 public static ASN1ObjectIdentifier getInstance( 026 Object obj) 027 { 028 if (obj == null || obj instanceof ASN1ObjectIdentifier) 029 { 030 return (ASN1ObjectIdentifier)obj; 031 } 032 033 if (obj instanceof ASN1Encodable && ((ASN1Encodable)obj).toASN1Primitive() instanceof ASN1ObjectIdentifier) 034 { 035 return (ASN1ObjectIdentifier)((ASN1Encodable)obj).toASN1Primitive(); 036 } 037 038 if (obj instanceof byte[]) 039 { 040 byte[] enc = (byte[])obj; 041 try 042 { 043 return (ASN1ObjectIdentifier)fromByteArray(enc); 044 } 045 catch (IOException e) 046 { 047 throw new IllegalArgumentException("failed to construct object identifier from byte[]: " + e.getMessage()); 048 } 049 } 050 051 throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); 052 } 053 054 /** 055 * return an Object Identifier from a tagged object. 056 * 057 * @param obj the tagged object holding the object we want 058 * @param explicit true if the object is meant to be explicitly 059 * tagged false otherwise. 060 * @throws IllegalArgumentException if the tagged object cannot 061 * be converted. 062 * @return an ASN1ObjectIdentifier instance, or null. 063 */ 064 public static ASN1ObjectIdentifier getInstance( 065 ASN1TaggedObject obj, 066 boolean explicit) 067 { 068 ASN1Primitive o = obj.getObject(); 069 070 if (explicit || o instanceof ASN1ObjectIdentifier) 071 { 072 return getInstance(o); 073 } 074 else 075 { 076 return ASN1ObjectIdentifier.fromOctetString(ASN1OctetString.getInstance(obj.getObject()).getOctets()); 077 } 078 } 079 080 private static final long LONG_LIMIT = (Long.MAX_VALUE >> 7) - 0x7f; 081 082 ASN1ObjectIdentifier( 083 byte[] bytes) 084 { 085 StringBuffer objId = new StringBuffer(); 086 long value = 0; 087 BigInteger bigValue = null; 088 boolean first = true; 089 090 for (int i = 0; i != bytes.length; i++) 091 { 092 int b = bytes[i] & 0xff; 093 094 if (value <= LONG_LIMIT) 095 { 096 value += (b & 0x7f); 097 if ((b & 0x80) == 0) // end of number reached 098 { 099 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 this.identifier = objId.toString(); 156 this.body = Arrays.clone(bytes); 157 } 158 159 /** 160 * Create an OID based on the passed in String. 161 * 162 * @param identifier a string representation of an OID. 163 */ 164 public ASN1ObjectIdentifier( 165 String identifier) 166 { 167 if (identifier == null) 168 { 169 throw new IllegalArgumentException("'identifier' cannot be null"); 170 } 171 if (!isValidIdentifier(identifier)) 172 { 173 throw new IllegalArgumentException("string " + identifier + " not an OID"); 174 } 175 176 this.identifier = identifier; 177 } 178 179 /** 180 * Create an OID that creates a branch under the current one. 181 * 182 * @param branchID node numbers for the new branch. 183 * @return the OID for the new created branch. 184 */ 185 ASN1ObjectIdentifier(ASN1ObjectIdentifier oid, String branchID) 186 { 187 if (!isValidBranchID(branchID, 0)) 188 { 189 throw new IllegalArgumentException("string " + branchID + " not a valid OID branch"); 190 } 191 192 this.identifier = oid.getId() + "." + branchID; 193 } 194 195 /** 196 * Return the OID as a string. 197 * 198 * @return the string representation of the OID carried by this object. 199 */ 200 public String getId() 201 { 202 return identifier; 203 } 204 205 /** 206 * Return an OID that creates a branch under the current one. 207 * 208 * @param branchID node numbers for the new branch. 209 * @return the OID for the new created branch. 210 */ 211 public ASN1ObjectIdentifier branch(String branchID) 212 { 213 return new ASN1ObjectIdentifier(this, branchID); 214 } 215 216 /** 217 * Return true if this oid is an extension of the passed in branch, stem. 218 * 219 * @param stem the arc or branch that is a possible parent. 220 * @return true if the branch is on the passed in stem, false otherwise. 221 */ 222 public boolean on(ASN1ObjectIdentifier stem) 223 { 224 String id = getId(), stemId = stem.getId(); 225 return id.length() > stemId.length() && id.charAt(stemId.length()) == '.' && id.startsWith(stemId); 226 } 227 228 private void writeField( 229 ByteArrayOutputStream out, 230 long fieldValue) 231 { 232 byte[] result = new byte[9]; 233 int pos = 8; 234 result[pos] = (byte)((int)fieldValue & 0x7f); 235 while (fieldValue >= (1L << 7)) 236 { 237 fieldValue >>= 7; 238 result[--pos] = (byte)((int)fieldValue & 0x7f | 0x80); 239 } 240 out.write(result, pos, 9 - pos); 241 } 242 243 private void writeField( 244 ByteArrayOutputStream out, 245 BigInteger fieldValue) 246 { 247 int byteCount = (fieldValue.bitLength() + 6) / 7; 248 if (byteCount == 0) 249 { 250 out.write(0); 251 } 252 else 253 { 254 BigInteger tmpValue = fieldValue; 255 byte[] tmp = new byte[byteCount]; 256 for (int i = byteCount - 1; i >= 0; i--) 257 { 258 tmp[i] = (byte)((tmpValue.intValue() & 0x7f) | 0x80); 259 tmpValue = tmpValue.shiftRight(7); 260 } 261 tmp[byteCount - 1] &= 0x7f; 262 out.write(tmp, 0, tmp.length); 263 } 264 } 265 266 private void doOutput(ByteArrayOutputStream aOut) 267 { 268 OIDTokenizer tok = new OIDTokenizer(identifier); 269 int first = Integer.parseInt(tok.nextToken()) * 40; 270 271 String secondToken = tok.nextToken(); 272 if (secondToken.length() <= 18) 273 { 274 writeField(aOut, first + Long.parseLong(secondToken)); 275 } 276 else 277 { 278 writeField(aOut, new BigInteger(secondToken).add(BigInteger.valueOf(first))); 279 } 280 281 while (tok.hasMoreTokens()) 282 { 283 String token = tok.nextToken(); 284 if (token.length() <= 18) 285 { 286 writeField(aOut, Long.parseLong(token)); 287 } 288 else 289 { 290 writeField(aOut, new BigInteger(token)); 291 } 292 } 293 } 294 295 protected synchronized byte[] getBody() 296 { 297 if (body == null) 298 { 299 ByteArrayOutputStream bOut = new ByteArrayOutputStream(); 300 301 doOutput(bOut); 302 303 body = bOut.toByteArray(); 304 } 305 306 return body; 307 } 308 309 boolean isConstructed() 310 { 311 return false; 312 } 313 314 int encodedLength() 315 throws IOException 316 { 317 int length = getBody().length; 318 319 return 1 + StreamUtil.calculateBodyLength(length) + length; 320 } 321 322 void encode( 323 ASN1OutputStream out) 324 throws IOException 325 { 326 byte[] enc = getBody(); 327 328 out.write(BERTags.OBJECT_IDENTIFIER); 329 out.writeLength(enc.length); 330 out.write(enc); 331 } 332 333 public int hashCode() 334 { 335 return identifier.hashCode(); 336 } 337 338 boolean asn1Equals( 339 ASN1Primitive o) 340 { 341 if (!(o instanceof ASN1ObjectIdentifier)) 342 { 343 return false; 344 } 345 346 return identifier.equals(((ASN1ObjectIdentifier)o).identifier); 347 } 348 349 public String toString() 350 { 351 return getId(); 352 } 353 354 private static boolean isValidBranchID( 355 String branchID, int start) 356 { 357 boolean periodAllowed = false; 358 359 int pos = branchID.length(); 360 while (--pos >= start) 361 { 362 char ch = branchID.charAt(pos); 363 364 // TODO Leading zeroes? 365 if ('0' <= ch && ch <= '9') 366 { 367 periodAllowed = true; 368 continue; 369 } 370 371 if (ch == '.') 372 { 373 if (!periodAllowed) 374 { 375 return false; 376 } 377 378 periodAllowed = false; 379 continue; 380 } 381 382 return false; 383 } 384 385 return periodAllowed; 386 } 387 388 private static boolean isValidIdentifier( 389 String identifier) 390 { 391 if (identifier.length() < 3 || identifier.charAt(1) != '.') 392 { 393 return false; 394 } 395 396 char first = identifier.charAt(0); 397 if (first < '0' || first > '2') 398 { 399 return false; 400 } 401 402 return isValidBranchID(identifier, 2); 403 } 404 405 private static ASN1ObjectIdentifier[][] cache = new ASN1ObjectIdentifier[256][]; 406 407 static ASN1ObjectIdentifier fromOctetString(byte[] enc) 408 { 409 if (enc.length < 3) 410 { 411 return new ASN1ObjectIdentifier(enc); 412 } 413 414 int idx1 = enc[enc.length - 2] & 0xff; 415 // in this case top bit is always zero 416 int idx2 = enc[enc.length - 1] & 0x7f; 417 418 ASN1ObjectIdentifier possibleMatch; 419 420 synchronized (cache) 421 { 422 ASN1ObjectIdentifier[] first = cache[idx1]; 423 if (first == null) 424 { 425 first = cache[idx1] = new ASN1ObjectIdentifier[128]; 426 } 427 428 possibleMatch = first[idx2]; 429 if (possibleMatch == null) 430 { 431 return first[idx2] = new ASN1ObjectIdentifier(enc); 432 } 433 434 if (Arrays.areEqual(enc, possibleMatch.getBody())) 435 { 436 return possibleMatch; 437 } 438 439 idx1 = (idx1 + 1) & 0xff; 440 first = cache[idx1]; 441 if (first == null) 442 { 443 first = cache[idx1] = new ASN1ObjectIdentifier[128]; 444 } 445 446 possibleMatch = first[idx2]; 447 if (possibleMatch == null) 448 { 449 return first[idx2] = new ASN1ObjectIdentifier(enc); 450 } 451 452 if (Arrays.areEqual(enc, possibleMatch.getBody())) 453 { 454 return possibleMatch; 455 } 456 457 idx2 = (idx2 + 1) & 0x7f; 458 possibleMatch = first[idx2]; 459 if (possibleMatch == null) 460 { 461 return first[idx2] = new ASN1ObjectIdentifier(enc); 462 } 463 } 464 465 if (Arrays.areEqual(enc, possibleMatch.getBody())) 466 { 467 return possibleMatch; 468 } 469 470 return new ASN1ObjectIdentifier(enc); 471 } 472}