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