blob: 94206afbac352d2c5cbdf6d919081eda00688bdc [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.remoteprovisioner.unittest;
import static android.hardware.security.keymint.SecurityLevel.TRUSTED_ENVIRONMENT;
import static android.security.keystore.KeyProperties.KEY_ALGORITHM_EC;
import static android.security.keystore.KeyProperties.PURPOSE_SIGN;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.os.ServiceManager;
import android.security.keystore.KeyGenParameterSpec;
import android.security.remoteprovisioning.AttestationPoolStatus;
import android.security.remoteprovisioning.ImplInfo;
import android.security.remoteprovisioning.IRemoteProvisioning;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnit4;
import com.android.remoteprovisioner.GeekResponse;
import com.android.remoteprovisioner.Provisioner;
import com.android.remoteprovisioner.ServerInterface;
import com.android.remoteprovisioner.SettingsManager;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.time.Duration;
import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
public class ServerToSystemTest {
private static final boolean IS_TEST_MODE = false;
private static final String SERVICE = "android.security.remoteprovisioning";
private static Context sContext;
private static IRemoteProvisioning sBinder;
private static int sCurve = 0;
private Duration mDuration;
private void assertPoolStatus(int total, int attested,
int unassigned, int expiring, Duration time) throws Exception {
AttestationPoolStatus pool = sBinder.getPoolStatus(time.toMillis(), TRUSTED_ENVIRONMENT);
assertEquals(total, pool.total);
assertEquals(attested, pool.attested);
assertEquals(unassigned, pool.unassigned);
assertEquals(expiring, pool.expiring);
}
private static Certificate[] generateKeyStoreKey(String alias) throws Exception {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM_EC,
"AndroidKeyStore");
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(alias, PURPOSE_SIGN)
.setAttestationChallenge("challenge".getBytes())
.build();
keyPairGenerator.initialize(spec);
keyPairGenerator.generateKeyPair();
Certificate[] certs = keyStore.getCertificateChain(spec.getKeystoreAlias());
keyStore.deleteEntry(alias);
return certs;
}
@BeforeClass
public static void init() throws Exception {
sContext = ApplicationProvider.getApplicationContext();
sBinder =
IRemoteProvisioning.Stub.asInterface(ServiceManager.getService(SERVICE));
assertNotNull(sBinder);
ImplInfo[] info = sBinder.getImplementationInfo();
for (int i = 0; i < info.length; i++) {
if (info[i].secLevel == TRUSTED_ENVIRONMENT) {
sCurve = info[i].supportedCurve;
break;
}
}
}
@Before
public void setUp() throws Exception {
SettingsManager.clearPreferences(sContext);
sBinder.deleteAllKeys();
mDuration = Duration.ofMillis(System.currentTimeMillis());
}
@After
public void tearDown() throws Exception {
SettingsManager.clearPreferences(sContext);
sBinder.deleteAllKeys();
}
@Test
public void testFullRoundTrip() throws Exception {
int numTestKeys = 1;
assertPoolStatus(0, 0, 0, 0, mDuration);
sBinder.generateKeyPair(IS_TEST_MODE, TRUSTED_ENVIRONMENT);
assertPoolStatus(numTestKeys, 0, 0, 0, mDuration);
GeekResponse geek = ServerInterface.fetchGeek(sContext);
assertNotNull(geek);
int numProvisioned =
Provisioner.provisionCerts(numTestKeys, TRUSTED_ENVIRONMENT,
geek.getGeekChain(sCurve), geek.getChallenge(), sBinder,
sContext);
assertEquals(numTestKeys, numProvisioned);
assertPoolStatus(numTestKeys, numTestKeys, numTestKeys, 0, mDuration);
// Certificate duration sent back from the server may change, however ~6 months should be
// pretty safe.
assertPoolStatus(numTestKeys, numTestKeys, numTestKeys,
numTestKeys, mDuration.plusDays(180));
}
@Test
public void testFallback() throws Exception {
// Feed a fake URL into the device config to ensure that remote provisioning fails.
SettingsManager.setDeviceConfig(sContext, 2 /* extraKeys */, mDuration /* expiringBy */,
"Not even a URL" /* url */);
int numTestKeys = 1;
assertPoolStatus(0, 0, 0, 0, mDuration);
Certificate[] fallbackKeyCerts1 = generateKeyStoreKey("test1");
SettingsManager.clearPreferences(sContext);
sBinder.generateKeyPair(IS_TEST_MODE, TRUSTED_ENVIRONMENT);
GeekResponse geek = ServerInterface.fetchGeek(sContext);
int numProvisioned =
Provisioner.provisionCerts(numTestKeys, TRUSTED_ENVIRONMENT,
geek.getGeekChain(sCurve), geek.getChallenge(), sBinder,
sContext);
assertEquals(numTestKeys, numProvisioned);
assertPoolStatus(numTestKeys, numTestKeys, numTestKeys, 0, mDuration);
Certificate[] provisionedKeyCerts = generateKeyStoreKey("test2");
sBinder.deleteAllKeys();
sBinder.generateKeyPair(IS_TEST_MODE, TRUSTED_ENVIRONMENT);
SettingsManager.setDeviceConfig(sContext, 2 /* extraKeys */, mDuration /* expiringBy */,
"Not even a URL" /* url */);
// Even if there is an unsigned key hanging around, fallback should still occur.
Certificate[] fallbackKeyCerts2 = generateKeyStoreKey("test3");
// Due to there being no attested keys in the pool, the provisioning service should not
// have even attempted to provision more certificates.
assertEquals(0, SettingsManager.getFailureCounter(sContext));
assertTrue(fallbackKeyCerts1.length == fallbackKeyCerts2.length);
for (int i = 1; i < fallbackKeyCerts1.length; i++) {
assertArrayEquals("Cert: " + i, fallbackKeyCerts1[i].getEncoded(),
fallbackKeyCerts2[i].getEncoded());
}
assertTrue(provisionedKeyCerts.length > 0);
// The root certificates should not match.
assertFalse("Provisioned and fallback attestation key root certificates match.",
Arrays.equals(fallbackKeyCerts1[fallbackKeyCerts1.length - 1].getEncoded(),
provisionedKeyCerts[provisionedKeyCerts.length - 1].getEncoded()));
}
}