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