diff options
| -rw-r--r-- | api/current.txt | 2 | ||||
| -rw-r--r-- | api/system-current.txt | 2 | ||||
| -rw-r--r-- | api/test-current.txt | 2 | ||||
| -rw-r--r-- | core/java/android/security/IKeystoreService.aidl | 2 | ||||
| -rw-r--r-- | core/java/android/security/keymaster/KeymasterCertificateChain.aidl | 20 | ||||
| -rw-r--r-- | core/java/android/security/keymaster/KeymasterCertificateChain.java | 85 | ||||
| -rw-r--r-- | core/java/android/security/keymaster/KeymasterDefs.java | 5 | ||||
| -rw-r--r-- | keystore/java/android/security/Credentials.java | 29 | ||||
| -rw-r--r-- | keystore/java/android/security/KeyStore.java | 13 | ||||
| -rw-r--r-- | keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java | 194 | ||||
| -rw-r--r-- | keystore/java/android/security/keystore/KeyGenParameterSpec.java | 109 | ||||
| -rw-r--r-- | keystore/java/android/security/keystore/Utils.java | 4 |
12 files changed, 389 insertions, 78 deletions
diff --git a/api/current.txt b/api/current.txt index 40ead48e14c7..b9f932becc17 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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); diff --git a/api/system-current.txt b/api/system-current.txt index dfe1e08d6b57..b404bb05ec51 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -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); diff --git a/api/test-current.txt b/api/test-current.txt index 10733f9f34ab..9f8b8a89cc5d 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -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); diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl index 7cf1d715e947..8689dce3fef5 100644 --- a/core/java/android/security/IKeystoreService.aidl +++ b/core/java/android/security/IKeystoreService.aidl @@ -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); } diff --git a/core/java/android/security/keymaster/KeymasterCertificateChain.aidl b/core/java/android/security/keymaster/KeymasterCertificateChain.aidl new file mode 100644 index 000000000000..dc1876aaaebd --- /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 000000000000..243b9fe5f7c6 --- /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 04d59522e40b..e01f2a044ea7 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -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; diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java index c8333c87c69c..302b0bd73065 100644 --- a/keystore/java/android/security/Credentials.java +++ b/keystore/java/android/security/Credentials.java @@ -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"); - - 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()); + 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()); + } } + 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 1b87a419941d..3090ac1c6180 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -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. diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java index 65460b5ceb29..3a0ff1c44ad9 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -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); - } + if (!iter.hasNext()) { + return; + } - final X509Certificate cert; - try { - cert = generateSelfSignedCertificate(result.getPrivate(), result.getPublic()); - } catch (Exception e) { - throw new ProviderException("Failed to generate self-signed certificate", e); - } + ByteArrayOutputStream certificateConcatenationStream = new ByteArrayOutputStream(); + while (iter.hasNext()) { + byte[] data = iter.next(); + certificateConcatenationStream.write(data, 0, data.length); + } - byte[] certBytes; - try { - certBytes = cert.getEncoded(); - } catch (CertificateEncodingException e) { - throw new ProviderException( - "Failed to obtain encoded form of self-signed certificate", e); - } + storeCertificate(Credentials.CA_CERTIFICATE, certificateConcatenationStream.toByteArray(), + flags, "Failed to store attestation CA certificate"); + } - 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)); - } + 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)); + } + } - success = true; - return result; - } finally { - if (!success) { - Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid); - } + 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; diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index add199f139a0..f3fd1299354a 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -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; } /** @@ -539,6 +545,48 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { } /** + * 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 @@ 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); } } } diff --git a/keystore/java/android/security/keystore/Utils.java b/keystore/java/android/security/keystore/Utils.java index 9bec6821c252..5722c7b53ef4 100644 --- a/keystore/java/android/security/keystore/Utils.java +++ b/keystore/java/android/security/keystore/Utils.java @@ -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; + } } |