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