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