Add attestation API to Android KeyStore.
Bug: 22914603
Change-Id: I7c6162dc7a390aa48a2542494780959b01c23bd4
diff --git a/api/current.txt b/api/current.txt
index ae343cd..ea6a679 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -34044,6 +34044,7 @@
public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
method public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
+ method public byte[] getAttestationChallenge();
method public java.lang.String[] getBlockModes();
method public java.util.Date getCertificateNotAfter();
method public java.util.Date getCertificateNotBefore();
@@ -34068,6 +34069,7 @@
ctor public KeyGenParameterSpec.Builder(java.lang.String, int);
method public android.security.keystore.KeyGenParameterSpec build();
method public android.security.keystore.KeyGenParameterSpec.Builder setAlgorithmParameterSpec(java.security.spec.AlgorithmParameterSpec);
+ method public android.security.keystore.KeyGenParameterSpec.Builder setAttestationChallenge(byte[]);
method public android.security.keystore.KeyGenParameterSpec.Builder setBlockModes(java.lang.String...);
method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotAfter(java.util.Date);
method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotBefore(java.util.Date);
diff --git a/api/system-current.txt b/api/system-current.txt
index bcd39fe..0288a49 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -36454,6 +36454,7 @@
public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
method public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
+ method public byte[] getAttestationChallenge();
method public java.lang.String[] getBlockModes();
method public java.util.Date getCertificateNotAfter();
method public java.util.Date getCertificateNotBefore();
@@ -36478,6 +36479,7 @@
ctor public KeyGenParameterSpec.Builder(java.lang.String, int);
method public android.security.keystore.KeyGenParameterSpec build();
method public android.security.keystore.KeyGenParameterSpec.Builder setAlgorithmParameterSpec(java.security.spec.AlgorithmParameterSpec);
+ method public android.security.keystore.KeyGenParameterSpec.Builder setAttestationChallenge(byte[]);
method public android.security.keystore.KeyGenParameterSpec.Builder setBlockModes(java.lang.String...);
method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotAfter(java.util.Date);
method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotBefore(java.util.Date);
diff --git a/api/test-current.txt b/api/test-current.txt
index c1cd275..5fb0b52 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -34059,6 +34059,7 @@
public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
method public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
+ method public byte[] getAttestationChallenge();
method public java.lang.String[] getBlockModes();
method public java.util.Date getCertificateNotAfter();
method public java.util.Date getCertificateNotBefore();
@@ -34083,6 +34084,7 @@
ctor public KeyGenParameterSpec.Builder(java.lang.String, int);
method public android.security.keystore.KeyGenParameterSpec build();
method public android.security.keystore.KeyGenParameterSpec.Builder setAlgorithmParameterSpec(java.security.spec.AlgorithmParameterSpec);
+ method public android.security.keystore.KeyGenParameterSpec.Builder setAttestationChallenge(byte[]);
method public android.security.keystore.KeyGenParameterSpec.Builder setBlockModes(java.lang.String...);
method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotAfter(java.util.Date);
method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotBefore(java.util.Date);
diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl
index 7cf1d71..8689dce 100644
--- a/core/java/android/security/IKeystoreService.aidl
+++ b/core/java/android/security/IKeystoreService.aidl
@@ -19,6 +19,7 @@
import android.security.keymaster.ExportResult;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterCertificateChain;
import android.security.keymaster.KeymasterBlob;
import android.security.keymaster.OperationResult;
import android.security.KeystoreArguments;
@@ -74,4 +75,5 @@
int addAuthToken(in byte[] authToken);
int onUserAdded(int userId, int parentId);
int onUserRemoved(int userId);
+ int attestKey(String alias, in KeymasterArguments params, out KeymasterCertificateChain chain);
}
diff --git a/core/java/android/security/keymaster/KeymasterCertificateChain.aidl b/core/java/android/security/keymaster/KeymasterCertificateChain.aidl
new file mode 100644
index 0000000..dc1876a
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterCertificateChain.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2016 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.keymaster;
+
+/* @hide */
+parcelable KeymasterCertificateChain;
diff --git a/core/java/android/security/keymaster/KeymasterCertificateChain.java b/core/java/android/security/keymaster/KeymasterCertificateChain.java
new file mode 100644
index 0000000..243b9fe
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterCertificateChain.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 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.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class for the Java side of keystore-generated certificate chains.
+ *
+ * Serialization code for this must be kept in sync with system/security/keystore
+ * @hide
+ */
+public class KeymasterCertificateChain implements Parcelable {
+
+ private List<byte[]> mCertificates;
+
+ public static final Parcelable.Creator<KeymasterCertificateChain> CREATOR = new
+ Parcelable.Creator<KeymasterCertificateChain>() {
+ public KeymasterCertificateChain createFromParcel(Parcel in) {
+ return new KeymasterCertificateChain(in);
+ }
+ public KeymasterCertificateChain[] newArray(int size) {
+ return new KeymasterCertificateChain[size];
+ }
+ };
+
+ public KeymasterCertificateChain() {
+ mCertificates = null;
+ }
+
+ public KeymasterCertificateChain(List<byte[]> mCertificates) {
+ this.mCertificates = mCertificates;
+ }
+
+ private KeymasterCertificateChain(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public List<byte[]> getCertificates() {
+ return mCertificates;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ if (mCertificates == null) {
+ out.writeInt(0);
+ } else {
+ out.writeInt(mCertificates.size());
+ for (byte[] arg : mCertificates) {
+ out.writeByteArray(arg);
+ }
+ }
+ }
+
+ public void readFromParcel(Parcel in) {
+ int length = in.readInt();
+ mCertificates = new ArrayList<byte[]>(length);
+ for (int i = 0; i < length; i++) {
+ mCertificates.add(in.createByteArray());
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index 04d5952..e01f2a0 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -58,6 +58,8 @@
public static final int KM_TAG_BLOB_USAGE_REQUIREMENTS = KM_ENUM | 705;
public static final int KM_TAG_RSA_PUBLIC_EXPONENT = KM_ULONG | 200;
+ public static final int KM_TAG_INCLUDE_UNIQUE_ID = KM_BOOL | 202;
+
public static final int KM_TAG_ACTIVE_DATETIME = KM_DATE | 400;
public static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME = KM_DATE | 401;
public static final int KM_TAG_USAGE_EXPIRE_DATETIME = KM_DATE | 402;
@@ -74,11 +76,12 @@
public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600;
public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601;
- public static final int KM_TAG_APPLICATION_DATA = KM_BYTES | 700;
public static final int KM_TAG_CREATION_DATETIME = KM_DATE | 701;
public static final int KM_TAG_ORIGIN = KM_ENUM | 702;
public static final int KM_TAG_ROLLBACK_RESISTANT = KM_BOOL | 703;
public static final int KM_TAG_ROOT_OF_TRUST = KM_BYTES | 704;
+ public static final int KM_TAG_UNIQUE_ID = KM_BYTES | 707;
+ public static final int KM_TAG_ATTESTATION_CHALLENGE = KM_BYTES | 708;
public static final int KM_TAG_ASSOCIATED_DATA = KM_BYTES | 1000;
public static final int KM_TAG_NONCE = KM_BYTES | 1001;
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index c8333c8..302b0bd 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -20,9 +20,11 @@
import android.content.Context;
import android.content.Intent;
import android.util.Log;
+
import com.android.org.bouncycastle.util.io.pem.PemObject;
import com.android.org.bouncycastle.util.io.pem.PemReader;
import com.android.org.bouncycastle.util.io.pem.PemWriter;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -147,20 +149,23 @@
Reader reader = new InputStreamReader(bai, StandardCharsets.US_ASCII);
PemReader pr = new PemReader(reader);
- CertificateFactory cf = CertificateFactory.getInstance("X509");
+ try {
+ CertificateFactory cf = CertificateFactory.getInstance("X509");
- List<X509Certificate> result = new ArrayList<X509Certificate>();
- PemObject o;
- while ((o = pr.readPemObject()) != null) {
- if (o.getType().equals("CERTIFICATE")) {
- Certificate c = cf.generateCertificate(new ByteArrayInputStream(o.getContent()));
- result.add((X509Certificate) c);
- } else {
- throw new IllegalArgumentException("Unknown type " + o.getType());
+ List<X509Certificate> result = new ArrayList<X509Certificate>();
+ PemObject o;
+ while ((o = pr.readPemObject()) != null) {
+ if (o.getType().equals("CERTIFICATE")) {
+ Certificate c = cf.generateCertificate(new ByteArrayInputStream(o.getContent()));
+ result.add((X509Certificate) c);
+ } else {
+ throw new IllegalArgumentException("Unknown type " + o.getType());
+ }
}
+ return result;
+ } finally {
+ pr.close();
}
- pr.close();
- return result;
}
private static Credentials singleton;
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 1b87a41..3090ac1 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -19,7 +19,6 @@
import android.app.ActivityThread;
import android.app.Application;
import android.app.KeyguardManager;
-
import android.content.Context;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Binder;
@@ -32,6 +31,7 @@
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterBlob;
+import android.security.keymaster.KeymasterCertificateChain;
import android.security.keymaster.KeymasterDefs;
import android.security.keymaster.OperationResult;
import android.security.keystore.KeyExpiredException;
@@ -615,6 +615,17 @@
return onUserPasswordChanged(UserHandle.getUserId(Process.myUid()), newPassword);
}
+ public int attestKey(
+ String alias, KeymasterArguments params, KeymasterCertificateChain outChain) {
+ try {
+ return mBinder.attestKey(alias, params, outChain);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return SYSTEM_ERROR;
+ }
+ }
+
+
/**
* Returns a {@link KeyStoreException} corresponding to the provided keystore/keymaster error
* code.
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 65460b5..3a0ff1c 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -22,6 +22,7 @@
import android.security.KeyStore;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterCertificateChain;
import android.security.keymaster.KeymasterDefs;
import com.android.org.bouncycastle.asn1.ASN1EncodableVector;
@@ -46,6 +47,8 @@
import libcore.util.EmptyArray;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
@@ -57,14 +60,17 @@
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -166,6 +172,7 @@
mOriginalKeymasterAlgorithm = keymasterAlgorithm;
}
+ @SuppressWarnings("deprecation")
@Override
public void initialize(int keysize, SecureRandom random) {
throw new IllegalArgumentException(
@@ -173,6 +180,7 @@
+ " required to initialize this KeyPairGenerator");
}
+ @SuppressWarnings("deprecation")
@Override
public void initialize(AlgorithmParameterSpec params, SecureRandom random)
throws InvalidAlgorithmParameterException {
@@ -447,6 +455,69 @@
+ ", but the user has not yet entered the credential");
}
+ byte[] additionalEntropy =
+ KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
+ mRng, (mKeySizeBits + 7) / 8);
+
+ Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid);
+ final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + mEntryAlias;
+ boolean success = false;
+ try {
+ generateKeystoreKeyPair(
+ privateKeyAlias, constructKeyGenerationArguments(), additionalEntropy, flags);
+ KeyPair keyPair = loadKeystoreKeyPair(privateKeyAlias);
+
+ storeCertificateChain(flags, createCertificateChain(privateKeyAlias, keyPair));
+
+ success = true;
+ return keyPair;
+ } finally {
+ if (!success) {
+ Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid);
+ }
+ }
+ }
+
+ private Iterable<byte[]> createCertificateChain(final String privateKeyAlias, KeyPair keyPair)
+ throws ProviderException {
+ byte[] challenge = mSpec.getAttestationChallenge();
+ if (challenge != null) {
+ KeymasterArguments args = new KeymasterArguments();
+ args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, challenge);
+ return getAttestationChain(privateKeyAlias, keyPair, args);
+ }
+
+ // Very short certificate chain in the non-attestation case.
+ return Collections.singleton(generateSelfSignedCertificateBytes(keyPair));
+ }
+
+ private void generateKeystoreKeyPair(final String privateKeyAlias, KeymasterArguments args,
+ byte[] additionalEntropy, final int flags) throws ProviderException {
+ KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
+ int errorCode = mKeyStore.generateKey(privateKeyAlias, args, additionalEntropy,
+ mEntryUid, flags, resultingKeyCharacteristics);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new ProviderException(
+ "Failed to generate key pair", KeyStore.getKeyStoreException(errorCode));
+ }
+ }
+
+ private KeyPair loadKeystoreKeyPair(final String privateKeyAlias) throws ProviderException {
+ try {
+ KeyPair result = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
+ mKeyStore, privateKeyAlias, mEntryUid);
+ if (!mJcaKeyAlgorithm.equalsIgnoreCase(result.getPrivate().getAlgorithm())) {
+ throw new ProviderException(
+ "Generated key pair algorithm does not match requested algorithm: "
+ + result.getPrivate().getAlgorithm() + " vs " + mJcaKeyAlgorithm);
+ }
+ return result;
+ } catch (UnrecoverableKeyException e) {
+ throw new ProviderException("Failed to load generated key pair from keystore", e);
+ }
+ }
+
+ private KeymasterArguments constructKeyGenerationArguments() {
KeymasterArguments args = new KeymasterArguments();
args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits);
args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm);
@@ -466,73 +537,72 @@
mSpec.getKeyValidityForConsumptionEnd());
addAlgorithmSpecificParameters(args);
- byte[] additionalEntropy =
- KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
- mRng, (mKeySizeBits + 7) / 8);
+ if (mSpec.isUniqueIdIncluded())
+ args.addBoolean(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID);
- final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + mEntryAlias;
- boolean success = false;
- try {
- Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid);
- KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
- int errorCode = mKeyStore.generateKey(
- privateKeyAlias,
- args,
- additionalEntropy,
- mEntryUid,
- flags,
- resultingKeyCharacteristics);
- if (errorCode != KeyStore.NO_ERROR) {
- throw new ProviderException(
- "Failed to generate key pair", KeyStore.getKeyStoreException(errorCode));
- }
+ return args;
+ }
- KeyPair result;
- try {
- result = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
- mKeyStore, privateKeyAlias, mEntryUid);
- } catch (UnrecoverableKeyException e) {
- throw new ProviderException("Failed to load generated key pair from keystore", e);
- }
+ private void storeCertificateChain(final int flags, Iterable<byte[]> iterable)
+ throws ProviderException {
+ Iterator<byte[]> iter = iterable.iterator();
+ storeCertificate(
+ Credentials.USER_CERTIFICATE, iter.next(), flags, "Failed to store certificate");
- if (!mJcaKeyAlgorithm.equalsIgnoreCase(result.getPrivate().getAlgorithm())) {
- throw new ProviderException(
- "Generated key pair algorithm does not match requested algorithm: "
- + result.getPrivate().getAlgorithm() + " vs " + mJcaKeyAlgorithm);
- }
-
- final X509Certificate cert;
- try {
- cert = generateSelfSignedCertificate(result.getPrivate(), result.getPublic());
- } catch (Exception e) {
- throw new ProviderException("Failed to generate self-signed certificate", e);
- }
-
- byte[] certBytes;
- try {
- certBytes = cert.getEncoded();
- } catch (CertificateEncodingException e) {
- throw new ProviderException(
- "Failed to obtain encoded form of self-signed certificate", e);
- }
-
- int insertErrorCode = mKeyStore.insert(
- Credentials.USER_CERTIFICATE + mEntryAlias,
- certBytes,
- mEntryUid,
- flags);
- if (insertErrorCode != KeyStore.NO_ERROR) {
- throw new ProviderException("Failed to store self-signed certificate",
- KeyStore.getKeyStoreException(insertErrorCode));
- }
-
- success = true;
- return result;
- } finally {
- if (!success) {
- Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid);
- }
+ if (!iter.hasNext()) {
+ return;
}
+
+ ByteArrayOutputStream certificateConcatenationStream = new ByteArrayOutputStream();
+ while (iter.hasNext()) {
+ byte[] data = iter.next();
+ certificateConcatenationStream.write(data, 0, data.length);
+ }
+
+ storeCertificate(Credentials.CA_CERTIFICATE, certificateConcatenationStream.toByteArray(),
+ flags, "Failed to store attestation CA certificate");
+ }
+
+ private void storeCertificate(String prefix, byte[] certificateBytes, final int flags,
+ String failureMessage) throws ProviderException {
+ int insertErrorCode = mKeyStore.insert(
+ prefix + mEntryAlias,
+ certificateBytes,
+ mEntryUid,
+ flags);
+ if (insertErrorCode != KeyStore.NO_ERROR) {
+ throw new ProviderException(failureMessage,
+ KeyStore.getKeyStoreException(insertErrorCode));
+ }
+ }
+
+ private byte[] generateSelfSignedCertificateBytes(KeyPair keyPair) throws ProviderException {
+ try {
+ return generateSelfSignedCertificate(keyPair.getPrivate(), keyPair.getPublic())
+ .getEncoded();
+ } catch (IOException | CertificateParsingException e) {
+ throw new ProviderException("Failed to generate self-signed certificate", e);
+ } catch (CertificateEncodingException e) {
+ throw new ProviderException(
+ "Failed to obtain encoded form of self-signed certificate", e);
+ }
+ }
+
+ private Iterable<byte[]> getAttestationChain(String privateKeyAlias,
+ KeyPair keyPair, KeymasterArguments args)
+ throws ProviderException {
+ KeymasterCertificateChain outChain = new KeymasterCertificateChain();
+ int errorCode = mKeyStore.attestKey(privateKeyAlias, args, outChain);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new ProviderException("Failed to generate attestation certificate chain",
+ KeyStore.getKeyStoreException(errorCode));
+ }
+ Collection<byte[]> chain = outChain.getCertificates();
+ if (chain.size() < 2) {
+ throw new ProviderException("Attestation certificate chain contained "
+ + chain.size() + " entries. At least two are required.");
+ }
+ return chain;
}
private void addAlgorithmSpecificParameters(KeymasterArguments keymasterArgs) {
@@ -548,8 +618,8 @@
}
}
- private X509Certificate generateSelfSignedCertificate(
- PrivateKey privateKey, PublicKey publicKey) throws Exception {
+ private X509Certificate generateSelfSignedCertificate(PrivateKey privateKey,
+ PublicKey publicKey) throws CertificateParsingException, IOException {
String signatureAlgorithm =
getCertificateSignatureAlgorithm(mKeymasterAlgorithm, mKeySizeBits, mSpec);
if (signatureAlgorithm == null) {
@@ -587,7 +657,7 @@
@SuppressWarnings("deprecation")
private X509Certificate generateSelfSignedCertificateWithFakeSignature(
- PublicKey publicKey) throws Exception {
+ PublicKey publicKey) throws IOException, CertificateParsingException {
V3TBSCertificateGenerator tbsGenerator = new V3TBSCertificateGenerator();
ASN1ObjectIdentifier sigAlgOid;
AlgorithmIdentifier sigAlgId;
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index add199f..f3fd129 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -250,6 +250,8 @@
private final boolean mRandomizedEncryptionRequired;
private final boolean mUserAuthenticationRequired;
private final int mUserAuthenticationValidityDurationSeconds;
+ private final byte[] mAttestationChallenge;
+ private final boolean mUniqueIdIncluded;
/**
* @hide should be built with Builder
@@ -273,7 +275,9 @@
@KeyProperties.BlockModeEnum String[] blockModes,
boolean randomizedEncryptionRequired,
boolean userAuthenticationRequired,
- int userAuthenticationValidityDurationSeconds) {
+ int userAuthenticationValidityDurationSeconds,
+ byte[] attestationChallenge,
+ boolean uniqueIdIncluded) {
if (TextUtils.isEmpty(keyStoreAlias)) {
throw new IllegalArgumentException("keyStoreAlias must not be empty");
}
@@ -315,6 +319,8 @@
mRandomizedEncryptionRequired = randomizedEncryptionRequired;
mUserAuthenticationRequired = userAuthenticationRequired;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge);
+ mUniqueIdIncluded = uniqueIdIncluded;
}
/**
@@ -539,6 +545,48 @@
}
/**
+ * Returns the attestation challenge value that will be placed in attestation certificate for
+ * this key pair.
+ *
+ * <p>If this method returns non-{@code null}, the public key certificate for this key pair will
+ * contain an extension that describes the details of the key's configuration and
+ * authorizations, including the content of the attestation challenge value. If the key is in
+ * secure hardware, and if the secure hardware supports attestation, the 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.
+ *
+ * <p>If this method returns {@code null}, and the spec is used to generate an asymmetric (RSA
+ * or EC) key pair, the public key will have a self-signed certificate if it has purpose {@link
+ * KeyProperties#PURPOSE_SIGN} (see {@link #KeyGenParameterSpec(String, int)). If does not have
+ * purpose {@link KeyProperties#PURPOSE_SIGN}, it will have a fake certificate.
+ *
+ * <p>Symmetric keys, such as AES and HMAC keys, do not have public key certificates. If a
+ * {@link KeyGenParameterSpec} with {@link #hasAttestationCertificate()} returning
+ * non-{@code null} is used to generate a symmetric (AES or HMAC) key,
+ * {@link KeyGenerator#generateKey())} will throw
+ * {@link java.security.InvalidAlgorithmParameterException}.
+ *
+ * @see Builder#setAttestationChallenge(byte[])
+ */
+ /*
+ * TODO(swillden): Update this documentation to describe the hardware and software root keys,
+ * including information about CRL/OCSP services for discovering revocations, and to link to
+ * documentation of the extension format and content.
+ */
+ public byte[] getAttestationChallenge() {
+ return Utils.cloneIfNotNull(mAttestationChallenge);
+ }
+
+ /**
+ * @hide This is a system-only API
+ *
+ * Returns {@code true} if the attestation certificate will contain a unique ID field.
+ */
+ public boolean isUniqueIdIncluded() {
+ return mUniqueIdIncluded;
+ }
+
+ /**
* Builder of {@link KeyGenParameterSpec} instances.
*/
public final static class Builder {
@@ -562,6 +610,8 @@
private boolean mRandomizedEncryptionRequired = true;
private boolean mUserAuthenticationRequired;
private int mUserAuthenticationValidityDurationSeconds = -1;
+ private byte[] mAttestationChallenge = null;
+ private boolean mUniqueIdIncluded = false;
/**
* Creates a new instance of the {@code Builder}.
@@ -957,6 +1007,59 @@
return this;
}
+ /*
+ * TODO(swillden): Update this documentation to describe the hardware and software root
+ * keys, including information about CRL/OCSP services for discovering revocations, and to
+ * link to documentation of the extension format and content.
+ */
+ /**
+ * Sets whether an attestation certificate will be generated for this key pair, and what
+ * challenge value will be placed in the certificate. The attestation certificate chain
+ * can be retrieved with with {@link java.security.KeyStore#getCertificateChain(String)}.
+ *
+ * <p>If {@code attestationChallenge} is not {@code null}, the public key certificate for
+ * this key pair will contain an extension that describes the details of the key's
+ * configuration and authorizations, including the {@code attestationChallenge} value. If
+ * the key is in secure hardware, and if the secure hardware supports attestation, the
+ * 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.
+ *
+ * <p>The purpose of the challenge value is to enable relying parties to verify that the key
+ * was created in response to a specific request. If attestation is desired but no
+ * challenged is needed, any non-{@code null} value may be used, including an empty byte
+ * array.
+ *
+ * <p>If {@code attestationChallenge} is {@code null}, and this spec is used to generate an
+ * asymmetric (RSA or EC) key pair, the public key certificate will be self-signed if the
+ * key has purpose {@link KeyProperties#PURPOSE_SIGN} (see
+ * {@link #KeyGenParameterSpec(String, int)). If the key does not have purpose
+ * {@link KeyProperties#PURPOSE_SIGN}, it is not possible to use the key to sign a
+ * certificate, so the public key certificate will contain a dummy signature.
+ *
+ * <p>Symmetric keys, such as AES and HMAC keys, do not have public key certificates. If a
+ * {@code getAttestationChallenge} returns non-{@code null} and the spec is used to
+ * generate a symmetric (AES or HMAC) key, {@link KeyGenerator#generateKey()} will throw
+ * {@link java.security.InvalidAlgorithmParameterException}.
+ *
+ * @see Builder#setAttestationChallenge(String attestationChallenge)
+ */
+ @NonNull
+ public Builder setAttestationChallenge(byte[] attestationChallenge) {
+ mAttestationChallenge = attestationChallenge;
+ return this;
+ }
+
+ /**
+ * @hide Only system apps can use this method.
+ *
+ * Sets whether to include a temporary unique ID field in the attestation certificate.
+ */
+ @NonNull
+ public Builder setUniqueIdIncluded(boolean uniqueIdIncluded) {
+ mUniqueIdIncluded = uniqueIdIncluded;
+ return this;
+ }
+
/**
* Builds an instance of {@code KeyGenParameterSpec}.
*/
@@ -981,7 +1084,9 @@
mBlockModes,
mRandomizedEncryptionRequired,
mUserAuthenticationRequired,
- mUserAuthenticationValidityDurationSeconds);
+ mUserAuthenticationValidityDurationSeconds,
+ mAttestationChallenge,
+ mUniqueIdIncluded);
}
}
}
diff --git a/keystore/java/android/security/keystore/Utils.java b/keystore/java/android/security/keystore/Utils.java
index 9bec682..5722c7b 100644
--- a/keystore/java/android/security/keystore/Utils.java
+++ b/keystore/java/android/security/keystore/Utils.java
@@ -29,4 +29,8 @@
static Date cloneIfNotNull(Date value) {
return (value != null) ? (Date) value.clone() : null;
}
+
+ static byte[] cloneIfNotNull(byte[] value) {
+ return (value != null) ? value.clone() : null;
+ }
}