blob: c11f058e9711aae4e48b67400fa32ab6cc56210b [file] [log] [blame]
/*
* Copyright (C) 2014 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.deviceandprofileowner;
import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO;
import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
import static android.keystore.cts.CertificateUtils.createCertificate;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.keystore.cts.Attestation;
import android.keystore.cts.AuthorizationList;
import android.net.Uri;
import android.os.Build;
import android.security.AttestedKeyPair;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.security.KeyChainException;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.support.test.uiautomator.UiDevice;
import android.telephony.TelephonyManager;
import com.android.compatibility.common.util.FakeKeys.FAKE_RSA_1;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.security.auth.x500.X500Principal;
public class KeyManagementTest extends BaseDeviceAdminTest {
private static final long KEYCHAIN_TIMEOUT_MINS = 6;
private static class SupportedKeyAlgorithm {
public final String keyAlgorithm;
public final String signatureAlgorithm;
public final String[] signaturePaddingSchemes;
public SupportedKeyAlgorithm(
String keyAlgorithm, String signatureAlgorithm,
String[] signaturePaddingSchemes) {
this.keyAlgorithm = keyAlgorithm;
this.signatureAlgorithm = signatureAlgorithm;
this.signaturePaddingSchemes = signaturePaddingSchemes;
}
}
private final SupportedKeyAlgorithm[] SUPPORTED_KEY_ALGORITHMS = new SupportedKeyAlgorithm[] {
new SupportedKeyAlgorithm(KeyProperties.KEY_ALGORITHM_RSA, "SHA256withRSA",
new String[] {KeyProperties.SIGNATURE_PADDING_RSA_PSS,
KeyProperties.SIGNATURE_PADDING_RSA_PKCS1}),
new SupportedKeyAlgorithm(KeyProperties.KEY_ALGORITHM_EC, "SHA256withECDSA", null)
};
private KeyManagementActivity mActivity;
@Override
public void setUp() throws Exception {
super.setUp();
final UiDevice device = UiDevice.getInstance(getInstrumentation());
mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(),
KeyManagementActivity.class, null);
device.waitForIdle();
}
@Override
public void tearDown() throws Exception {
mActivity.finish();
super.tearDown();
}
public void testCanInstallAndRemoveValidRsaKeypair() throws Exception {
final String alias = "com.android.test.valid-rsa-key-1";
final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey , "RSA");
final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
// Install keypair.
assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, cert, alias));
try {
// Request and retrieve using the alias.
assertGranted(alias, false);
assertEquals(alias, new KeyChainAliasFuture(alias).get());
assertGranted(alias, true);
// Verify key is at least something like the one we put in.
assertEquals(KeyChain.getPrivateKey(mActivity, alias).getAlgorithm(), "RSA");
} finally {
// Delete regardless of whether the test succeeded.
assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
}
// Verify alias is actually deleted.
assertGranted(alias, false);
}
public void testCanInstallWithAutomaticAccess() throws Exception {
final String grant = "com.android.test.autogrant-key-1";
final String withhold = "com.android.test.nongrant-key-1";
final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey , "RSA");
final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
// Install keypairs.
assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, new Certificate[] {cert},
grant, true));
assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, new Certificate[] {cert},
withhold, false));
try {
// Verify only the requested key was actually granted.
assertGranted(grant, true);
assertGranted(withhold, false);
// Verify the granted key is actually obtainable in PrivateKey form.
assertEquals(KeyChain.getPrivateKey(mActivity, grant).getAlgorithm(), "RSA");
} finally {
// Delete both keypairs.
assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), grant));
assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), withhold));
}
// Verify they're actually gone.
assertGranted(grant, false);
assertGranted(withhold, false);
}
private List<Certificate> loadCertificateChain(String assetName) throws Exception {
final Collection<Certificate> certs = loadCertificatesFromAsset(assetName);
final ArrayList<Certificate> certChain = new ArrayList(certs);
// Some sanity check on the cert chain
assertTrue(certs.size() > 1);
for (int i = 1; i < certChain.size(); i++) {
certChain.get(i - 1).verify(certChain.get(i).getPublicKey());
}
return certChain;
}
public void testCanInstallCertChain() throws Exception {
// Use assets/generate-client-cert-chain.sh to regenerate the client cert chain.
final PrivateKey privKey = loadPrivateKeyFromAsset("user-cert-chain.key");
final Certificate[] certChain = loadCertificateChain("user-cert-chain.crt")
.toArray(new Certificate[0]);
final String alias = "com.android.test.clientkeychain";
// Install keypairs.
assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, certChain, alias, true));
try {
// Verify only the requested key was actually granted.
assertGranted(alias, true);
// Verify the granted key is actually obtainable in PrivateKey form.
assertEquals(KeyChain.getPrivateKey(mActivity, alias).getAlgorithm(), "RSA");
// Verify the certificate chain is correct
X509Certificate[] returnedCerts = KeyChain.getCertificateChain(mActivity, alias);
assertTrue(Arrays.equals(certChain, returnedCerts));
} finally {
// Delete both keypairs.
assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
}
// Verify they're actually gone.
assertGranted(alias, false);
}
public void testGrantsDoNotPersistBetweenInstallations() throws Exception {
final String alias = "com.android.test.persistent-key-1";
final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey , "RSA");
final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
// Install keypair.
assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, new Certificate[] {cert},
alias, true));
try {
assertGranted(alias, true);
} finally {
// Delete and verify.
assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
}
assertGranted(alias, false);
// Install again.
assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, new Certificate[] {cert},
alias, false));
try {
assertGranted(alias, false);
} finally {
// Delete.
assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
}
}
public void testNullKeyParamsFailPredictably() throws Exception {
final String alias = "com.android.test.null-key-1";
final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey, "RSA");
final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
try {
mDevicePolicyManager.installKeyPair(getWho(), null, cert, alias);
fail("Exception should have been thrown for null PrivateKey");
} catch (NullPointerException expected) {
}
try {
mDevicePolicyManager.installKeyPair(getWho(), privKey, null, alias);
fail("Exception should have been thrown for null Certificate");
} catch (NullPointerException expected) {
}
}
public void testNullAdminComponentIsDenied() throws Exception {
final String alias = "com.android.test.null-admin-1";
final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey, "RSA");
final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
try {
mDevicePolicyManager.installKeyPair(null, privKey, cert, alias);
fail("Exception should have been thrown for null ComponentName");
} catch (SecurityException expected) {
}
}
public void testNotUserSelectableAliasCanBeChosenViaPolicy() throws Exception {
final String alias = "com.android.test.not-selectable-key-1";
final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey , "RSA");
final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
// Install keypair.
assertTrue(mDevicePolicyManager.installKeyPair(
getWho(), privKey, new Certificate[] {cert}, alias, 0));
try {
// Request and retrieve using the alias.
assertGranted(alias, false);
assertEquals(alias, new KeyChainAliasFuture(alias).get());
assertGranted(alias, true);
} finally {
// Delete regardless of whether the test succeeded.
assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
}
}
byte[] signDataWithKey(String algoIdentifier, PrivateKey privateKey) throws Exception {
byte[] data = new String("hello").getBytes();
Signature sign = Signature.getInstance(algoIdentifier);
sign.initSign(privateKey);
sign.update(data);
return sign.sign();
}
void verifySignature(String algoIdentifier, PublicKey publicKey, byte[] signature)
throws Exception {
byte[] data = new String("hello").getBytes();
Signature verify = Signature.getInstance(algoIdentifier);
verify.initVerify(publicKey);
verify.update(data);
assertTrue(verify.verify(signature));
}
void verifySignatureOverData(String algoIdentifier, KeyPair keyPair) throws Exception {
verifySignature(algoIdentifier, keyPair.getPublic(),
signDataWithKey(algoIdentifier, keyPair.getPrivate()));
}
private KeyGenParameterSpec buildRsaKeySpec(String alias, boolean useStrongBox) {
return 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(useStrongBox)
.build();
}
public void testCanGenerateRSAKeyPair() throws Exception {
final String alias = "com.android.test.generated-rsa-1";
try {
AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
getWho(), "RSA", buildRsaKeySpec(alias, false /* useStrongBox */),
0 /* idAttestationFlags */);
assertNotNull(generated);
verifySignatureOverData("SHA256withRSA", generated.getKeyPair());
} finally {
assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
}
}
public void testCanGenerateRSAKeyPairUsingStrongBox() throws Exception {
final String alias = "com.android.test.generated-rsa-sb-1";
AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
getWho(), "RSA", buildRsaKeySpec(alias, true /* useStrongBox */),
0 /* idAttestationFlags */);
if (generated == null) {
assertFalse(hasStrongBox());
return;
}
verifySignatureOverData("SHA256withRSA", generated.getKeyPair());
assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
}
private KeyGenParameterSpec buildEcKeySpec(String alias, boolean useStrongBox) {
return new KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
.setDigests(KeyProperties.DIGEST_SHA256)
.setIsStrongBoxBacked(useStrongBox)
.build();
}
public void testCanGenerateECKeyPair() throws Exception {
final String alias = "com.android.test.generated-ec-1";
try {
AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
getWho(), "EC", buildEcKeySpec(alias, false /* useStrongBox */),
0 /* idAttestationFlags */);
assertNotNull(generated);
verifySignatureOverData("SHA256withECDSA", generated.getKeyPair());
} finally {
assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
}
}
public void testCanGenerateECKeyPairUsingStrongBox() throws Exception {
final String alias = "com.android.test.generated-ec-sb-1";
AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
getWho(), "EC", buildEcKeySpec(alias, true /* useStrongBox */), 0);
if (generated == null) {
assertFalse(hasStrongBox());
return;
}
verifySignatureOverData("SHA256withECDSA", generated.getKeyPair());
assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
}
private void validateDeviceIdAttestationData(Certificate leaf,
String expectedSerial, String expectedImei, String expectedMeid)
throws CertificateParsingException {
Attestation attestationRecord = new Attestation((X509Certificate) leaf);
AuthorizationList teeAttestation = attestationRecord.getTeeEnforced();
assertNotNull(teeAttestation);
assertEquals(Build.BRAND, teeAttestation.getBrand());
assertEquals(Build.DEVICE, teeAttestation.getDevice());
assertEquals(Build.PRODUCT, teeAttestation.getProduct());
assertEquals(Build.MANUFACTURER, teeAttestation.getManufacturer());
assertEquals(Build.MODEL, teeAttestation.getModel());
assertEquals(expectedSerial, teeAttestation.getSerialNumber());
assertEquals(expectedImei, teeAttestation.getImei());
assertEquals(expectedMeid, teeAttestation.getMeid());
}
private void validateAttestationRecord(List<Certificate> attestation,
byte[] providedChallenge) throws CertificateParsingException {
assertNotNull(attestation);
assertTrue(attestation.size() >= 2);
X509Certificate leaf = (X509Certificate) attestation.get(0);
Attestation attestationRecord = new Attestation(leaf);
assertTrue(Arrays.equals(providedChallenge,
attestationRecord.getAttestationChallenge()));
}
private void validateSignatureChain(List<Certificate> chain, PublicKey leafKey)
throws GeneralSecurityException {
X509Certificate leaf = (X509Certificate) chain.get(0);
PublicKey keyFromCert = leaf.getPublicKey();
assertTrue(Arrays.equals(keyFromCert.getEncoded(), leafKey.getEncoded()));
// Check that the certificate chain is valid.
for (int i = 1; i < chain.size(); i++) {
X509Certificate intermediate = (X509Certificate) chain.get(i);
PublicKey intermediateKey = intermediate.getPublicKey();
leaf.verify(intermediateKey);
leaf = intermediate;
}
// leaf is now the root, verify the root is self-signed.
PublicKey rootKey = leaf.getPublicKey();
leaf.verify(rootKey);
}
private boolean isDeviceIdAttestationSupported() {
return mDevicePolicyManager.isDeviceIdAttestationSupported();
}
private boolean isDeviceIdAttestationRequested(int deviceIdAttestationFlags) {
return deviceIdAttestationFlags != 0;
}
/**
* Generates a key using DevicePolicyManager.generateKeyPair using the given key algorithm,
* then test signing and verifying using generated key.
* If {@code signaturePaddings} is not null, it is added to the key parameters specification.
* Returns the Attestation leaf certificate.
*/
private Certificate generateKeyAndCheckAttestation(
String keyAlgorithm, String signatureAlgorithm,
String[] signaturePaddings, boolean useStrongBox,
int deviceIdAttestationFlags) throws Exception {
final String alias =
String.format("com.android.test.attested-%s", keyAlgorithm.toLowerCase());
byte[] attestationChallenge = new byte[] {0x01, 0x02, 0x03};
try {
KeyGenParameterSpec.Builder specBuilder = new KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
.setDigests(KeyProperties.DIGEST_SHA256)
.setAttestationChallenge(attestationChallenge)
.setIsStrongBoxBacked(useStrongBox);
if (signaturePaddings != null) {
specBuilder.setSignaturePaddings(signaturePaddings);
}
KeyGenParameterSpec spec = specBuilder.build();
AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
getWho(), keyAlgorithm, spec, deviceIdAttestationFlags);
// Bail out early if StrongBox was requested and generation failed.
// Note the underlying assumption that StrongBox supports key attestation _and_
// Device ID attestation (i.e. no StrongBox implementations that do not implement
// Device ID attestation).
// If generation has failed and StrongBox was requested, it is then a failure
// regardless of the kind of attestation requested.
if (useStrongBox && generated == null) {
assertFalse("StrongBox-backed key attestation must not fail if the device " +
"declares support for StrongBox", hasStrongBox());
return null;
}
// If Device ID attestation was requested, check it succeeded if and only if device ID
// attestation is supported.
if (isDeviceIdAttestationRequested(deviceIdAttestationFlags)) {
if (generated == null) {
assertFalse(String.format("Failed getting Device ID attestation for key " +
"algorithm %s, with flags %s, despite device declaring support.",
keyAlgorithm, deviceIdAttestationFlags),
isDeviceIdAttestationSupported());
return null;
} else {
assertTrue(String.format("Device ID attestation for key " +
"algorithm %s, with flags %d should not have succeeded.",
keyAlgorithm, deviceIdAttestationFlags),
isDeviceIdAttestationSupported());
}
} else {
assertNotNull(
String.format("Key generation (of type %s) must succeed when Device ID " +
"attestation was not requested.", keyAlgorithm), generated);
}
final KeyPair keyPair = generated.getKeyPair();
verifySignatureOverData(signatureAlgorithm, keyPair);
List<Certificate> attestation = generated.getAttestationRecord();
validateAttestationRecord(attestation, attestationChallenge);
validateSignatureChain(attestation, keyPair.getPublic());
return attestation.get(0);
} catch (UnsupportedOperationException ex) {
assertTrue(String.format("Unexpected failure while generating key %s with ID flags %d: %s",
keyAlgorithm, deviceIdAttestationFlags, ex),
isDeviceIdAttestationRequested(deviceIdAttestationFlags) &&
!isDeviceIdAttestationSupported());
return null;
} finally {
assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
}
}
/**
* Test key generation, including requesting Key Attestation, for all supported key
* algorithms.
*/
public void testCanGenerateKeyPairWithKeyAttestation() throws Exception {
for (SupportedKeyAlgorithm supportedKey: SUPPORTED_KEY_ALGORITHMS) {
assertNotNull(generateKeyAndCheckAttestation(
supportedKey.keyAlgorithm, supportedKey.signatureAlgorithm,
supportedKey.signaturePaddingSchemes, false /* useStrongBox */, 0));
}
}
public void testCanGenerateKeyPairWithKeyAttestationUsingStrongBox() throws Exception {
for (SupportedKeyAlgorithm supportedKey: SUPPORTED_KEY_ALGORITHMS) {
generateKeyAndCheckAttestation(
supportedKey.keyAlgorithm, supportedKey.signatureAlgorithm,
supportedKey.signaturePaddingSchemes, true /* useStrongBox */,
0 /* idAttestationFlags */);
}
}
public void assertAllVariantsOfDeviceIdAttestation(boolean useStrongBox) throws Exception {
List<Integer> modesToTest = new ArrayList<Integer>();
String imei = null;
String meid = null;
// All devices must support at least basic device information attestation as well as serial
// number attestation. Although attestation of unique device ids are only callable by device
// owner.
modesToTest.add(ID_TYPE_BASE_INFO);
if (isDeviceOwner()) {
modesToTest.add(ID_TYPE_SERIAL);
// Get IMEI and MEID of the device.
TelephonyManager telephonyService = (TelephonyManager) mActivity.getSystemService(
Context.TELEPHONY_SERVICE);
assertNotNull("Need to be able to read device identifiers", telephonyService);
imei = telephonyService.getImei(0);
meid = telephonyService.getMeid(0);
// If the device has a valid IMEI it must support attestation for it.
if (imei != null) {
modesToTest.add(ID_TYPE_IMEI);
}
// Same for MEID
if (meid != null) {
modesToTest.add(ID_TYPE_MEID);
}
}
int numCombinations = 1 << modesToTest.size();
for (int i = 1; i < numCombinations; i++) {
// Set the bits in devIdOpt to be passed into generateKeyPair according to the
// current modes tested.
int devIdOpt = 0;
for (int j = 0; j < modesToTest.size(); j++) {
if ((i & (1 << j)) != 0) {
devIdOpt = devIdOpt | modesToTest.get(j);
}
}
try {
// Now run the test with all supported key algorithms
for (SupportedKeyAlgorithm supportedKey: SUPPORTED_KEY_ALGORITHMS) {
Certificate attestation = generateKeyAndCheckAttestation(
supportedKey.keyAlgorithm, supportedKey.signatureAlgorithm,
supportedKey.signaturePaddingSchemes, useStrongBox, devIdOpt);
// generateKeyAndCheckAttestation should return null if device ID attestation
// is not supported. Simply continue to test the next combination.
if (attestation == null && !isDeviceIdAttestationSupported()) {
continue;
}
// The attestation must only be null if StrongBox attestation was requested,
// but StrongBox is not available on the device.
if (attestation == null && useStrongBox) {
assertFalse(hasStrongBox());
continue;
}
assertNotNull(String.format(
"Attestation should be valid for key %s with attestation modes %s",
supportedKey.keyAlgorithm, devIdOpt), attestation);
// Set the expected values for serial, IMEI and MEID depending on whether
// attestation for them was requested.
String expectedSerial = null;
if ((devIdOpt & ID_TYPE_SERIAL) != 0) {
expectedSerial = Build.getSerial();
}
String expectedImei = null;
if ((devIdOpt & ID_TYPE_IMEI) != 0) {
expectedImei = imei;
}
String expectedMeid = null;
if ((devIdOpt & ID_TYPE_MEID) != 0) {
expectedMeid = meid;
}
validateDeviceIdAttestationData(attestation, expectedSerial, expectedImei,
expectedMeid);
}
} catch (UnsupportedOperationException expected) {
// Make sure the test only fails if the device is not meant to support Device
// ID attestation.
assertFalse(isDeviceIdAttestationSupported());
}
}
}
public void testAllVariationsOfDeviceIdAttestation() throws Exception {
assertAllVariantsOfDeviceIdAttestation(false /* useStrongBox */);
}
public void testAllVariationsOfDeviceIdAttestationUsingStrongBox() throws Exception {
assertAllVariantsOfDeviceIdAttestation(true /* useStrongBox */);
}
public void testProfileOwnerCannotAttestDeviceUniqueIds() throws Exception {
if (isDeviceOwner()) {
return;
}
int[] forbiddenModes = new int[] {ID_TYPE_SERIAL, ID_TYPE_IMEI, ID_TYPE_MEID};
for (int i = 0; i < forbiddenModes.length; i++) {
try {
for (SupportedKeyAlgorithm supportedKey: SUPPORTED_KEY_ALGORITHMS) {
generateKeyAndCheckAttestation(supportedKey.keyAlgorithm,
supportedKey.signatureAlgorithm,
supportedKey.signaturePaddingSchemes,
false /* useStrongBox */,
forbiddenModes[i]);
fail("Attestation of device UID (" + forbiddenModes[i] + ") should not be "
+ "possible from profile owner");
}
} catch (SecurityException e) {
assertTrue(e.getMessage().contains("does not own the device"));
}
}
}
public void testCanSetKeyPairCert() throws Exception {
final String alias = "com.android.test.set-ec-1";
try {
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
.setDigests(KeyProperties.DIGEST_SHA256)
.build();
AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
getWho(), "EC", spec, 0);
assertNotNull(generated);
// Create a self-signed cert to go with it.
X500Principal issuer = new X500Principal("CN=SelfSigned, O=Android, C=US");
X500Principal subject = new X500Principal("CN=Subject, O=Android, C=US");
X509Certificate cert = createCertificate(generated.getKeyPair(), subject, issuer);
// Set the certificate chain
List<Certificate> certs = new ArrayList<Certificate>();
certs.add(cert);
mDevicePolicyManager.setKeyPairCertificate(getWho(), alias, certs, true);
// Make sure that the alias can now be obtained.
assertEquals(alias, new KeyChainAliasFuture(alias).get());
// And can be retrieved from KeyChain
X509Certificate[] fetchedCerts = KeyChain.getCertificateChain(mActivity, alias);
assertEquals(fetchedCerts.length, certs.size());
assertTrue(Arrays.equals(fetchedCerts[0].getEncoded(), certs.get(0).getEncoded()));
} finally {
assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
}
}
public void testCanSetKeyPairCertChain() throws Exception {
final String alias = "com.android.test.set-ec-2";
try {
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
.setDigests(KeyProperties.DIGEST_SHA256)
.build();
AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
getWho(), "EC", spec, 0);
assertNotNull(generated);
List<Certificate> chain = loadCertificateChain("user-cert-chain.crt");
mDevicePolicyManager.setKeyPairCertificate(getWho(), alias, chain, true);
// Make sure that the alias can now be obtained.
assertEquals(alias, new KeyChainAliasFuture(alias).get());
// And can be retrieved from KeyChain
X509Certificate[] fetchedCerts = KeyChain.getCertificateChain(mActivity, alias);
assertEquals(fetchedCerts.length, chain.size());
for (int i = 0; i < chain.size(); i++) {
assertTrue(Arrays.equals(fetchedCerts[i].getEncoded(), chain.get(i).getEncoded()));
}
} finally {
assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
}
}
private void assertGranted(String alias, boolean expected) throws InterruptedException {
boolean granted = false;
try {
granted = (KeyChain.getPrivateKey(mActivity, alias) != null);
} catch (KeyChainException e) {
if (expected) {
e.printStackTrace();
}
}
assertEquals("Grant for alias: \"" + alias + "\"", expected, granted);
}
private static PrivateKey getPrivateKey(final byte[] key, String type)
throws NoSuchAlgorithmException, InvalidKeySpecException {
return KeyFactory.getInstance(type).generatePrivate(
new PKCS8EncodedKeySpec(key));
}
private static Certificate getCertificate(byte[] cert) throws CertificateException {
return CertificateFactory.getInstance("X.509").generateCertificate(
new ByteArrayInputStream(cert));
}
private Collection<Certificate> loadCertificatesFromAsset(String assetName) {
try {
final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
AssetManager am = mActivity.getAssets();
InputStream is = am.open(assetName);
return (Collection<Certificate>) certFactory.generateCertificates(is);
} catch (IOException | CertificateException e) {
e.printStackTrace();
}
return null;
}
private PrivateKey loadPrivateKeyFromAsset(String assetName) {
try {
AssetManager am = mActivity.getAssets();
InputStream is = am.open(assetName);
ByteArrayOutputStream output = new ByteArrayOutputStream();
int length;
byte[] buffer = new byte[4096];
while ((length = is.read(buffer, 0, buffer.length)) != -1) {
output.write(buffer, 0, length);
}
return getPrivateKey(output.toByteArray(), "RSA");
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
e.printStackTrace();
}
return null;
}
private class KeyChainAliasFuture implements KeyChainAliasCallback {
private final CountDownLatch mLatch = new CountDownLatch(1);
private String mChosenAlias = null;
@Override
public void alias(final String chosenAlias) {
mChosenAlias = chosenAlias;
mLatch.countDown();
}
public KeyChainAliasFuture(String alias)
throws UnsupportedEncodingException {
/* Pass the alias as a GET to an imaginary server instead of explicitly asking for it,
* to make sure the DPC actually has to do some work to grant the cert.
*/
final Uri uri =
Uri.parse("https://example.org/?alias=" + URLEncoder.encode(alias, "UTF-8"));
KeyChain.choosePrivateKeyAlias(mActivity, this,
null /* keyTypes */, null /* issuers */, uri, null /* alias */);
}
public String get() throws InterruptedException {
assertTrue("Chooser timeout", mLatch.await(KEYCHAIN_TIMEOUT_MINS, TimeUnit.MINUTES));
return mChosenAlias;
}
}
protected ComponentName getWho() {
return ADMIN_RECEIVER_COMPONENT;
}
boolean hasStrongBox() {
return mActivity.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE);
}
}