1 /* 2 * Copyright (C) 2016 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.internal.telephony.uicc.asn1; 18 19 import android.annotation.Nullable; 20 21 import com.android.internal.telephony.uicc.IccUtils; 22 23 import java.nio.charset.StandardCharsets; 24 import java.util.ArrayList; 25 import java.util.Collections; 26 import java.util.List; 27 28 /** 29 * This represents a primitive or constructed data defined by ASN.1. A constructed node can have 30 * child nodes. A non-constructed node can have a value. This class is read-only. To build a node, 31 * you can use the {@link #newBuilder(int)} method to get a {@link Builder} instance. This class is 32 * not thread-safe. 33 */ 34 public final class Asn1Node { 35 private static final int INT_BYTES = Integer.SIZE / Byte.SIZE; 36 private static final List<Asn1Node> EMPTY_NODE_LIST = Collections.emptyList(); 37 38 // Bytes for boolean values. 39 private static final byte[] TRUE_BYTES = new byte[] {-1}; 40 private static final byte[] FALSE_BYTES = new byte[] {0}; 41 42 /** 43 * This class is used to build an Asn1Node instance of a constructed tag. This class is not 44 * thread-safe. 45 */ 46 public static final class Builder { 47 private final int mTag; 48 private final List<Asn1Node> mChildren; 49 Builder(int tag)50 private Builder(int tag) { 51 if (!isConstructedTag(tag)) { 52 throw new IllegalArgumentException( 53 "Builder should be created for a constructed tag: " + tag); 54 } 55 mTag = tag; 56 mChildren = new ArrayList<>(); 57 } 58 59 /** 60 * Adds a child from an existing node. 61 * 62 * @return This builder. 63 * @throws IllegalArgumentException If the child is a non-existing node. 64 */ addChild(Asn1Node child)65 public Builder addChild(Asn1Node child) { 66 mChildren.add(child); 67 return this; 68 } 69 70 /** 71 * Adds a child from another builder. The child will be built with the call to this method, 72 * and any changes to the child builder after the call to this method doesn't have effect. 73 * 74 * @return This builder. 75 */ addChild(Builder child)76 public Builder addChild(Builder child) { 77 mChildren.add(child.build()); 78 return this; 79 } 80 81 /** 82 * Adds children from bytes. This method calls {@link Asn1Decoder} to verify the {@code 83 * encodedBytes} and adds all nodes parsed from it as children. 84 * 85 * @return This builder. 86 * @throws InvalidAsn1DataException If the data bytes cannot be parsed. 87 */ addChildren(byte[] encodedBytes)88 public Builder addChildren(byte[] encodedBytes) throws InvalidAsn1DataException { 89 Asn1Decoder subDecoder = new Asn1Decoder(encodedBytes, 0, encodedBytes.length); 90 while (subDecoder.hasNextNode()) { 91 mChildren.add(subDecoder.nextNode()); 92 } 93 return this; 94 } 95 96 /** 97 * Adds a child of non-constructed tag with an integer as the data. 98 * 99 * @return This builder. 100 * @throws IllegalStateException If the {@code tag} is not constructed.. 101 */ addChildAsInteger(int tag, int value)102 public Builder addChildAsInteger(int tag, int value) { 103 if (isConstructedTag(tag)) { 104 throw new IllegalStateException("Cannot set value of a constructed tag: " + tag); 105 } 106 byte[] dataBytes = IccUtils.signedIntToBytes(value); 107 addChild(new Asn1Node(tag, dataBytes, 0, dataBytes.length)); 108 return this; 109 } 110 111 /** 112 * Adds a child of non-constructed tag with a string as the data. 113 * 114 * @return This builder. 115 * @throws IllegalStateException If the {@code tag} is not constructed.. 116 */ addChildAsString(int tag, String value)117 public Builder addChildAsString(int tag, String value) { 118 if (isConstructedTag(tag)) { 119 throw new IllegalStateException("Cannot set value of a constructed tag: " + tag); 120 } 121 byte[] dataBytes = value.getBytes(StandardCharsets.UTF_8); 122 addChild(new Asn1Node(tag, dataBytes, 0, dataBytes.length)); 123 return this; 124 } 125 126 /** 127 * Adds a child of non-constructed tag with a byte array as the data. 128 * 129 * @param value The value will be owned by this node. 130 * @return This builder. 131 * @throws IllegalStateException If the {@code tag} is not constructed.. 132 */ addChildAsBytes(int tag, byte[] value)133 public Builder addChildAsBytes(int tag, byte[] value) { 134 if (isConstructedTag(tag)) { 135 throw new IllegalStateException("Cannot set value of a constructed tag: " + tag); 136 } 137 addChild(new Asn1Node(tag, value, 0, value.length)); 138 return this; 139 } 140 141 /** 142 * Adds a child of non-constructed tag with a byte array as the data from a hex string. 143 * 144 * @return This builder. 145 * @throws IllegalStateException If the {@code tag} is not constructed.. 146 */ addChildAsBytesFromHex(int tag, String hex)147 public Builder addChildAsBytesFromHex(int tag, String hex) { 148 return addChildAsBytes(tag, IccUtils.hexStringToBytes(hex)); 149 } 150 151 /** 152 * Adds a child of non-constructed tag with bits as the data. 153 * 154 * @return This builder. 155 * @throws IllegalStateException If the {@code tag} is not constructed.. 156 */ addChildAsBits(int tag, int value)157 public Builder addChildAsBits(int tag, int value) { 158 if (isConstructedTag(tag)) { 159 throw new IllegalStateException("Cannot set value of a constructed tag: " + tag); 160 } 161 // Always allocate 5 bytes for simplicity. 162 byte[] dataBytes = new byte[INT_BYTES + 1]; 163 // Puts the integer into the byte[1-4]. 164 value = Integer.reverse(value); 165 int dataLength = 0; 166 for (int i = 1; i < dataBytes.length; i++) { 167 dataBytes[i] = (byte) (value >> ((INT_BYTES - i) * Byte.SIZE)); 168 if (dataBytes[i] != 0) { 169 dataLength = i; 170 } 171 } 172 dataLength++; 173 // The first byte is the number of trailing zeros of the last byte. 174 dataBytes[0] = IccUtils.countTrailingZeros(dataBytes[dataLength - 1]); 175 addChild(new Asn1Node(tag, dataBytes, 0, dataLength)); 176 return this; 177 } 178 179 /** 180 * Adds a child of non-constructed tag with a boolean as the data. 181 * 182 * @return This builder. 183 * @throws IllegalStateException If the {@code tag} is not constructed.. 184 */ addChildAsBoolean(int tag, boolean value)185 public Builder addChildAsBoolean(int tag, boolean value) { 186 if (isConstructedTag(tag)) { 187 throw new IllegalStateException("Cannot set value of a constructed tag: " + tag); 188 } 189 addChild(new Asn1Node(tag, value ? TRUE_BYTES : FALSE_BYTES, 0, 1)); 190 return this; 191 } 192 193 /** Builds the node. */ build()194 public Asn1Node build() { 195 return new Asn1Node(mTag, mChildren); 196 } 197 } 198 199 private final int mTag; 200 private final boolean mConstructed; 201 // Do not use this field directly in the methods other than the constructor and encoding 202 // methods (e.g., toBytes()), but always use getChildren() instead. 203 private final List<Asn1Node> mChildren; 204 205 // Byte array that actually holds the data. For a non-constructed node, this stores its actual 206 // value. If the value is not set, this is null. For constructed node, this stores encoded data 207 // of its children, which will be decoded on the first call to getChildren(). 208 private @Nullable byte[] mDataBytes; 209 // Offset of the data in above byte array. 210 private int mDataOffset; 211 // Length of the data in above byte array. If it's a constructed node, this is always the total 212 // length of all its children. 213 private int mDataLength; 214 // Length of the total bytes required to encode this node. 215 private int mEncodedLength; 216 217 /** 218 * Creates a new ASN.1 data node builder with the given tag. The tag is an encoded tag including 219 * the tag class, tag number, and constructed mask. 220 */ newBuilder(int tag)221 public static Builder newBuilder(int tag) { 222 return new Builder(tag); 223 } 224 isConstructedTag(int tag)225 private static boolean isConstructedTag(int tag) { 226 // Constructed mask is at the 6th bit. 227 byte[] tagBytes = IccUtils.unsignedIntToBytes(tag); 228 return (tagBytes[0] & 0x20) != 0; 229 } 230 calculateEncodedBytesNumForLength(int length)231 private static int calculateEncodedBytesNumForLength(int length) { 232 // Constructed mask is at the 6th bit. 233 int len = 1; 234 if (length > 127) { 235 len += IccUtils.byteNumForUnsignedInt(length); 236 } 237 return len; 238 } 239 240 /** 241 * Creates a node with given data bytes. If it is a constructed node, its children will be 242 * parsed when they are visited. 243 */ Asn1Node(int tag, @Nullable byte[] src, int offset, int length)244 Asn1Node(int tag, @Nullable byte[] src, int offset, int length) { 245 mTag = tag; 246 // Constructed mask is at the 6th bit. 247 mConstructed = isConstructedTag(tag); 248 mDataBytes = src; 249 mDataOffset = offset; 250 mDataLength = length; 251 mChildren = mConstructed ? new ArrayList<Asn1Node>() : EMPTY_NODE_LIST; 252 mEncodedLength = 253 IccUtils.byteNumForUnsignedInt(mTag) 254 + calculateEncodedBytesNumForLength(mDataLength) 255 + mDataLength; 256 } 257 258 /** Creates a constructed node with given children. */ Asn1Node(int tag, List<Asn1Node> children)259 private Asn1Node(int tag, List<Asn1Node> children) { 260 mTag = tag; 261 mConstructed = true; 262 mChildren = children; 263 264 mDataLength = 0; 265 int size = children.size(); 266 for (int i = 0; i < size; i++) { 267 mDataLength += children.get(i).mEncodedLength; 268 } 269 mEncodedLength = 270 IccUtils.byteNumForUnsignedInt(mTag) 271 + calculateEncodedBytesNumForLength(mDataLength) 272 + mDataLength; 273 } 274 getTag()275 public int getTag() { 276 return mTag; 277 } 278 isConstructed()279 public boolean isConstructed() { 280 return mConstructed; 281 } 282 283 /** 284 * Tests if a node has a child. 285 * 286 * @param tag The tag of an immediate child. 287 * @param tags The tags of lineal descendant. 288 */ hasChild(int tag, int... tags)289 public boolean hasChild(int tag, int... tags) throws InvalidAsn1DataException { 290 try { 291 getChild(tag, tags); 292 } catch (TagNotFoundException e) { 293 return false; 294 } 295 return true; 296 } 297 298 /** 299 * Gets the first child node having the given {@code tag} and {@code tags}. 300 * 301 * @param tag The tag of an immediate child. 302 * @param tags The tags of lineal descendant. 303 * @throws TagNotFoundException If the child cannot be found. 304 */ getChild(int tag, int... tags)305 public Asn1Node getChild(int tag, int... tags) 306 throws TagNotFoundException, InvalidAsn1DataException { 307 if (!mConstructed) { 308 throw new TagNotFoundException(tag); 309 } 310 int index = 0; 311 Asn1Node node = this; 312 while (node != null) { 313 List<Asn1Node> children = node.getChildren(); 314 int size = children.size(); 315 Asn1Node foundChild = null; 316 for (int i = 0; i < size; i++) { 317 Asn1Node child = children.get(i); 318 if (child.getTag() == tag) { 319 foundChild = child; 320 break; 321 } 322 } 323 node = foundChild; 324 if (index >= tags.length) { 325 break; 326 } 327 tag = tags[index++]; 328 } 329 if (node == null) { 330 throw new TagNotFoundException(tag); 331 } 332 return node; 333 } 334 335 /** 336 * Gets all child nodes which have the given {@code tag}. 337 * 338 * @return If this is primitive or no such children are found, an empty list will be returned. 339 */ getChildren(int tag)340 public List<Asn1Node> getChildren(int tag) 341 throws TagNotFoundException, InvalidAsn1DataException { 342 if (!mConstructed) { 343 return EMPTY_NODE_LIST; 344 } 345 346 List<Asn1Node> children = getChildren(); 347 if (children.isEmpty()) { 348 return EMPTY_NODE_LIST; 349 } 350 List<Asn1Node> output = new ArrayList<>(); 351 int size = children.size(); 352 for (int i = 0; i < size; i++) { 353 Asn1Node child = children.get(i); 354 if (child.getTag() == tag) { 355 output.add(child); 356 } 357 } 358 return output.isEmpty() ? EMPTY_NODE_LIST : output; 359 } 360 361 /** 362 * Gets all child nodes of this node. If it's a constructed node having encoded data, it's 363 * children will be decoded here. 364 * 365 * @return If this is primitive, an empty list will be returned. Do not modify the returned list 366 * directly. 367 */ getChildren()368 public List<Asn1Node> getChildren() throws InvalidAsn1DataException { 369 if (!mConstructed) { 370 return EMPTY_NODE_LIST; 371 } 372 373 if (mDataBytes != null) { 374 Asn1Decoder subDecoder = new Asn1Decoder(mDataBytes, mDataOffset, mDataLength); 375 while (subDecoder.hasNextNode()) { 376 mChildren.add(subDecoder.nextNode()); 377 } 378 mDataBytes = null; 379 mDataOffset = 0; 380 } 381 return mChildren; 382 } 383 384 /** @return Whether this node has a value. False will be returned for a constructed node. */ hasValue()385 public boolean hasValue() { 386 return !mConstructed && mDataBytes != null; 387 } 388 389 /** 390 * @return The data as an integer. If the data length is larger than 4, only the first 4 bytes 391 * will be parsed. 392 * @throws InvalidAsn1DataException If the data bytes cannot be parsed. 393 */ asInteger()394 public int asInteger() throws InvalidAsn1DataException { 395 if (mConstructed) { 396 throw new IllegalStateException("Cannot get value of a constructed node."); 397 } 398 if (mDataBytes == null) { 399 throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null."); 400 } 401 try { 402 return IccUtils.bytesToInt(mDataBytes, mDataOffset, mDataLength); 403 } catch (IllegalArgumentException | IndexOutOfBoundsException e) { 404 throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e); 405 } 406 } 407 408 /** 409 * @return The data as a long variable which can be both positive and negative. If the data 410 * length is larger than 8, only the first 8 bytes will be parsed. 411 * @throws InvalidAsn1DataException If the data bytes cannot be parsed. 412 */ asRawLong()413 public long asRawLong() throws InvalidAsn1DataException { 414 if (mConstructed) { 415 throw new IllegalStateException("Cannot get value of a constructed node."); 416 } 417 if (mDataBytes == null) { 418 throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null."); 419 } 420 try { 421 return IccUtils.bytesToRawLong(mDataBytes, mDataOffset, mDataLength); 422 } catch (IllegalArgumentException | IndexOutOfBoundsException e) { 423 throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e); 424 } 425 } 426 427 /** 428 * @return The data as a string in UTF-8 encoding. 429 * @throws InvalidAsn1DataException If the data bytes cannot be parsed. 430 */ asString()431 public String asString() throws InvalidAsn1DataException { 432 if (mConstructed) { 433 throw new IllegalStateException("Cannot get value of a constructed node."); 434 } 435 if (mDataBytes == null) { 436 throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null."); 437 } 438 try { 439 return new String(mDataBytes, mDataOffset, mDataLength, StandardCharsets.UTF_8); 440 } catch (IndexOutOfBoundsException e) { 441 throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e); 442 } 443 } 444 445 /** 446 * @return The data as a byte array. 447 * @throws InvalidAsn1DataException If the data bytes cannot be parsed. 448 */ asBytes()449 public byte[] asBytes() throws InvalidAsn1DataException { 450 if (mConstructed) { 451 throw new IllegalStateException("Cannot get value of a constructed node."); 452 } 453 if (mDataBytes == null) { 454 throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null."); 455 } 456 byte[] output = new byte[mDataLength]; 457 try { 458 System.arraycopy(mDataBytes, mDataOffset, output, 0, mDataLength); 459 } catch (IndexOutOfBoundsException e) { 460 throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e); 461 } 462 return output; 463 } 464 465 /** 466 * Gets the data as an integer for BIT STRING. DER actually stores the bits in a reversed order. 467 * The returned integer here has the order fixed (first bit is at the lowest position). This 468 * method currently only support at most 32 bits which fit in an integer. 469 * 470 * @return The data as an integer. If this is constructed, a {@code null} will be returned. 471 * @throws InvalidAsn1DataException If the data bytes cannot be parsed. 472 */ asBits()473 public int asBits() throws InvalidAsn1DataException { 474 if (mConstructed) { 475 throw new IllegalStateException("Cannot get value of a constructed node."); 476 } 477 if (mDataBytes == null) { 478 throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null."); 479 } 480 int bits; 481 try { 482 bits = IccUtils.bytesToInt(mDataBytes, mDataOffset + 1, mDataLength - 1); 483 } catch (IllegalArgumentException | IndexOutOfBoundsException e) { 484 throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e); 485 } 486 for (int i = mDataLength - 1; i < INT_BYTES; i++) { 487 bits <<= Byte.SIZE; 488 } 489 return Integer.reverse(bits); 490 } 491 492 /** 493 * @return The data as a boolean. 494 * @throws InvalidAsn1DataException If the data bytes cannot be parsed. 495 */ asBoolean()496 public boolean asBoolean() throws InvalidAsn1DataException { 497 if (mConstructed) { 498 throw new IllegalStateException("Cannot get value of a constructed node."); 499 } 500 if (mDataBytes == null) { 501 throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null."); 502 } 503 if (mDataLength != 1) { 504 throw new InvalidAsn1DataException( 505 mTag, "Cannot parse data bytes as boolean: length=" + mDataLength); 506 } 507 if (mDataOffset < 0 || mDataOffset >= mDataBytes.length) { 508 throw new InvalidAsn1DataException( 509 mTag, 510 "Cannot parse data bytes.", 511 new ArrayIndexOutOfBoundsException(mDataOffset)); 512 } 513 // ASN.1 has "true" as 0xFF. 514 if (mDataBytes[mDataOffset] == -1) { 515 return Boolean.TRUE; 516 } else if (mDataBytes[mDataOffset] == 0) { 517 return Boolean.FALSE; 518 } 519 throw new InvalidAsn1DataException( 520 mTag, "Cannot parse data bytes as boolean: " + mDataBytes[mDataOffset]); 521 } 522 523 /** @return The number of required bytes for encoding this node in DER. */ getEncodedLength()524 public int getEncodedLength() { 525 return mEncodedLength; 526 } 527 528 /** @return The number of required bytes for encoding this node's data in DER. */ getDataLength()529 public int getDataLength() { 530 return mDataLength; 531 } 532 533 /** 534 * Writes the DER encoded bytes of this node into a byte array. The number of written bytes is 535 * {@link #getEncodedLength()}. 536 * 537 * @throws IndexOutOfBoundsException If the {@code dest} doesn't have enough space to write. 538 */ writeToBytes(byte[] dest, int offset)539 public void writeToBytes(byte[] dest, int offset) { 540 if (offset < 0 || offset + mEncodedLength > dest.length) { 541 throw new IndexOutOfBoundsException( 542 "Not enough space to write. Required bytes: " + mEncodedLength); 543 } 544 write(dest, offset); 545 } 546 547 /** Writes the DER encoded bytes of this node into a new byte array. */ toBytes()548 public byte[] toBytes() { 549 byte[] dest = new byte[mEncodedLength]; 550 write(dest, 0); 551 return dest; 552 } 553 554 /** Gets a hex string representing the DER encoded bytes of this node. */ toHex()555 public String toHex() { 556 return IccUtils.bytesToHexString(toBytes()); 557 } 558 559 /** Gets header (tag + length) as hex string. */ getHeadAsHex()560 public String getHeadAsHex() { 561 String headHex = IccUtils.bytesToHexString(IccUtils.unsignedIntToBytes(mTag)); 562 if (mDataLength <= 127) { 563 headHex += IccUtils.byteToHex((byte) mDataLength); 564 } else { 565 byte[] lenBytes = IccUtils.unsignedIntToBytes(mDataLength); 566 headHex += IccUtils.byteToHex((byte) (lenBytes.length | 0x80)); 567 headHex += IccUtils.bytesToHexString(lenBytes); 568 } 569 return headHex; 570 } 571 572 /** Returns the new offset where to write the next node data. */ write(byte[] dest, int offset)573 private int write(byte[] dest, int offset) { 574 // Writes the tag. 575 offset += IccUtils.unsignedIntToBytes(mTag, dest, offset); 576 // Writes the length. 577 if (mDataLength <= 127) { 578 dest[offset++] = (byte) mDataLength; 579 } else { 580 // Bytes required for encoding the length 581 int lenLen = IccUtils.unsignedIntToBytes(mDataLength, dest, ++offset); 582 dest[offset - 1] = (byte) (lenLen | 0x80); 583 offset += lenLen; 584 } 585 // Writes the data. 586 if (mConstructed && mDataBytes == null) { 587 int size = mChildren.size(); 588 for (int i = 0; i < size; i++) { 589 Asn1Node child = mChildren.get(i); 590 offset = child.write(dest, offset); 591 } 592 } else if (mDataBytes != null) { 593 System.arraycopy(mDataBytes, mDataOffset, dest, offset, mDataLength); 594 offset += mDataLength; 595 } 596 return offset; 597 } 598 } 599