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.hardware.security.keymint.SecurityLevel.TRUSTED_ENVIRONMENT;
20 import static android.security.keystore.KeyProperties.KEY_ALGORITHM_EC;
21 import static android.security.keystore.KeyProperties.PURPOSE_SIGN;
22 
23 import static org.junit.Assert.assertArrayEquals;
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertFalse;
26 import static org.junit.Assert.assertNotNull;
27 import static org.junit.Assert.assertTrue;
28 
29 import android.content.Context;
30 import android.os.ServiceManager;
31 import android.security.keystore.KeyGenParameterSpec;
32 import android.security.remoteprovisioning.AttestationPoolStatus;
33 import android.security.remoteprovisioning.ImplInfo;
34 import android.security.remoteprovisioning.IRemoteProvisioning;
35 
36 import androidx.test.core.app.ApplicationProvider;
37 import androidx.test.runner.AndroidJUnit4;
38 
39 import com.android.remoteprovisioner.GeekResponse;
40 import com.android.remoteprovisioner.Provisioner;
41 import com.android.remoteprovisioner.ServerInterface;
42 import com.android.remoteprovisioner.SettingsManager;
43 
44 import org.junit.After;
45 import org.junit.Before;
46 import org.junit.BeforeClass;
47 import org.junit.Test;
48 import org.junit.runner.RunWith;
49 
50 import java.security.KeyPairGenerator;
51 import java.security.KeyStore;
52 import java.security.cert.Certificate;
53 import java.time.Duration;
54 import java.util.Arrays;
55 
56 @RunWith(AndroidJUnit4.class)
57 public class ServerToSystemTest {
58 
59     private static final boolean IS_TEST_MODE = false;
60     private static final String SERVICE = "android.security.remoteprovisioning";
61 
62     private static Context sContext;
63     private static IRemoteProvisioning sBinder;
64     private static int sCurve = 0;
65 
66     private Duration mDuration;
67 
assertPoolStatus(int total, int attested, int unassigned, int expiring, Duration time)68     private void assertPoolStatus(int total, int attested,
69                                   int unassigned, int expiring, Duration time) throws Exception {
70         AttestationPoolStatus pool = sBinder.getPoolStatus(time.toMillis(), TRUSTED_ENVIRONMENT);
71         assertEquals(total, pool.total);
72         assertEquals(attested, pool.attested);
73         assertEquals(unassigned, pool.unassigned);
74         assertEquals(expiring, pool.expiring);
75     }
76 
generateKeyStoreKey(String alias)77     private static Certificate[] generateKeyStoreKey(String alias) throws Exception {
78         KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
79         keyStore.load(null);
80         KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM_EC,
81                 "AndroidKeyStore");
82         KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(alias, PURPOSE_SIGN)
83                 .setAttestationChallenge("challenge".getBytes())
84                 .build();
85         keyPairGenerator.initialize(spec);
86         keyPairGenerator.generateKeyPair();
87         Certificate[] certs = keyStore.getCertificateChain(spec.getKeystoreAlias());
88         keyStore.deleteEntry(alias);
89         return certs;
90     }
91 
92     @BeforeClass
init()93     public static void init() throws Exception {
94         sContext = ApplicationProvider.getApplicationContext();
95         sBinder =
96               IRemoteProvisioning.Stub.asInterface(ServiceManager.getService(SERVICE));
97         assertNotNull(sBinder);
98         ImplInfo[] info = sBinder.getImplementationInfo();
99         for (int i = 0; i < info.length; i++) {
100             if (info[i].secLevel == TRUSTED_ENVIRONMENT) {
101                 sCurve = info[i].supportedCurve;
102                 break;
103             }
104         }
105     }
106 
107     @Before
setUp()108     public void setUp() throws Exception {
109         SettingsManager.clearPreferences(sContext);
110         sBinder.deleteAllKeys();
111         mDuration = Duration.ofMillis(System.currentTimeMillis());
112     }
113 
114     @After
tearDown()115     public void tearDown() throws Exception {
116         SettingsManager.clearPreferences(sContext);
117         sBinder.deleteAllKeys();
118     }
119 
120     @Test
testFullRoundTrip()121     public void testFullRoundTrip() throws Exception {
122         int numTestKeys = 1;
123         assertPoolStatus(0, 0, 0, 0, mDuration);
124         sBinder.generateKeyPair(IS_TEST_MODE, TRUSTED_ENVIRONMENT);
125         assertPoolStatus(numTestKeys, 0, 0, 0, mDuration);
126         GeekResponse geek = ServerInterface.fetchGeek(sContext);
127         assertNotNull(geek);
128         int numProvisioned =
129                 Provisioner.provisionCerts(numTestKeys, TRUSTED_ENVIRONMENT,
130                                            geek.getGeekChain(sCurve), geek.getChallenge(), sBinder,
131                                            sContext);
132         assertEquals(numTestKeys, numProvisioned);
133         assertPoolStatus(numTestKeys, numTestKeys, numTestKeys, 0, mDuration);
134         // Certificate duration sent back from the server may change, however ~6 months should be
135         // pretty safe.
136         assertPoolStatus(numTestKeys, numTestKeys, numTestKeys,
137                          numTestKeys, mDuration.plusDays(180));
138     }
139 
140     @Test
testFallback()141     public void testFallback() throws Exception {
142         // Feed a fake URL into the device config to ensure that remote provisioning fails.
143         SettingsManager.setDeviceConfig(sContext, 2 /* extraKeys */, mDuration /* expiringBy */,
144                                         "Not even a URL" /* url */);
145         int numTestKeys = 1;
146         assertPoolStatus(0, 0, 0, 0, mDuration);
147         Certificate[] fallbackKeyCerts1 = generateKeyStoreKey("test1");
148 
149         SettingsManager.clearPreferences(sContext);
150         sBinder.generateKeyPair(IS_TEST_MODE, TRUSTED_ENVIRONMENT);
151         GeekResponse geek = ServerInterface.fetchGeek(sContext);
152         int numProvisioned =
153                 Provisioner.provisionCerts(numTestKeys, TRUSTED_ENVIRONMENT,
154                                            geek.getGeekChain(sCurve), geek.getChallenge(), sBinder,
155                                            sContext);
156         assertEquals(numTestKeys, numProvisioned);
157         assertPoolStatus(numTestKeys, numTestKeys, numTestKeys, 0, mDuration);
158         Certificate[] provisionedKeyCerts = generateKeyStoreKey("test2");
159         sBinder.deleteAllKeys();
160         sBinder.generateKeyPair(IS_TEST_MODE, TRUSTED_ENVIRONMENT);
161 
162         SettingsManager.setDeviceConfig(sContext, 2 /* extraKeys */, mDuration /* expiringBy */,
163                                         "Not even a URL" /* url */);
164         // Even if there is an unsigned key hanging around, fallback should still occur.
165         Certificate[] fallbackKeyCerts2 = generateKeyStoreKey("test3");
166         // Due to there being no attested keys in the pool, the provisioning service should not
167         // have even attempted to provision more certificates.
168         assertEquals(0, SettingsManager.getFailureCounter(sContext));
169         assertTrue(fallbackKeyCerts1.length == fallbackKeyCerts2.length);
170         for (int i = 1; i < fallbackKeyCerts1.length; i++) {
171             assertArrayEquals("Cert: " + i, fallbackKeyCerts1[i].getEncoded(),
172                               fallbackKeyCerts2[i].getEncoded());
173         }
174         assertTrue(provisionedKeyCerts.length > 0);
175         // The root certificates should not match.
176         assertFalse("Provisioned and fallback attestation key root certificates match.",
177                     Arrays.equals(fallbackKeyCerts1[fallbackKeyCerts1.length - 1].getEncoded(),
178                               provisionedKeyCerts[provisionedKeyCerts.length - 1].getEncoded()));
179     }
180 }
181