DevicePolicyManager: Add key generation functionality.
This is the crux of the Verified Access feature implementation:
Adding the ability to generate KeyChain keys directly by the
secure hardware, rather than installing software-generated keys
into KeyChain.
Add generateKeyPair to the DevicePolicyManager, which delegates key
generation (via the DevicePolicyManagerService) to the KeyChainService.
Design highlights:
* The key generation is delegated via the DevicePolicyManagerService to
check that only authorized callers request key generation in KeyChain.
* KeyChainService performs the actual key generation so it owns the key
in Keystore outright.
* DevicePolicyManagerService then grants the calling app access to the
Keystore key, so it can actually be used.
* Loading the public/private key pair, as well as attestation
certificate chain, is done in the client code (DevicePolicyManager)
to save parceling / unparceling those objects across process
boundaries twice (for no good reason).
NOTE: The key attestation functionality (that includes Device ID) is
missing/untested. Will be added in a follow-up CL as this one is quite
big already.
HIGHLIGHT FOR REVIEWERS:
* API: New API in DevicePolicyManager.
Bug: 63388672
Test: cts-tradefed run commandAndExit cts-dev -a armeabi-v7a -m CtsDevicePolicyManagerTestCases -t com.android.cts.devicepolicy.DeviceOwnerTest#testKeyManagement -l DEBUG; adb shell am instrument 'android.security.tests/android.support.test.runner.AndroidJUnitRunner' (After building the KeystoreTests target and installing the apk)
Change-Id: I73762c9123f32a94d454ba4f8b533883b55c44cc
diff --git a/api/current.txt b/api/current.txt
index d230aea..402aa8f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6320,6 +6320,7 @@
method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
method public void enableSystemApp(android.content.ComponentName, java.lang.String);
method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
+ method public android.security.AttestedKeyPair generateKeyPair(android.content.ComponentName, java.lang.String, android.security.keystore.KeyGenParameterSpec);
method public java.lang.String[] getAccountTypesWithManagementDisabled();
method public java.util.List<android.content.ComponentName> getActiveAdmins();
method public java.util.Set<java.lang.String> getAffiliationIds(android.content.ComponentName);
@@ -37109,6 +37110,11 @@
package android.security {
+ public final class AttestedKeyPair {
+ method public java.util.List<java.security.cert.Certificate> getAttestationRecord();
+ method public java.security.KeyPair getKeyPair();
+ }
+
public final class KeyChain {
ctor public KeyChain();
method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int, java.lang.String);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f0117f2..e94356d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -57,7 +57,12 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.ContactsContract.Directory;
+import android.security.AttestedKeyPair;
import android.security.Credentials;
+import android.security.KeyChain;
+import android.security.KeyChainException;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.ParcelableKeyGenParameterSpec;
import android.service.restrictions.RestrictionsReceiver;
import android.telephony.TelephonyManager;
import android.util.ArraySet;
@@ -75,6 +80,7 @@
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.security.KeyFactory;
+import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
@@ -3943,6 +3949,50 @@
}
/**
+ * Called by a device or profile owner, or delegated certificate installer, to generate a
+ * new private/public key pair. If the device supports key generation via secure hardware,
+ * this method is useful for creating a key in KeyChain that never left the secure hardware.
+ *
+ * Access to the key is controlled the same way as in {@link #installKeyPair}.
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if calling from a delegated certificate installer.
+ * @param algorithm The key generation algorithm, see {@link java.security.KeyPairGenerator}.
+ * @param keySpec Specification of the key to generate, see
+ * {@link java.security.KeyPairGenerator}.
+ * @return A non-null {@code AttestedKeyPair} if the key generation succeeded, null otherwise.
+ * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
+ * owner.
+ * @throws IllegalArgumentException if the alias in {@code keySpec} is empty, or if the
+ * algorithm specification in {@code keySpec} is not {@code RSAKeyGenParameterSpec}
+ * or {@code ECGenParameterSpec}.
+ */
+ public AttestedKeyPair generateKeyPair(@Nullable ComponentName admin,
+ @NonNull String algorithm, @NonNull KeyGenParameterSpec keySpec) {
+ throwIfParentInstance("generateKeyPair");
+ try {
+ final ParcelableKeyGenParameterSpec parcelableSpec =
+ new ParcelableKeyGenParameterSpec(keySpec);
+ final boolean success = mService.generateKeyPair(
+ admin, mContext.getPackageName(), algorithm, parcelableSpec);
+ if (!success) {
+ Log.e(TAG, "Error generating key via DevicePolicyManagerService.");
+ return null;
+ }
+
+ final KeyPair keyPair = KeyChain.getKeyPair(mContext, keySpec.getKeystoreAlias());
+ return new AttestedKeyPair(keyPair, null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (KeyChainException e) {
+ Log.w(TAG, "Failed to generate key", e);
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Interrupted while generating key", e);
+ Thread.currentThread().interrupt();
+ }
+ return null;
+ }
+
+ /**
* @return the alias of a given CA certificate in the certificate store, or {@code null} if it
* doesn't exist.
*/
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 802d42f..21d7a25 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -36,6 +36,7 @@
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.UserHandle;
+import android.security.keystore.ParcelableKeyGenParameterSpec;
import java.util.List;
@@ -165,6 +166,7 @@
in byte[] certBuffer, in byte[] certChainBuffer, String alias, boolean requestAccess,
boolean isUserSelectable);
boolean removeKeyPair(in ComponentName who, in String callerPackage, String alias);
+ boolean generateKeyPair(in ComponentName who, in String callerPackage, in String algorithm, in ParcelableKeyGenParameterSpec keySpec);
void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback);
void setDelegatedScopes(in ComponentName who, in String delegatePackage, in List<String> scopes);
diff --git a/keystore/java/android/security/AttestedKeyPair.java b/keystore/java/android/security/AttestedKeyPair.java
new file mode 100644
index 0000000..c6bff5c
--- /dev/null
+++ b/keystore/java/android/security/AttestedKeyPair.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 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 android.security;
+
+import java.security.KeyPair;
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The {@code AttestedKeyPair} class contains a {@code KeyPair} instance of
+ * keys generated by Keystore and owned by KeyChain, as well as an attestation
+ * record for the key.
+ *
+ * <p>Such keys can be obtained by calling
+ * {@link android.app.admin.DevicePolicyManager#generateKeyPair}.
+ */
+
+public final class AttestedKeyPair {
+ private final KeyPair mKeyPair;
+ private final Certificate[] mAttestationRecord;
+
+ /**
+ * @hide Only created by the platform, no need to expose as public API.
+ */
+ public AttestedKeyPair(KeyPair keyPair, Certificate[] attestationRecord) {
+ mKeyPair = keyPair;
+ mAttestationRecord = attestationRecord;
+ }
+
+ /**
+ * Returns the generated key pair associated with the attestation record
+ * in this instance.
+ */
+ public KeyPair getKeyPair() {
+ return mKeyPair;
+ }
+
+ /**
+ * Returns the attestation record for the key pair in this instance.
+ *
+ * The attestation record is a chain of certificates. The leaf certificate links to the public
+ * key of this key pair and other properties of the key or the device. If the key is in secure
+ * hardware, and if the secure hardware supports attestation, the leaf certificate will be
+ * signed by a chain of certificates rooted at a trustworthy CA key. Otherwise the chain will be
+ * rooted at an untrusted certificate.
+ *
+ * The attestation record could be for properties of the key, or include device identifiers.
+ *
+ * See {@link android.security.keystore.KeyGenParameterSpec.Builder#setAttestationChallenge}
+ * and <a href="https://developer.android.com/training/articles/security-key-attestation.html">
+ * Key Attestation</a> for the format of the attestation record inside the certificate.
+ */
+ public List<Certificate> getAttestationRecord() {
+ if (mAttestationRecord == null) {
+ return new ArrayList();
+ }
+ return Arrays.asList(mAttestationRecord);
+ }
+}
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index 635432d..b4331b2 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -16,6 +16,7 @@
package android.security;
import android.content.pm.StringParceledListSlice;
+import android.security.keystore.ParcelableKeyGenParameterSpec;
/**
* Caller is required to ensure that {@link KeyStore#unlock
@@ -31,6 +32,8 @@
boolean isUserSelectable(String alias);
void setUserSelectable(String alias, boolean isUserSelectable);
+ boolean generateKeyPair(in String algorithm, in ParcelableKeyGenParameterSpec spec);
+
// APIs used by CertInstaller and DevicePolicyManager
String installCaCertificate(in byte[] caCertificate);
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.aidl b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.aidl
new file mode 100644
index 0000000..3fb7b4f
--- /dev/null
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 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 android.security.keystore;
+
+parcelable ParcelableKeyGenParameterSpec;
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
new file mode 100644
index 0000000..b15e0a2
--- /dev/null
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2017 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 android.security.keystore;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import java.math.BigInteger;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.RSAKeyGenParameterSpec;
+import java.util.Date;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * A parcelable version of KeyGenParameterSpec
+ * @hide only used for communicating with the DPMS.
+ */
+public final class ParcelableKeyGenParameterSpec implements Parcelable {
+ private static final int ALGORITHM_PARAMETER_SPEC_NONE = 1;
+ private static final int ALGORITHM_PARAMETER_SPEC_RSA = 2;
+ private static final int ALGORITHM_PARAMETER_SPEC_EC = 3;
+
+ private final KeyGenParameterSpec mSpec;
+
+ public ParcelableKeyGenParameterSpec(
+ KeyGenParameterSpec spec) {
+ mSpec = spec;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ private static void writeOptionalDate(Parcel out, Date date) {
+ if (date != null) {
+ out.writeBoolean(true);
+ out.writeLong(date.getTime());
+ } else {
+ out.writeBoolean(false);
+ }
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mSpec.getKeystoreAlias());
+ out.writeInt(mSpec.getPurposes());
+ out.writeInt(mSpec.getUid());
+ out.writeInt(mSpec.getKeySize());
+
+ // Only needs to support RSAKeyGenParameterSpec and ECGenParameterSpec.
+ AlgorithmParameterSpec algoSpec = mSpec.getAlgorithmParameterSpec();
+ if (algoSpec == null) {
+ out.writeInt(ALGORITHM_PARAMETER_SPEC_NONE);
+ } else if (algoSpec instanceof RSAKeyGenParameterSpec) {
+ RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) algoSpec;
+ out.writeInt(ALGORITHM_PARAMETER_SPEC_RSA);
+ out.writeInt(rsaSpec.getKeysize());
+ out.writeByteArray(rsaSpec.getPublicExponent().toByteArray());
+ } else if (algoSpec instanceof ECGenParameterSpec) {
+ ECGenParameterSpec ecSpec = (ECGenParameterSpec) algoSpec;
+ out.writeInt(ALGORITHM_PARAMETER_SPEC_EC);
+ out.writeString(ecSpec.getName());
+ } else {
+ throw new IllegalArgumentException(
+ String.format("Unknown algorithm parameter spec: %s", algoSpec.getClass()));
+ }
+ out.writeByteArray(mSpec.getCertificateSubject().getEncoded());
+ out.writeByteArray(mSpec.getCertificateSerialNumber().toByteArray());
+ writeOptionalDate(out, mSpec.getCertificateNotBefore());
+ writeOptionalDate(out, mSpec.getCertificateNotAfter());
+ writeOptionalDate(out, mSpec.getKeyValidityStart());
+ writeOptionalDate(out, mSpec.getKeyValidityForOriginationEnd());
+ writeOptionalDate(out, mSpec.getKeyValidityForConsumptionEnd());
+ out.writeStringArray(mSpec.getDigests());
+ out.writeStringArray(mSpec.getEncryptionPaddings());
+ out.writeStringArray(mSpec.getSignaturePaddings());
+ out.writeStringArray(mSpec.getBlockModes());
+ out.writeBoolean(mSpec.isRandomizedEncryptionRequired());
+ out.writeBoolean(mSpec.isUserAuthenticationRequired());
+ out.writeInt(mSpec.getUserAuthenticationValidityDurationSeconds());
+ out.writeByteArray(mSpec.getAttestationChallenge());
+ out.writeBoolean(mSpec.isUniqueIdIncluded());
+ out.writeBoolean(mSpec.isUserAuthenticationValidWhileOnBody());
+ out.writeBoolean(mSpec.isInvalidatedByBiometricEnrollment());
+ }
+
+ private static Date readDateOrNull(Parcel in) {
+ boolean hasDate = in.readBoolean();
+ if (hasDate) {
+ return new Date(in.readLong());
+ } else {
+ return null;
+ }
+ }
+
+ private ParcelableKeyGenParameterSpec(Parcel in) {
+ String keystoreAlias = in.readString();
+ int purposes = in.readInt();
+ KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keystoreAlias, purposes);
+ builder.setUid(in.readInt());
+ builder.setKeySize(in.readInt());
+
+ int keySpecType = in.readInt();
+ AlgorithmParameterSpec algorithmSpec = null;
+ if (keySpecType == ALGORITHM_PARAMETER_SPEC_NONE) {
+ algorithmSpec = null;
+ } else if (keySpecType == ALGORITHM_PARAMETER_SPEC_RSA) {
+ int rsaKeySize = in.readInt();
+ BigInteger publicExponent = new BigInteger(in.createByteArray());
+ algorithmSpec = new RSAKeyGenParameterSpec(rsaKeySize, publicExponent);
+ } else if (keySpecType == ALGORITHM_PARAMETER_SPEC_EC) {
+ String stdName = in.readString();
+ algorithmSpec = new ECGenParameterSpec(stdName);
+ } else {
+ throw new IllegalArgumentException(
+ String.format("Unknown algorithm parameter spec: %d", algorithmSpec));
+ }
+ builder.setAlgorithmParameterSpec(algorithmSpec);
+ builder.setCertificateSubject(new X500Principal(in.createByteArray()));
+ builder.setCertificateSerialNumber(new BigInteger(in.createByteArray()));
+ builder.setCertificateNotBefore(readDateOrNull(in));
+ builder.setCertificateNotAfter(readDateOrNull(in));
+ builder.setKeyValidityStart(readDateOrNull(in));
+ builder.setKeyValidityForOriginationEnd(readDateOrNull(in));
+ builder.setKeyValidityForConsumptionEnd(readDateOrNull(in));
+ builder.setDigests(in.createStringArray());
+ builder.setEncryptionPaddings(in.createStringArray());
+ builder.setSignaturePaddings(in.createStringArray());
+ builder.setBlockModes(in.createStringArray());
+ builder.setRandomizedEncryptionRequired(in.readBoolean());
+ builder.setUserAuthenticationRequired(in.readBoolean());
+ builder.setUserAuthenticationValidityDurationSeconds(in.readInt());
+ builder.setAttestationChallenge(in.createByteArray());
+ builder.setUniqueIdIncluded(in.readBoolean());
+ builder.setUserAuthenticationValidWhileOnBody(in.readBoolean());
+ builder.setInvalidatedByBiometricEnrollment(in.readBoolean());
+ mSpec = builder.build();
+ }
+
+ public static final Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() {
+ @Override
+ public ParcelableKeyGenParameterSpec createFromParcel(Parcel in) {
+ return new ParcelableKeyGenParameterSpec(in);
+ }
+
+ @Override
+ public ParcelableKeyGenParameterSpec[] newArray(int size) {
+ return new ParcelableKeyGenParameterSpec[size];
+ }
+ };
+
+ public KeyGenParameterSpec getSpec() {
+ return mSpec;
+ }
+}
diff --git a/keystore/tests/Android.mk b/keystore/tests/Android.mk
new file mode 100644
index 0000000..51adde4
--- /dev/null
+++ b/keystore/tests/Android.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# LOCAL_MODULE := keystore
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ legacy-android-test
+
+LOCAL_PACKAGE_NAME := KeystoreTests
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
+
diff --git a/keystore/tests/AndroidManifest.xml b/keystore/tests/AndroidManifest.xml
new file mode 100644
index 0000000..9bf2d0c
--- /dev/null
+++ b/keystore/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.security.tests">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.security.tests"
+ android:label="Tests for Keystore">
+ </instrumentation>
+</manifest>
+
diff --git a/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
new file mode 100644
index 0000000..73b489f
--- /dev/null
+++ b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2017 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 android.security;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import android.os.Parcel;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.ParcelableKeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.support.test.runner.AndroidJUnit4;
+import java.math.BigInteger;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.RSAKeyGenParameterSpec;
+import java.util.Date;
+import javax.security.auth.x500.X500Principal;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link ParcelableKeyGenParameterSpec}. */
+@RunWith(AndroidJUnit4.class)
+public final class ParcelableKeyGenParameterSpecTest {
+ static final String ALIAS = "keystore-alias";
+ static final String ANOTHER_ALIAS = "another-keystore-alias";
+ static final int KEY_PURPOSES = KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY;
+ static final int UID = 1230;
+ static final int KEYSIZE = 2048;
+ static final X500Principal SUBJECT = new X500Principal("CN=subject");
+ static final BigInteger SERIAL = new BigInteger("1234567890");
+ static final Date NOT_BEFORE = new Date(1511799590);
+ static final Date NOT_AFTER = new Date(1511899590);
+ static final Date KEY_VALIDITY_START = new Date(1511799591);
+ static final Date KEY_VALIDITY_FOR_ORIG_END = new Date(1511799593);
+ static final Date KEY_VALIDITY_FOR_CONSUMPTION_END = new Date(1511799594);
+ static final String DIGEST = KeyProperties.DIGEST_SHA256;
+ static final String ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1;
+ static final String SIGNATURE_PADDING = KeyProperties.SIGNATURE_PADDING_RSA_PSS;
+ static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC;
+ static final int USER_AUTHENTICATION_DURATION = 300;
+ static final byte[] ATTESTATION_CHALLENGE = new byte[] {'c', 'h'};
+
+ KeyGenParameterSpec configureDefaultSpec() {
+ return new KeyGenParameterSpec.Builder(ALIAS, KEY_PURPOSES)
+ .setUid(UID)
+ .setKeySize(KEYSIZE)
+ .setCertificateSubject(SUBJECT)
+ .setCertificateSerialNumber(SERIAL)
+ .setCertificateNotBefore(NOT_BEFORE)
+ .setCertificateNotAfter(NOT_AFTER)
+ .setKeyValidityStart(KEY_VALIDITY_START)
+ .setKeyValidityForOriginationEnd(KEY_VALIDITY_FOR_ORIG_END)
+ .setKeyValidityForConsumptionEnd(KEY_VALIDITY_FOR_CONSUMPTION_END)
+ .setDigests(DIGEST)
+ .setEncryptionPaddings(ENCRYPTION_PADDING)
+ .setSignaturePaddings(SIGNATURE_PADDING)
+ .setBlockModes(BLOCK_MODE)
+ .setRandomizedEncryptionRequired(true)
+ .setUserAuthenticationRequired(true)
+ .setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_DURATION)
+ .setAttestationChallenge(ATTESTATION_CHALLENGE)
+ .setUniqueIdIncluded(true)
+ .setUserAuthenticationValidWhileOnBody(true)
+ .setInvalidatedByBiometricEnrollment(true)
+ .build();
+ }
+
+ void validateSpecValues(KeyGenParameterSpec spec, int uid, String alias) {
+ assertThat(spec.getKeystoreAlias(), is(alias));
+ assertThat(spec.getPurposes(), is(KEY_PURPOSES));
+ assertThat(spec.getUid(), is(uid));
+ assertThat(spec.getKeySize(), is(KEYSIZE));
+ assertThat(spec.getCertificateSubject(), is(SUBJECT));
+ assertThat(spec.getCertificateSerialNumber(), is(SERIAL));
+ assertThat(spec.getCertificateNotBefore(), is(NOT_BEFORE));
+ assertThat(spec.getCertificateNotAfter(), is(NOT_AFTER));
+ assertThat(spec.getKeyValidityStart(), is(KEY_VALIDITY_START));
+ assertThat(spec.getKeyValidityForOriginationEnd(), is(KEY_VALIDITY_FOR_ORIG_END));
+ assertThat(spec.getKeyValidityForConsumptionEnd(), is(KEY_VALIDITY_FOR_CONSUMPTION_END));
+ assertThat(spec.getDigests(), is(new String[] {DIGEST}));
+ assertThat(spec.getEncryptionPaddings(), is(new String[] {ENCRYPTION_PADDING}));
+ assertThat(spec.getSignaturePaddings(), is(new String[] {SIGNATURE_PADDING}));
+ assertThat(spec.getBlockModes(), is(new String[] {BLOCK_MODE}));
+ assertThat(spec.isRandomizedEncryptionRequired(), is(true));
+ assertThat(spec.isUserAuthenticationRequired(), is(true));
+ assertThat(
+ spec.getUserAuthenticationValidityDurationSeconds(),
+ is(USER_AUTHENTICATION_DURATION));
+ assertThat(spec.getAttestationChallenge(), is(ATTESTATION_CHALLENGE));
+ assertThat(spec.isUniqueIdIncluded(), is(true));
+ assertThat(spec.isUserAuthenticationValidWhileOnBody(), is(true));
+ assertThat(spec.isInvalidatedByBiometricEnrollment(), is(true));
+ }
+
+ private Parcel parcelForReading(ParcelableKeyGenParameterSpec spec) {
+ Parcel parcel = Parcel.obtain();
+ spec.writeToParcel(parcel, spec.describeContents());
+
+ parcel.setDataPosition(0);
+ return parcel;
+ }
+
+ @Test
+ public void testParcelingWithAllValues() {
+ ParcelableKeyGenParameterSpec spec =
+ new ParcelableKeyGenParameterSpec(configureDefaultSpec());
+ Parcel parcel = parcelForReading(spec);
+ ParcelableKeyGenParameterSpec fromParcel =
+ ParcelableKeyGenParameterSpec.CREATOR.createFromParcel(parcel);
+ validateSpecValues(fromParcel.getSpec(), UID, ALIAS);
+ assertThat(parcel.dataAvail(), is(0));
+ }
+
+ @Test
+ public void testParcelingWithNullValues() {
+ ParcelableKeyGenParameterSpec spec = new ParcelableKeyGenParameterSpec(
+ new KeyGenParameterSpec.Builder(ALIAS, KEY_PURPOSES).build());
+
+ Parcel parcel = parcelForReading(spec);
+ KeyGenParameterSpec fromParcel = ParcelableKeyGenParameterSpec.CREATOR
+ .createFromParcel(parcel)
+ .getSpec();
+ assertThat(fromParcel.getKeystoreAlias(), is(ALIAS));
+ assertThat(fromParcel.getPurposes(), is(KEY_PURPOSES));
+ assertThat(fromParcel.getCertificateNotBefore(), is(new Date(0L)));
+ assertThat(fromParcel.getCertificateNotAfter(), is(new Date(2461449600000L)));
+ assertThat(parcel.dataAvail(), is(0));
+ }
+
+ @Test
+ public void testParcelingRSAAlgoParameter() {
+ RSAKeyGenParameterSpec rsaSpec =
+ new RSAKeyGenParameterSpec(2048, new BigInteger("5231123"));
+ ParcelableKeyGenParameterSpec spec = new ParcelableKeyGenParameterSpec(
+ new KeyGenParameterSpec.Builder(ALIAS, KEY_PURPOSES)
+ .setAlgorithmParameterSpec(rsaSpec)
+ .build());
+
+ Parcel parcel = parcelForReading(spec);
+ KeyGenParameterSpec fromParcel =
+ ParcelableKeyGenParameterSpec.CREATOR.createFromParcel(parcel).getSpec();
+ RSAKeyGenParameterSpec parcelSpec =
+ (RSAKeyGenParameterSpec) fromParcel.getAlgorithmParameterSpec();
+ // Compare individual fields as RSAKeyGenParameterSpec, on android, does not
+ // implement equals()
+ assertEquals(parcelSpec.getKeysize(), rsaSpec.getKeysize());
+ assertEquals(parcelSpec.getPublicExponent(), rsaSpec.getPublicExponent());
+ }
+
+ @Test
+ public void testParcelingECAlgoParameter() {
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec("P-256");
+ ParcelableKeyGenParameterSpec spec = new ParcelableKeyGenParameterSpec(
+ new KeyGenParameterSpec.Builder(ALIAS, KEY_PURPOSES)
+ .setAlgorithmParameterSpec(ecSpec)
+ .build());
+ Parcel parcel = parcelForReading(spec);
+ KeyGenParameterSpec fromParcel =
+ ParcelableKeyGenParameterSpec.CREATOR.createFromParcel(parcel).getSpec();
+ // Compare individual fields as ECGenParameterSpec, on android, does not
+ // implement equals()
+ ECGenParameterSpec parcelSpec = (ECGenParameterSpec) fromParcel.getAlgorithmParameterSpec();
+ assertEquals(parcelSpec.getName(), ecSpec.getName());
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 663083c..4a8c0b46 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -150,6 +150,9 @@
import android.security.IKeyChainService;
import android.security.KeyChain;
import android.security.KeyChain.KeyChainConnection;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.ParcelableKeyGenParameterSpec;
+import android.security.KeyStore;
import android.service.persistentdata.PersistentDataBlockManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -5011,6 +5014,54 @@
}
@Override
+ public boolean generateKeyPair(ComponentName who, String callerPackage, String algorithm,
+ ParcelableKeyGenParameterSpec parcelableKeySpec) {
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_CERT_INSTALL);
+ final KeyGenParameterSpec keySpec = parcelableKeySpec.getSpec();
+ if (TextUtils.isEmpty(keySpec.getKeystoreAlias())) {
+ throw new IllegalArgumentException("Empty alias provided.");
+ }
+ // As the caller will be granted access to the key, ensure no UID was specified, as
+ // it will not have the desired effect.
+ if (keySpec.getUid() != KeyStore.UID_SELF) {
+ Log.e(LOG_TAG, "Only the caller can be granted access to the generated keypair.");
+ return false;
+ }
+ final int callingUid = mInjector.binderGetCallingUid();
+
+ final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
+ final long id = mInjector.binderClearCallingIdentity();
+ try {
+ try (KeyChainConnection keyChainConnection =
+ KeyChain.bindAsUser(mContext, userHandle)) {
+ IKeyChainService keyChain = keyChainConnection.getService();
+ final boolean generationResult = keyChain.generateKeyPair(algorithm, parcelableKeySpec);
+ if (!generationResult) {
+ Log.e(LOG_TAG, "KeyChain failed to generate a keypair.");
+ return false;
+ }
+
+ // Set a grant for the caller here so that when the client calls
+ // requestPrivateKey, it will be able to get the key from Keystore.
+ // Note the use of the calling UID, since the request for the private
+ // key will come from the client's process, so the grant has to be for
+ // that UID.
+ keyChain.setGrant(callingUid, keySpec.getKeystoreAlias(), true);
+ return true;
+ }
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "KeyChain error while generating a keypair", e);
+ } catch (InterruptedException e) {
+ Log.w(LOG_TAG, "Interrupted while generating keypair", e);
+ Thread.currentThread().interrupt();
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
+ return false;
+ }
+
+ @Override
public void choosePrivateKeyAlias(final int uid, final Uri uri, final String alias,
final IBinder response) {
// Caller UID needs to be trusted, so we restrict this method to SYSTEM_UID callers.