• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import static android.security.keystore.KeyProperties.SECURITY_LEVEL_TRUSTED_ENVIRONMENT;
23 
24 import static org.junit.Assert.assertArrayEquals;
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertFalse;
27 import static org.junit.Assert.assertNotNull;
28 import static org.junit.Assert.assertTrue;
29 import static org.junit.Assert.fail;
30 
31 import android.Manifest;
32 import android.app.ActivityThread;
33 import android.content.Context;
34 import android.net.ConnectivityManager;
35 import android.net.NetworkInfo;
36 import android.os.ServiceManager;
37 import android.os.SystemProperties;
38 import android.security.GenerateRkpKey;
39 import android.security.KeyStoreException;
40 import android.security.NetworkSecurityPolicy;
41 import android.security.keystore.KeyGenParameterSpec;
42 import android.security.remoteprovisioning.AttestationPoolStatus;
43 import android.security.remoteprovisioning.IRemoteProvisioning;
44 import android.security.remoteprovisioning.ImplInfo;
45 import android.system.keystore2.ResponseCode;
46 import android.util.Base64;
47 import android.util.Log;
48 
49 import androidx.test.core.app.ApplicationProvider;
50 import androidx.test.runner.AndroidJUnit4;
51 import androidx.work.ListenableWorker;
52 import androidx.work.testing.TestWorkerBuilder;
53 
54 import com.android.bedstead.nene.TestApis;
55 import com.android.bedstead.nene.permissions.PermissionContext;
56 import com.android.remoteprovisioner.GeekResponse;
57 import com.android.remoteprovisioner.PeriodicProvisioner;
58 import com.android.remoteprovisioner.Provisioner;
59 import com.android.remoteprovisioner.ProvisionerMetrics;
60 import com.android.remoteprovisioner.RemoteProvisioningException;
61 import com.android.remoteprovisioner.ServerInterface;
62 import com.android.remoteprovisioner.SettingsManager;
63 
64 import org.junit.After;
65 import org.junit.Assert;
66 import org.junit.Assume;
67 import org.junit.Before;
68 import org.junit.BeforeClass;
69 import org.junit.Test;
70 import org.junit.runner.RunWith;
71 
72 import java.io.ByteArrayInputStream;
73 import java.io.IOException;
74 import java.security.KeyPairGenerator;
75 import java.security.KeyStore;
76 import java.security.ProviderException;
77 import java.security.cert.Certificate;
78 import java.time.Duration;
79 import java.util.Arrays;
80 import java.util.concurrent.Executors;
81 
82 import fi.iki.elonen.NanoHTTPD;
83 
84 @RunWith(AndroidJUnit4.class)
85 public class ServerToSystemTest {
86 
87     private static final String Tag = "ServerToSystemTest";
88     private static final boolean IS_TEST_MODE = false;
89     private static final String SERVICE = "android.security.remoteprovisioning";
90     private static final String RKP_ONLY_PROP = "remote_provisioning.tee.rkp_only";
91 
92     private static final byte[] GEEK_RESPONSE = Base64.decode(
93             "g4KCAYOEQ6EBJqBYTaUBAgMmIAEhWCD3FIrbl/TMU+/SZBHE43UfZh+kcQxsz/oJRoB0h1TyrSJY"
94                     + "IF5/W/bs5PYZzP8TN/0PociT2xgGdsRd5tdqd4bDLa+PWEAvl45C+74HLZVHhUeTQLAf1JtHpMRE"
95                     + "qfKhB4cQx5/LEfS/n+g74Oc0TBX8e8N+MwX00TQ87QIEYHoV4HnTiv8khEOhASagWE2lAQIDJiAB"
96                     + "IVggUYCsz4+WjOwPUOGpG7eQhjSL48OsZQJNtPYxDghGMjkiWCBU65Sd/ra05HM6JU4vH52dvfpm"
97                     + "wRGL6ZaMQ+Qw9tp2q1hAmDj7NDpl23OYsSeiFXTyvgbnjSJO3fC/wgF0xLcpayQctdjSZvpE7/Uw"
98                     + "LAR07ejGYNrOn1ZXJ3Qh096Tj+O4zYRDoQEmoFhxpgECAlggg5/4/RAcEp+SQcdbjeRO9BkTmscb"
99                     + "bacOlfJkU12nHcEDOBggASFYIBakUhJjs4ZWUNjf8qCofbzZbqdoYOqMXPGT5ZcZDazeIlggib7M"
100                     + "bD9esDk0r5e6ONEWHaHMHWTTjEhO+HKBGzs+Me5YQPrazy2rpTAMc8Xlq0mSWWBE+sTyM+UEsmwZ"
101                     + "ZOkc42Q7NIYAZS313a+qAcmvg8lO+FqU6GWTUeMYHjmAp2lLM82CAoOEQ6EBJ6BYKqQBAQMnIAYh"
102                     + "WCCZue7dXuRS9oXGTGLcPmGrV0h9dTcprXaAMtKzy2NY2VhAHiIIS6S3pMjXTgMO/rivFEynO2+l"
103                     + "zdzaecYrZP6ZOa9254D6ZgCFDQeYKqyRXKclFEkGNHXKiid62eNaSesCA4RDoQEnoFgqpAEBAycg"
104                     + "BiFYIOovhQ6eagxc973Z+igyv9pV6SCiUQPJA5MYzqAVKezRWECCa8ddpjZXt8dxEq0cwmqzLCMq"
105                     + "3RQwy4IUtonF0x4xu7hQIUpJTbqRDG8zTYO8WCsuhNvFWQ+YYeLB6ony0K4EhEOhASegWE6lAQEC"
106                     + "WCBvktEEbXHYp46I2NFWgV+W0XiD5jAbh+2/INFKO/5qLgM4GCAEIVggtl0cS5qDOp21FVk3oSb7"
107                     + "D9/nnKwB1aTsyDopAIhYJTlYQICyn9Aynp1K/rAl8sLSImhGxiCwqugWrGShRYObzElUJX+rFgVT"
108                     + "8L01k/PGu1lOXvneIQcUo7ako4uPgpaWugNYHQAAAYBINcxrASC0rWP9VTSO7LdABvcdkv7W2vh+"
109                     + "onV0aW1lX3RvX3JlZnJlc2hfaG91cnMYSHgabnVtX2V4dHJhX2F0dGVzdGF0aW9uX2tleXMU",
110             Base64.DEFAULT);
111 
112     // Same as GEEK_RESPONSE, but the "num_extra_attestation_keys" value is 0, disabling RKP.
113     private static final byte[] GEEK_RESPONSE_RKP_DISABLED = Base64.decode(
114             "g4KCAYOEQ6EBJqBYTaUBAgMmIAEhWCD3FIrbl/TMU+/SZBHE43UfZh+kcQxsz/oJRoB0h1TyrSJY"
115                     + "IF5/W/bs5PYZzP8TN/0PociT2xgGdsRd5tdqd4bDLa+PWEAvl45C+74HLZVHhUeTQLAf1JtHpMRE"
116                     + "qfKhB4cQx5/LEfS/n+g74Oc0TBX8e8N+MwX00TQ87QIEYHoV4HnTiv8khEOhASagWE2lAQIDJiAB"
117                     + "IVggUYCsz4+WjOwPUOGpG7eQhjSL48OsZQJNtPYxDghGMjkiWCBU65Sd/ra05HM6JU4vH52dvfpm"
118                     + "wRGL6ZaMQ+Qw9tp2q1hAmDj7NDpl23OYsSeiFXTyvgbnjSJO3fC/wgF0xLcpayQctdjSZvpE7/Uw"
119                     + "LAR07ejGYNrOn1ZXJ3Qh096Tj+O4zYRDoQEmoFhxpgECAlggg5/4/RAcEp+SQcdbjeRO9BkTmscb"
120                     + "bacOlfJkU12nHcEDOBggASFYIBakUhJjs4ZWUNjf8qCofbzZbqdoYOqMXPGT5ZcZDazeIlggib7M"
121                     + "bD9esDk0r5e6ONEWHaHMHWTTjEhO+HKBGzs+Me5YQPrazy2rpTAMc8Xlq0mSWWBE+sTyM+UEsmwZ"
122                     + "ZOkc42Q7NIYAZS313a+qAcmvg8lO+FqU6GWTUeMYHjmAp2lLM82CAoOEQ6EBJ6BYKqQBAQMnIAYh"
123                     + "WCCZue7dXuRS9oXGTGLcPmGrV0h9dTcprXaAMtKzy2NY2VhAHiIIS6S3pMjXTgMO/rivFEynO2+l"
124                     + "zdzaecYrZP6ZOa9254D6ZgCFDQeYKqyRXKclFEkGNHXKiid62eNaSesCA4RDoQEnoFgqpAEBAycg"
125                     + "BiFYIOovhQ6eagxc973Z+igyv9pV6SCiUQPJA5MYzqAVKezRWECCa8ddpjZXt8dxEq0cwmqzLCMq"
126                     + "3RQwy4IUtonF0x4xu7hQIUpJTbqRDG8zTYO8WCsuhNvFWQ+YYeLB6ony0K4EhEOhASegWE6lAQEC"
127                     + "WCBvktEEbXHYp46I2NFWgV+W0XiD5jAbh+2/INFKO/5qLgM4GCAEIVggtl0cS5qDOp21FVk3oSb7"
128                     + "D9/nnKwB1aTsyDopAIhYJTlYQICyn9Aynp1K/rAl8sLSImhGxiCwqugWrGShRYObzElUJX+rFgVT"
129                     + "8L01k/PGu1lOXvneIQcUo7ako4uPgpaWugNYHQAAAYBINcxrASC0rWP9VTSO7LdABvcdkv7W2vh+"
130                     + "onV0aW1lX3RvX3JlZnJlc2hfaG91cnMYSHgabnVtX2V4dHJhX2F0dGVzdGF0aW9uX2tleXMA",
131             Base64.DEFAULT);
132 
133     private static Context sContext;
134     private static IRemoteProvisioning sBinder;
135     private static int sCurve = 0;
136 
137     private Duration mDuration;
138 
139     // Helper class that sets rkp_only to true if it's not already set, then restores the state on
140     // close. Intended to be used in a try expression: try (RkpOnlyContext c = new RkpOnlyContext())
141     private static class ForceRkpOnlyContext implements AutoCloseable {
142         private final boolean mOriginalPropertyValue;
143 
ForceRkpOnlyContext()144         ForceRkpOnlyContext() {
145             mOriginalPropertyValue = SystemProperties.getBoolean(RKP_ONLY_PROP, false);
146             if (!mOriginalPropertyValue) {
147                 SystemProperties.set(RKP_ONLY_PROP, "true");
148             }
149         }
150 
151         @Override
close()152         public void close() {
153             if (!mOriginalPropertyValue) {
154                 SystemProperties.set(RKP_ONLY_PROP, "false");
155             }
156         }
157     }
158 
assertPoolStatus(int total, int attested, int unassigned, int expiring, Duration time)159     private void assertPoolStatus(int total, int attested,
160                                   int unassigned, int expiring, Duration time) throws Exception {
161         AttestationPoolStatus pool = sBinder.getPoolStatus(time.toMillis(), TRUSTED_ENVIRONMENT);
162         assertEquals(total, pool.total);
163         assertEquals(attested, pool.attested);
164         assertEquals(unassigned, pool.unassigned);
165         assertEquals(expiring, pool.expiring);
166     }
167 
generateKeyStoreKey(String alias)168     private static Certificate[] generateKeyStoreKey(String alias) throws Exception {
169         KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
170         keyStore.load(null);
171         if (keyStore.containsAlias(alias)) {
172             keyStore.deleteEntry(alias);
173         }
174         KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM_EC,
175                 "AndroidKeyStore");
176         KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(alias, PURPOSE_SIGN)
177                 .setAttestationChallenge("challenge".getBytes())
178                 .build();
179         keyPairGenerator.initialize(spec);
180         keyPairGenerator.generateKeyPair();
181         Certificate[] certs = keyStore.getCertificateChain(spec.getKeystoreAlias());
182         keyStore.deleteEntry(alias);
183         return certs;
184     }
185 
186     @BeforeClass
init()187     public static void init() throws Exception {
188         sContext = ApplicationProvider.getApplicationContext();
189         sBinder =
190               IRemoteProvisioning.Stub.asInterface(ServiceManager.getService(SERVICE));
191         assertNotNull(sBinder);
192         ImplInfo[] info = sBinder.getImplementationInfo();
193         for (int i = 0; i < info.length; i++) {
194             if (info[i].secLevel == TRUSTED_ENVIRONMENT) {
195                 sCurve = info[i].supportedCurve;
196                 break;
197             }
198         }
199     }
200 
201     @Before
setUp()202     public void setUp() throws Exception {
203         SettingsManager.clearPreferences(sContext);
204         sBinder.deleteAllKeys();
205         mDuration = Duration.ofMillis(System.currentTimeMillis());
206     }
207 
208     @After
tearDown()209     public void tearDown() throws Exception {
210         SettingsManager.clearPreferences(sContext);
211         sBinder.deleteAllKeys();
212     }
213 
214     @Test
testFullRoundTrip()215     public void testFullRoundTrip() throws Exception {
216         ProvisionerMetrics metrics = ProvisionerMetrics.createScheduledAttemptMetrics(sContext);
217         int numTestKeys = 1;
218         assertPoolStatus(0, 0, 0, 0, mDuration);
219         sBinder.generateKeyPair(IS_TEST_MODE, TRUSTED_ENVIRONMENT);
220         assertPoolStatus(numTestKeys, 0, 0, 0, mDuration);
221         GeekResponse geek = ServerInterface.fetchGeek(sContext, metrics);
222         assertEquals(0, SettingsManager.getErrDataBudgetConsumed(sContext));
223         assertNotNull(geek);
224         int numProvisioned =
225                 Provisioner.provisionCerts(numTestKeys, TRUSTED_ENVIRONMENT,
226                                            geek.getGeekChain(sCurve), geek.getChallenge(), sBinder,
227                                            sContext, metrics);
228         assertEquals(0, SettingsManager.getErrDataBudgetConsumed(sContext));
229         assertEquals(numTestKeys, numProvisioned);
230         assertPoolStatus(numTestKeys, numTestKeys, numTestKeys, 0, mDuration);
231         // Certificate duration sent back from the server may change, however ~6 months should be
232         // pretty safe.
233         assertPoolStatus(numTestKeys, numTestKeys, numTestKeys,
234                          numTestKeys, mDuration.plusDays(180));
235     }
236 
237     @Test
testPeriodicProvisionerRoundTrip()238     public void testPeriodicProvisionerRoundTrip() throws Exception {
239         PeriodicProvisioner provisioner = TestWorkerBuilder.from(
240                 sContext,
241                 PeriodicProvisioner.class,
242                 Executors.newSingleThreadExecutor()).build();
243         assertEquals(provisioner.doWork(), ListenableWorker.Result.success());
244         AttestationPoolStatus pool = sBinder.getPoolStatus(mDuration.toMillis(),
245                 TRUSTED_ENVIRONMENT);
246         assertTrue("Pool must not be empty", pool.total > 0);
247         assertEquals("All keys must be attested", pool.total, pool.attested);
248         assertEquals("Nobody should have consumed keys yet", pool.total, pool.unassigned);
249         assertEquals("All keys should be freshly generated", 0, pool.expiring);
250     }
251 
252     @Test
testPeriodicProvisionerNoop()253     public void testPeriodicProvisionerNoop() throws Exception {
254         // Similar to the PeriodicProvisioner round trip, except first we actually populate the
255         // key pool to ensure that the PeriodicProvisioner just noops.
256         PeriodicProvisioner provisioner = TestWorkerBuilder.from(
257                 sContext,
258                 PeriodicProvisioner.class,
259                 Executors.newSingleThreadExecutor()).build();
260         assertEquals(provisioner.doWork(), ListenableWorker.Result.success());
261         final AttestationPoolStatus pool = sBinder.getPoolStatus(mDuration.toMillis(),
262                 TRUSTED_ENVIRONMENT);
263         assertTrue("Pool must not be empty", pool.total > 0);
264         assertEquals("All keys must be attested", pool.total, pool.attested);
265         assertEquals("Nobody should have consumed keys yet", pool.total, pool.unassigned);
266         assertEquals("All keys should be freshly generated", 0, pool.expiring);
267 
268         // The metrics host test will perform additional validation by ensuring correct metrics
269         // are recorded.
270         assertEquals(provisioner.doWork(), ListenableWorker.Result.success());
271         assertPoolStatus(pool.total, pool.attested, pool.unassigned, pool.expiring, mDuration);
272     }
273 
274     @Test
testPeriodicProvisionerDataBudgetEmpty()275     public void testPeriodicProvisionerDataBudgetEmpty() throws Exception {
276         // Check the data budget in order to initialize a rolling window.
277         assertTrue(SettingsManager.hasErrDataBudget(sContext, null /* curTime */));
278         SettingsManager.consumeErrDataBudget(sContext, SettingsManager.FAILURE_DATA_USAGE_MAX);
279 
280         PeriodicProvisioner provisioner = TestWorkerBuilder.from(
281                 sContext,
282                 PeriodicProvisioner.class,
283                 Executors.newSingleThreadExecutor()).build();
284         assertEquals(provisioner.doWork(), ListenableWorker.Result.failure());
285         AttestationPoolStatus pool = sBinder.getPoolStatus(mDuration.toMillis(),
286                 TRUSTED_ENVIRONMENT);
287         assertTrue("Keys should have been generated", pool.total > 0);
288         assertEquals("No keys should be attested", 0, pool.attested);
289         assertEquals("No keys should have been assigned", 0, pool.unassigned);
290         assertEquals("No keys can possibly be expiring yet", 0, pool.expiring);
291     }
292 
293     @Test
testPeriodicProvisionerProvisioningDisabled()294     public void testPeriodicProvisionerProvisioningDisabled() throws Exception {
295         // We need to run an HTTP server that returns a config indicating no keys are needed
296         final NanoHTTPD server = new NanoHTTPD("localhost", 0) {
297             @Override
298             public Response serve(IHTTPSession session) {
299                 consumeRequestBody((HTTPSession) session);
300                 if (session.getUri().contains(":fetchEekChain")) {
301                     return newFixedLengthResponse(Response.Status.OK, "application/cbor",
302                             new ByteArrayInputStream(GEEK_RESPONSE_RKP_DISABLED),
303                             GEEK_RESPONSE_RKP_DISABLED.length);
304                 }
305                 Assert.fail("Unexpected HTTP request: " + session.getUri());
306                 return null;
307             }
308 
309             void consumeRequestBody(HTTPSession session) {
310                 try {
311                     session.getInputStream().readNBytes((int) session.getBodySize());
312                 } catch (IOException e) {
313                     Assert.fail("Error reading request bytes: " + e.toString());
314                 }
315             }
316         };
317         server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
318 
319         final boolean cleartextPolicy =
320                 NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted();
321         NetworkSecurityPolicy.getInstance().setCleartextTrafficPermitted(true);
322         SettingsManager.setDeviceConfig(sContext, 1 /* extraKeys */, mDuration /* expiringBy */,
323                 "http://localhost:" + server.getListeningPort() + "/");
324 
325         PeriodicProvisioner provisioner = TestWorkerBuilder.from(
326                 sContext,
327                 PeriodicProvisioner.class,
328                 Executors.newSingleThreadExecutor()).build();
329         assertEquals(provisioner.doWork(), ListenableWorker.Result.success());
330         assertPoolStatus(0, 0, 0, 0, mDuration);
331     }
332 
333     @Test
testFallback()334     public void testFallback() throws Exception {
335         ProvisionerMetrics metrics = ProvisionerMetrics.createScheduledAttemptMetrics(sContext);
336         Assume.assumeFalse(
337                 "Skipping test as this system does not support fallback from RKP keys",
338                 SystemProperties.getBoolean(RKP_ONLY_PROP, false));
339 
340         // Feed a fake URL into the device config to ensure that remote provisioning fails.
341         SettingsManager.setDeviceConfig(sContext, 1 /* extraKeys */, mDuration /* expiringBy */,
342                                         "Not even a URL" /* url */);
343         int numTestKeys = 1;
344         assertPoolStatus(0, 0, 0, 0, mDuration);
345         // Note that due to the GenerateRkpKeyService, this call to generate an attested key will
346         // still cause the service to generate keys up the number specified as `extraKeys` in the
347         // `setDeviceConfig`. This will provide us 1 key for the followup call to provisionCerts.
348         Certificate[] fallbackKeyCerts1 = generateKeyStoreKey("test1");
349 
350         SettingsManager.clearPreferences(sContext);
351         GeekResponse geek = ServerInterface.fetchGeek(sContext, metrics);
352         int numProvisioned =
353                 Provisioner.provisionCerts(numTestKeys, TRUSTED_ENVIRONMENT,
354                                            geek.getGeekChain(sCurve), geek.getChallenge(), sBinder,
355                                            sContext, metrics);
356         assertEquals(numTestKeys, numProvisioned);
357         assertPoolStatus(numTestKeys, numTestKeys, numTestKeys, 0, mDuration);
358         Certificate[] provisionedKeyCerts = generateKeyStoreKey("test2");
359         sBinder.deleteAllKeys();
360         sBinder.generateKeyPair(IS_TEST_MODE, TRUSTED_ENVIRONMENT);
361 
362         SettingsManager.setDeviceConfig(sContext, 2 /* extraKeys */, mDuration /* expiringBy */,
363                                         "Not even a URL" /* url */);
364         // Even if there is an unsigned key hanging around, fallback should still occur.
365         Certificate[] fallbackKeyCerts2 = generateKeyStoreKey("test3");
366         assertTrue(fallbackKeyCerts1.length == fallbackKeyCerts2.length);
367         for (int i = 1; i < fallbackKeyCerts1.length; i++) {
368             assertArrayEquals("Cert: " + i, fallbackKeyCerts1[i].getEncoded(),
369                               fallbackKeyCerts2[i].getEncoded());
370         }
371         assertTrue(provisionedKeyCerts.length > 0);
372         // Match against the batch provisioned key, which should be the second entry in the array.
373         assertFalse("Provisioned and fallback attestation key intermediate certificates match.",
374                     Arrays.equals(fallbackKeyCerts1[1].getEncoded(),
375                               provisionedKeyCerts[1].getEncoded()));
376     }
377 
378     @Test
testDataBudgetEmptyFetchGeek()379     public void testDataBudgetEmptyFetchGeek() throws Exception {
380         // Check the data budget in order to initialize a rolling window.
381         assertTrue(SettingsManager.hasErrDataBudget(sContext, null /* curTime */));
382         SettingsManager.consumeErrDataBudget(sContext, SettingsManager.FAILURE_DATA_USAGE_MAX);
383         ProvisionerMetrics metrics = ProvisionerMetrics.createScheduledAttemptMetrics(sContext);
384         try {
385             ServerInterface.fetchGeek(sContext, metrics);
386             fail("Network transaction should not have proceeded.");
387         } catch (RemoteProvisioningException e) {
388             return;
389         }
390     }
391 
392     @Test
testDataBudgetEmptySignCerts()393     public void testDataBudgetEmptySignCerts() throws Exception {
394         // Check the data budget in order to initialize a rolling window.
395         assertTrue(SettingsManager.hasErrDataBudget(sContext, null /* curTime */));
396         SettingsManager.consumeErrDataBudget(sContext, SettingsManager.FAILURE_DATA_USAGE_MAX);
397         ProvisionerMetrics metrics = ProvisionerMetrics.createScheduledAttemptMetrics(sContext);
398         try {
399             ServerInterface.requestSignedCertificates(sContext, null, null, metrics);
400             fail("Network transaction should not have proceeded.");
401         } catch (RemoteProvisioningException e) {
402             return;
403         }
404     }
405 
406     @Test
testDataBudgetEmptyCallGenerateRkpKeyService()407     public void testDataBudgetEmptyCallGenerateRkpKeyService() throws Exception {
408         // Check the data budget in order to initialize a rolling window.
409         assertTrue(SettingsManager.hasErrDataBudget(sContext, null /* curTime */));
410         SettingsManager.consumeErrDataBudget(sContext, SettingsManager.FAILURE_DATA_USAGE_MAX);
411         GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread.currentApplication());
412         keyGen.notifyKeyGenerated(SECURITY_LEVEL_TRUSTED_ENVIRONMENT);
413         // Nothing to check here. This test is primarily used by the Metrics host test to
414         // validate that correct metrics are logged.
415     }
416 
417     @Test
testGenerateKeyRkpOnly()418     public void testGenerateKeyRkpOnly() throws Exception {
419         try (ForceRkpOnlyContext c = new ForceRkpOnlyContext()) {
420             Certificate[] certs = generateKeyStoreKey("this-better-work");
421             assertTrue(certs.length > 0);
422         }
423     }
424 
425     @Test
testRetryableRkpError()426     public void testRetryableRkpError() throws Exception {
427         try (ForceRkpOnlyContext c = new ForceRkpOnlyContext()) {
428             SettingsManager.setDeviceConfig(sContext, 1 /* extraKeys */, mDuration /* expiringBy */,
429                     "Not even a URL" /* url */);
430             generateKeyStoreKey("should-never-succeed");
431             Assert.fail("Expected a keystore exception");
432         } catch (ProviderException e) {
433             Assert.assertTrue(e.getCause() instanceof KeyStoreException);
434             KeyStoreException keyStoreException = (KeyStoreException) e.getCause();
435             Assert.assertEquals(ResponseCode.OUT_OF_KEYS, keyStoreException.getErrorCode());
436             Assert.assertTrue(keyStoreException.isTransientFailure());
437             Assert.assertEquals(KeyStoreException.RETRY_WITH_EXPONENTIAL_BACKOFF,
438                     keyStoreException.getRetryPolicy());
439         }
440     }
441 
setAirplaneMode(boolean enable)442     private void setAirplaneMode(boolean enable) throws Exception {
443         ConnectivityManager cm = sContext.getSystemService(ConnectivityManager.class);
444         try (PermissionContext c = TestApis.permissions().withPermission(
445                 Manifest.permission.NETWORK_SETTINGS)) {
446             cm.setAirplaneMode(enable);
447 
448             // Now wait a "reasonable" time for the network to go down
449             for (int i = 0; i < 100; ++i) {
450                 NetworkInfo networkInfo = cm.getActiveNetworkInfo();
451                 Log.e(Tag, "Checking active network... " + networkInfo);
452                 if (enable) {
453                     if (networkInfo == null || !networkInfo.isConnected()) {
454                         Log.e(Tag, "Successfully disconnected from to the network.");
455                         return;
456                     }
457                 } else if (networkInfo != null && networkInfo.isConnected()) {
458                     Log.e(Tag, "Successfully reconnected to the network.");
459                     return;
460                 }
461                 Thread.sleep(300);
462             }
463         }
464         Assert.fail("Failed to successfully " + (enable ? "enable" : "disable") + " airplane mode");
465     }
466 
467     @Test
testRetryWithoutNetworkTee()468     public void testRetryWithoutNetworkTee() throws Exception {
469         setAirplaneMode(true);
470         try (ForceRkpOnlyContext c = new ForceRkpOnlyContext()) {
471             assertPoolStatus(0, 0, 0, 0, mDuration);
472             generateKeyStoreKey("should-never-succeed");
473             Assert.fail("Expected a keystore exception");
474         } catch (ProviderException e) {
475             Assert.assertTrue(e.getCause() instanceof KeyStoreException);
476             KeyStoreException keyStoreException = (KeyStoreException) e.getCause();
477             Assert.assertEquals(ResponseCode.OUT_OF_KEYS, keyStoreException.getErrorCode());
478             Assert.assertTrue(keyStoreException.isTransientFailure());
479             Assert.assertEquals(KeyStoreException.RETRY_WHEN_CONNECTIVITY_AVAILABLE,
480                     keyStoreException.getRetryPolicy());
481         } finally {
482             setAirplaneMode(false);
483         }
484     }
485 
486     @Test
testRetryNeverWhenDeviceNotRegistered()487     public void testRetryNeverWhenDeviceNotRegistered() throws Exception {
488         final NanoHTTPD server = new NanoHTTPD("localhost", 0) {
489             @Override
490             public Response serve(IHTTPSession session) {
491                 // We must consume all bytes in the request, else they get interpreted as a
492                 // sepearate (bad) request by the HTTP server.
493                 consumeRequestBody((HTTPSession) session);
494                 if (session.getUri().contains(":fetchEekChain")) {
495                     return newFixedLengthResponse(Response.Status.OK, "application/cbor",
496                             new ByteArrayInputStream(GEEK_RESPONSE), GEEK_RESPONSE.length);
497                 } else if (session.getUri().contains(":signCertificates")) {
498                     Response.IStatus status = new Response.IStatus() {
499                         @Override
500                         public String getDescription() {
501                             return "444 Device Not Registered";
502                         }
503 
504                         @Override
505                         public int getRequestStatus() {
506                             return 444;
507                         }
508                     };
509                     return newFixedLengthResponse(status, NanoHTTPD.MIME_PLAINTEXT,
510                             "device not registered");
511                 }
512                 Assert.fail("Unexpected HTTP request: " + session.getUri());
513                 return null;
514             }
515 
516             void consumeRequestBody(HTTPSession session) {
517                 try {
518                     session.getInputStream().readNBytes((int) session.getBodySize());
519                 } catch (IOException e) {
520                     Assert.fail("Error reading request bytes: " + e.toString());
521                 }
522             }
523         };
524         server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
525 
526         final boolean cleartextPolicy =
527                 NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted();
528         NetworkSecurityPolicy.getInstance().setCleartextTrafficPermitted(true);
529         SettingsManager.setDeviceConfig(sContext, 1 /* extraKeys */, mDuration /* expiringBy */,
530                 "http://localhost:" + server.getListeningPort() + "/");
531 
532         try (ForceRkpOnlyContext c = new ForceRkpOnlyContext()) {
533             assertPoolStatus(0, 0, 0, 0, mDuration);
534             generateKeyStoreKey("should-never-succeed");
535             Assert.fail("Expected a keystore exception");
536         } catch (ProviderException e) {
537             Assert.assertTrue(e.getCause() instanceof KeyStoreException);
538             KeyStoreException keyStoreException = (KeyStoreException) e.getCause();
539             Assert.assertEquals(ResponseCode.OUT_OF_KEYS, keyStoreException.getErrorCode());
540             Assert.assertFalse(keyStoreException.isTransientFailure());
541             Assert.assertEquals(KeyStoreException.RETRY_NEVER, keyStoreException.getRetryPolicy());
542         } finally {
543             NetworkSecurityPolicy.getInstance().setCleartextTrafficPermitted(cleartextPolicy);
544             SettingsManager.clearPreferences(sContext);
545             server.stop();
546         }
547     }
548 }
549