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