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