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