1 /*
2  * Copyright (C) 2020 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 android.util.Log;
20 
21 import co.nstant.in.cbor.CborDecoder;
22 import co.nstant.in.cbor.CborException;
23 import co.nstant.in.cbor.model.DataItem;
24 import co.nstant.in.cbor.model.Map;
25 import co.nstant.in.cbor.model.Number;
26 import co.nstant.in.cbor.model.UnicodeString;
27 
28 import org.bouncycastle.asn1.ASN1Encodable;
29 
30 import java.security.cert.CertificateParsingException;
31 import java.security.cert.X509Certificate;
32 import java.util.Arrays;
33 import java.util.List;
34 
35 public class EatAttestation extends Attestation {
36     static final String TAG = "EatAttestation";
37     final Map extension;
38     final RootOfTrust rootOfTrust;
39 
40     /**
41      * Constructs an {@code EatAttestation} object from the provided {@link X509Certificate},
42      * extracting the attestation data from the attestation extension.
43      *
44      * @throws CertificateParsingException if the certificate does not contain a properly-formatted
45      *     attestation extension.
46      */
EatAttestation(X509Certificate x509Cert)47     public EatAttestation(X509Certificate x509Cert)
48             throws CertificateParsingException, CborException {
49         super(x509Cert);
50         extension = getEatExtension(x509Cert);
51 
52         RootOfTrust.Builder rootOfTrustBuilder = new RootOfTrust.Builder();
53         List<Boolean> bootState = null;
54         boolean officialBuild = false;
55 
56         for (DataItem key : extension.getKeys()) {
57             int keyInt = ((Number) key).getValue().intValue();
58             switch (keyInt) {
59                 default:
60                     throw new CertificateParsingException(
61                             "Unknown EAT tag: " + key + "\n in EAT extension:\n" + toString());
62 
63                 case EatClaim.ATTESTATION_VERSION:
64                     attestationVersion = CborUtils.getInt(extension, key);
65                     break;
66                 case EatClaim.KEYMASTER_VERSION:
67                     keymasterVersion = CborUtils.getInt(extension, key);
68                     break;
69                 case EatClaim.SECURITY_LEVEL:
70                     keymasterSecurityLevel =
71                             eatSecurityLevelToKeymintSecurityLevel(
72                                     CborUtils.getInt(extension, key));
73                     break;
74                 case EatClaim.SUBMODS:
75                     Map submods = (Map) extension.get(key);
76                     softwareEnforced =
77                             new AuthorizationList(
78                                     (Map) submods.get(new UnicodeString(EatClaim.SUBMOD_SOFTWARE)));
79                     teeEnforced =
80                             new AuthorizationList(
81                                     (Map) submods.get(new UnicodeString(EatClaim.SUBMOD_TEE)));
82                     break;
83                 case EatClaim.VERIFIED_BOOT_KEY:
84                     rootOfTrustBuilder.setVerifiedBootKey(CborUtils.getBytes(extension, key));
85                     break;
86                 case EatClaim.DEVICE_LOCKED:
87                     rootOfTrustBuilder.setDeviceLocked(CborUtils.getBoolean(extension, key));
88                     break;
89                 case EatClaim.BOOT_STATE:
90                     bootState = CborUtils.getBooleanList(extension, key);
91                     break;
92                 case EatClaim.OFFICIAL_BUILD:
93                     officialBuild = CborUtils.getBoolean(extension, key);
94                     break;
95                 case EatClaim.NONCE:
96                     attestationChallenge = CborUtils.getBytes(extension, key);
97                     break;
98                 case EatClaim.CTI:
99                     Log.i(TAG, "Got CTI claim: " +
100                             Arrays.toString(CborUtils.getBytes(extension, key)));
101                     uniqueId = CborUtils.getBytes(extension, key);
102                     break;
103                 case EatClaim.VERIFIED_BOOT_HASH:
104                     // TODO: ignored for now, as this is not checked in original ASN.1 tests
105                     break;
106             }
107         }
108 
109         if (bootState != null) {
110             rootOfTrustBuilder.setVerifiedBootState(
111                     eatBootStateTypeToVerifiedBootState(bootState, officialBuild));
112         }
113         rootOfTrust = rootOfTrustBuilder.build();
114     }
115 
116     /** Find the submod containing the key information, and return its security level. */
getAttestationSecurityLevel()117     public int getAttestationSecurityLevel() {
118         if (teeEnforced != null && teeEnforced.getAlgorithm() != null) {
119             return teeEnforced.getSecurityLevel();
120         } else if (softwareEnforced != null && softwareEnforced.getAlgorithm() != null) {
121             return softwareEnforced.getSecurityLevel();
122         } else {
123             return -1;
124         }
125     }
126 
getRootOfTrust()127     public RootOfTrust getRootOfTrust() {
128         return rootOfTrust;
129     }
130 
toString()131     public String toString() {
132         return super.toString() + "\nEncoded CBOR: " + extension;
133     }
134 
getEatExtension(X509Certificate x509Cert)135     Map getEatExtension(X509Certificate x509Cert)
136             throws CertificateParsingException, CborException {
137         byte[] attestationExtensionBytes = x509Cert.getExtensionValue(Attestation.EAT_OID);
138         if (attestationExtensionBytes == null || attestationExtensionBytes.length == 0) {
139             throw new CertificateParsingException("Did not find extension with OID " + EAT_OID);
140         }
141         ASN1Encodable asn1 = Asn1Utils.getAsn1EncodableFromBytes(attestationExtensionBytes);
142         byte[] cborBytes = Asn1Utils.getByteArrayFromAsn1(asn1);
143         List<DataItem> cbor = CborDecoder.decode(cborBytes);
144         return (Map) cbor.get(0);
145     }
146 
eatSecurityLevelToKeymintSecurityLevel(int eatSecurityLevel)147     static int eatSecurityLevelToKeymintSecurityLevel(int eatSecurityLevel) {
148         switch (eatSecurityLevel) {
149             case EatClaim.SECURITY_LEVEL_UNRESTRICTED:
150                 return Attestation.KM_SECURITY_LEVEL_SOFTWARE;
151             case EatClaim.SECURITY_LEVEL_SECURE_RESTRICTED:
152                 return Attestation.KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT;
153             case EatClaim.SECURITY_LEVEL_HARDWARE:
154                 return Attestation.KM_SECURITY_LEVEL_STRONG_BOX;
155             default:
156                 throw new RuntimeException("Invalid EAT security level: " + eatSecurityLevel);
157         }
158     }
159 
eatBootStateTypeToVerifiedBootState(List<Boolean> bootState, Boolean officialBuild)160     static int eatBootStateTypeToVerifiedBootState(List<Boolean> bootState, Boolean officialBuild) {
161         if (bootState.size() != 5) {
162             throw new RuntimeException("Boot state map has unexpected size: " + bootState.size());
163         }
164         if (bootState.get(4)) {
165             throw new RuntimeException("debug-permanent-disable must never be true: " + bootState);
166         }
167         boolean verifiedOrSelfSigned = bootState.get(0);
168         if (verifiedOrSelfSigned != bootState.get(1)
169                 && verifiedOrSelfSigned != bootState.get(2)
170                 && verifiedOrSelfSigned != bootState.get(3)) {
171             throw new RuntimeException("Unexpected boot state: " + bootState);
172         }
173 
174         if (officialBuild) {
175             if (!verifiedOrSelfSigned) {
176                 throw new AssertionError("Non-verified official build");
177             }
178             return RootOfTrust.KM_VERIFIED_BOOT_VERIFIED;
179         } else {
180             return verifiedOrSelfSigned
181                     ? RootOfTrust.KM_VERIFIED_BOOT_SELF_SIGNED
182                     : RootOfTrust.KM_VERIFIED_BOOT_UNVERIFIED;
183         }
184     }
185 }
186