1 /*
2  * Copyright (C) 2021 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.remoteprovisioner.unittest;
18 
19 import static android.security.keystore.KeyProperties.KEY_ALGORITHM_EC;
20 import static android.security.keystore.KeyProperties.PURPOSE_SIGN;
21 
22 import static com.android.remoteprovisioner.unittest.Utils.generateEcdsaKeyPair;
23 import static com.android.remoteprovisioner.unittest.Utils.getP256PubKeyFromBytes;
24 import static com.android.remoteprovisioner.unittest.Utils.signPublicKey;
25 
26 import static org.junit.Assert.assertArrayEquals;
27 import static org.junit.Assert.assertEquals;
28 import static org.junit.Assert.assertNotNull;
29 import static org.junit.Assert.assertTrue;
30 
31 import android.hardware.security.keymint.DeviceInfo;
32 import android.hardware.security.keymint.ProtectedData;
33 import android.hardware.security.keymint.SecurityLevel;
34 import android.os.ServiceManager;
35 import android.platform.test.annotations.Presubmit;
36 import android.security.keystore.KeyGenParameterSpec;
37 import android.security.remoteprovisioning.IRemoteProvisioning;
38 
39 import androidx.test.runner.AndroidJUnit4;
40 
41 import com.android.remoteprovisioner.CborUtils;
42 import com.android.remoteprovisioner.SystemInterface;
43 import com.android.remoteprovisioner.X509Utils;
44 
45 import com.google.crypto.tink.subtle.Hkdf;
46 import com.google.crypto.tink.subtle.X25519;
47 
48 import co.nstant.in.cbor.CborBuilder;
49 import co.nstant.in.cbor.CborDecoder;
50 import co.nstant.in.cbor.CborEncoder;
51 import co.nstant.in.cbor.model.Array;
52 import co.nstant.in.cbor.model.ByteString;
53 import co.nstant.in.cbor.model.DataItem;
54 import co.nstant.in.cbor.model.MajorType;
55 import co.nstant.in.cbor.model.Map;
56 import co.nstant.in.cbor.model.NegativeInteger;
57 import co.nstant.in.cbor.model.UnsignedInteger;
58 
59 import org.junit.After;
60 import org.junit.Before;
61 import org.junit.Test;
62 import org.junit.runner.RunWith;
63 
64 import java.io.ByteArrayInputStream;
65 import java.io.ByteArrayOutputStream;
66 import java.security.KeyPair;
67 import java.security.KeyPairGenerator;
68 import java.security.KeyStore;
69 import java.security.PublicKey;
70 import java.security.cert.Certificate;
71 import java.security.cert.X509Certificate;
72 import java.util.Arrays;
73 import java.util.List;
74 import java.util.Random;
75 
76 import javax.crypto.Cipher;
77 import javax.crypto.spec.GCMParameterSpec;
78 import javax.crypto.spec.SecretKeySpec;
79 
80 @RunWith(AndroidJUnit4.class)
81 public class SystemInterfaceTest {
82 
83     private static final String SERVICE = "android.security.remoteprovisioning";
84 
85     private IRemoteProvisioning mBinder;
86 
87     @Before
setUp()88     public void setUp() throws Exception {
89         mBinder =
90               IRemoteProvisioning.Stub.asInterface(ServiceManager.getService(SERVICE));
91         assertNotNull(mBinder);
92         mBinder.deleteAllKeys();
93     }
94 
95     @After
tearDown()96     public void tearDown() throws Exception {
97         mBinder.deleteAllKeys();
98     }
99 
generateEekChain(byte[] eek)100     private byte[] generateEekChain(byte[] eek) throws Exception {
101         com.google.crypto.tink.subtle.Ed25519Sign.KeyPair kp =
102                 com.google.crypto.tink.subtle.Ed25519Sign.KeyPair.newKeyPair();
103         ByteArrayOutputStream baos = new ByteArrayOutputStream();
104         new CborEncoder(baos).encode(new CborBuilder()
105                 .addArray()
106                     .add(Utils.encodeAndSignSign1Ed25519(
107                             Utils.encodeEd25519PubKey(kp.getPublicKey()), kp.getPrivateKey()))
108                     .add(Utils.encodeAndSignSign1Ed25519(
109                             Utils.encodeX25519PubKey(eek), kp.getPrivateKey()))
110                     .end()
111                 .build());
112         return baos.toByteArray();
113     }
114 
115     @Presubmit
116     @Test
testGenerateCSR()117     public void testGenerateCSR() throws Exception {
118         DeviceInfo deviceInfo = new DeviceInfo();
119         ProtectedData encryptedBundle = new ProtectedData();
120         byte[] eek = new byte[32];
121         new Random().nextBytes(eek);
122         byte[] bundle =
123             SystemInterface.generateCsr(true /* testMode */, 0 /* numKeys */,
124                                         SecurityLevel.TRUSTED_ENVIRONMENT,
125                                         generateEekChain(eek),
126                                         new byte[] {0x02}, encryptedBundle, deviceInfo, mBinder);
127         // encryptedBundle should contain a COSE_Encrypt message
128         ByteArrayInputStream bais = new ByteArrayInputStream(encryptedBundle.protectedData);
129         List<DataItem> dataItems = new CborDecoder(bais).decode();
130         assertEquals(1, dataItems.size());
131         assertEquals(MajorType.ARRAY, dataItems.get(0).getMajorType());
132         Array encMsg = (Array) dataItems.get(0);
133         assertEquals(4, encMsg.getDataItems().size());
134     }
135 
generateKeyStoreKey(String alias)136     private static Certificate[] generateKeyStoreKey(String alias) throws Exception {
137         KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
138         keyStore.load(null);
139         KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM_EC,
140                 "AndroidKeyStore");
141         KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(alias, PURPOSE_SIGN)
142                 .setAttestationChallenge("challenge".getBytes())
143                 .build();
144         keyPairGenerator.initialize(spec);
145         keyPairGenerator.generateKeyPair();
146         Certificate[] certs = keyStore.getCertificateChain(spec.getKeystoreAlias());
147         keyStore.deleteEntry(alias);
148         return certs;
149     }
150 
151     @Presubmit
152     @Test
testGenerateCSRProvisionAndUseKey()153     public void testGenerateCSRProvisionAndUseKey() throws Exception {
154         DeviceInfo deviceInfo = new DeviceInfo();
155         ProtectedData encryptedBundle = new ProtectedData();
156         int numKeys = 10;
157         byte[] eek = new byte[32];
158         new Random().nextBytes(eek);
159         for (int i = 0; i < numKeys; i++) {
160             mBinder.generateKeyPair(true /* testMode */, SecurityLevel.TRUSTED_ENVIRONMENT);
161         }
162         byte[] bundle =
163             SystemInterface.generateCsr(true /* testMode */, numKeys,
164                                         SecurityLevel.TRUSTED_ENVIRONMENT,
165                                         generateEekChain(eek),
166                                         new byte[] {0x02}, encryptedBundle, deviceInfo, mBinder);
167         assertNotNull(bundle);
168         // The return value of generateCsr should be a COSE_Mac0 message
169         ByteArrayInputStream bais = new ByteArrayInputStream(bundle);
170         List<DataItem> dataItems = new CborDecoder(bais).decode();
171         assertEquals(1, dataItems.size());
172         assertEquals(MajorType.ARRAY, dataItems.get(0).getMajorType());
173         Array macMsg = (Array) dataItems.get(0);
174         assertEquals(4, macMsg.getDataItems().size());
175 
176         // The payload for the COSE_Mac0 should contain the array of public keys
177         bais = new ByteArrayInputStream(((ByteString) macMsg.getDataItems().get(2)).getBytes());
178         List<DataItem> publicKeysArr = new CborDecoder(bais).decode();
179         assertEquals(1, publicKeysArr.size());
180         assertEquals(MajorType.ARRAY, publicKeysArr.get(0).getMajorType());
181         Array publicKeys = (Array) publicKeysArr.get(0);
182         assertEquals(numKeys, publicKeys.getDataItems().size());
183         KeyPair rootKeyPair = generateEcdsaKeyPair();
184         KeyPair intermediateKeyPair = generateEcdsaKeyPair();
185         X509Certificate[][] certChain = new X509Certificate[numKeys][3];
186         for (int i = 0; i < numKeys; i++) {
187             Map publicKey = (Map) publicKeys.getDataItems().get(i);
188             byte[] xPub = ((ByteString) publicKey.get(new NegativeInteger(-2))).getBytes();
189             byte[] yPub = ((ByteString) publicKey.get(new NegativeInteger(-3))).getBytes();
190             assertEquals(xPub.length, 32);
191             assertEquals(yPub.length, 32);
192             PublicKey leafKeyToSign = getP256PubKeyFromBytes(xPub, yPub);
193             certChain[i][0] = signPublicKey(intermediateKeyPair, leafKeyToSign);
194             certChain[i][1] = signPublicKey(rootKeyPair, intermediateKeyPair.getPublic());
195             certChain[i][2] = signPublicKey(rootKeyPair, rootKeyPair.getPublic());
196             ByteArrayOutputStream os = new ByteArrayOutputStream();
197             for (int j = 0; j < certChain[i].length; j++) {
198                 os.write(certChain[i][j].getEncoded());
199             }
200             SystemInterface.provisionCertChain(X509Utils.getAndFormatRawPublicKey(certChain[i][0]),
201                                                certChain[i][0].getEncoded() /* leafCert */,
202                                                os.toByteArray() /* certChain */,
203                                                System.currentTimeMillis() + 2000 /* validity */,
204                                                SecurityLevel.TRUSTED_ENVIRONMENT,
205                                                mBinder);
206         }
207         // getPoolStatus will clean the key pool before we go to assign a new provisioned key
208         mBinder.getPoolStatus(0, SecurityLevel.TRUSTED_ENVIRONMENT);
209         Certificate[] provisionedCerts1 = generateKeyStoreKey("alias");
210         Certificate[] provisionedCerts2 = generateKeyStoreKey("alias2");
211         assertEquals(4, provisionedCerts1.length);
212         assertEquals(4, provisionedCerts2.length);
213         boolean matched = false;
214         for (int i = 0; i < certChain.length; i++) {
215             if (Arrays.equals(provisionedCerts1[1].getEncoded(), certChain[i][0].getEncoded())) {
216                 matched = true;
217                 assertArrayEquals("Second key: j = 0",
218                         provisionedCerts2[1].getEncoded(), certChain[i][0].getEncoded());
219                 for (int j = 1; j < certChain[i].length; j++) {
220                     assertArrayEquals("First key: j = " + j,
221                             provisionedCerts1[j + 1].getEncoded(), certChain[i][j].getEncoded());
222                     assertArrayEquals("Second key: j = " + j,
223                             provisionedCerts2[j + 1].getEncoded(), certChain[i][j].getEncoded());
224                 }
225             }
226         }
227         assertTrue(matched);
228     }
229 
extractRecipientKey(Array recipients)230     private static byte[] extractRecipientKey(Array recipients) {
231         // Recipients is an Array of recipient Arrays
232         Map recipientUnprotectedHeaders = (Map) ((Array) recipients.getDataItems().get(0))
233                                                                    .getDataItems().get(1);
234         Map recipientKeyMap = (Map) recipientUnprotectedHeaders.get(new NegativeInteger(-1));
235         return ((ByteString) recipientKeyMap.get(new NegativeInteger(-2))).getBytes();
236     }
237 
buildKdfContext(byte[] serverPub, byte[] ephemeralPub)238     private static byte[] buildKdfContext(byte[] serverPub, byte[] ephemeralPub) throws Exception {
239         ByteArrayOutputStream baos = new ByteArrayOutputStream();
240         new CborEncoder(baos).encode(new CborBuilder()
241                 .addArray()
242                     .add(3) // AlgorithmID: AES-GCM 256
243                     .addArray()
244                         .add("client".getBytes("UTF8"))
245                         .add(new byte[0])
246                         .add(ephemeralPub)
247                         .end()
248                     .addArray()
249                         .add("server".getBytes("UTF8"))
250                         .add(new byte[0])
251                         .add(serverPub)
252                         .end()
253                     .addArray()
254                         .add(256) // key length
255                         .add(new byte[0])
256                         .end()
257                     .end()
258                 .build());
259         return baos.toByteArray();
260     }
261 
buildEncStructure(byte[] protectedHeaders, byte[] externalAad)262     private static byte[] buildEncStructure(byte[] protectedHeaders, byte[] externalAad)
263             throws Exception {
264         ByteArrayOutputStream baos = new ByteArrayOutputStream();
265         new CborEncoder(baos).encode(new CborBuilder()
266                 .addArray()
267                     .add("Encrypt")
268                     .add(protectedHeaders)
269                     .add(externalAad)
270                     .end()
271                 .build());
272         return baos.toByteArray();
273     }
274 
275     @Presubmit
276     @Test
testDecryptProtectedPayload()277     public void testDecryptProtectedPayload() throws Exception {
278         DeviceInfo deviceInfo = new DeviceInfo();
279         ProtectedData encryptedBundle = new ProtectedData();
280         int numKeys = 1;
281         byte[] eekPriv = X25519.generatePrivateKey();
282         byte[] eekPub = X25519.publicFromPrivate(eekPriv);
283         mBinder.generateKeyPair(true /* testMode */, SecurityLevel.TRUSTED_ENVIRONMENT);
284         byte[] bundle =
285             SystemInterface.generateCsr(true /* testMode */, numKeys,
286                                         SecurityLevel.TRUSTED_ENVIRONMENT,
287                                         generateEekChain(eekPub),
288                                         new byte[] {0x02}, encryptedBundle, deviceInfo, mBinder);
289         ByteArrayInputStream bais = new ByteArrayInputStream(encryptedBundle.protectedData);
290         List<DataItem> dataItems = new CborDecoder(bais).decode();
291         // Parse encMsg into components: protected and unprotected headers, payload, and recipient
292         List<DataItem> encMsg = ((Array) dataItems.get(0)).getDataItems();
293         byte[] protectedHeaders = ((ByteString) encMsg.get(0)).getBytes();
294         Map unprotectedHeaders = (Map) encMsg.get(1);
295         byte[] encryptedContent = ((ByteString) encMsg.get(2)).getBytes();
296         Array recipients = (Array) encMsg.get(3);
297 
298         byte[] iv = ((ByteString) unprotectedHeaders.get(new UnsignedInteger(5))).getBytes();
299         byte[] ephemeralPub = extractRecipientKey(recipients);
300         assertEquals(32, ephemeralPub.length);
301         byte[] sharedSecret = X25519.computeSharedSecret(eekPriv, ephemeralPub);
302         byte[] context = buildKdfContext(eekPub, ephemeralPub);
303         byte[] decryptionKey = Hkdf.computeHkdf("HMACSHA256", sharedSecret, null /* salt */,
304                                                 context, 32);
305 
306         Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
307         cipher.init(
308                 Cipher.DECRYPT_MODE,
309                 new SecretKeySpec(decryptionKey, "AES"),
310                 new GCMParameterSpec(128 /* iv length */, iv));
311         cipher.updateAAD(buildEncStructure(protectedHeaders, new byte[0]));
312 
313         byte[] protectedData = cipher.doFinal(encryptedContent);
314         bais = new ByteArrayInputStream(protectedData);
315         List<DataItem> protectedDataArray = new CborDecoder(bais).decode();
316         assertEquals(1, protectedDataArray.size());
317         assertEquals(MajorType.ARRAY, protectedDataArray.get(0).getMajorType());
318         List<DataItem> protectedDataPayload = ((Array) protectedDataArray.get(0)).getDataItems();
319         assertTrue(protectedDataPayload.size() == 2 || protectedDataPayload.size() == 3);
320     }
321 }
322