Home | History | Annotate | Download | only in asn1
      1 package org.bouncycastle.asn1;
      2 
      3 import java.io.ByteArrayOutputStream;
      4 import java.io.IOException;
      5 import java.math.BigInteger;
      6 
      7 import org.bouncycastle.util.Arrays;
      8 
      9 public 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