• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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