/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @author Vladimir N. Molotkov, Stepan M. Mishura */ package org.apache.harmony.security.asn1; import java.io.IOException; import java.math.BigInteger; import java.util.Arrays; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; import org.apache.harmony.security.internal.nls.Messages; /** * This abstract class represents ASN.1 Choice type. * * To implement custom ASN.1 choice type an application class must provide * implementation for the following methods: getIndex() getObjectToEncode() * * There are two ways to implement custom ASN.1 choice type: with application * class that represents ASN.1 custom choice type or without. The key point is * how a value of choice type is stored by application classes. * * For example, let's consider the following ASN.1 notations (see * http://www.ietf.org/rfc/rfc3280.txt) * * Time ::= CHOICE { utcTime UTCTime, generalTime GeneralizedTime } * * Validity ::= SEQUENCE { notBefore Time, notAfter Time } * * 1)First approach: No application class to represent ASN.1 Time notation * * The Time notation is a choice of different time formats: UTC and Generalized. * Both of them are mapped to java.util.Date object, so an application class * that represents ASN.1 Validity notation may keep values as Date objects. * * So a custom ASN.1 Time choice type should map its notation to Date object. * * class Time { * * // custom ASN.1 choice class: maps Time to is notation public static final * ASN1Choice asn1 = new ASN1Choice(new ASN1Type[] { ASN1GeneralizedTime.asn1, * ASN1UTCTime.asn1 }) { * * public int getIndex(java.lang.Object object) { return 0; // always encode as * ASN1GeneralizedTime } * * public Object getObjectToEncode(Object object) { * * // A value to be encoded value is a Date object // pass it to custom time * class return object; } }; } * * class Validity { * * private Date notBefore; // choice as Date private Date notAfter; // choice as * Date * * ... // constructors and other methods go here * * // custom ASN.1 sequence class: maps Validity class to is notation public * static final ASN1Sequence ASN1 = new ASN1Sequence(new ASN1Type[] {Time.asn1, * Time.asn1 }) { * * protected Object getObject(Object[] values) { * * // ASN.1 Time choice passed Data object - use it return new Validity((Date) * values[0], (Date) values[1]); } * * protected void getValues(Object object, Object[] values) { * * Validity validity = (Validity) object; * * // pass Date objects to ASN.1 Time choice values[0] = validity.notBefore; * values[1] = validity.notAfter; } } } * * 2)Second approach: There is an application class to represent ASN.1 Time * notation * * If it is a matter what time format should be used to decode/encode Date * objects a class to represent ASN.1 Time notation must be created. * * For example, * * class Time { * * private Date utcTime; private Date gTime; * * ... // constructors and other methods go here * * // custom ASN.1 choice class: maps Time to is notation public static final * ASN1Choice asn1 = new ASN1Choice(new ASN1Type[] { ASN1GeneralizedTime.asn1, * ASN1UTCTime.asn1 }) { * * public Object getDecodedObject(BerInputStream in) { * * // create Time object to pass as decoded value Time time = new Time(); * * if (in.choiceIndex==0) { // we decoded GeneralizedTime // store decoded Date * value in corresponding field time.gTime = in.content; // return it return * time; } else { // we decoded UTCTime // store decoded Date value in * corresponding field time.utcTime = in.content; // return it return time; } } * * public int getIndex(java.lang.Object object) { Time time = (Time)object; * if(time.utcTime!=null){ // encode Date as UTCTime return 1; } else { // * otherwise encode Date as GeneralizedTime return 0; } } * * public Object getObjectToEncode(Object object) { Time time = (Time)object; * if(time.utcTime!=null){ // encode Date as UTCTime return 1; } else { // * otherwise encode Date as GeneralizedTime return 0; } } }; } * * So now Validity class must keep all values in Time object and its custom * ASN.1 sequence class must handle this class of objects * * class Validity { * * private Time notBefore; // now it is a Time!!! private Time notAfter; // now * it is a Time!!! * * ... // constructors and other methods go here * * // custom ASN.1 sequence class: maps Validity class to is notation public * static final ASN1Sequence ASN1 = new ASN1Sequence(new ASN1Type[] {Time.asn1, * Time.asn1 }) { * * protected Object getObject(Object[] values) { * * // We've gotten Time objects here !!! return new Validity((Time) values[0], * (Time) values[1]); } * * protected void getValues(Object object, Object[] values) { * * Validity validity = (Validity) object; * * // pass Time objects to ASN.1 Time choice values[0] = validity.notBefore; * values[1] = validity.notAfter; } } } * * @see http://asn1.elibel.tm.fr/en/standards/index.htm */ public abstract class ASN1Choice extends ASN1Type { public final ASN1Type[] type; // identifiers table: [2][number of distinct identifiers] // identifiers[0]: stores identifiers (includes nested choices) // identifiers[1]: stores identifiers' indexes in array of types private final int[][] identifiers; /** * Constructs ASN.1 choice type. * * @param type * - an array of one or more ASN.1 type alternatives. * @throws IllegalArgumentException * - type parameter is invalid */ public ASN1Choice(ASN1Type[] type) { super(TAG_CHOICE); // has not tag number if (type.length == 0) { throw new IllegalArgumentException(Messages.getString( "security.10E", //$NON-NLS-1$ getClass().getName())); } // create map of all identifiers final TreeMap map = new TreeMap(); for (int index = 0; index < type.length; index++) { final ASN1Type t = type[index]; if (t instanceof ASN1Any) { // ASN.1 ANY is not allowed, // even it is a single component (not good for nested choices) throw new IllegalArgumentException(Messages.getString( "security.10F", //$NON-NLS-1$ getClass().getName())); // FIXME name } else if (t instanceof ASN1Choice) { // add all choice's identifiers final int[][] choiceToAdd = ((ASN1Choice) t).identifiers; for (int j = 0; j < choiceToAdd[0].length; j++) { addIdentifier(map, choiceToAdd[0][j], index); } continue; } // add primitive identifier if (t.checkTag(t.id)) { addIdentifier(map, t.id, index); } // add constructed identifier if (t.checkTag(t.constrId)) { addIdentifier(map, t.constrId, index); } } // fill identifiers array final int size = map.size(); identifiers = new int[2][size]; final Iterator it = map.entrySet().iterator(); for (int i = 0; i < size; i++) { final Map.Entry entry = (Map.Entry) it.next(); final BigInteger identifier = (BigInteger) entry.getKey(); identifiers[0][i] = identifier.intValue(); identifiers[1][i] = ((BigInteger) entry.getValue()).intValue(); } this.type = type; } private void addIdentifier(TreeMap map, int identifier, int index) { if (map.put(BigInteger.valueOf(identifier), BigInteger.valueOf(index)) != null) { throw new IllegalArgumentException(Messages.getString( "security.10F", //$NON-NLS-1$ getClass().getName())); // FIXME name } } // // // DECODE // // /** * Tests whether one of choice alternatives has the same identifier or not. * * @param identifier * - ASN.1 identifier to be verified * @return - true if one of choice alternatives has the same identifier, * otherwise false; */ @Override public final boolean checkTag(int identifier) { return Arrays.binarySearch(identifiers[0], identifier) >= 0; } @Override public Object decode(BerInputStream in) throws IOException { int index = Arrays.binarySearch(identifiers[0], in.tag); if (index < 0) { throw new ASN1Exception(Messages.getString("security.110", //$NON-NLS-1$ getClass().getName()));// FIXME message } index = identifiers[1][index]; in.content = type[index].decode(in); // set index for getDecodedObject method in.choiceIndex = index; if (in.isVerify) { return null; } return getDecodedObject(in); } // // // ENCODE // // @Override public void encodeASN(BerOutputStream out) { encodeContent(out); } @Override public final void encodeContent(BerOutputStream out) { out.encodeChoice(this); } /** * TODO Put method description here * * @param object * - an object to be encoded * @return */ public abstract int getIndex(Object object); public abstract Object getObjectToEncode(Object object); @Override public final void setEncodingContent(BerOutputStream out) { out.getChoiceLength(this); } }