All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.dongliu.apk.parser.cert.asn1.Asn1BerParser Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed 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.
 */

package net.dongliu.apk.parser.cert.asn1;

import net.dongliu.apk.parser.cert.asn1.ber.*;
import net.dongliu.apk.parser.utils.Buffers;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Parser of ASN.1 BER-encoded structures.
 * 

*

Structure is described to the parser by providing a class annotated with {@link Asn1Class}, * containing fields annotated with {@link Asn1Field}. */ public final class Asn1BerParser { private Asn1BerParser() { } /** * Returns the ASN.1 structure contained in the BER encoded input. * * @param encoded encoded input. If the decoding operation succeeds, the position of this buffer * is advanced to the first position following the end of the consumed structure. * @param containerClass class describing the structure of the input. The class must meet the * following requirements: *

    *
  • The class must be annotated with {@link Asn1Class}.
  • *
  • The class must expose a public no-arg constructor.
  • *
  • Member fields of the class which are populated with parsed input must be * annotated with {@link Asn1Field} and be public and non-final.
  • *
* @throws Asn1DecodingException if the input could not be decoded into the specified Java * object */ public static T parse(ByteBuffer encoded, Class containerClass) throws Asn1DecodingException { BerDataValue containerDataValue; try { containerDataValue = new ByteBufferBerDataValueReader(encoded).readDataValue(); } catch (BerDataValueFormatException e) { throw new Asn1DecodingException("Failed to decode top-level data value", e); } if (containerDataValue == null) { throw new Asn1DecodingException("Empty input"); } return parse(containerDataValue, containerClass); } /** * Returns the implicit {@code SET OF} contained in the provided ASN.1 BER input. Implicit means * that this method does not care whether the tag number of this data structure is * {@code SET OF} and whether the tag class is {@code UNIVERSAL}. *

*

Note: The returned type is {@link List} rather than {@link java.util.Set} because ASN.1 * SET may contain duplicate elements. * * @param encoded encoded input. If the decoding operation succeeds, the position of this buffer * is advanced to the first position following the end of the consumed structure. * @param elementClass class describing the structure of the values/elements contained in this * container. The class must meet the following requirements: *

    *
  • The class must be annotated with {@link Asn1Class}.
  • *
  • The class must expose a public no-arg constructor.
  • *
  • Member fields of the class which are populated with parsed input must be * annotated with {@link Asn1Field} and be public and non-final.
  • *
* @throws Asn1DecodingException if the input could not be decoded into the specified Java * object */ public static List parseImplicitSetOf(ByteBuffer encoded, Class elementClass) throws Asn1DecodingException { BerDataValue containerDataValue; try { containerDataValue = new ByteBufferBerDataValueReader(encoded).readDataValue(); } catch (BerDataValueFormatException e) { throw new Asn1DecodingException("Failed to decode top-level data value", e); } if (containerDataValue == null) { throw new Asn1DecodingException("Empty input"); } return parseSetOf(containerDataValue, elementClass); } private static T parse(BerDataValue container, Class containerClass) throws Asn1DecodingException { if (container == null) { throw new NullPointerException("container == null"); } if (containerClass == null) { throw new NullPointerException("containerClass == null"); } Asn1Type dataType = getContainerAsn1Type(containerClass); switch (dataType) { case CHOICE: return parseChoice(container, containerClass); case SEQUENCE: { int expectedTagClass = BerEncoding.TAG_CLASS_UNIVERSAL; int expectedTagNumber = BerEncoding.getTagNumber(dataType); if ((container.getTagClass() != expectedTagClass) || (container.getTagNumber() != expectedTagNumber)) { throw new Asn1UnexpectedTagException( "Unexpected data value read as " + containerClass.getName() + ". Expected " + BerEncoding.tagClassAndNumberToString( expectedTagClass, expectedTagNumber) + ", but read: " + BerEncoding.tagClassAndNumberToString( container.getTagClass(), container.getTagNumber())); } return parseSequence(container, containerClass); } default: throw new Asn1DecodingException("Parsing container " + dataType + " not supported"); } } private static T parseChoice(BerDataValue dataValue, Class containerClass) throws Asn1DecodingException { List fields = getAnnotatedFields(containerClass); if (fields.isEmpty()) { throw new Asn1DecodingException( "No fields annotated with " + Asn1Field.class.getName() + " in CHOICE class " + containerClass.getName()); } // Check that class + tagNumber don't clash between the choices for (int i = 0; i < fields.size() - 1; i++) { AnnotatedField f1 = fields.get(i); int tagNumber1 = f1.getBerTagNumber(); int tagClass1 = f1.getBerTagClass(); for (int j = i + 1; j < fields.size(); j++) { AnnotatedField f2 = fields.get(j); int tagNumber2 = f2.getBerTagNumber(); int tagClass2 = f2.getBerTagClass(); if ((tagNumber1 == tagNumber2) && (tagClass1 == tagClass2)) { throw new Asn1DecodingException( "CHOICE fields are indistinguishable because they have the same tag" + " class and number: " + containerClass.getName() + "." + f1.getField().getName() + " and ." + f2.getField().getName()); } } } // Instantiate the container object / result T obj; try { obj = containerClass.getConstructor().newInstance(); } catch (IllegalArgumentException | ReflectiveOperationException e) { throw new Asn1DecodingException("Failed to instantiate " + containerClass.getName(), e); } // Set the matching field's value from the data value for (AnnotatedField field : fields) { try { field.setValueFrom(dataValue, obj); return obj; } catch (Asn1UnexpectedTagException expected) { // not a match } } throw new Asn1DecodingException( "No options of CHOICE " + containerClass.getName() + " matched"); } private static T parseSequence(BerDataValue container, Class containerClass) throws Asn1DecodingException { List fields = getAnnotatedFields(containerClass); Collections.sort( fields, new Comparator() { @Override public int compare(AnnotatedField f1, AnnotatedField f2) { return f1.getAnnotation().index() - f2.getAnnotation().index(); } }); // Check that there are no fields with the same index if (fields.size() > 1) { AnnotatedField lastField = null; for (AnnotatedField field : fields) { if ((lastField != null) && (lastField.getAnnotation().index() == field.getAnnotation().index())) { throw new Asn1DecodingException( "Fields have the same index: " + containerClass.getName() + "." + lastField.getField().getName() + " and ." + field.getField().getName()); } lastField = field; } } // Instantiate the container object / result T t; try { t = containerClass.getConstructor().newInstance(); } catch (IllegalArgumentException | ReflectiveOperationException e) { throw new Asn1DecodingException("Failed to instantiate " + containerClass.getName(), e); } // Parse fields one by one. A complication is that there may be optional fields. int nextUnreadFieldIndex = 0; BerDataValueReader elementsReader = container.contentsReader(); while (nextUnreadFieldIndex < fields.size()) { BerDataValue dataValue; try { dataValue = elementsReader.readDataValue(); } catch (BerDataValueFormatException e) { throw new Asn1DecodingException("Malformed data value", e); } if (dataValue == null) { break; } for (int i = nextUnreadFieldIndex; i < fields.size(); i++) { AnnotatedField field = fields.get(i); try { if (field.isOptional()) { // Optional field -- might not be present and we may thus be trying to set // it from the wrong tag. try { field.setValueFrom(dataValue, t); nextUnreadFieldIndex = i + 1; break; } catch (Asn1UnexpectedTagException e) { // This field is not present, attempt to use this data value for the // next / iteration of the loop continue; } } else { // Mandatory field -- if we can't set its value from this data value, then // it's an error field.setValueFrom(dataValue, t); nextUnreadFieldIndex = i + 1; break; } } catch (Asn1DecodingException e) { throw new Asn1DecodingException( "Failed to parse " + containerClass.getName() + "." + field.getField().getName(), e); } } } return t; } // NOTE: This method returns List rather than Set because ASN.1 SET_OF does require uniqueness // of elements -- it's an unordered collection. @SuppressWarnings("unchecked") private static List parseSetOf(BerDataValue container, Class elementClass) throws Asn1DecodingException { List result = new ArrayList<>(); BerDataValueReader elementsReader = container.contentsReader(); while (true) { BerDataValue dataValue; try { dataValue = elementsReader.readDataValue(); } catch (BerDataValueFormatException e) { throw new Asn1DecodingException("Malformed data value", e); } if (dataValue == null) { break; } T element; if (ByteBuffer.class.equals(elementClass)) { element = (T) dataValue.getEncodedContents(); } else if (Asn1OpaqueObject.class.equals(elementClass)) { element = (T) new Asn1OpaqueObject(dataValue.getEncoded()); } else { element = parse(dataValue, elementClass); } result.add(element); } return result; } private static Asn1Type getContainerAsn1Type(Class containerClass) throws Asn1DecodingException { Asn1Class containerAnnotation = containerClass.getAnnotation(Asn1Class.class); if (containerAnnotation == null) { throw new Asn1DecodingException( containerClass.getName() + " is not annotated with " + Asn1Class.class.getName()); } switch (containerAnnotation.type()) { case CHOICE: case SEQUENCE: return containerAnnotation.type(); default: throw new Asn1DecodingException( "Unsupported ASN.1 container annotation type: " + containerAnnotation.type()); } } private static Class getElementType(Field field) throws Asn1DecodingException, ClassNotFoundException { String type = field.getGenericType().toString(); int delimiterIndex = type.indexOf('<'); if (delimiterIndex == -1) { throw new Asn1DecodingException("Not a container type: " + field.getGenericType()); } int startIndex = delimiterIndex + 1; int endIndex = type.indexOf('>', startIndex); // TODO: handle comma? if (endIndex == -1) { throw new Asn1DecodingException("Not a container type: " + field.getGenericType()); } String elementClassName = type.substring(startIndex, endIndex); return Class.forName(elementClassName); } private static final class AnnotatedField { private final Field mField; private final Asn1Field mAnnotation; private final Asn1Type mDataType; private final Asn1TagClass mTagClass; private final int mBerTagClass; private final int mBerTagNumber; private final Asn1Tagging mTagging; private final boolean mOptional; public AnnotatedField(Field field, Asn1Field annotation) throws Asn1DecodingException { mField = field; mAnnotation = annotation; mDataType = annotation.type(); Asn1TagClass tagClass = annotation.cls(); if (tagClass == Asn1TagClass.AUTOMATIC) { if (annotation.tagNumber() != -1) { tagClass = Asn1TagClass.CONTEXT_SPECIFIC; } else { tagClass = Asn1TagClass.UNIVERSAL; } } mTagClass = tagClass; mBerTagClass = BerEncoding.getTagClass(mTagClass); int tagNumber; if (annotation.tagNumber() != -1) { tagNumber = annotation.tagNumber(); } else if ((mDataType == Asn1Type.CHOICE) || (mDataType == Asn1Type.ANY)) { tagNumber = -1; } else { tagNumber = BerEncoding.getTagNumber(mDataType); } mBerTagNumber = tagNumber; mTagging = annotation.tagging(); if (((mTagging == Asn1Tagging.EXPLICIT) || (mTagging == Asn1Tagging.IMPLICIT)) && (annotation.tagNumber() == -1)) { throw new Asn1DecodingException( "Tag number must be specified when tagging mode is " + mTagging); } mOptional = annotation.optional(); } public Field getField() { return mField; } public Asn1Field getAnnotation() { return mAnnotation; } public boolean isOptional() { return mOptional; } public int getBerTagClass() { return mBerTagClass; } public int getBerTagNumber() { return mBerTagNumber; } public void setValueFrom(BerDataValue dataValue, Object obj) throws Asn1DecodingException { int readTagClass = dataValue.getTagClass(); if (mBerTagNumber != -1) { int readTagNumber = dataValue.getTagNumber(); if ((readTagClass != mBerTagClass) || (readTagNumber != mBerTagNumber)) { throw new Asn1UnexpectedTagException( "Tag mismatch. Expected: " + BerEncoding.tagClassAndNumberToString(mBerTagClass, mBerTagNumber) + ", but found " + BerEncoding.tagClassAndNumberToString(readTagClass, readTagNumber)); } } else { if (readTagClass != mBerTagClass) { throw new Asn1UnexpectedTagException( "Tag mismatch. Expected class: " + BerEncoding.tagClassToString(mBerTagClass) + ", but found " + BerEncoding.tagClassToString(readTagClass)); } } if (mTagging == Asn1Tagging.EXPLICIT) { try { dataValue = dataValue.contentsReader().readDataValue(); } catch (BerDataValueFormatException e) { throw new Asn1DecodingException( "Failed to read contents of EXPLICIT data value", e); } } BerToJavaConverter.setFieldValue(obj, mField, mDataType, dataValue); } } private static class Asn1UnexpectedTagException extends Asn1DecodingException { private static final long serialVersionUID = 1L; public Asn1UnexpectedTagException(String message) { super(message); } } private static String oidToString(ByteBuffer encodedOid) throws Asn1DecodingException { if (!encodedOid.hasRemaining()) { throw new Asn1DecodingException("Empty OBJECT IDENTIFIER"); } // First component encodes the first two nodes, X.Y, as X * 40 + Y, with 0 <= X <= 2 long firstComponent = decodeBase128UnsignedLong(encodedOid); int firstNode = (int) Math.min(firstComponent / 40, 2); long secondNode = firstComponent - firstNode * 40; StringBuilder result = new StringBuilder(); result.append(Long.toString(firstNode)).append('.') .append(Long.toString(secondNode)); // Each consecutive node is encoded as a separate component while (encodedOid.hasRemaining()) { long node = decodeBase128UnsignedLong(encodedOid); result.append('.').append(Long.toString(node)); } return result.toString(); } private static long decodeBase128UnsignedLong(ByteBuffer encoded) throws Asn1DecodingException { if (!encoded.hasRemaining()) { return 0; } long result = 0; while (encoded.hasRemaining()) { if (result > Long.MAX_VALUE >>> 7) { throw new Asn1DecodingException("Base-128 number too large"); } int b = encoded.get() & 0xff; result <<= 7; result |= b & 0x7f; if ((b & 0x80) == 0) { return result; } } throw new Asn1DecodingException( "Truncated base-128 encoded input: missing terminating byte, with highest bit not" + " set"); } private static BigInteger integerToBigInteger(ByteBuffer encoded) { if (!encoded.hasRemaining()) { return BigInteger.ZERO; } return new BigInteger(Buffers.readBytes(encoded)); } private static int integerToInt(ByteBuffer encoded) throws Asn1DecodingException { BigInteger value = integerToBigInteger(encoded); try { return value.intValue(); } catch (ArithmeticException e) { throw new Asn1DecodingException( String.format("INTEGER cannot be represented as int: %1$d (0x%1$x)", value), e); } } private static long integerToLong(ByteBuffer encoded) throws Asn1DecodingException { BigInteger value = integerToBigInteger(encoded); try { return value.intValue(); } catch (ArithmeticException e) { throw new Asn1DecodingException( String.format("INTEGER cannot be represented as long: %1$d (0x%1$x)", value), e); } } private static List getAnnotatedFields(Class containerClass) throws Asn1DecodingException { Field[] declaredFields = containerClass.getDeclaredFields(); List result = new ArrayList<>(declaredFields.length); for (Field field : declaredFields) { Asn1Field annotation = field.getAnnotation(Asn1Field.class); if (annotation == null) { continue; } if (Modifier.isStatic(field.getModifiers())) { throw new Asn1DecodingException( Asn1Field.class.getName() + " used on a static field: " + containerClass.getName() + "." + field.getName()); } AnnotatedField annotatedField; try { annotatedField = new AnnotatedField(field, annotation); } catch (Asn1DecodingException e) { throw new Asn1DecodingException( "Invalid ASN.1 annotation on " + containerClass.getName() + "." + field.getName(), e); } result.add(annotatedField); } return result; } private static final class BerToJavaConverter { private BerToJavaConverter() { } public static void setFieldValue( Object obj, Field field, Asn1Type type, BerDataValue dataValue) throws Asn1DecodingException { try { switch (type) { case SET_OF: case SEQUENCE_OF: if (Asn1OpaqueObject.class.equals(field.getType())) { field.set(obj, convert(type, dataValue, field.getType())); } else { field.set(obj, parseSetOf(dataValue, getElementType(field))); } return; default: field.set(obj, convert(type, dataValue, field.getType())); break; } } catch (ReflectiveOperationException e) { throw new Asn1DecodingException( "Failed to set value of " + obj.getClass().getName() + "." + field.getName(), e); } } private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; @SuppressWarnings("unchecked") public static T convert( Asn1Type sourceType, BerDataValue dataValue, Class targetType) throws Asn1DecodingException { if (ByteBuffer.class.equals(targetType)) { return (T) dataValue.getEncodedContents(); } else if (byte[].class.equals(targetType)) { ByteBuffer resultBuf = dataValue.getEncodedContents(); if (!resultBuf.hasRemaining()) { return (T) EMPTY_BYTE_ARRAY; } byte[] result = new byte[resultBuf.remaining()]; resultBuf.get(result); return (T) result; } else if (Asn1OpaqueObject.class.equals(targetType)) { return (T) new Asn1OpaqueObject(dataValue.getEncoded()); } ByteBuffer encodedContents = dataValue.getEncodedContents(); switch (sourceType) { case INTEGER: if ((int.class.equals(targetType)) || (Integer.class.equals(targetType))) { return (T) Integer.valueOf(integerToInt(encodedContents)); } else if ((long.class.equals(targetType)) || (Long.class.equals(targetType))) { return (T) Long.valueOf(integerToLong(encodedContents)); } else if (BigInteger.class.equals(targetType)) { return (T) integerToBigInteger(encodedContents); } break; case OBJECT_IDENTIFIER: if (String.class.equals(targetType)) { return (T) oidToString(encodedContents); } break; case SEQUENCE: { Asn1Class containerAnnotation = targetType.getAnnotation(Asn1Class.class); if ((containerAnnotation != null) && (containerAnnotation.type() == Asn1Type.SEQUENCE)) { return parseSequence(dataValue, targetType); } break; } case CHOICE: { Asn1Class containerAnnotation = targetType.getAnnotation(Asn1Class.class); if ((containerAnnotation != null) && (containerAnnotation.type() == Asn1Type.CHOICE)) { return parseChoice(dataValue, targetType); } break; } default: break; } throw new Asn1DecodingException( "Unsupported conversion: ASN.1 " + sourceType + " to " + targetType.getName()); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy