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