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