Home | History | Annotate | Download | only in asn1
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 /**
     18 * @author Vladimir N. Molotkov, Stepan M. Mishura
     19 * @version $Revision$
     20 */
     21 
     22 package org.apache.harmony.security.asn1;
     23 
     24 import java.io.IOException;
     25 import java.math.BigInteger;
     26 import java.util.Arrays;
     27 import java.util.Iterator;
     28 import java.util.Map;
     29 import java.util.TreeMap;
     30 
     31 
     32 /**
     33  * This abstract class represents ASN.1 Choice type.
     34  *
     35  * To implement custom ASN.1 choice type an application class
     36  * must provide implementation for the following methods:
     37  *     getIndex()
     38  *     getObjectToEncode()
     39  *
     40  * There are two ways to implement custom ASN.1 choice type:
     41  * with application class that represents ASN.1 custom choice type or without.
     42  * The key point is how a value of choice type is stored by application classes.
     43  *
     44  * For example, let's consider the following ASN.1 notations
     45  * (see http://www.ietf.org/rfc/rfc3280.txt)
     46  *
     47  * Time ::= CHOICE {
     48  *       utcTime        UTCTime,
     49  *       generalTime    GeneralizedTime
     50  * }
     51  *
     52  * Validity ::= SEQUENCE {
     53  *       notBefore      Time,
     54  *       notAfter       Time
     55  *  }
     56  *
     57  * 1)First approach:
     58  * No application class to represent ASN.1 Time notation
     59  *
     60  * The Time notation is a choice of different time formats: UTC and Generalized.
     61  * Both of them are mapped to java.util.Date object, so an application
     62  * class that represents ASN.1 Validity notation may keep values
     63  * as Date objects.
     64  *
     65  * So a custom ASN.1 Time choice type should map its notation to Date object.
     66  *
     67  * class Time {
     68  *
     69  *     // custom ASN.1 choice class: maps Time to is notation
     70  *     public static final ASN1Choice asn1 = new ASN1Choice(new ASN1Type[] {
     71  *         ASN1GeneralizedTime.asn1, ASN1UTCTime.asn1 }) {
     72  *
     73  *         public int getIndex(java.lang.Object object) {
     74  *             return 0; // always encode as ASN1GeneralizedTime
     75  *         }
     76  *
     77  *         public Object getObjectToEncode(Object object) {
     78  *
     79  *             // A value to be encoded value is a Date object
     80  *             // pass it to custom time class
     81  *             return object;
     82  *         }
     83  *     };
     84  * }
     85  *
     86  * class Validity {
     87  *
     88  *     private Date notBefore;    // choice as Date
     89  *     private Date notAfter;     // choice as Date
     90  *
     91  *     ... // constructors and other methods go here
     92  *
     93  *     // custom ASN.1 sequence class: maps Validity class to is notation
     94  *     public static final ASN1Sequence ASN1
     95  *         = new ASN1Sequence(new ASN1Type[] {Time.asn1, Time.asn1 }) {
     96  *
     97  *         protected Object getObject(Object[] values) {
     98  *
     99  *             // ASN.1 Time choice passed Data object - use it
    100  *             return new Validity((Date) values[0], (Date) values[1]);
    101  *         }
    102  *
    103  *         protected void getValues(Object object, Object[] values) {
    104  *
    105  *             Validity validity = (Validity) object;
    106  *
    107  *             // pass Date objects to ASN.1 Time choice
    108  *             values[0] = validity.notBefore;
    109  *             values[1] = validity.notAfter;
    110  *         }
    111  *     }
    112  * }
    113  *
    114  * 2)Second approach:
    115  * There is an application class to represent ASN.1 Time notation
    116  *
    117  * If it is a matter what time format should be used to decode/encode
    118  * Date objects a class to represent ASN.1 Time notation must be created.
    119  *
    120  * For example,
    121  *
    122  * class Time {
    123  *
    124  *     private Date utcTime;
    125  *     private Date gTime;
    126  *
    127  *     ... // constructors and other methods go here
    128  *
    129  *     // custom ASN.1 choice class: maps Time to is notation
    130  *     public static final ASN1Choice asn1 = new ASN1Choice(new ASN1Type[] {
    131  *         ASN1GeneralizedTime.asn1, ASN1UTCTime.asn1 }) {
    132  *
    133  *         public Object getDecodedObject(BerInputStream in) {
    134  *
    135  *             // create Time object to pass as decoded value
    136  *             Time time = new Time();
    137  *
    138  *             if (in.choiceIndex==0) {
    139  *                 // we decoded GeneralizedTime
    140  *                 // store decoded Date value in corresponding field
    141  *                 time.gTime = in.content;
    142  *                 // return it
    143  *                 return time;
    144  *             } else {
    145  *                 // we decoded UTCTime
    146  *                 // store decoded Date value in corresponding field
    147  *                 time.utcTime = in.content;
    148  *                 // return it
    149  *                 return time;
    150  *             }
    151  *         }
    152  *
    153  *         public int getIndex(java.lang.Object object) {
    154  *             Time time = (Time)object;
    155  *             if(time.utcTime!=null){
    156  *                 // encode Date as UTCTime
    157  *                 return 1;
    158  *             } else {
    159  *                 // otherwise encode Date as GeneralizedTime
    160  *                 return 0;
    161  *             }
    162  *         }
    163  *
    164  *         public Object getObjectToEncode(Object object) {
    165  *             Time time = (Time)object;
    166  *             if(time.utcTime!=null){
    167  *                 // encode Date as UTCTime
    168  *                 return 1;
    169  *             } else {
    170  *                 // otherwise encode Date as GeneralizedTime
    171  *                 return 0;
    172  *             }
    173  *         }
    174  *     };
    175  * }
    176  *
    177  * So now Validity class must keep all values in Time object
    178  * and its custom ASN.1 sequence class must handle this class of objects
    179  *
    180  * class Validity {
    181  *
    182  *     private Time notBefore;    // now it is a Time!!!
    183  *     private Time notAfter;     // now it is a Time!!!
    184  *
    185  *     ... // constructors and other methods go here
    186  *
    187  *     // custom ASN.1 sequence class: maps Validity class to is notation
    188  *     public static final ASN1Sequence ASN1
    189  *         = new ASN1Sequence(new ASN1Type[] {Time.asn1, Time.asn1 }) {
    190  *
    191  *         protected Object getObject(Object[] values) {
    192  *
    193  *             // We've gotten Time objects here !!!
    194  *             return new Validity((Time) values[0], (Time) values[1]);
    195  *         }
    196  *
    197  *         protected void getValues(Object object, Object[] values) {
    198  *
    199  *             Validity validity = (Validity) object;
    200  *
    201  *             // pass Time objects to ASN.1 Time choice
    202  *             values[0] = validity.notBefore;
    203  *             values[1] = validity.notAfter;
    204  *         }
    205  *     }
    206  * }
    207  *
    208  * @see <a href="http://asn1.elibel.tm.fr/en/standards/index.htm">ASN.1</a>
    209  */
    210 public abstract class ASN1Choice extends ASN1Type {
    211     public final ASN1Type[] type;
    212 
    213     /**
    214      * identifiers table: [2][number of distinct identifiers]
    215      * identifiers[0]: stores identifiers (includes nested choices)
    216      * identifiers[1]: stores identifiers' indexes in array of types
    217      */
    218     private final int[][] identifiers;
    219 
    220     /**
    221      * Constructs ASN.1 choice type.
    222      *
    223      * @param type -
    224      *            an array of one or more ASN.1 type alternatives.
    225      * @throws IllegalArgumentException -
    226      *             type parameter is invalid
    227      */
    228     public ASN1Choice(ASN1Type[] type) {
    229         super(TAG_CHOICE); // has not tag number
    230 
    231         if (type.length == 0) {
    232             throw new IllegalArgumentException("ASN.1 choice type MUST have at least one alternative: " + getClass().getName());
    233         }
    234 
    235         // create map of all identifiers
    236         TreeMap<BigInteger, BigInteger> map = new TreeMap<BigInteger, BigInteger>();
    237         for (int index = 0; index < type.length; index++) {
    238             ASN1Type t = type[index];
    239 
    240             if (t instanceof ASN1Any) {
    241                 // ASN.1 ANY is not allowed,
    242                 // even it is a single component (not good for nested choices)
    243                 throw new IllegalArgumentException("ASN.1 choice type MUST have alternatives with distinct tags: " + getClass().getName()); // FIXME name
    244             } else if (t instanceof ASN1Choice) {
    245 
    246                 // add all choice's identifiers
    247                 int[][] choiceToAdd = ((ASN1Choice) t).identifiers;
    248                 for (int j = 0; j < choiceToAdd[0].length; j++) {
    249                     addIdentifier(map, choiceToAdd[0][j], index);
    250                 }
    251                 continue;
    252             }
    253 
    254             // add primitive identifier
    255             if (t.checkTag(t.id)) {
    256                 addIdentifier(map, t.id, index);
    257             }
    258 
    259             // add constructed identifier
    260             if (t.checkTag(t.constrId)) {
    261                 addIdentifier(map, t.constrId, index);
    262             }
    263         }
    264 
    265         // fill identifiers array
    266         int size = map.size();
    267         identifiers = new int[2][size];
    268         Iterator<Map.Entry<BigInteger, BigInteger>> it = map.entrySet().iterator();
    269 
    270         for (int i = 0; i < size; i++) {
    271             Map.Entry<BigInteger, BigInteger> entry = it.next();
    272             BigInteger identifier = entry.getKey();
    273 
    274             identifiers[0][i] = identifier.intValue();
    275             identifiers[1][i] = entry.getValue().intValue();
    276         }
    277 
    278         this.type = type;
    279     }
    280 
    281     private void addIdentifier(TreeMap<BigInteger, BigInteger> map, int identifier, int index){
    282         if (map.put(BigInteger.valueOf(identifier), BigInteger.valueOf(index)) != null) {
    283             throw new IllegalArgumentException("ASN.1 choice type MUST have alternatives "
    284                     + "with distinct tags: " + getClass().getName());
    285         }
    286     }
    287 
    288     /**
    289      * Tests whether one of choice alternatives has the same identifier or not.
    290      *
    291      * @param identifier -
    292      *            ASN.1 identifier to be verified
    293      * @return - true if one of choice alternatives has the same identifier,
    294      *         otherwise false;
    295      */
    296     public final boolean checkTag(int identifier) {
    297         return Arrays.binarySearch(identifiers[0], identifier) >= 0;
    298     }
    299 
    300     public Object decode(BerInputStream in) throws IOException {
    301         int index = Arrays.binarySearch(identifiers[0], in.tag);
    302         if (index < 0) {
    303             throw new ASN1Exception("Failed to decode ASN.1 choice type.  No alternatives were found for " + getClass().getName());// FIXME message
    304         }
    305 
    306         index = identifiers[1][index];
    307 
    308         in.content = type[index].decode(in);
    309 
    310         // set index for getDecodedObject method
    311         in.choiceIndex = index;
    312 
    313         if (in.isVerify) {
    314             return null;
    315         }
    316         return getDecodedObject(in);
    317     }
    318 
    319     public void encodeASN(BerOutputStream out) {
    320         encodeContent(out);
    321     }
    322 
    323     public final void encodeContent(BerOutputStream out) {
    324         out.encodeChoice(this);
    325     }
    326 
    327     public abstract int getIndex(Object object);
    328 
    329     public abstract Object getObjectToEncode(Object object);
    330 
    331     public final void setEncodingContent(BerOutputStream out) {
    332         out.getChoiceLength(this);
    333     }
    334 }
    335