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 211 public abstract class ASN1Choice extends ASN1Type { 212 213 public final ASN1Type[] type; 214 215 // identifiers table: [2][number of distinct identifiers] 216 // identifiers[0]: stores identifiers (includes nested choices) 217 // identifiers[1]: stores identifiers' indexes in array of types 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 map = new TreeMap(); 237 for (int index = 0; index < type.length; index++) { 238 239 ASN1Type t = type[index]; 240 241 if (t instanceof ASN1Any) { 242 // ASN.1 ANY is not allowed, 243 // even it is a single component (not good for nested choices) 244 throw new IllegalArgumentException("ASN.1 choice type MUST have alternatives with distinct tags: " + getClass().getName()); // FIXME name 245 } else if (t instanceof ASN1Choice) { 246 247 // add all choice's identifiers 248 int[][] choiceToAdd = ((ASN1Choice) t).identifiers; 249 for (int j = 0; j < choiceToAdd[0].length; j++) { 250 addIdentifier(map, choiceToAdd[0][j], index); 251 } 252 continue; 253 } 254 255 // add primitive identifier 256 if (t.checkTag(t.id)) { 257 addIdentifier(map, t.id, index); 258 } 259 260 // add constructed identifier 261 if (t.checkTag(t.constrId)) { 262 addIdentifier(map, t.constrId, index); 263 } 264 } 265 266 // fill identifiers array 267 int size = map.size(); 268 identifiers = new int[2][size]; 269 Iterator it = map.entrySet().iterator(); 270 271 for (int i = 0; i < size; i++) { 272 Map.Entry entry = (Map.Entry) it.next(); 273 BigInteger identifier = (BigInteger) entry.getKey(); 274 275 identifiers[0][i] = identifier.intValue(); 276 identifiers[1][i] = ((BigInteger) entry.getValue()).intValue(); 277 } 278 279 this.type = type; 280 } 281 282 private void addIdentifier(TreeMap map, int identifier, int index){ 283 if (map.put(BigInteger.valueOf(identifier), BigInteger.valueOf(index)) != null) { 284 throw new IllegalArgumentException("ASN.1 choice type MUST have alternatives with distinct tags: " + getClass().getName()); // FIXME name 285 } 286 } 287 288 // 289 // 290 // DECODE 291 // 292 // 293 294 /** 295 * Tests whether one of choice alternatives has the same identifier or not. 296 * 297 * @param identifier - 298 * ASN.1 identifier to be verified 299 * @return - true if one of choice alternatives has the same identifier, 300 * otherwise false; 301 */ 302 public final boolean checkTag(int identifier) { 303 return Arrays.binarySearch(identifiers[0], identifier) >= 0; 304 } 305 306 public Object decode(BerInputStream in) throws IOException { 307 308 int index = Arrays.binarySearch(identifiers[0], in.tag); 309 if (index < 0) { 310 throw new ASN1Exception("Failed to decode ASN.1 choice type. No alternatives were found for " + getClass().getName());// FIXME message 311 } 312 313 index = identifiers[1][index]; 314 315 in.content = type[index].decode(in); 316 317 // set index for getDecodedObject method 318 in.choiceIndex = index; 319 320 if (in.isVerify) { 321 return null; 322 } 323 return getDecodedObject(in); 324 } 325 326 // 327 // 328 // ENCODE 329 // 330 // 331 332 public void encodeASN(BerOutputStream out) { 333 encodeContent(out); 334 } 335 336 public final void encodeContent(BerOutputStream out) { 337 out.encodeChoice(this); 338 } 339 340 /** 341 * TODO Put method description here 342 * 343 * @param object - an object to be encoded 344 * @return 345 */ 346 public abstract int getIndex(Object object); 347 348 public abstract Object getObjectToEncode(Object object); 349 350 public final void setEncodingContent(BerOutputStream out) { 351 out.getChoiceLength(this); 352 } 353 } 354