001package org.apache.commons.ssl.org.bouncycastle.asn1;
002
003import java.io.ByteArrayOutputStream;
004import java.io.IOException;
005import java.math.BigInteger;
006
007import org.bouncycastle.util.Arrays;
008
009/**
010 * Class representing the ASN.1 OBJECT IDENTIFIER type.
011 */
012public class ASN1ObjectIdentifier
013    extends ASN1Primitive
014{
015    String identifier;
016
017    private byte[] body;
018
019    /**
020     * return an OID from the passed in object
021     * @param obj an ASN1ObjectIdentifier or an object that can be converted into one.
022     * @throws IllegalArgumentException if the object cannot be converted.
023     * @return an ASN1ObjectIdentifier instance, or null.
024     */
025    public static ASN1ObjectIdentifier getInstance(
026        Object obj)
027    {
028        if (obj == null || obj instanceof ASN1ObjectIdentifier)
029        {
030            return (ASN1ObjectIdentifier)obj;
031        }
032
033        if (obj instanceof ASN1Encodable && ((ASN1Encodable)obj).toASN1Primitive() instanceof ASN1ObjectIdentifier)
034        {
035            return (ASN1ObjectIdentifier)((ASN1Encodable)obj).toASN1Primitive();
036        }
037
038        if (obj instanceof byte[])
039        {
040            byte[] enc = (byte[])obj;
041            try
042            {
043                return (ASN1ObjectIdentifier)fromByteArray(enc);
044            }
045            catch (IOException e)
046            {
047                throw new IllegalArgumentException("failed to construct object identifier from byte[]: " + e.getMessage());
048            }
049        }
050
051        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
052    }
053
054    /**
055     * return an Object Identifier from a tagged object.
056     *
057     * @param obj      the tagged object holding the object we want
058     * @param explicit true if the object is meant to be explicitly
059     *                 tagged false otherwise.
060     * @throws IllegalArgumentException if the tagged object cannot
061     * be converted.
062     * @return an ASN1ObjectIdentifier instance, or null.
063     */
064    public static ASN1ObjectIdentifier getInstance(
065        ASN1TaggedObject obj,
066        boolean explicit)
067    {
068        ASN1Primitive o = obj.getObject();
069
070        if (explicit || o instanceof ASN1ObjectIdentifier)
071        {
072            return getInstance(o);
073        }
074        else
075        {
076            return ASN1ObjectIdentifier.fromOctetString(ASN1OctetString.getInstance(obj.getObject()).getOctets());
077        }
078    }
079
080    private static final long LONG_LIMIT = (Long.MAX_VALUE >> 7) - 0x7f;
081
082    ASN1ObjectIdentifier(
083        byte[] bytes)
084    {
085        StringBuffer objId = new StringBuffer();
086        long value = 0;
087        BigInteger bigValue = null;
088        boolean first = true;
089
090        for (int i = 0; i != bytes.length; i++)
091        {
092            int b = bytes[i] & 0xff;
093
094            if (value <= LONG_LIMIT)
095            {
096                value += (b & 0x7f);
097                if ((b & 0x80) == 0)             // end of number reached
098                {
099                    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        this.identifier = objId.toString();
156        this.body = Arrays.clone(bytes);
157    }
158
159    /**
160     * Create an OID based on the passed in String.
161     *
162     * @param identifier a string representation of an OID.
163     */
164    public ASN1ObjectIdentifier(
165        String identifier)
166    {
167        if (identifier == null)
168        {
169            throw new IllegalArgumentException("'identifier' cannot be null");
170        }
171        if (!isValidIdentifier(identifier))
172        {
173            throw new IllegalArgumentException("string " + identifier + " not an OID");
174        }
175
176        this.identifier = identifier;
177    }
178
179     /**
180      * Create an OID that creates a branch under the current one.
181      *
182      * @param branchID node numbers for the new branch.
183      * @return the OID for the new created branch.
184      */
185    ASN1ObjectIdentifier(ASN1ObjectIdentifier oid, String branchID)
186    {
187        if (!isValidBranchID(branchID, 0))
188        {
189            throw new IllegalArgumentException("string " + branchID + " not a valid OID branch");
190        }
191
192        this.identifier = oid.getId() + "." + branchID;
193    }
194
195    /**
196     * Return the OID as a string.
197     *
198     * @return the string representation of the OID carried by this object.
199     */
200    public String getId()
201    {
202        return identifier;
203    }
204
205    /**
206     * Return an OID that creates a branch under the current one.
207     *
208     * @param branchID node numbers for the new branch.
209     * @return the OID for the new created branch.
210     */
211    public ASN1ObjectIdentifier branch(String branchID)
212    {
213        return new ASN1ObjectIdentifier(this, branchID);
214    }
215
216    /**
217     * Return  true if this oid is an extension of the passed in branch, stem.
218     *
219     * @param stem the arc or branch that is a possible parent.
220     * @return true if the branch is on the passed in stem, false otherwise.
221     */
222    public boolean on(ASN1ObjectIdentifier stem)
223    {
224        String id = getId(), stemId = stem.getId();
225        return id.length() > stemId.length() && id.charAt(stemId.length()) == '.' && id.startsWith(stemId);
226    }
227
228    private void writeField(
229        ByteArrayOutputStream out,
230        long fieldValue)
231    {
232        byte[] result = new byte[9];
233        int pos = 8;
234        result[pos] = (byte)((int)fieldValue & 0x7f);
235        while (fieldValue >= (1L << 7))
236        {
237            fieldValue >>= 7;
238            result[--pos] = (byte)((int)fieldValue & 0x7f | 0x80);
239        }
240        out.write(result, pos, 9 - pos);
241    }
242
243    private void writeField(
244        ByteArrayOutputStream out,
245        BigInteger fieldValue)
246    {
247        int byteCount = (fieldValue.bitLength() + 6) / 7;
248        if (byteCount == 0)
249        {
250            out.write(0);
251        }
252        else
253        {
254            BigInteger tmpValue = fieldValue;
255            byte[] tmp = new byte[byteCount];
256            for (int i = byteCount - 1; i >= 0; i--)
257            {
258                tmp[i] = (byte)((tmpValue.intValue() & 0x7f) | 0x80);
259                tmpValue = tmpValue.shiftRight(7);
260            }
261            tmp[byteCount - 1] &= 0x7f;
262            out.write(tmp, 0, tmp.length);
263        }
264    }
265
266    private void doOutput(ByteArrayOutputStream aOut)
267    {
268        OIDTokenizer tok = new OIDTokenizer(identifier);
269        int first = Integer.parseInt(tok.nextToken()) * 40;
270
271        String secondToken = tok.nextToken();
272        if (secondToken.length() <= 18)
273        {
274            writeField(aOut, first + Long.parseLong(secondToken));
275        }
276        else
277        {
278            writeField(aOut, new BigInteger(secondToken).add(BigInteger.valueOf(first)));
279        }
280
281        while (tok.hasMoreTokens())
282        {
283            String token = tok.nextToken();
284            if (token.length() <= 18)
285            {
286                writeField(aOut, Long.parseLong(token));
287            }
288            else
289            {
290                writeField(aOut, new BigInteger(token));
291            }
292        }
293    }
294
295    protected synchronized byte[] getBody()
296    {
297        if (body == null)
298        {
299            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
300
301            doOutput(bOut);
302
303            body = bOut.toByteArray();
304        }
305
306        return body;
307    }
308
309    boolean isConstructed()
310    {
311        return false;
312    }
313
314    int encodedLength()
315        throws IOException
316    {
317        int length = getBody().length;
318
319        return 1 + StreamUtil.calculateBodyLength(length) + length;
320    }
321
322    void encode(
323        ASN1OutputStream out)
324        throws IOException
325    {
326        byte[] enc = getBody();
327
328        out.write(BERTags.OBJECT_IDENTIFIER);
329        out.writeLength(enc.length);
330        out.write(enc);
331    }
332
333    public int hashCode()
334    {
335        return identifier.hashCode();
336    }
337
338    boolean asn1Equals(
339        ASN1Primitive o)
340    {
341        if (!(o instanceof ASN1ObjectIdentifier))
342        {
343            return false;
344        }
345
346        return identifier.equals(((ASN1ObjectIdentifier)o).identifier);
347    }
348
349    public String toString()
350    {
351        return getId();
352    }
353
354    private static boolean isValidBranchID(
355        String branchID, int start)
356    {
357        boolean periodAllowed = false;
358
359        int pos = branchID.length();
360        while (--pos >= start)
361        {
362            char ch = branchID.charAt(pos);
363
364            // TODO Leading zeroes?
365            if ('0' <= ch && ch <= '9')
366            {
367                periodAllowed = true;
368                continue;
369            }
370
371            if (ch == '.')
372            {
373                if (!periodAllowed)
374                {
375                    return false;
376                }
377
378                periodAllowed = false;
379                continue;
380            }
381
382            return false;
383        }
384
385        return periodAllowed;
386    }
387
388    private static boolean isValidIdentifier(
389        String identifier)
390    {
391        if (identifier.length() < 3 || identifier.charAt(1) != '.')
392        {
393            return false;
394        }
395
396        char first = identifier.charAt(0);
397        if (first < '0' || first > '2')
398        {
399            return false;
400        }
401
402        return isValidBranchID(identifier, 2);
403    }
404
405    private static ASN1ObjectIdentifier[][] cache = new ASN1ObjectIdentifier[256][];
406
407    static ASN1ObjectIdentifier fromOctetString(byte[] enc)
408    {
409        if (enc.length < 3)
410        {
411            return new ASN1ObjectIdentifier(enc);
412        }
413
414        int idx1 = enc[enc.length - 2] & 0xff;
415        // in this case top bit is always zero
416        int idx2 = enc[enc.length - 1] & 0x7f;
417
418        ASN1ObjectIdentifier possibleMatch;
419
420        synchronized (cache)
421        {
422            ASN1ObjectIdentifier[] first = cache[idx1];
423            if (first == null)
424            {
425                first = cache[idx1] = new ASN1ObjectIdentifier[128];
426            }
427
428            possibleMatch = first[idx2];
429            if (possibleMatch == null)
430            {
431                return first[idx2] = new ASN1ObjectIdentifier(enc);
432            }
433
434            if (Arrays.areEqual(enc, possibleMatch.getBody()))
435            {
436                return possibleMatch;
437            }
438
439            idx1 = (idx1 + 1) & 0xff;
440            first = cache[idx1];
441            if (first == null)
442            {
443                first = cache[idx1] = new ASN1ObjectIdentifier[128];
444            }
445
446            possibleMatch = first[idx2];
447            if (possibleMatch == null)
448            {
449                return first[idx2] = new ASN1ObjectIdentifier(enc);
450            }
451
452            if (Arrays.areEqual(enc, possibleMatch.getBody()))
453            {
454                return possibleMatch;
455            }
456
457            idx2 = (idx2 + 1) & 0x7f;
458            possibleMatch = first[idx2];
459            if (possibleMatch == null)
460            {
461                return first[idx2] = new ASN1ObjectIdentifier(enc);
462            }
463        }
464
465        if (Arrays.areEqual(enc, possibleMatch.getBody()))
466        {
467            return possibleMatch;
468        }
469
470        return new ASN1ObjectIdentifier(enc);
471    }
472}