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 android.keystore.cts;
18 
19 import com.google.common.collect.ImmutableSet;
20 
21 import org.bouncycastle.asn1.ASN1Boolean;
22 import org.bouncycastle.asn1.ASN1Encodable;
23 import org.bouncycastle.asn1.ASN1Enumerated;
24 import org.bouncycastle.asn1.ASN1InputStream;
25 import org.bouncycastle.asn1.ASN1Integer;
26 import org.bouncycastle.asn1.ASN1OctetString;
27 import org.bouncycastle.asn1.ASN1Primitive;
28 import org.bouncycastle.asn1.ASN1Sequence;
29 import org.bouncycastle.asn1.ASN1Set;
30 import org.bouncycastle.asn1.DEROctetString;
31 
32 import java.io.IOException;
33 import java.io.UnsupportedEncodingException;
34 import java.lang.String;
35 import java.math.BigInteger;
36 import java.security.cert.CertificateParsingException;
37 import java.util.Date;
38 import java.util.Enumeration;
39 import java.util.Set;
40 
41 public class Asn1Utils {
42 
getIntegerFromAsn1(ASN1Encodable asn1Value)43     public static int getIntegerFromAsn1(ASN1Encodable asn1Value)
44             throws CertificateParsingException {
45         if (asn1Value instanceof ASN1Integer) {
46             return bigIntegerToInt(((ASN1Integer) asn1Value).getValue());
47         } else if (asn1Value instanceof ASN1Enumerated) {
48             return bigIntegerToInt(((ASN1Enumerated) asn1Value).getValue());
49         } else {
50             throw new CertificateParsingException(
51                     "Integer value expected, " + asn1Value.getClass().getName() + " found.");
52         }
53     }
54 
getLongFromAsn1(ASN1Encodable asn1Value)55     public static Long getLongFromAsn1(ASN1Encodable asn1Value) throws CertificateParsingException {
56         if (asn1Value instanceof ASN1Integer) {
57             return bigIntegerToLong(((ASN1Integer) asn1Value).getValue());
58         } else {
59             throw new CertificateParsingException(
60                     "Integer value expected, " + asn1Value.getClass().getName() + " found.");
61         }
62     }
63 
getByteArrayFromAsn1(ASN1Encodable asn1Encodable)64     public static byte[] getByteArrayFromAsn1(ASN1Encodable asn1Encodable)
65             throws CertificateParsingException {
66         if (asn1Encodable == null || !(asn1Encodable instanceof DEROctetString)) {
67             throw new CertificateParsingException("Expected DEROctetString");
68         }
69         ASN1OctetString derOctectString = (ASN1OctetString) asn1Encodable;
70         return derOctectString.getOctets();
71     }
72 
getAsn1EncodableFromBytes(byte[] bytes)73     public static ASN1Encodable getAsn1EncodableFromBytes(byte[] bytes)
74             throws CertificateParsingException {
75         try (ASN1InputStream asn1InputStream = new ASN1InputStream(bytes)) {
76             return asn1InputStream.readObject();
77         } catch (IOException e) {
78             throw new CertificateParsingException("Failed to parse Encodable", e);
79         }
80     }
81 
getAsn1SequenceFromBytes(byte[] bytes)82     public static ASN1Sequence getAsn1SequenceFromBytes(byte[] bytes)
83             throws CertificateParsingException {
84         try (ASN1InputStream asn1InputStream = new ASN1InputStream(bytes)) {
85             return getAsn1SequenceFromStream(asn1InputStream);
86         } catch (IOException e) {
87             throw new CertificateParsingException("Failed to parse SEQUENCE", e);
88         }
89     }
90 
getAsn1SequenceFromStream(final ASN1InputStream asn1InputStream)91     public static ASN1Sequence getAsn1SequenceFromStream(final ASN1InputStream asn1InputStream)
92             throws IOException, CertificateParsingException {
93         ASN1Primitive asn1Primitive = asn1InputStream.readObject();
94         if (!(asn1Primitive instanceof ASN1OctetString)) {
95             throw new CertificateParsingException(
96                     "Expected octet stream, found " + asn1Primitive.getClass().getName());
97         }
98         try (ASN1InputStream seqInputStream = new ASN1InputStream(
99                 ((ASN1OctetString) asn1Primitive).getOctets())) {
100             asn1Primitive = seqInputStream.readObject();
101             if (!(asn1Primitive instanceof ASN1Sequence)) {
102                 throw new CertificateParsingException(
103                         "Expected sequence, found " + asn1Primitive.getClass().getName());
104             }
105             return (ASN1Sequence) asn1Primitive;
106         }
107     }
108 
getIntegersFromAsn1Set(ASN1Encodable set)109     public static Set<Integer> getIntegersFromAsn1Set(ASN1Encodable set)
110             throws CertificateParsingException {
111         if (!(set instanceof ASN1Set)) {
112             throw new CertificateParsingException(
113                     "Expected set, found " + set.getClass().getName());
114         }
115 
116         ImmutableSet.Builder<Integer> builder = ImmutableSet.builder();
117         for (Enumeration<?> e = ((ASN1Set) set).getObjects(); e.hasMoreElements();) {
118             builder.add(getIntegerFromAsn1((ASN1Integer) e.nextElement()));
119         }
120         return builder.build();
121     }
122 
getStringFromAsn1OctetStreamAssumingUTF8(ASN1Encodable encodable)123     public static String getStringFromAsn1OctetStreamAssumingUTF8(ASN1Encodable encodable)
124             throws CertificateParsingException, UnsupportedEncodingException {
125         if (!(encodable instanceof ASN1OctetString)) {
126             throw new CertificateParsingException(
127                     "Expected octet string, found " + encodable.getClass().getName());
128         }
129 
130         ASN1OctetString octetString = (ASN1OctetString) encodable;
131         return new String(octetString.getOctets(), "UTF-8");
132     }
133 
getDateFromAsn1(ASN1Primitive value)134     public static Date getDateFromAsn1(ASN1Primitive value) throws CertificateParsingException {
135         return new Date(getLongFromAsn1(value));
136     }
137 
getBooleanFromAsn1(ASN1Encodable value)138     public static boolean getBooleanFromAsn1(ASN1Encodable value)
139             throws CertificateParsingException {
140         return getBooleanFromAsn1(value, true);
141     }
142 
getBooleanFromAsn1(ASN1Encodable value, boolean strictParsing)143     public static boolean getBooleanFromAsn1(ASN1Encodable value, boolean strictParsing)
144             throws CertificateParsingException {
145         if (!(value instanceof ASN1Boolean)) {
146             throw new CertificateParsingException(
147                     "Expected boolean, found " + value.getClass().getName());
148         }
149         ASN1Boolean booleanValue = (ASN1Boolean) value;
150 
151         if (booleanValue.equals(ASN1Boolean.TRUE)) {
152             return true;
153         } else if (booleanValue.equals((ASN1Boolean.FALSE))) {
154             return false;
155         } else if (!strictParsing) {
156             // Value is not 0xFF nor 0x00, but some other non-zero value.
157             // This is invalid DER, but if we're not being strict,
158             // consider it true, otherwise fall through and throw exception
159             return true;
160         }
161 
162         throw new CertificateParsingException(
163                 "DER-encoded boolean values must contain either 0x00 or 0xFF");
164     }
165 
bigIntegerToInt(BigInteger bigInt)166     private static int bigIntegerToInt(BigInteger bigInt) throws CertificateParsingException {
167         if (bigInt.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0
168                 || bigInt.compareTo(BigInteger.ZERO) < 0) {
169             throw new CertificateParsingException("INTEGER out of bounds");
170         }
171         return bigInt.intValue();
172     }
173 
bigIntegerToLong(BigInteger bigInt)174     private static long bigIntegerToLong(BigInteger bigInt) throws CertificateParsingException {
175         if (bigInt.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0
176                 || bigInt.compareTo(BigInteger.ZERO) < 0) {
177             throw new CertificateParsingException("INTEGER out of bounds");
178         }
179         return bigInt.longValue();
180     }
181 }
182