DERObjectIdentifier.java revision a198e1ecc615e26a167d0f2dca9fa7e5fc62de10
1package org.bouncycastle.asn1;
2
3import java.io.ByteArrayOutputStream;
4import java.io.IOException;
5import java.math.BigInteger;
6
7import org.bouncycastle.util.Arrays;
8
9public 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