Home | History | Annotate | Download | only in asn1
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.apksig.internal.asn1;
     18 
     19 import com.android.apksig.internal.asn1.ber.BerEncoding;
     20 import java.io.ByteArrayOutputStream;
     21 import java.lang.reflect.Field;
     22 import java.lang.reflect.Modifier;
     23 import java.math.BigInteger;
     24 import java.nio.ByteBuffer;
     25 import java.util.ArrayList;
     26 import java.util.Collection;
     27 import java.util.Collections;
     28 import java.util.Comparator;
     29 import java.util.List;
     30 
     31 /**
     32  * Encoder of ASN.1 structures into DER-encoded form.
     33  *
     34  * <p>Structure is described to the encoder by providing a class annotated with {@link Asn1Class},
     35  * containing fields annotated with {@link Asn1Field}.
     36  */
     37 public final class Asn1DerEncoder {
     38     private Asn1DerEncoder() {}
     39 
     40     /**
     41      * Returns the DER-encoded form of the provided ASN.1 structure.
     42      *
     43      * @param container container to be encoded. The container's class must meet the following
     44      *        requirements:
     45      *        <ul>
     46      *        <li>The class must be annotated with {@link Asn1Class}.</li>
     47      *        <li>Member fields of the class which are to be encoded must be annotated with
     48      *            {@link Asn1Field} and be public.</li>
     49      *        </ul>
     50      *
     51      * @throws Asn1EncodingException if the input could not be encoded
     52      */
     53     public static byte[] encode(Object container) throws Asn1EncodingException {
     54         Class<?> containerClass = container.getClass();
     55         Asn1Class containerAnnotation = containerClass.getDeclaredAnnotation(Asn1Class.class);
     56         if (containerAnnotation == null) {
     57             throw new Asn1EncodingException(
     58                     containerClass.getName() + " not annotated with " + Asn1Class.class.getName());
     59         }
     60 
     61         Asn1Type containerType = containerAnnotation.type();
     62         switch (containerType) {
     63             case CHOICE:
     64                 return toChoice(container);
     65             case SEQUENCE:
     66                 return toSequence(container);
     67             default:
     68                 throw new Asn1EncodingException("Unsupported container type: " + containerType);
     69         }
     70     }
     71 
     72     private static byte[] toChoice(Object container) throws Asn1EncodingException {
     73         Class<?> containerClass = container.getClass();
     74         List<AnnotatedField> fields = getAnnotatedFields(container);
     75         if (fields.isEmpty()) {
     76             throw new Asn1EncodingException(
     77                     "No fields annotated with " + Asn1Field.class.getName()
     78                             + " in CHOICE class " + containerClass.getName());
     79         }
     80 
     81         AnnotatedField resultField = null;
     82         for (AnnotatedField field : fields) {
     83             Object fieldValue = getMemberFieldValue(container, field.getField());
     84             if (fieldValue != null) {
     85                 if (resultField != null) {
     86                     throw new Asn1EncodingException(
     87                             "Multiple non-null fields in CHOICE class " + containerClass.getName()
     88                                     + ": " + resultField.getField().getName()
     89                                     + ", " + field.getField().getName());
     90                 }
     91                 resultField = field;
     92             }
     93         }
     94 
     95         if (resultField == null) {
     96             throw new Asn1EncodingException(
     97                     "No non-null fields in CHOICE class " + containerClass.getName());
     98         }
     99 
    100         return resultField.toDer();
    101     }
    102 
    103     private static byte[] toSequence(Object container) throws Asn1EncodingException {
    104         Class<?> containerClass = container.getClass();
    105         List<AnnotatedField> fields = getAnnotatedFields(container);
    106         Collections.sort(
    107                 fields, (f1, f2) -> f1.getAnnotation().index() - f2.getAnnotation().index());
    108         if (fields.size() > 1) {
    109             AnnotatedField lastField = null;
    110             for (AnnotatedField field : fields) {
    111                 if ((lastField != null)
    112                         && (lastField.getAnnotation().index() == field.getAnnotation().index())) {
    113                     throw new Asn1EncodingException(
    114                             "Fields have the same index: " + containerClass.getName()
    115                                     + "." + lastField.getField().getName()
    116                                     + " and ." + field.getField().getName());
    117                 }
    118                 lastField = field;
    119             }
    120         }
    121 
    122         List<byte[]> serializedFields = new ArrayList<>(fields.size());
    123         for (AnnotatedField field : fields) {
    124             byte[] serializedField;
    125             try {
    126                 serializedField = field.toDer();
    127             } catch (Asn1EncodingException e) {
    128                 throw new Asn1EncodingException(
    129                         "Failed to encode " + containerClass.getName()
    130                                 + "." + field.getField().getName(),
    131                         e);
    132             }
    133             if (serializedField != null) {
    134                 serializedFields.add(serializedField);
    135             }
    136         }
    137 
    138         return createTag(
    139                 BerEncoding.TAG_CLASS_UNIVERSAL, true, BerEncoding.TAG_NUMBER_SEQUENCE,
    140                 serializedFields.toArray(new byte[0][]));
    141     }
    142 
    143     private static byte[] toSetOf(Collection<?> values, Asn1Type elementType)
    144             throws Asn1EncodingException {
    145         List<byte[]> serializedValues = new ArrayList<>(values.size());
    146         for (Object value : values) {
    147             serializedValues.add(JavaToDerConverter.toDer(value, elementType, null));
    148         }
    149         if (serializedValues.size() > 1) {
    150             Collections.sort(serializedValues, ByteArrayLexicographicComparator.INSTANCE);
    151         }
    152         return createTag(
    153                 BerEncoding.TAG_CLASS_UNIVERSAL, true, BerEncoding.TAG_NUMBER_SET,
    154                 serializedValues.toArray(new byte[0][]));
    155     }
    156 
    157     /**
    158      * Compares two bytes arrays based on their lexicographic order. Corresponding elements of the
    159      * two arrays are compared in ascending order. Elements at out of range indices are assumed to
    160      * be smaller than the smallest possible value for an element.
    161      */
    162     private static class ByteArrayLexicographicComparator implements Comparator<byte[]> {
    163             private static final ByteArrayLexicographicComparator INSTANCE =
    164                     new ByteArrayLexicographicComparator();
    165 
    166             @Override
    167             public int compare(byte[] arr1, byte[] arr2) {
    168                 int commonLength = Math.min(arr1.length, arr2.length);
    169                 for (int i = 0; i < commonLength; i++) {
    170                     int diff = (arr1[i] & 0xff) - (arr2[i] & 0xff);
    171                     if (diff != 0) {
    172                         return diff;
    173                     }
    174                 }
    175                 return arr1.length - arr2.length;
    176             }
    177     }
    178 
    179     private static List<AnnotatedField> getAnnotatedFields(Object container)
    180             throws Asn1EncodingException {
    181         Class<?> containerClass = container.getClass();
    182         Field[] declaredFields = containerClass.getDeclaredFields();
    183         List<AnnotatedField> result = new ArrayList<>(declaredFields.length);
    184         for (Field field : declaredFields) {
    185             Asn1Field annotation = field.getDeclaredAnnotation(Asn1Field.class);
    186             if (annotation == null) {
    187                 continue;
    188             }
    189             if (Modifier.isStatic(field.getModifiers())) {
    190                 throw new Asn1EncodingException(
    191                         Asn1Field.class.getName() + " used on a static field: "
    192                                 + containerClass.getName() + "." + field.getName());
    193             }
    194 
    195             AnnotatedField annotatedField;
    196             try {
    197                 annotatedField = new AnnotatedField(container, field, annotation);
    198             } catch (Asn1EncodingException e) {
    199                 throw new Asn1EncodingException(
    200                         "Invalid ASN.1 annotation on "
    201                                 + containerClass.getName() + "." + field.getName(),
    202                         e);
    203             }
    204             result.add(annotatedField);
    205         }
    206         return result;
    207     }
    208 
    209     private static byte[] toInteger(int value) {
    210         return toInteger((long) value);
    211     }
    212 
    213     private static byte[] toInteger(long value) {
    214         return toInteger(BigInteger.valueOf(value));
    215     }
    216 
    217     private static byte[] toInteger(BigInteger value) {
    218         return createTag(
    219                 BerEncoding.TAG_CLASS_UNIVERSAL, false, BerEncoding.TAG_NUMBER_INTEGER,
    220                 value.toByteArray());
    221     }
    222 
    223     private static byte[] toOid(String oid) throws Asn1EncodingException {
    224         ByteArrayOutputStream encodedValue = new ByteArrayOutputStream();
    225         String[] nodes = oid.split("\\.");
    226         if (nodes.length < 2) {
    227             throw new Asn1EncodingException(
    228                     "OBJECT IDENTIFIER must contain at least two nodes: " + oid);
    229         }
    230         int firstNode;
    231         try {
    232             firstNode = Integer.parseInt(nodes[0]);
    233         } catch (NumberFormatException e) {
    234             throw new Asn1EncodingException("Node #1 not numeric: " + nodes[0]);
    235         }
    236         if ((firstNode > 6) || (firstNode < 0)) {
    237             throw new Asn1EncodingException("Invalid value for node #1: " + firstNode);
    238         }
    239 
    240         int secondNode;
    241         try {
    242             secondNode = Integer.parseInt(nodes[1]);
    243         } catch (NumberFormatException e) {
    244             throw new Asn1EncodingException("Node #2 not numeric: " + nodes[1]);
    245         }
    246         if ((secondNode >= 40) || (secondNode < 0)) {
    247             throw new Asn1EncodingException("Invalid value for node #2: " + secondNode);
    248         }
    249         int firstByte = firstNode * 40 + secondNode;
    250         if (firstByte > 0xff) {
    251             throw new Asn1EncodingException(
    252                     "First two nodes out of range: " + firstNode + "." + secondNode);
    253         }
    254 
    255         encodedValue.write(firstByte);
    256         for (int i = 2; i < nodes.length; i++) {
    257             String nodeString = nodes[i];
    258             int node;
    259             try {
    260                 node = Integer.parseInt(nodeString);
    261             } catch (NumberFormatException e) {
    262                 throw new Asn1EncodingException("Node #" + (i + 1) + " not numeric: " + nodeString);
    263             }
    264             if (node < 0) {
    265                 throw new Asn1EncodingException("Invalid value for node #" + (i + 1) + ": " + node);
    266             }
    267             if (node <= 0x7f) {
    268                 encodedValue.write(node);
    269                 continue;
    270             }
    271             if (node < 1 << 14) {
    272                 encodedValue.write(0x80 | (node >> 7));
    273                 encodedValue.write(node & 0x7f);
    274                 continue;
    275             }
    276             if (node < 1 << 21) {
    277                 encodedValue.write(0x80 | (node >> 14));
    278                 encodedValue.write(0x80 | ((node >> 7) & 0x7f));
    279                 encodedValue.write(node & 0x7f);
    280                 continue;
    281             }
    282             throw new Asn1EncodingException("Node #" + (i + 1) + " too large: " + node);
    283         }
    284 
    285         return createTag(
    286                 BerEncoding.TAG_CLASS_UNIVERSAL, false, BerEncoding.TAG_NUMBER_OBJECT_IDENTIFIER,
    287                 encodedValue.toByteArray());
    288     }
    289 
    290     private static Object getMemberFieldValue(Object obj, Field field)
    291             throws Asn1EncodingException {
    292         try {
    293             return field.get(obj);
    294         } catch (ReflectiveOperationException e) {
    295             throw new Asn1EncodingException(
    296                     "Failed to read " + obj.getClass().getName() + "." + field.getName(), e);
    297         }
    298     }
    299 
    300     private static final class AnnotatedField {
    301         private final Field mField;
    302         private final Object mObject;
    303         private final Asn1Field mAnnotation;
    304         private final Asn1Type mDataType;
    305         private final Asn1Type mElementDataType;
    306         private final Asn1TagClass mTagClass;
    307         private final int mDerTagClass;
    308         private final int mDerTagNumber;
    309         private final Asn1Tagging mTagging;
    310         private final boolean mOptional;
    311 
    312         public AnnotatedField(Object obj, Field field, Asn1Field annotation)
    313                 throws Asn1EncodingException {
    314             mObject = obj;
    315             mField = field;
    316             mAnnotation = annotation;
    317             mDataType = annotation.type();
    318             mElementDataType = annotation.elementType();
    319 
    320             Asn1TagClass tagClass = annotation.cls();
    321             if (tagClass == Asn1TagClass.AUTOMATIC) {
    322                 if (annotation.tagNumber() != -1) {
    323                     tagClass = Asn1TagClass.CONTEXT_SPECIFIC;
    324                 } else {
    325                     tagClass = Asn1TagClass.UNIVERSAL;
    326                 }
    327             }
    328             mTagClass = tagClass;
    329             mDerTagClass = BerEncoding.getTagClass(mTagClass);
    330 
    331             int tagNumber;
    332             if (annotation.tagNumber() != -1) {
    333                 tagNumber = annotation.tagNumber();
    334             } else if ((mDataType == Asn1Type.CHOICE) || (mDataType == Asn1Type.ANY)) {
    335                 tagNumber = -1;
    336             } else {
    337                 tagNumber = BerEncoding.getTagNumber(mDataType);
    338             }
    339             mDerTagNumber = tagNumber;
    340 
    341             mTagging = annotation.tagging();
    342             if (((mTagging == Asn1Tagging.EXPLICIT) || (mTagging == Asn1Tagging.IMPLICIT))
    343                     && (annotation.tagNumber() == -1)) {
    344                 throw new Asn1EncodingException(
    345                         "Tag number must be specified when tagging mode is " + mTagging);
    346             }
    347 
    348             mOptional = annotation.optional();
    349         }
    350 
    351         public Field getField() {
    352             return mField;
    353         }
    354 
    355         public Asn1Field getAnnotation() {
    356             return mAnnotation;
    357         }
    358 
    359         public byte[] toDer() throws Asn1EncodingException {
    360             Object fieldValue = getMemberFieldValue(mObject, mField);
    361             if (fieldValue == null) {
    362                 if (mOptional) {
    363                     return null;
    364                 }
    365                 throw new Asn1EncodingException("Required field not set");
    366             }
    367 
    368             byte[] encoded = JavaToDerConverter.toDer(fieldValue, mDataType, mElementDataType);
    369             switch (mTagging) {
    370                 case NORMAL:
    371                     return encoded;
    372                 case EXPLICIT:
    373                     return createTag(mDerTagClass, true, mDerTagNumber, encoded);
    374                 case IMPLICIT:
    375                     int originalTagNumber = BerEncoding.getTagNumber(encoded[0]);
    376                     if (originalTagNumber == 0x1f) {
    377                         throw new Asn1EncodingException("High-tag-number form not supported");
    378                     }
    379                     if (mDerTagNumber >= 0x1f) {
    380                         throw new Asn1EncodingException(
    381                                 "Unsupported high tag number: " + mDerTagNumber);
    382                     }
    383                     encoded[0] = BerEncoding.setTagNumber(encoded[0], mDerTagNumber);
    384                     encoded[0] = BerEncoding.setTagClass(encoded[0], mDerTagClass);
    385                     return encoded;
    386                 default:
    387                     throw new RuntimeException("Unknown tagging mode: " + mTagging);
    388             }
    389         }
    390     }
    391 
    392     private static byte[] createTag(
    393             int tagClass, boolean constructed, int tagNumber, byte[]... contents) {
    394         if (tagNumber >= 0x1f) {
    395             throw new IllegalArgumentException("High tag numbers not supported: " + tagNumber);
    396         }
    397         // tag class & number fit into the first byte
    398         byte firstIdentifierByte =
    399                 (byte) ((tagClass << 6) | (constructed ? 1 << 5 : 0) | tagNumber);
    400 
    401         int contentsLength = 0;
    402         for (byte[] c : contents) {
    403             contentsLength += c.length;
    404         }
    405         int contentsPosInResult;
    406         byte[] result;
    407         if (contentsLength < 0x80) {
    408             // Length fits into one byte
    409             contentsPosInResult = 2;
    410             result = new byte[contentsPosInResult + contentsLength];
    411             result[0] = firstIdentifierByte;
    412             result[1] = (byte) contentsLength;
    413         } else {
    414             // Length is represented as multiple bytes
    415             // The low 7 bits of the first byte represent the number of length bytes (following the
    416             // first byte) in which the length is in big-endian base-256 form
    417             if (contentsLength <= 0xff) {
    418                 contentsPosInResult = 3;
    419                 result = new byte[contentsPosInResult + contentsLength];
    420                 result[1] = (byte) 0x81; // 1 length byte
    421                 result[2] = (byte) contentsLength;
    422             } else if (contentsLength <= 0xffff) {
    423                 contentsPosInResult = 4;
    424                 result = new byte[contentsPosInResult + contentsLength];
    425                 result[1] = (byte) 0x82; // 2 length bytes
    426                 result[2] = (byte) (contentsLength >> 8);
    427                 result[3] = (byte) (contentsLength & 0xff);
    428             } else if (contentsLength <= 0xffffff) {
    429                 contentsPosInResult = 5;
    430                 result = new byte[contentsPosInResult + contentsLength];
    431                 result[1] = (byte) 0x83; // 3 length bytes
    432                 result[2] = (byte) (contentsLength >> 16);
    433                 result[3] = (byte) ((contentsLength >> 8) & 0xff);
    434                 result[4] = (byte) (contentsLength & 0xff);
    435             } else {
    436                 contentsPosInResult = 6;
    437                 result = new byte[contentsPosInResult + contentsLength];
    438                 result[1] = (byte) 0x84; // 4 length bytes
    439                 result[2] = (byte) (contentsLength >> 24);
    440                 result[3] = (byte) ((contentsLength >> 16) & 0xff);
    441                 result[4] = (byte) ((contentsLength >> 8) & 0xff);
    442                 result[5] = (byte) (contentsLength & 0xff);
    443             }
    444             result[0] = firstIdentifierByte;
    445         }
    446         for (byte[] c : contents) {
    447             System.arraycopy(c, 0, result, contentsPosInResult, c.length);
    448             contentsPosInResult += c.length;
    449         }
    450         return result;
    451     }
    452 
    453     private static final class JavaToDerConverter {
    454         private JavaToDerConverter() {}
    455 
    456         public static byte[] toDer(Object source, Asn1Type targetType, Asn1Type targetElementType)
    457                 throws Asn1EncodingException {
    458             Class<?> sourceType = source.getClass();
    459             if (Asn1OpaqueObject.class.equals(sourceType)) {
    460                 ByteBuffer buf = ((Asn1OpaqueObject) source).getEncoded();
    461                 byte[] result = new byte[buf.remaining()];
    462                 buf.get(result);
    463                 return result;
    464             }
    465 
    466             if ((targetType == null) || (targetType == Asn1Type.ANY)) {
    467                 return encode(source);
    468             }
    469 
    470             switch (targetType) {
    471                 case OCTET_STRING:
    472                     byte[] value = null;
    473                     if (source instanceof ByteBuffer) {
    474                         ByteBuffer buf = (ByteBuffer) source;
    475                         value = new byte[buf.remaining()];
    476                         buf.slice().get(value);
    477                     } else if (source instanceof byte[]) {
    478                         value = (byte[]) source;
    479                     }
    480                     if (value != null) {
    481                         return createTag(
    482                                 BerEncoding.TAG_CLASS_UNIVERSAL,
    483                                 false,
    484                                 BerEncoding.TAG_NUMBER_OCTET_STRING,
    485                                 value);
    486                     }
    487                     break;
    488                 case INTEGER:
    489                     if (source instanceof Integer) {
    490                         return toInteger((Integer) source);
    491                     } else if (source instanceof Long) {
    492                         return toInteger((Long) source);
    493                     } else if (source instanceof BigInteger) {
    494                         return toInteger((BigInteger) source);
    495                     }
    496                     break;
    497                 case OBJECT_IDENTIFIER:
    498                     if (source instanceof String) {
    499                         return toOid((String) source);
    500                     }
    501                     break;
    502                 case SEQUENCE:
    503                 {
    504                     Asn1Class containerAnnotation =
    505                             sourceType.getDeclaredAnnotation(Asn1Class.class);
    506                     if ((containerAnnotation != null)
    507                             && (containerAnnotation.type() == Asn1Type.SEQUENCE)) {
    508                         return toSequence(source);
    509                     }
    510                     break;
    511                 }
    512                 case CHOICE:
    513                 {
    514                     Asn1Class containerAnnotation =
    515                             sourceType.getDeclaredAnnotation(Asn1Class.class);
    516                     if ((containerAnnotation != null)
    517                             && (containerAnnotation.type() == Asn1Type.CHOICE)) {
    518                         return toChoice(source);
    519                     }
    520                     break;
    521                 }
    522                 case SET_OF:
    523                     return toSetOf((Collection<?>) source, targetElementType);
    524                 default:
    525                     break;
    526             }
    527 
    528             throw new Asn1EncodingException(
    529                     "Unsupported conversion: " + sourceType.getName() + " to ASN.1 " + targetType);
    530         }
    531     }
    532 }
    533