blob: fa7a21e152660d9b90bfb72003b86a302b1e4366 [file] [log] [blame]
/*
* Copyright (C) 2018 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.cts.certinstaller;
import static com.android.cts.devicepolicy.TestCertificates.TEST_CA;
import static com.android.cts.devicepolicy.TestCertificates.TEST_CERT;
import static com.android.cts.devicepolicy.TestCertificates.TEST_KEY;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.testng.Assert.assertThrows;
import static java.util.Collections.singleton;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Process;
import android.security.AttestedKeyPair;
import android.security.KeyChain;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.telephony.TelephonyManager;
import android.test.InstrumentationTestCase;
import android.util.Base64;
import android.util.Base64InputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.List;
import java.util.Map;
/*
* Tests the delegated certificate installer functionality.
*
* This class is configured as DelegatedCertInstaller by the DelegatedCertInstallerHelper and is
* invoked directly from the host class,
* DeviceAndProfileOwnerTest#testDelegatedCertInstallerDirectly.
*
* TODO: this class is missing more functionality of the DelegatedCertInstaller tests.
* When this class is done then the DelegatedCertInstallerTest can be deleted.
*/
public class DirectDelegatedCertInstallerTest extends InstrumentationTestCase {
private static final String TEST_ALIAS = "DirectDelegatedCertInstallerTest-keypair";
private static final String NON_EXISTENT_ALIAS = "DirectDelegatedCertInstallerTest-nonexistent";
private DevicePolicyManager mDpm;
private PrivateKey mTestPrivateKey;
private Certificate mTestCertificate;
private boolean mHasTelephony = false;
private TelephonyManager mTelephonyManager;
@Override
public void setUp() throws Exception {
super.setUp();
mTestPrivateKey = rsaKeyFromString(TEST_KEY);
mTestCertificate = certificateFromString(TEST_CERT);
mDpm = getContext().getSystemService(DevicePolicyManager.class);
PackageManager pm = getContext().getPackageManager();
if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
mHasTelephony = true;
mTelephonyManager = (TelephonyManager) getContext().getSystemService(
Context.TELEPHONY_SERVICE);
}
}
@Override
public void tearDown() throws Exception {
mDpm.uninstallCaCert(null, TEST_CA.getBytes());
mDpm.removeKeyPair(null, TEST_ALIAS);
super.tearDown();
}
public void testCaCertsOperations() throws GeneralSecurityException, IOException {
final byte[] cert = TEST_CA.getBytes();
final Certificate caCert = CertificateFactory.getInstance("X.509")
.generateCertificate(new ByteArrayInputStream(cert));
// Exercise installCaCert()
KeyStore keyStore = KeyStore.getInstance("AndroidCAStore");
keyStore.load(null, null);
assertWithMessage("CA certificate must not be installed in KeyStore at the"
+ " beginning of the test").that(keyStore.getCertificateAlias(caCert)).isNull();
assertWithMessage("CA certificate must not be installed in the DPM at the"
+ " beginning of the test").that(mDpm.hasCaCertInstalled(null, cert)).isFalse();
assertWithMessage("Expecting CA certificate installation to succeed").that(
mDpm.installCaCert(null, cert)).isTrue();
assertWithMessage("Expecting CA cert to be installed").that(
mDpm.hasCaCertInstalled(null, cert)).isTrue();
// Exercise getInstalledCaCerts()
assertWithMessage("Expecting CA cert to be in the list of installed CA certs").that(
containsCertificate(mDpm.getInstalledCaCerts(null), cert)).isTrue();
// Verify that the CA cert was marked as installed by the Device Owner / Profile Owner.
assertWithMessage("CA cert should have a KeyStore alias").that(
keyStore.getCertificateAlias(caCert)).isNotNull();
mDpm.uninstallCaCert(null, cert);
assertWithMessage("Expecting CA cert to no longer be installed").that(
mDpm.hasCaCertInstalled(null, cert)).isFalse();
}
public void testInstallKeyPair() throws Exception {
final String alias = "delegated-cert-installer-test-key";
assertThat(mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate},
alias, true)).isTrue();
// Test that the installed private key can be obtained.
PrivateKey obtainedKey = KeyChain.getPrivateKey(getContext(), alias);
assertThat(obtainedKey).isNotNull();
assertThat(obtainedKey.getAlgorithm()).isEqualTo("RSA");
// Test cleaning up the key.
assertThat(mDpm.removeKeyPair(null, alias)).isTrue();
assertThat(KeyChain.getPrivateKey(getContext(), alias)).isNull();
}
// Test that a key generation request succeeds when device identifiers are not requested.
public void testGenerateKeyPairWithoutDeviceIdAttestation() {
final String alias = "com.android.test.generated-rsa-1";
try {
KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
.setKeySize(2048)
.setDigests(KeyProperties.DIGEST_SHA256)
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS,
KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.setIsStrongBoxBacked(false)
.setAttestationChallenge(new byte[]{'a', 'b', 'c'})
.build();
AttestedKeyPair generated = mDpm.generateKeyPair(
null, "RSA", keySpec, 0);
assertThat(generated).isNotNull();
} finally {
assertThat(mDpm.removeKeyPair(null, alias)).isTrue();
}
}
public void testAccessToDeviceIdentifiers() {
final String adminPackageName = "com.android.cts.deviceandprofileowner";
if (mDpm.isDeviceOwnerApp(adminPackageName)) {
validateCanAccessDeviceIdentifiers();
} else {
validateNoAccessToIdentifier();
}
}
private void validateNoAccessToIdentifier() {
assertThrows(SecurityException.class, () -> Build.getSerial());
if (!mHasTelephony) {
return;
}
assertWithMessage("Telephony service must be available.")
.that(mTelephonyManager).isNotNull();
assertThrows(SecurityException.class, () -> mTelephonyManager.getImei());
}
public void validateCanAccessDeviceIdentifiers() {
assertThat(Build.getSerial()).doesNotMatch(Build.UNKNOWN);
if (!mHasTelephony) {
return;
}
assertWithMessage("Telephony service must be available.")
.that(mTelephonyManager).isNotNull();
try {
mTelephonyManager.getImei();
} catch (SecurityException e) {
fail("Should have permission to access IMEI: " + e);
}
}
public void testHasKeyPair_NonExistent() {
assertThat(mDpm.hasKeyPair(NON_EXISTENT_ALIAS)).isFalse();
}
public void testHasKeyPair_Installed() {
mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS,
/* requestAccess= */ true);
assertThat(mDpm.hasKeyPair(TEST_ALIAS)).isTrue();
}
public void testHasKeyPair_Removed() {
mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS,
/* requestAccess= */ true);
mDpm.removeKeyPair(null, TEST_ALIAS);
assertThat(mDpm.hasKeyPair(TEST_ALIAS)).isFalse();
}
public void testGetKeyPairGrants_Empty() {
// Not granting upon install.
mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS,
/* requestAccess= */ false);
assertThat(mDpm.getKeyPairGrants(TEST_ALIAS)).isEmpty();
}
public void testGetKeyPairGrants_NonEmpty() {
// Granting upon install.
mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS,
/* requestAccess= */ true);
assertThat(mDpm.getKeyPairGrants(TEST_ALIAS))
.isEqualTo(Map.of(Process.myUid(), singleton(getContext().getPackageName())));
}
public void testIsWifiGrant_default() {
mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate},
TEST_ALIAS, /* requestAccess= */ false);
assertThat(mDpm.isKeyPairGrantedToWifiAuth(TEST_ALIAS)).isFalse();
}
public void testIsWifiGrant_allowed() {
mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate},
TEST_ALIAS, /* requestAccess= */ false);
assertTrue(mDpm.grantKeyPairToWifiAuth(TEST_ALIAS));
assertThat(mDpm.isKeyPairGrantedToWifiAuth(TEST_ALIAS)).isTrue();
}
public void testIsWifiGrant_denied() {
mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate},
TEST_ALIAS, /* requestAccess= */ false);
assertTrue(mDpm.grantKeyPairToWifiAuth(TEST_ALIAS));
assertTrue(mDpm.revokeKeyPairFromWifiAuth(TEST_ALIAS));
assertThat(mDpm.isKeyPairGrantedToWifiAuth(TEST_ALIAS)).isFalse();
}
private PrivateKey rsaKeyFromString(String key) throws Exception {
final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(
Base64.decode(key, Base64.DEFAULT));
return KeyFactory.getInstance("RSA").generatePrivate(keySpec);
}
private Certificate certificateFromString(String cert) throws Exception {
return CertificateFactory.getInstance("X.509").generateCertificate(
new Base64InputStream(new ByteArrayInputStream(cert.getBytes()), Base64.DEFAULT));
}
private static boolean containsCertificate(List<byte[]> certificates, byte[] toMatch)
throws CertificateException {
Certificate certificateToMatch = readCertificate(toMatch);
for (byte[] certBuffer : certificates) {
Certificate cert = readCertificate(certBuffer);
if (certificateToMatch.equals(cert)) {
return true;
}
}
return false;
}
private static Certificate readCertificate(byte[] certBuffer) throws CertificateException {
final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
return certFactory.generateCertificate(new ByteArrayInputStream(certBuffer));
}
private Context getContext() {
return getInstrumentation().getContext();
}
}