Home | History | Annotate | Download | only in asn1
      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 
     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          */
     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          */
     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          */
     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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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. */
    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      */
    221     public static Builder newBuilder(int tag) {
    222         return new Builder(tag);
    223     }
    224 
    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 
    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      */
    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. */
    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 
    275     public int getTag() {
    276         return mTag;
    277     }
    278 
    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      */
    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      */
    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      */
    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      */
    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. */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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. */
    524     public int getEncodedLength() {
    525         return mEncodedLength;
    526     }
    527 
    528     /** @return The number of required bytes for encoding this node's data in DER. */
    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      */
    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. */
    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. */
    555     public String toHex() {
    556         return IccUtils.bytesToHexString(toBytes());
    557     }
    558 
    559     /** Gets header (tag + length) as hex string. */
    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. */
    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