Merge "Add attestation API to Android KeyStore." into nyc-dev

This commit is contained in:
Shawn Willden
2016-02-09 22:57:23 +00:00
committed by Android (Google) Code Review
12 changed files with 392 additions and 81 deletions

View File

@@ -34045,6 +34045,7 @@ package android.security.keystore {
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();
@@ -34069,6 +34070,7 @@ package android.security.keystore {
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);

View File

@@ -36528,6 +36528,7 @@ package android.security.keystore {
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();
@@ -36552,6 +36553,7 @@ package android.security.keystore {
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);

View File

@@ -34060,6 +34060,7 @@ package android.security.keystore {
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();
@@ -34084,6 +34085,7 @@ package android.security.keystore {
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);

View File

@@ -19,6 +19,7 @@ package android.security;
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 @@ interface IKeystoreService {
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);
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -58,6 +58,8 @@ public final class KeymasterDefs {
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 final class KeymasterDefs {
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;

View File

@@ -20,9 +20,11 @@ import android.content.ActivityNotFoundException;
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 @@ public class Credentials {
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;

View File

@@ -19,7 +19,6 @@ package android.security;
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.ExportResult;
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 @@ public class KeyStore {
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.

View File

@@ -22,6 +22,7 @@ import android.security.KeyPairGeneratorSpec;
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 com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
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.PublicKey;
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 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
mOriginalKeymasterAlgorithm = keymasterAlgorithm;
}
@SuppressWarnings("deprecation")
@Override
public void initialize(int keysize, SecureRandom random) {
throw new IllegalArgumentException(
@@ -173,6 +180,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
+ " required to initialize this KeyPairGenerator");
}
@SuppressWarnings("deprecation")
@Override
public void initialize(AlgorithmParameterSpec params, SecureRandom random)
throws InvalidAlgorithmParameterException {
@@ -447,6 +455,69 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
+ ", 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 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
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 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
}
}
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 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
@SuppressWarnings("deprecation")
private X509Certificate generateSelfSignedCertificateWithFakeSignature(
PublicKey publicKey) throws Exception {
PublicKey publicKey) throws IOException, CertificateParsingException {
V3TBSCertificateGenerator tbsGenerator = new V3TBSCertificateGenerator();
ASN1ObjectIdentifier sigAlgOid;
AlgorithmIdentifier sigAlgId;

View File

@@ -250,6 +250,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
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 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
@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 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
mRandomizedEncryptionRequired = randomizedEncryptionRequired;
mUserAuthenticationRequired = userAuthenticationRequired;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge);
mUniqueIdIncluded = uniqueIdIncluded;
}
/**
@@ -538,6 +544,48 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
return mUserAuthenticationValidityDurationSeconds;
}
/**
* 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.
*/
@@ -562,6 +610,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
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 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
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 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
mBlockModes,
mRandomizedEncryptionRequired,
mUserAuthenticationRequired,
mUserAuthenticationValidityDurationSeconds);
mUserAuthenticationValidityDurationSeconds,
mAttestationChallenge,
mUniqueIdIncluded);
}
}
}

View File

@@ -29,4 +29,8 @@ abstract class Utils {
static Date cloneIfNotNull(Date value) {
return (value != null) ? (Date) value.clone() : null;
}
static byte[] cloneIfNotNull(byte[] value) {
return (value != null) ? value.clone() : null;
}
}