diff options
author | Eran Messeri <eranm@google.com> | 2017-11-15 05:55:52 +0000 |
---|---|---|
committer | Eran Messeri <eranm@google.com> | 2017-12-07 15:12:30 +0000 |
commit | 852c8f121f2e502e1e8503bfc230dccb81b681d4 (patch) | |
tree | 27c90a754791b77990afbcb369cac3fad401a3bf | |
parent | d52efa56adaca0bc70fb72082c7c663adcb669cc (diff) |
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
11 files changed, 618 insertions, 0 deletions
diff --git a/api/current.txt b/api/current.txt index d230aea89eab..402aa8fffbc4 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6320,6 +6320,7 @@ package android.app.admin { 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.sax { 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 f0117f20dfeb..e94356d90acb 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -57,7 +57,12 @@ import android.os.RemoteException; 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.lang.annotation.RetentionPolicy; 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 @@ public class DevicePolicyManager { } /** + * 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 802d42f232ba..21d7a2568ba1 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -36,6 +36,7 @@ import android.os.Bundle; 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 @@ interface IDevicePolicyManager { 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 000000000000..c6bff5c11a5d --- /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 635432da7942..b4331b21cf13 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 @@ interface IKeyChainService { 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 000000000000..3fb7b4f99463 --- /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 000000000000..b15e0a221c6f --- /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 000000000000..51adde4d68af --- /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 000000000000..9bf2d0c761e6 --- /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 000000000000..73b489f98e1d --- /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 663083c41790..4a8c0b46edb9 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.IKeyChainAliasCallback; 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @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. |