diff options
Diffstat (limited to 'keystore/java')
33 files changed, 2588 insertions, 1959 deletions
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java index 6283e02..5d777b0 100644 --- a/keystore/java/android/security/Credentials.java +++ b/keystore/java/android/security/Credentials.java @@ -216,7 +216,7 @@ public class Credentials { * particular {@code alias}. All three can exist for any given alias. * Returns {@code true} if there was at least one of those types. */ - static boolean deleteAllTypesForAlias(KeyStore keystore, String alias) { + public static boolean deleteAllTypesForAlias(KeyStore keystore, String alias) { /* * Make sure every type is deleted. There can be all three types, so * don't use a conditional here. @@ -231,7 +231,7 @@ public class Credentials { * particular {@code alias}. All three can exist for any given alias. * Returns {@code true} if there was at least one of those types. */ - static boolean deleteCertificateTypesForAlias(KeyStore keystore, String alias) { + public static boolean deleteCertificateTypesForAlias(KeyStore keystore, String alias) { /* * Make sure every certificate type is deleted. There can be two types, * so don't use a conditional here. @@ -252,7 +252,7 @@ public class Credentials { * Delete secret key for a particular {@code alias}. * Returns {@code true} if an entry was was deleted. */ - static boolean deleteSecretKeyTypeForAlias(KeyStore keystore, String alias) { + public static boolean deleteSecretKeyTypeForAlias(KeyStore keystore, String alias) { return keystore.delete(Credentials.USER_SECRET_KEY + alias); } } diff --git a/keystore/java/android/security/EcIesParameterSpec.java b/keystore/java/android/security/EcIesParameterSpec.java index a3e5aec..1cd8784 100644 --- a/keystore/java/android/security/EcIesParameterSpec.java +++ b/keystore/java/android/security/EcIesParameterSpec.java @@ -1,6 +1,8 @@ package android.security; import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -49,45 +51,44 @@ import javax.crypto.Mac; */ public class EcIesParameterSpec implements AlgorithmParameterSpec { + /** + * @hide + */ @Retention(RetentionPolicy.SOURCE) - @IntDef(value = {PointFormat.UNCOMPRESSED, PointFormat.COMPRESSED}) + @IntDef({ + POINT_FORMAT_UNSPECIFIED, + POINT_FORMAT_UNCOMPRESSED, + POINT_FORMAT_COMPRESSED, + }) public @interface PointFormatEnum {} + /** Unspecified EC point format. */ + public static final int POINT_FORMAT_UNSPECIFIED = -1; + /** - * Wire format of the EC point. + * Uncompressed EC point format: both coordinates are stored separately. + * + * <p>The wire format is byte {@code 0x04} followed by binary representation of the {@code x} + * coordinate followed by binary representation of the {@code y} coordinate. See + * {@code ISO 18033-2} section {@code 5.4.3}. */ - public static abstract class PointFormat { - - private PointFormat() {} + public static final int POINT_FORMAT_UNCOMPRESSED = 0; - /** Unspecified point format. */ - public static final int UNSPECIFIED = -1; - - /** - * Uncompressed point format: both coordinates are stored separately. - * - * <p>The wire format is byte {@code 0x04} followed by binary representation of the - * {@code x} coordinate followed by binary representation of the {@code y} coordinate. See - * {@code ISO 18033-2} section {@code 5.4.3}. - */ - public static final int UNCOMPRESSED = 0; - - /** - * Compressed point format: only one coordinate is stored. - * - * <p>The wire format is byte {@code 0x02} or {@code 0x03} (depending on the value of the - * stored coordinate) followed by the binary representation of the {@code x} coordinate. - * See {@code ISO 18033-2} section {@code 5.4.3}. - */ - public static final int COMPRESSED = 1; - } + /** + * Compressed EC point format: only one coordinate is stored. + * + * <p>The wire format is byte {@code 0x02} or {@code 0x03} (depending on the value of the stored + * coordinate) followed by the binary representation of the {@code x} coordinate. See + * {@code ISO 18033-2} section {@code 5.4.3}. + */ + public static final int POINT_FORMAT_COMPRESSED = 1; /** * Default parameter spec: compressed point format, {@code HKDFwithSHA256}, DEM uses 128-bit AES * GCM. */ public static final EcIesParameterSpec DEFAULT = new EcIesParameterSpec( - PointFormat.COMPRESSED, + POINT_FORMAT_COMPRESSED, "HKDFwithSHA256", "AES/GCM/NoPadding", 128, @@ -117,7 +118,7 @@ public class EcIesParameterSpec implements AlgorithmParameterSpec { } /** - * Returns KEM EC point wire format or {@link PointFormat#UNSPECIFIED} if not specified. + * Returns KEM EC point wire format or {@link #POINT_FORMAT_UNSPECIFIED} if not specified. */ public @PointFormatEnum int getKemPointFormat() { return mKemPointFormat; @@ -127,6 +128,7 @@ public class EcIesParameterSpec implements AlgorithmParameterSpec { * Returns KEM KDF algorithm (e.g., {@code HKDFwithSHA256} or {@code KDF1withSHA1}) or * {@code null} if not specified. */ + @Nullable public String getKemKdfAlgorithm() { return mKemKdfAlgorithm; } @@ -138,6 +140,7 @@ public class EcIesParameterSpec implements AlgorithmParameterSpec { * @see Cipher#getInstance(String) * @see #getDemCipherKeySize() */ + @Nullable public String getDemCipherTransformation() { return mDemCipherTransformation; } @@ -158,6 +161,7 @@ public class EcIesParameterSpec implements AlgorithmParameterSpec { * @see Mac#getInstance(String) * @see #getDemMacKeySize() */ + @Nullable public String getDemMacAlgorithm() { return mDemMacAlgorithm; } @@ -175,7 +179,7 @@ public class EcIesParameterSpec implements AlgorithmParameterSpec { * Builder of {@link EcIesParameterSpec}. */ public static class Builder { - private @PointFormatEnum int mKemPointFormat = PointFormat.UNSPECIFIED; + private @PointFormatEnum int mKemPointFormat = POINT_FORMAT_UNSPECIFIED; private String mKemKdfAlgorithm; private String mDemCipherTransformation; private int mDemCipherKeySize = 128; @@ -194,7 +198,8 @@ public class EcIesParameterSpec implements AlgorithmParameterSpec { * Sets KEM KDF algorithm. For example, {@code HKDFwithSHA256}, {@code KDF2withSHA256}, or * {@code KDF1withSHA1}. */ - public Builder setKemKdfAlgorithm(String algorithm) { + @NonNull + public Builder setKemKdfAlgorithm(@Nullable String algorithm) { mKemKdfAlgorithm = algorithm; return this; } @@ -205,7 +210,8 @@ public class EcIesParameterSpec implements AlgorithmParameterSpec { * * @see Cipher#getInstance(String) */ - public Builder setDemCipherTransformation(String transformation) { + @NonNull + public Builder setDemCipherTransformation(@Nullable String transformation) { mDemCipherTransformation = transformation; return this; } @@ -217,6 +223,7 @@ public class EcIesParameterSpec implements AlgorithmParameterSpec { * * @see #setDemCipherTransformation(String) */ + @NonNull public Builder setDemCipherKeySize(int sizeBits) { mDemCipherKeySize = sizeBits; return this; @@ -227,7 +234,8 @@ public class EcIesParameterSpec implements AlgorithmParameterSpec { * * @see Mac#getInstance(String) */ - public Builder setDemMacAlgorithm(String algorithm) { + @NonNull + public Builder setDemMacAlgorithm(@Nullable String algorithm) { mDemMacAlgorithm = algorithm; return this; } @@ -239,6 +247,7 @@ public class EcIesParameterSpec implements AlgorithmParameterSpec { * * @see #setDemCipherKeySize(int) */ + @NonNull public Builder setDemMacKeySize(int sizeBits) { mDemMacKeySize = sizeBits; return this; @@ -247,6 +256,7 @@ public class EcIesParameterSpec implements AlgorithmParameterSpec { /** * Returns a new {@link EcIesParameterSpec} based on the current state of this builder. */ + @NonNull public EcIesParameterSpec build() { int demMacKeySize = (mDemMacKeySize != -1) ? mDemMacKeySize : mDemCipherKeySize; return new EcIesParameterSpec( diff --git a/keystore/java/android/security/GateKeeper.java b/keystore/java/android/security/GateKeeper.java index 5617836..c1df28c 100644 --- a/keystore/java/android/security/GateKeeper.java +++ b/keystore/java/android/security/GateKeeper.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package android.security; import android.os.RemoteException; diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index 8e27dc3..817b7c9 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -15,17 +15,22 @@ */ package android.security; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Activity; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.net.Uri; import android.os.IBinder; import android.os.Looper; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.security.keystore.KeyProperties; + import java.io.ByteArrayInputStream; import java.io.Closeable; import java.security.InvalidKeyException; @@ -115,19 +120,7 @@ public final class KeyChain { * Extra for use with {@link #ACTION_CHOOSER} * @hide Also used by KeyChainActivity implementation */ - public static final String EXTRA_HOST = "host"; - - /** - * Extra for use with {@link #ACTION_CHOOSER} - * @hide Also used by KeyChainActivity implementation - */ - public static final String EXTRA_PORT = "port"; - - /** - * Extra for use with {@link #ACTION_CHOOSER} - * @hide Also used by KeyChainActivity implementation - */ - public static final String EXTRA_URL = "url"; + public static final String EXTRA_URI = "uri"; /** * Extra for use with {@link #ACTION_CHOOSER} @@ -217,6 +210,7 @@ public final class KeyChain { * successfully installed, otherwise {@link * Activity#RESULT_CANCELED} will be returned. */ + @NonNull public static Intent createInstallIntent() { Intent intent = new Intent(ACTION_INSTALL); intent.setClassName(CERT_INSTALLER_PACKAGE, @@ -261,10 +255,17 @@ public final class KeyChain { * @param alias The alias to preselect if available, or null if * unavailable. */ - public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response, - @KeyStoreKeyProperties.AlgorithmEnum String[] keyTypes, Principal[] issuers, - String host, int port, String alias) { - choosePrivateKeyAlias(activity, response, keyTypes, issuers, host, port, null, alias); + public static void choosePrivateKeyAlias(@NonNull Activity activity, + @NonNull KeyChainAliasCallback response, + @KeyProperties.KeyAlgorithmEnum String[] keyTypes, Principal[] issuers, + @Nullable String host, int port, @Nullable String alias) { + Uri uri = null; + if (host != null) { + uri = new Uri.Builder() + .authority(host + (port != -1 ? ":" + port : "")) + .build(); + } + choosePrivateKeyAlias(activity, response, keyTypes, issuers, uri, alias); } /** @@ -297,18 +298,15 @@ public final class KeyChain { * "EC" or "RSA", or a null array. * @param issuers The acceptable certificate issuers for the * certificate matching the private key, or null. - * @param host The host name of the server requesting the - * certificate, or null if unavailable. - * @param port The port number of the server requesting the - * certificate, or -1 if unavailable. - * @param url The full url the server is requesting the certificate + * @param uri The full URI the server is requesting the certificate * for, or null if unavailable. * @param alias The alias to preselect if available, or null if * unavailable. */ - public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response, - @KeyStoreKeyProperties.AlgorithmEnum String[] keyTypes, Principal[] issuers, - String host, int port, String url, String alias) { + public static void choosePrivateKeyAlias(@NonNull Activity activity, + @NonNull KeyChainAliasCallback response, + @KeyProperties.KeyAlgorithmEnum String[] keyTypes, Principal[] issuers, + @Nullable Uri uri, @Nullable String alias) { /* * TODO currently keyTypes, issuers are unused. They are meant * to follow the semantics and purpose of X509KeyManager @@ -334,9 +332,7 @@ public final class KeyChain { Intent intent = new Intent(ACTION_CHOOSER); intent.setPackage(KEYCHAIN_PACKAGE); intent.putExtra(EXTRA_RESPONSE, new AliasResponse(response)); - intent.putExtra(EXTRA_HOST, host); - intent.putExtra(EXTRA_PORT, port); - intent.putExtra(EXTRA_URL, url); + intent.putExtra(EXTRA_URI, uri); intent.putExtra(EXTRA_ALIAS, alias); // the PendingIntent is used to get calling package name intent.putExtra(EXTRA_SENDER, PendingIntent.getActivity(activity, 0, new Intent(), 0)); @@ -361,7 +357,8 @@ public final class KeyChain { * returned via {@link KeyChainAliasCallback#alias}. * @throws KeyChainException if the alias was valid but there was some problem accessing it. */ - public static PrivateKey getPrivateKey(Context context, String alias) + @Nullable + public static PrivateKey getPrivateKey(@NonNull Context context, @NonNull String alias) throws KeyChainException, InterruptedException { if (alias == null) { throw new NullPointerException("alias == null"); @@ -396,8 +393,9 @@ public final class KeyChain { * returned via {@link KeyChainAliasCallback#alias}. * @throws KeyChainException if the alias was valid but there was some problem accessing it. */ - public static X509Certificate[] getCertificateChain(Context context, String alias) - throws KeyChainException, InterruptedException { + @Nullable + public static X509Certificate[] getCertificateChain(@NonNull Context context, + @NonNull String alias) throws KeyChainException, InterruptedException { if (alias == null) { throw new NullPointerException("alias == null"); } @@ -432,10 +430,10 @@ public final class KeyChain { * "RSA"). */ public static boolean isKeyAlgorithmSupported( - @KeyStoreKeyProperties.AlgorithmEnum String algorithm) { + @NonNull @KeyProperties.KeyAlgorithmEnum String algorithm) { final String algUpper = algorithm.toUpperCase(Locale.US); - return KeyStoreKeyProperties.Algorithm.EC.equals(algUpper) - || KeyStoreKeyProperties.Algorithm.RSA.equals(algUpper); + return KeyProperties.KEY_ALGORITHM_EC.equals(algUpper) + || KeyProperties.KEY_ALGORITHM_RSA.equals(algUpper); } /** @@ -446,7 +444,7 @@ public final class KeyChain { * that makes it non-exportable. */ public static boolean isBoundKeyAlgorithm( - @KeyStoreKeyProperties.AlgorithmEnum String algorithm) { + @NonNull @KeyProperties.KeyAlgorithmEnum String algorithm) { if (!isKeyAlgorithmSupported(algorithm)) { return false; } @@ -455,7 +453,8 @@ public final class KeyChain { } /** @hide */ - public static X509Certificate toCertificate(byte[] bytes) { + @NonNull + public static X509Certificate toCertificate(@NonNull byte[] bytes) { if (bytes == null) { throw new IllegalArgumentException("bytes == null"); } @@ -496,14 +495,14 @@ public final class KeyChain { * * Caller should call unbindService on the result when finished. */ - public static KeyChainConnection bind(Context context) throws InterruptedException { + public static KeyChainConnection bind(@NonNull Context context) throws InterruptedException { return bindAsUser(context, Process.myUserHandle()); } /** * @hide */ - public static KeyChainConnection bindAsUser(Context context, UserHandle user) + public static KeyChainConnection bindAsUser(@NonNull Context context, UserHandle user) throws InterruptedException { if (context == null) { throw new NullPointerException("context == null"); @@ -537,7 +536,7 @@ public final class KeyChain { return new KeyChainConnection(context, keyChainServiceConnection, q.take()); } - private static void ensureNotOnMainThread(Context context) { + private static void ensureNotOnMainThread(@NonNull Context context) { Looper looper = Looper.myLooper(); if (looper != null && looper == context.getMainLooper()) { throw new IllegalStateException( diff --git a/keystore/java/android/security/KeyChainAliasCallback.java b/keystore/java/android/security/KeyChainAliasCallback.java index 2500863..8e41377 100644 --- a/keystore/java/android/security/KeyChainAliasCallback.java +++ b/keystore/java/android/security/KeyChainAliasCallback.java @@ -15,6 +15,8 @@ */ package android.security; +import android.annotation.Nullable; + /** * The KeyChainAliasCallback is the callback for {@link * KeyChain#choosePrivateKeyAlias}. @@ -25,5 +27,5 @@ public interface KeyChainAliasCallback { * Called with the alias of the certificate chosen by the user, or * null if no value was chosen. */ - public void alias(String alias); + public void alias(@Nullable String alias); } diff --git a/keystore/java/android/security/KeyGeneratorSpec.java b/keystore/java/android/security/KeyGeneratorSpec.java deleted file mode 100644 index 729646d..0000000 --- a/keystore/java/android/security/KeyGeneratorSpec.java +++ /dev/null @@ -1,462 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security; - -import android.content.Context; -import android.text.TextUtils; - -import java.security.spec.AlgorithmParameterSpec; -import java.util.Date; - -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; - -/** - * {@link AlgorithmParameterSpec} for initializing a {@code KeyGenerator} that works with - * <a href="{@docRoot}training/articles/keystore.html">Android KeyStore facility</a>. - * - * <p>The Android KeyStore facility is accessed through a {@link KeyGenerator} API using the - * {@code AndroidKeyStore} provider. The {@code context} passed in may be used to pop up some UI to - * ask the user to unlock or initialize the Android KeyStore facility. - * - * <p>After generation, the {@code keyStoreAlias} is used with the - * {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter)} - * interface to retrieve the {@link SecretKey}. - */ -public class KeyGeneratorSpec implements AlgorithmParameterSpec { - - private final Context mContext; - private final String mKeystoreAlias; - private final int mFlags; - private final int mKeySize; - private final Date mKeyValidityStart; - private final Date mKeyValidityForOriginationEnd; - private final Date mKeyValidityForConsumptionEnd; - private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; - private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; - private final boolean mRandomizedEncryptionRequired; - private final boolean mUserAuthenticationRequired; - private final int mUserAuthenticationValidityDurationSeconds; - - private KeyGeneratorSpec( - Context context, - String keyStoreAlias, - int flags, - int keySize, - Date keyValidityStart, - Date keyValidityForOriginationEnd, - Date keyValidityForConsumptionEnd, - @KeyStoreKeyProperties.PurposeEnum int purposes, - @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, - @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, - boolean randomizedEncryptionRequired, - boolean userAuthenticationRequired, - int userAuthenticationValidityDurationSeconds) { - if (context == null) { - throw new IllegalArgumentException("context == null"); - } else if (TextUtils.isEmpty(keyStoreAlias)) { - throw new IllegalArgumentException("keyStoreAlias must not be empty"); - } else if ((userAuthenticationValidityDurationSeconds < 0) - && (userAuthenticationValidityDurationSeconds != -1)) { - throw new IllegalArgumentException( - "userAuthenticationValidityDurationSeconds must not be negative"); - } - - mContext = context; - mKeystoreAlias = keyStoreAlias; - mFlags = flags; - mKeySize = keySize; - mKeyValidityStart = keyValidityStart; - mKeyValidityForOriginationEnd = keyValidityForOriginationEnd; - mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd; - mPurposes = purposes; - mEncryptionPaddings = - ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings)); - mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes)); - mRandomizedEncryptionRequired = randomizedEncryptionRequired; - mUserAuthenticationRequired = userAuthenticationRequired; - mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; - } - - /** - * Gets the Android context used for operations with this instance. - */ - public Context getContext() { - return mContext; - } - - /** - * Returns the alias that will be used in the {@code java.security.KeyStore} in conjunction with - * the {@code AndroidKeyStore}. - */ - public String getKeystoreAlias() { - return mKeystoreAlias; - } - - /** - * @hide - */ - public int getFlags() { - return mFlags; - } - - /** - * Returns the requested key size or {@code -1} if default size should be used. - */ - public int getKeySize() { - return mKeySize; - } - - /** - * Gets the time instant before which the key is not yet valid. - * - * @return instant or {@code null} if not restricted. - */ - public Date getKeyValidityStart() { - return mKeyValidityStart; - } - - /** - * Gets the time instant after which the key is no longer valid for decryption and verification. - * - * @return instant or {@code null} if not restricted. - */ - public Date getKeyValidityForConsumptionEnd() { - return mKeyValidityForConsumptionEnd; - } - - /** - * Gets the time instant after which the key is no longer valid for encryption and signing. - * - * @return instant or {@code null} if not restricted. - */ - public Date getKeyValidityForOriginationEnd() { - return mKeyValidityForOriginationEnd; - } - - /** - * Gets the set of purposes for which the key can be used. - */ - public @KeyStoreKeyProperties.PurposeEnum int getPurposes() { - return mPurposes; - } - - /** - * Gets the set of padding schemes with which the key can be used when encrypting/decrypting. - */ - public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { - return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); - } - - /** - * Gets the set of block modes with which the key can be used. - */ - public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { - return ArrayUtils.cloneIfNotEmpty(mBlockModes); - } - - /** - * Returns {@code true} if encryption using this key must be sufficiently randomized to produce - * different ciphertexts for the same plaintext every time. The formal cryptographic property - * being required is <em>indistinguishability under chosen-plaintext attack ({@code - * IND-CPA})</em>. This property is important because it mitigates several classes of - * weaknesses due to which ciphertext may leak information about plaintext. For example, if a - * given plaintext always produces the same ciphertext, an attacker may see the repeated - * ciphertexts and be able to deduce something about the plaintext. - */ - public boolean isRandomizedEncryptionRequired() { - return mRandomizedEncryptionRequired; - } - - /** - * Returns {@code true} if user authentication is required for this key to be used. - * - * @see #getUserAuthenticationValidityDurationSeconds() - */ - public boolean isUserAuthenticationRequired() { - return mUserAuthenticationRequired; - } - - /** - * Gets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. This has effect only if user authentication is required. - * - * @return duration in seconds or {@code -1} if authentication is required for every use of the - * key. - * - * @see #isUserAuthenticationRequired() - */ - public int getUserAuthenticationValidityDurationSeconds() { - return mUserAuthenticationValidityDurationSeconds; - } - - /** - * Returns {@code true} if the key must be encrypted in the {@link java.security.KeyStore}. - */ - public boolean isEncryptionRequired() { - return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0; - } - - public static class Builder { - private final Context mContext; - private String mKeystoreAlias; - private int mFlags; - private int mKeySize = -1; - private Date mKeyValidityStart; - private Date mKeyValidityForOriginationEnd; - private Date mKeyValidityForConsumptionEnd; - private @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; - private @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; - private boolean mRandomizedEncryptionRequired = true; - private boolean mUserAuthenticationRequired; - private int mUserAuthenticationValidityDurationSeconds = -1; - - /** - * Creates a new instance of the {@code Builder} with the given {@code context}. The - * {@code context} passed in may be used to pop up some UI to ask the user to unlock or - * initialize the Android KeyStore facility. - */ - public Builder(Context context) { - if (context == null) { - throw new NullPointerException("context == null"); - } - mContext = context; - } - - /** - * Sets the alias to be used to retrieve the key later from a {@link java.security.KeyStore} - * instance using the {@code AndroidKeyStore} provider. - * - * <p>The alias must be provided. There is no default. - */ - public Builder setAlias(String alias) { - if (alias == null) { - throw new NullPointerException("alias == null"); - } - mKeystoreAlias = alias; - return this; - } - - /** - * Sets the size (in bits) of the key to be generated. - * - * <p>By default, the key size will be determines based on the key algorithm. For example, - * for {@code HmacSHA256}, the key size will default to {@code 256}. - */ - public Builder setKeySize(int keySize) { - mKeySize = keySize; - return this; - } - - /** - * Indicates that this key must be encrypted at rest on storage. Note that enabling this - * will require that the user enable a strong lock screen (e.g., PIN, password) before - * creating or using the generated key is successful. - */ - public Builder setEncryptionRequired(boolean required) { - if (required) { - mFlags |= KeyStore.FLAG_ENCRYPTED; - } else { - mFlags &= ~KeyStore.FLAG_ENCRYPTED; - } - return this; - } - - /** - * Sets the time instant before which the key is not yet valid. - * - * <p>By default, the key is valid at any instant. - * - * @see #setKeyValidityEnd(Date) - */ - public Builder setKeyValidityStart(Date startDate) { - mKeyValidityStart = startDate; - return this; - } - - /** - * Sets the time instant after which the key is no longer valid. - * - * <p>By default, the key is valid at any instant. - * - * @see #setKeyValidityStart(Date) - * @see #setKeyValidityForConsumptionEnd(Date) - * @see #setKeyValidityForOriginationEnd(Date) - */ - public Builder setKeyValidityEnd(Date endDate) { - setKeyValidityForOriginationEnd(endDate); - setKeyValidityForConsumptionEnd(endDate); - return this; - } - - /** - * Sets the time instant after which the key is no longer valid for encryption and signing. - * - * <p>By default, the key is valid at any instant. - * - * @see #setKeyValidityForConsumptionEnd(Date) - */ - public Builder setKeyValidityForOriginationEnd(Date endDate) { - mKeyValidityForOriginationEnd = endDate; - return this; - } - - /** - * Sets the time instant after which the key is no longer valid for decryption and - * verification. - * - * <p>By default, the key is valid at any instant. - * - * @see #setKeyValidityForOriginationEnd(Date) - */ - public Builder setKeyValidityForConsumptionEnd(Date endDate) { - mKeyValidityForConsumptionEnd = endDate; - return this; - } - - /** - * Sets the set of purposes for which the key can be used. - * - * <p>This must be specified for all keys. There is no default. - */ - public Builder setPurposes(@KeyStoreKeyProperties.PurposeEnum int purposes) { - mPurposes = purposes; - return this; - } - - /** - * Sets the set of padding schemes with which the key can be used when - * encrypting/decrypting. Attempts to use the key with any other padding scheme will be - * rejected. - * - * <p>This must be specified for keys which are used for encryption/decryption. - */ - public Builder setEncryptionPaddings( - @KeyStoreKeyProperties.EncryptionPaddingEnum String... paddings) { - mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings); - return this; - } - - /** - * Sets the set of block modes with which the key can be used when encrypting/decrypting. - * Attempts to use the key with any other block modes will be rejected. - * - * <p>This must be specified for encryption/decryption keys. - */ - public Builder setBlockModes(@KeyStoreKeyProperties.BlockModeEnum String... blockModes) { - mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes); - return this; - } - - /** - * Sets whether encryption using this key must be sufficiently randomized to produce - * different ciphertexts for the same plaintext every time. The formal cryptographic - * property being required is <em>indistinguishability under chosen-plaintext attack - * ({@code IND-CPA})</em>. This property is important because it mitigates several classes - * of weaknesses due to which ciphertext may leak information about plaintext. For example, - * if a given plaintext always produces the same ciphertext, an attacker may see the - * repeated ciphertexts and be able to deduce something about the plaintext. - * - * <p>By default, {@code IND-CPA} is required. - * - * <p>When {@code IND-CPA} is required: - * <ul> - * <li>block modes which do not offer {@code IND-CPA}, such as {@code ECB}, are prohibited; - * </li> - * <li>in block modes which use an IV, such as {@code CBC}, {@code CTR}, and {@code GCM}, - * caller-provided IVs are rejected when encrypting, to ensure that only random IVs are - * used.</li> - * - * <p>Before disabling this requirement, consider the following approaches instead: - * <ul> - * <li>If you are generating a random IV for encryption and then initializing a {@code} - * Cipher using the IV, the solution is to let the {@code Cipher} generate a random IV - * instead. This will occur if the {@code Cipher} is initialized for encryption without an - * IV. The IV can then be queried via {@link Cipher#getIV()}.</li> - * <li>If you are generating a non-random IV (e.g., an IV derived from something not fully - * random, such as the name of the file being encrypted, or transaction ID, or password, - * or a device identifier), consider changing your design to use a random IV which will then - * be provided in addition to the ciphertext to the entities which need to decrypt the - * ciphertext.</li> - * </ul> - */ - public Builder setRandomizedEncryptionRequired(boolean required) { - mRandomizedEncryptionRequired = required; - return this; - } - - /** - * Sets whether user authentication is required to use this key. - * - * <p>By default, the key can be used without user authentication. - * - * <p>When user authentication is required, the user authorizes the use of the key by - * authenticating to this Android device using a subset of their secure lock screen - * credentials. Different authentication methods are used depending on whether the every - * use of the key must be authenticated (as specified by - * {@link #setUserAuthenticationValidityDurationSeconds(int)}). - * <a href="{@docRoot}training/articles/keystore.html#UserAuthentication">More - * information</a>. - * - * @see #setUserAuthenticationValidityDurationSeconds(int) - */ - public Builder setUserAuthenticationRequired(boolean required) { - mUserAuthenticationRequired = required; - return this; - } - - /** - * Sets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. This has effect only if user authentication is required. - * - * <p>By default, the user needs to authenticate for every use of the key. - * - * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for - * every use of the key. - * - * @see #setUserAuthenticationRequired(boolean) - */ - public Builder setUserAuthenticationValidityDurationSeconds(int seconds) { - mUserAuthenticationValidityDurationSeconds = seconds; - return this; - } - - /** - * Builds a new instance instance of {@code KeyGeneratorSpec}. - * - * @throws IllegalArgumentException if a required field is missing or violates a constraint. - */ - public KeyGeneratorSpec build() { - return new KeyGeneratorSpec(mContext, - mKeystoreAlias, - mFlags, - mKeySize, - mKeyValidityStart, - mKeyValidityForOriginationEnd, - mKeyValidityForConsumptionEnd, - mPurposes, - mEncryptionPaddings, - mBlockModes, - mRandomizedEncryptionRequired, - mUserAuthenticationRequired, - mUserAuthenticationValidityDurationSeconds); - } - } -} diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java index 25c61fd..efbce41 100644 --- a/keystore/java/android/security/KeyPairGeneratorSpec.java +++ b/keystore/java/android/security/KeyPairGeneratorSpec.java @@ -16,7 +16,12 @@ package android.security; +import android.app.KeyguardManager; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; import android.text.TextUtils; import java.math.BigInteger; @@ -49,14 +54,12 @@ import javax.security.auth.x500.X500Principal; * <p> * The self-signed X.509 certificate may be replaced at a later time by a * certificate signed by a real Certificate Authority. + * + * @deprecated Use {@link KeyGenParameterSpec} instead. */ +@Deprecated public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { - private static final X500Principal DEFAULT_CERT_SUBJECT = new X500Principal("CN=fake"); - private static final BigInteger DEFAULT_CERT_SERIAL_NUMBER = new BigInteger("1"); - private static final Date DEFAULT_CERT_NOT_BEFORE = new Date(0L); // Jan 1 1970 - private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048 - private final Context mContext; private final String mKeystoreAlias; @@ -77,28 +80,6 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { private final int mFlags; - private final Date mKeyValidityStart; - - private final Date mKeyValidityForOriginationEnd; - - private final Date mKeyValidityForConsumptionEnd; - - private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - - private final @KeyStoreKeyProperties.DigestEnum String[] mDigests; - - private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; - - private final @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; - - private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; - - private final boolean mRandomizedEncryptionRequired; - - private final boolean mUserAuthenticationRequired; - - private final int mUserAuthenticationValidityDurationSeconds; - /** * Parameter specification for the "{@code AndroidKeyPairGenerator}" * instance of the {@link java.security.KeyPairGenerator} API. The @@ -119,7 +100,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * @param context Android context for the activity * @param keyStoreAlias name to use for the generated key in the Android * keystore - * @param keyType key algorithm to use (EC, RSA) + * @param keyType key algorithm to use (RSA, DSA, EC) * @param keySize size of key to generate * @param spec the underlying key type parameters * @param subjectDN X.509 v3 Subject Distinguished Name @@ -133,39 +114,21 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { */ public KeyPairGeneratorSpec(Context context, String keyStoreAlias, String keyType, int keySize, AlgorithmParameterSpec spec, X500Principal subjectDN, BigInteger serialNumber, - Date startDate, Date endDate, int flags, - Date keyValidityStart, - Date keyValidityForOriginationEnd, - Date keyValidityForConsumptionEnd, - @KeyStoreKeyProperties.PurposeEnum int purposes, - @KeyStoreKeyProperties.DigestEnum String[] digests, - @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, - @KeyStoreKeyProperties.SignaturePaddingEnum String[] signaturePaddings, - @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, - boolean randomizedEncryptionRequired, - boolean userAuthenticationRequired, - int userAuthenticationValidityDurationSeconds) { + Date startDate, Date endDate, int flags) { if (context == null) { throw new IllegalArgumentException("context == null"); } else if (TextUtils.isEmpty(keyStoreAlias)) { throw new IllegalArgumentException("keyStoreAlias must not be empty"); - } else if ((userAuthenticationValidityDurationSeconds < 0) - && (userAuthenticationValidityDurationSeconds != -1)) { - throw new IllegalArgumentException( - "userAuthenticationValidityDurationSeconds must not be negative"); - } - - if (subjectDN == null) { - subjectDN = DEFAULT_CERT_SUBJECT; - } - if (startDate == null) { - startDate = DEFAULT_CERT_NOT_BEFORE; - } - if (endDate == null) { - endDate = DEFAULT_CERT_NOT_AFTER; - } - if (serialNumber == null) { - serialNumber = DEFAULT_CERT_SERIAL_NUMBER; + } else if (subjectDN == null) { + throw new IllegalArgumentException("subjectDN == null"); + } else if (serialNumber == null) { + throw new IllegalArgumentException("serialNumber == null"); + } else if (startDate == null) { + throw new IllegalArgumentException("startDate == null"); + } else if (endDate == null) { + throw new IllegalArgumentException("endDate == null"); + } else if (endDate.before(startDate)) { + throw new IllegalArgumentException("endDate < startDate"); } if (endDate.before(startDate)) { @@ -182,50 +145,6 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { mStartDate = startDate; mEndDate = endDate; mFlags = flags; - mKeyValidityStart = keyValidityStart; - mKeyValidityForOriginationEnd = keyValidityForOriginationEnd; - mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd; - mPurposes = purposes; - mDigests = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(digests)); - mEncryptionPaddings = - ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings)); - mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(signaturePaddings)); - mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes)); - mRandomizedEncryptionRequired = randomizedEncryptionRequired; - mUserAuthenticationRequired = userAuthenticationRequired; - mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; - } - - /** - * TODO: Remove this constructor once tests are switched over to the new one above. - * @hide - */ - public KeyPairGeneratorSpec(Context context, String keyStoreAlias, String keyType, int keySize, - AlgorithmParameterSpec spec, X500Principal subjectDN, BigInteger serialNumber, - Date startDate, Date endDate, int flags) { - - this(context, - keyStoreAlias, - keyType, - keySize, - spec, - subjectDN, - serialNumber, - startDate, - endDate, - flags, - startDate, - endDate, - endDate, - 0, // purposes - null, // digests - null, // encryption paddings - null, // signature paddings - null, // block modes - false, // randomized encryption required - false, // user authentication required - -1 // user authentication validity duration (seconds) - ); } /** @@ -244,9 +163,11 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { } /** - * Returns the key type (e.g., "EC", "RSA") specified by this parameter. + * Returns the type of key pair (e.g., {@code EC}, {@code RSA}) to be generated. See + * {@link KeyProperties}.{@code KEY_ALGORITHM} constants. */ - public @KeyStoreKeyProperties.AlgorithmEnum String getKeyType() { + @Nullable + public @KeyProperties.KeyAlgorithmEnum String getKeyType() { return mKeyType; } @@ -263,6 +184,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * Returns the {@link AlgorithmParameterSpec} that will be used for creation * of the key pair. */ + @NonNull public AlgorithmParameterSpec getAlgorithmParameterSpec() { return mSpec; } @@ -271,6 +193,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * Gets the subject distinguished name to be used on the X.509 certificate * that will be put in the {@link java.security.KeyStore}. */ + @NonNull public X500Principal getSubjectDN() { return mSubjectDN; } @@ -279,6 +202,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * Gets the serial number to be used on the X.509 certificate that will be * put in the {@link java.security.KeyStore}. */ + @NonNull public BigInteger getSerialNumber() { return mSerialNumber; } @@ -287,6 +211,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * Gets the start date to be used on the X.509 certificate that will be put * in the {@link java.security.KeyStore}. */ + @NonNull public Date getStartDate() { return mStartDate; } @@ -295,6 +220,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * Gets the end date to be used on the X.509 certificate that will be put in * the {@link java.security.KeyStore}. */ + @NonNull public Date getEndDate() { return mEndDate; } @@ -302,120 +228,24 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { /** * @hide */ - int getFlags() { + public int getFlags() { return mFlags; } /** - * Returns {@code true} if this parameter will require generated keys to be - * encrypted in the {@link java.security.KeyStore}. - */ - public boolean isEncryptionRequired() { - return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0; - } - - /** - * Gets the time instant before which the key pair is not yet valid. + * Returns {@code true} if the key must be encrypted at rest. This will protect the key pair + * with the secure lock screen credential (e.g., password, PIN, or pattern). * - * @return instant or {@code null} if not restricted. - */ - public Date getKeyValidityStart() { - return mKeyValidityStart; - } - - /** - * Gets the time instant after which the key pair is no longer valid for decryption and - * verification. - * - * @return instant or {@code null} if not restricted. - */ - public Date getKeyValidityForConsumptionEnd() { - return mKeyValidityForConsumptionEnd; - } - - /** - * Gets the time instant after which the key pair is no longer valid for encryption and signing. + * <p>Note that encrypting the key at rest requires that the secure lock screen (e.g., password, + * PIN, pattern) is set up, otherwise key generation will fail. Moreover, this key will be + * deleted when the secure lock screen is disabled or reset (e.g., by the user or a Device + * Administrator). Finally, this key cannot be used until the user unlocks the secure lock + * screen after boot. * - * @return instant or {@code null} if not restricted. + * @see KeyguardManager#isDeviceSecure() */ - public Date getKeyValidityForOriginationEnd() { - return mKeyValidityForOriginationEnd; - } - - /** - * Gets the set of purposes for which the key can be used. - */ - public @KeyStoreKeyProperties.PurposeEnum int getPurposes() { - return mPurposes; - } - - /** - * Gets the set of digest algorithms with which the key can be used. - */ - public @KeyStoreKeyProperties.DigestEnum String[] getDigests() { - return ArrayUtils.cloneIfNotEmpty(mDigests); - } - - /** - * Gets the set of padding schemes with which the key can be used when encrypting/decrypting. - */ - public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { - return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); - } - - /** - * Gets the set of padding schemes with which the key can be used when signing/verifying. - */ - public @KeyStoreKeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { - return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings); - } - - /** - * Gets the set of block modes with which the key can be used. - */ - public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { - return ArrayUtils.cloneIfNotEmpty(mBlockModes); - } - - /** - * Returns {@code true} if encryption using this key must be sufficiently randomized to produce - * different ciphertexts for the same plaintext every time. The formal cryptographic property - * being required is <em>indistinguishability under chosen-plaintext attack ({@code - * IND-CPA})</em>. This property is important because it mitigates several classes of - * weaknesses due to which ciphertext may leak information about plaintext. For example, if a - * given plaintext always produces the same ciphertext, an attacker may see the repeated - * ciphertexts and be able to deduce something about the plaintext. - */ - public boolean isRandomizedEncryptionRequired() { - return mRandomizedEncryptionRequired; - } - - /** - * Returns {@code true} if user authentication is required for this key to be used. - * - * <p>This restriction applies only to private key operations. Public key operations are not - * restricted. - * - * @see #getUserAuthenticationValidityDurationSeconds() - */ - public boolean isUserAuthenticationRequired() { - return mUserAuthenticationRequired; - } - - /** - * Gets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. This has effect only if user authentication is required. - * - * <p>This restriction applies only to private key operations. Public key operations are not - * restricted. - * - * @return duration in seconds or {@code -1} if authentication is required for every use of the - * key. - * - * @see #isUserAuthenticationRequired() - */ - public int getUserAuthenticationValidityDurationSeconds() { - return mUserAuthenticationValidityDurationSeconds; + public boolean isEncryptionRequired() { + return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0; } /** @@ -438,7 +268,10 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * .setSubject(new X500Principal("CN=myKey")).setSerial(BigInteger.valueOf(1337)) * .setStartDate(start.getTime()).setEndDate(end.getTime()).build(); * </pre> + * + * @deprecated Use {@link KeyGenParameterSpec.Builder} instead. */ + @Deprecated public final static class Builder { private final Context mContext; @@ -460,35 +293,13 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { private int mFlags; - private Date mKeyValidityStart; - - private Date mKeyValidityForOriginationEnd; - - private Date mKeyValidityForConsumptionEnd; - - private @KeyStoreKeyProperties.PurposeEnum int mPurposes; - - private @KeyStoreKeyProperties.DigestEnum String[] mDigests; - - private @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; - - private @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; - - private @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; - - private boolean mRandomizedEncryptionRequired = true; - - private boolean mUserAuthenticationRequired; - - private int mUserAuthenticationValidityDurationSeconds = -1; - /** * Creates a new instance of the {@code Builder} with the given * {@code context}. The {@code context} passed in may be used to pop up * some UI to ask the user to unlock or initialize the Android KeyStore * facility. */ - public Builder(Context context) { + public Builder(@NonNull Context context) { if (context == null) { throw new NullPointerException("context == null"); } @@ -500,7 +311,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * {@link java.security.KeyStore} instance using the * {@code AndroidKeyStore} provider. */ - public Builder setAlias(String alias) { + @NonNull + public Builder setAlias(@NonNull String alias) { if (alias == null) { throw new NullPointerException("alias == null"); } @@ -509,9 +321,12 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { } /** - * Sets the key type (e.g., EC, RSA) of the keypair to be created. + * Sets the type of key pair (e.g., {@code EC}, {@code RSA}) of the key pair to be + * generated. See {@link KeyProperties}.{@code KEY_ALGORITHM} constants. + * */ - public Builder setKeyType(@KeyStoreKeyProperties.AlgorithmEnum String keyType) + @NonNull + public Builder setKeyType(@NonNull @KeyProperties.KeyAlgorithmEnum String keyType) throws NoSuchAlgorithmException { if (keyType == null) { throw new NullPointerException("keyType == null"); @@ -529,6 +344,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * key type of RSA this will set the modulus size and for a key type of * EC it will select a curve with a matching field size. */ + @NonNull public Builder setKeySize(int keySize) { if (keySize < 0) { throw new IllegalArgumentException("keySize < 0"); @@ -541,7 +357,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * Sets the algorithm-specific key generation parameters. For example, for RSA keys * this may be an instance of {@link java.security.spec.RSAKeyGenParameterSpec}. */ - public Builder setAlgorithmParameterSpec(AlgorithmParameterSpec spec) { + public Builder setAlgorithmParameterSpec(@NonNull AlgorithmParameterSpec spec) { if (spec == null) { throw new NullPointerException("spec == null"); } @@ -552,12 +368,9 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { /** * Sets the subject used for the self-signed certificate of the * generated key pair. - * - * <p>The subject must be specified on API Level - * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1 LOLLIPOP_MR1} and older platforms. On - * newer platforms the subject defaults to {@code CN=fake} if not specified. */ - public Builder setSubject(X500Principal subject) { + @NonNull + public Builder setSubject(@NonNull X500Principal subject) { if (subject == null) { throw new NullPointerException("subject == null"); } @@ -568,12 +381,9 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { /** * Sets the serial number used for the self-signed certificate of the * generated key pair. - * - * <p>The serial number must be specified on API Level - * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1 LOLLIPOP_MR1} and older platforms. On - * newer platforms the serial number defaults to {@code 1} if not specified. */ - public Builder setSerialNumber(BigInteger serialNumber) { + @NonNull + public Builder setSerialNumber(@NonNull BigInteger serialNumber) { if (serialNumber == null) { throw new NullPointerException("serialNumber == null"); } @@ -584,12 +394,9 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { /** * Sets the start of the validity period for the self-signed certificate * of the generated key pair. - * - * <p>The date must be specified on API Level - * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1 LOLLIPOP_MR1} and older platforms. On - * newer platforms the date defaults to {@code Jan 1 1970} if not specified. */ - public Builder setStartDate(Date startDate) { + @NonNull + public Builder setStartDate(@NonNull Date startDate) { if (startDate == null) { throw new NullPointerException("startDate == null"); } @@ -600,12 +407,9 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { /** * Sets the end of the validity period for the self-signed certificate * of the generated key pair. - * - * <p>The date must be specified on API Level - * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1 LOLLIPOP_MR1} and older platforms. On - * newer platforms the date defaults to {@code Jan 1 2048} if not specified. */ - public Builder setEndDate(Date endDate) { + @NonNull + public Builder setEndDate(@NonNull Date endDate) { if (endDate == null) { throw new NullPointerException("endDate == null"); } @@ -614,214 +418,20 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { } /** - * Indicates that this key must be encrypted at rest on storage. Note - * that enabling this will require that the user enable a strong lock - * screen (e.g., PIN, password) before creating or using the generated - * key is successful. - */ - public Builder setEncryptionRequired() { - mFlags |= KeyStore.FLAG_ENCRYPTED; - return this; - } - - /** - * Sets the time instant before which the key is not yet valid. - * - * <p>By default, the key is valid at any instant. - * - * <p><b>NOTE: This has currently no effect. - * - * @see #setKeyValidityEnd(Date) - */ - public Builder setKeyValidityStart(Date startDate) { - mKeyValidityStart = startDate; - return this; - } - - /** - * Sets the time instant after which the key is no longer valid. - * - * <p>By default, the key is valid at any instant. - * - * <p><b>NOTE: This has currently no effect. - * - * @see #setKeyValidityStart(Date) - * @see #setKeyValidityForConsumptionEnd(Date) - * @see #setKeyValidityForOriginationEnd(Date) - */ - public Builder setKeyValidityEnd(Date endDate) { - setKeyValidityForOriginationEnd(endDate); - setKeyValidityForConsumptionEnd(endDate); - return this; - } - - /** - * Sets the time instant after which the key is no longer valid for encryption and signing. - * - * <p>By default, the key is valid at any instant. - * - * <p><b>NOTE: This has currently no effect. - * - * @see #setKeyValidityForConsumptionEnd(Date) - */ - public Builder setKeyValidityForOriginationEnd(Date endDate) { - mKeyValidityForOriginationEnd = endDate; - return this; - } - - /** - * Sets the time instant after which the key is no longer valid for decryption and - * verification. + * Indicates that this key pair must be encrypted at rest. This will protect the key pair + * with the secure lock screen credential (e.g., password, PIN, or pattern). * - * <p>By default, the key is valid at any instant. + * <p>Note that this feature requires that the secure lock screen (e.g., password, PIN, + * pattern) is set up, otherwise key pair generation will fail. Moreover, this key pair will + * be deleted when the secure lock screen is disabled or reset (e.g., by the user or a + * Device Administrator). Finally, this key pair cannot be used until the user unlocks the + * secure lock screen after boot. * - * <p><b>NOTE: This has currently no effect. - * - * @see #setKeyValidityForOriginationEnd(Date) + * @see KeyguardManager#isDeviceSecure() */ - public Builder setKeyValidityForConsumptionEnd(Date endDate) { - mKeyValidityForConsumptionEnd = endDate; - return this; - } - - /** - * Sets the set of purposes for which the key can be used. - * - * <p>This must be specified for all keys. There is no default. - * - * <p><b>NOTE: This has currently no effect. - */ - public Builder setPurposes(@KeyStoreKeyProperties.PurposeEnum int purposes) { - mPurposes = purposes; - return this; - } - - /** - * Sets the set of digests with which the key can be used when signing/verifying. Attempts - * to use the key with any other digest will be rejected. - * - * <p>This must be specified for keys which are used for signing/verification. - * - * <p><b>NOTE: This has currently no effect. - */ - public Builder setDigests(@KeyStoreKeyProperties.DigestEnum String... digests) { - mDigests = ArrayUtils.cloneIfNotEmpty(digests); - return this; - } - - /** - * Sets the set of padding schemes with which the key can be used when - * encrypting/decrypting. Attempts to use the key with any other padding scheme will be - * rejected. - * - * <p>This must be specified for keys which are used for encryption/decryption. - * - * <p><b>NOTE: This has currently no effect. - */ - public Builder setEncryptionPaddings( - @KeyStoreKeyProperties.EncryptionPaddingEnum String... paddings) { - mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings); - return this; - } - - /** - * Sets the set of padding schemes with which the key can be used when - * signing/verifying. Attempts to use the key with any other padding scheme will be - * rejected. - * - * <p>This must be specified for RSA keys which are used for signing/verification. - * - * <p><b>NOTE: This has currently no effect. - */ - public Builder setSignaturePaddings( - @KeyStoreKeyProperties.SignaturePaddingEnum String... paddings) { - mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(paddings); - return this; - } - - /** - * Sets the set of block modes with which the key can be used when encrypting/decrypting. - * Attempts to use the key with any other block modes will be rejected. - * - * <p>This must be specified for encryption/decryption keys. - * - * <p><b>NOTE: This has currently no effect. - */ - public Builder setBlockModes(@KeyStoreKeyProperties.BlockModeEnum String... blockModes) { - mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes); - return this; - } - - /** - * Sets whether encryption using this key must be sufficiently randomized to produce - * different ciphertexts for the same plaintext every time. The formal cryptographic - * property being required is <em>indistinguishability under chosen-plaintext attack - * ({@code IND-CPA})</em>. This property is important because it mitigates several classes - * of weaknesses due to which ciphertext may leak information about plaintext. For example, - * if a given plaintext always produces the same ciphertext, an attacker may see the - * repeated ciphertexts and be able to deduce something about the plaintext. - * - * <p>By default, {@code IND-CPA} is required. - * - * <p>When {@code IND-CPA} is required, encryption/decryption transformations which do not - * offer {@code IND-CPA}, such as RSA without padding, are prohibited. - * - * <p>Before disabling this requirement, consider the following approaches instead: - * <ul> - * <li>If you are using RSA encryption without padding, consider switching to padding - * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li> - * </ul> - * - * <p><b>NOTE: This has currently no effect. - */ - public Builder setRandomizedEncryptionRequired(boolean required) { - mRandomizedEncryptionRequired = required; - return this; - } - - /** - * Sets whether user authentication is required to use this key. - * - * <p>By default, the key can be used without user authentication. - * - * <p>When user authentication is required, the user authorizes the use of the key by - * authenticating to this Android device using a subset of their secure lock screen - * credentials. Different authentication methods are used depending on whether the every - * use of the key must be authenticated (as specified by - * {@link #setUserAuthenticationValidityDurationSeconds(int)}). - * <a href="{@docRoot}training/articles/keystore.html#UserAuthentication">More - * information</a>. - * - * <p>This restriction applies only to private key operations. Public key operations are not - * restricted. - * - * <p><b>NOTE: This has currently no effect. - * - * @see #setUserAuthenticationValidityDurationSeconds(int) - */ - public Builder setUserAuthenticationRequired(boolean required) { - mUserAuthenticationRequired = required; - return this; - } - - /** - * Sets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. This has effect only if user authentication is required. - * - * <p>By default, the user needs to authenticate for every use of the key. - * - * <p>This restriction applies only to private key operations. Public key operations are not - * restricted. - * - * <p><b>NOTE: This has currently no effect. - * - * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for - * every use of the key. - * - * @see #setUserAuthenticationRequired(boolean) - */ - public Builder setUserAuthenticationValidityDurationSeconds(int seconds) { - mUserAuthenticationValidityDurationSeconds = seconds; + @NonNull + public Builder setEncryptionRequired() { + mFlags |= KeyStore.FLAG_ENCRYPTED; return this; } @@ -831,6 +441,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * @throws IllegalArgumentException if a required field is missing * @return built instance of {@code KeyPairGeneratorSpec} */ + @NonNull public KeyPairGeneratorSpec build() { return new KeyPairGeneratorSpec(mContext, mKeystoreAlias, @@ -841,18 +452,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { mSerialNumber, mStartDate, mEndDate, - mFlags, - mKeyValidityStart, - mKeyValidityForOriginationEnd, - mKeyValidityForConsumptionEnd, - mPurposes, - mDigests, - mEncryptionPaddings, - mSignaturePaddings, - mBlockModes, - mRandomizedEncryptionRequired, - mUserAuthenticationRequired, - mUserAuthenticationValidityDurationSeconds); + mFlags); } } } diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 304d277..06f5b06 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -18,20 +18,28 @@ package android.security; import android.app.ActivityThread; import android.app.Application; +import android.app.KeyguardManager; import com.android.org.conscrypt.NativeConstants; import android.content.Context; import android.hardware.fingerprint.FingerprintManager; import android.os.Binder; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.security.keymaster.ExportResult; import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterBlob; import android.security.keymaster.KeymasterDefs; import android.security.keymaster.OperationResult; +import android.security.keystore.KeyExpiredException; +import android.security.keystore.KeyNotYetValidException; +import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.security.keystore.KeyProperties; +import android.security.keystore.UserNotAuthenticatedException; import android.util.Log; import java.security.InvalidKeyException; @@ -71,6 +79,19 @@ public class KeyStore { // Flags for "put" "import" and "generate" public static final int FLAG_NONE = 0; + + /** + * Indicates that this key (or key pair) must be encrypted at rest. This will protect the key + * (or key pair) with the secure lock screen credential (e.g., password, PIN, or pattern). + * + * <p>Note that this requires that the secure lock screen (e.g., password, PIN, pattern) is set + * up, otherwise key (or key pair) generation or import will fail. Moreover, this key (or key + * pair) will be deleted when the secure lock screen is disabled or reset (e.g., by the user or + * a Device Administrator). Finally, this key (or key pair) cannot be used until the user + * unlocks the secure lock screen after boot. + * + * @see KeyguardManager#isDeviceSecure() + */ public static final int FLAG_ENCRYPTED = 1; // States @@ -85,10 +106,10 @@ public class KeyStore { private KeyStore(IKeystoreService binder) { mBinder = binder; - mContext = getContext(); + mContext = getApplicationContext(); } - private static Context getContext() { + public static Context getApplicationContext() { ActivityThread activityThread = ActivityThread.currentActivityThread(); if (activityThread == null) { throw new IllegalStateException( @@ -115,10 +136,10 @@ public class KeyStore { return mToken; } - static int getKeyTypeForAlgorithm(@KeyStoreKeyProperties.AlgorithmEnum String keyType) { - if (KeyStoreKeyProperties.Algorithm.RSA.equalsIgnoreCase(keyType)) { + public static int getKeyTypeForAlgorithm(@KeyProperties.KeyAlgorithmEnum String keyType) { + if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyType)) { return NativeConstants.EVP_PKEY_RSA; - } else if (KeyStoreKeyProperties.Algorithm.EC.equalsIgnoreCase(keyType)) { + } else if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyType)) { return NativeConstants.EVP_PKEY_EC; } else { return -1; @@ -212,15 +233,6 @@ public class KeyStore { } } - public boolean password(String password) { - try { - return mBinder.password(password) == NO_ERROR; - } catch (RemoteException e) { - Log.w(TAG, "Cannot connect to keystore", e); - return false; - } - } - public boolean lock() { try { return mBinder.lock() == NO_ERROR; @@ -230,9 +242,20 @@ public class KeyStore { } } - public boolean unlock(String password) { + /** + * Attempt to unlock the keystore for {@code user} with the password {@code password}. + * This is required before keystore entries created with FLAG_ENCRYPTED can be accessed or + * created. + * + * @param user Android user ID to operate on + * @param password user's keystore password. Should be the most recent value passed to + * {@link #onUserPasswordChanged} for the user. + * + * @return whether the keystore was unlocked. + */ + public boolean unlock(int userId, String password) { try { - mError = mBinder.unlock(password); + mError = mBinder.unlock(userId, password); return mError == NO_ERROR; } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); @@ -240,6 +263,10 @@ public class KeyStore { } } + public boolean unlock(String password) { + return unlock(UserHandle.getUserId(Process.myUid()), password); + } + public boolean isEmpty() { try { return mBinder.zero() == KEY_NOT_FOUND; @@ -540,17 +567,79 @@ public class KeyStore { } /** + * Notify keystore that a user's password has changed. + * + * @param userId the user whose password changed. + * @param newPassword the new password or "" if the password was removed. + */ + public boolean onUserPasswordChanged(int userId, String newPassword) { + // Parcel.cpp doesn't support deserializing null strings and treats them as "". Make that + // explicit here. + if (newPassword == null) { + newPassword = ""; + } + try { + return mBinder.onUserPasswordChanged(userId, newPassword) == NO_ERROR; + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return false; + } + } + + /** + * Notify keystore that a user was added. + * + * @param userId the new user. + * @param parentId the parent of the new user, or -1 if the user has no parent. If parentId is + * specified then the new user's keystore will be intialized with the same secure lockscreen + * password as the parent. + */ + public void onUserAdded(int userId, int parentId) { + try { + mBinder.onUserAdded(userId, parentId); + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + } + } + + /** + * Notify keystore that a user was added. + * + * @param userId the new user. + */ + public void onUserAdded(int userId) { + onUserAdded(userId, -1); + } + + /** + * Notify keystore that a user was removed. + * + * @param userId the removed user. + */ + public void onUserRemoved(int userId) { + try { + mBinder.onUserRemoved(userId); + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + } + } + + public boolean onUserPasswordChanged(String newPassword) { + return onUserPasswordChanged(UserHandle.getUserId(Process.myUid()), newPassword); + } + + /** * Returns a {@link KeyStoreException} corresponding to the provided keystore/keymaster error * code. */ - static KeyStoreException getKeyStoreException(int errorCode) { + public static KeyStoreException getKeyStoreException(int errorCode) { if (errorCode > 0) { // KeyStore layer error switch (errorCode) { case NO_ERROR: return new KeyStoreException(errorCode, "OK"); case LOCKED: - return new KeyStoreException(errorCode, "Keystore locked"); + return new KeyStoreException(errorCode, "User authentication required"); case UNINITIALIZED: return new KeyStoreException(errorCode, "Keystore not initialized"); case SYSTEM_ERROR: @@ -585,8 +674,11 @@ public class KeyStore { * Returns an {@link InvalidKeyException} corresponding to the provided * {@link KeyStoreException}. */ - InvalidKeyException getInvalidKeyException(String keystoreKeyAlias, KeyStoreException e) { + public InvalidKeyException getInvalidKeyException( + String keystoreKeyAlias, KeyStoreException e) { switch (e.getErrorCode()) { + case LOCKED: + return new UserNotAuthenticatedException(); case KeymasterDefs.KM_ERROR_KEY_EXPIRED: return new KeyExpiredException(); case KeymasterDefs.KM_ERROR_KEY_NOT_YET_VALID: @@ -654,7 +746,7 @@ public class KeyStore { * Returns an {@link InvalidKeyException} corresponding to the provided keystore/keymaster error * code. */ - InvalidKeyException getInvalidKeyException(String keystoreKeyAlias, int errorCode) { + public InvalidKeyException getInvalidKeyException(String keystoreKeyAlias, int errorCode) { return getInvalidKeyException(keystoreKeyAlias, getKeyStoreException(errorCode)); } } diff --git a/keystore/java/android/security/KeyStoreParameter.java b/keystore/java/android/security/KeyStoreParameter.java index 8d7a19f..174e03f 100644 --- a/keystore/java/android/security/KeyStoreParameter.java +++ b/keystore/java/android/security/KeyStoreParameter.java @@ -16,17 +16,18 @@ package android.security; +import android.annotation.NonNull; +import android.app.KeyguardManager; import android.content.Context; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProtection; -import java.security.Key; +import java.security.KeyPairGenerator; import java.security.KeyStore.ProtectionParameter; -import java.util.Date; - -import javax.crypto.Cipher; /** - * Parameters specifying how to secure and restrict the use of a key being - * imported into the + * This provides the optional parameters that can be specified for + * {@code KeyStore} entries that work with * <a href="{@docRoot}training/articles/keystore.html">Android KeyStore * facility</a>. The Android KeyStore facility is accessed through a * {@link java.security.KeyStore} API using the {@code AndroidKeyStore} @@ -37,59 +38,29 @@ import javax.crypto.Cipher; * there is only one logical instance of the {@code KeyStore} per application * UID so apps using the {@code sharedUid} facility will also share a * {@code KeyStore}. + * <p> + * Keys may be generated using the {@link KeyPairGenerator} facility with a + * {@link KeyPairGeneratorSpec} to specify the entry's {@code alias}. A + * self-signed X.509 certificate will be attached to generated entries, but that + * may be replaced at a later time by a certificate signed by a real Certificate + * Authority. + * + * @deprecated Use {@link KeyProtection} instead. */ +@Deprecated public final class KeyStoreParameter implements ProtectionParameter { private final Context mContext; private final int mFlags; - private final Date mKeyValidityStart; - private final Date mKeyValidityForOriginationEnd; - private final Date mKeyValidityForConsumptionEnd; - private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; - private final @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; - private final @KeyStoreKeyProperties.DigestEnum String[] mDigests; - private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; - private final boolean mRandomizedEncryptionRequired; - private final boolean mUserAuthenticationRequired; - private final int mUserAuthenticationValidityDurationSeconds; private KeyStoreParameter( Context context, - int flags, - Date keyValidityStart, - Date keyValidityForOriginationEnd, - Date keyValidityForConsumptionEnd, - @KeyStoreKeyProperties.PurposeEnum int purposes, - @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, - @KeyStoreKeyProperties.SignaturePaddingEnum String[] signaturePaddings, - @KeyStoreKeyProperties.DigestEnum String[] digests, - @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, - boolean randomizedEncryptionRequired, - boolean userAuthenticationRequired, - int userAuthenticationValidityDurationSeconds) { + int flags) { if (context == null) { throw new IllegalArgumentException("context == null"); - } else if ((userAuthenticationValidityDurationSeconds < 0) - && (userAuthenticationValidityDurationSeconds != -1)) { - throw new IllegalArgumentException( - "userAuthenticationValidityDurationSeconds must not be negative"); } mContext = context; mFlags = flags; - mKeyValidityStart = keyValidityStart; - mKeyValidityForOriginationEnd = keyValidityForOriginationEnd; - mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd; - mPurposes = purposes; - mEncryptionPaddings = - ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings)); - mSignaturePaddings = - ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(signaturePaddings)); - mDigests = ArrayUtils.cloneIfNotEmpty(digests); - mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes)); - mRandomizedEncryptionRequired = randomizedEncryptionRequired; - mUserAuthenticationRequired = userAuthenticationRequired; - mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; } /** @@ -107,126 +78,20 @@ public final class KeyStoreParameter implements ProtectionParameter { } /** - * Returns {@code true} if this parameter requires entries to be encrypted - * on the disk. - */ - public boolean isEncryptionRequired() { - return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0; - } - - /** - * Gets the time instant before which the key is not yet valid. + * Returns {@code true} if the {@link java.security.KeyStore} entry must be encrypted at rest. + * This will protect the entry with the secure lock screen credential (e.g., password, PIN, or + * pattern). * - * @return instant or {@code null} if not restricted. - */ - public Date getKeyValidityStart() { - return mKeyValidityStart; - } - - /** - * Gets the time instant after which the key is no long valid for decryption and verification. - * - * @return instant or {@code null} if not restricted. - */ - public Date getKeyValidityForConsumptionEnd() { - return mKeyValidityForConsumptionEnd; - } - - /** - * Gets the time instant after which the key is no long valid for encryption and signing. + * <p>Note that encrypting the key at rest requires that the secure lock screen (e.g., password, + * PIN, pattern) is set up, otherwise key generation will fail. Moreover, this key will be + * deleted when the secure lock screen is disabled or reset (e.g., by the user or a Device + * Administrator). Finally, this key cannot be used until the user unlocks the secure lock + * screen after boot. * - * @return instant or {@code null} if not restricted. + * @see KeyguardManager#isDeviceSecure() */ - public Date getKeyValidityForOriginationEnd() { - return mKeyValidityForOriginationEnd; - } - - /** - * Gets the set of purposes for which the key can be used. - */ - public @KeyStoreKeyProperties.PurposeEnum int getPurposes() { - return mPurposes; - } - - /** - * Gets the set of padding schemes with which the key can be used when encrypting/decrypting. - */ - public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { - return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); - } - - /** - * Gets the set of padding schemes with which the key can be used when signing or verifying - * signatures. - */ - public @KeyStoreKeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { - return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings); - } - - /** - * Gets the set of digest algorithms with which the key can be used. - * - * @throws IllegalStateException if this set has not been specified. - * - * @see #isDigestsSpecified() - */ - public @KeyStoreKeyProperties.DigestEnum String[] getDigests() { - if (mDigests == null) { - throw new IllegalStateException("Digests not specified"); - } - return ArrayUtils.cloneIfNotEmpty(mDigests); - } - - /** - * Returns {@code true} if the set of digest algorithms with which the key can be used has been - * specified. - * - * @see #getDigests() - */ - public boolean isDigestsSpecified() { - return mDigests != null; - } - - /** - * Gets the set of block modes with which the key can be used. - */ - public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { - return ArrayUtils.cloneIfNotEmpty(mBlockModes); - } - - /** - * Returns {@code true} if encryption using this key must be sufficiently randomized to produce - * different ciphertexts for the same plaintext every time. The formal cryptographic property - * being required is <em>indistinguishability under chosen-plaintext attack ({@code - * IND-CPA})</em>. This property is important because it mitigates several classes of - * weaknesses due to which ciphertext may leak information about plaintext. For example, if a - * given plaintext always produces the same ciphertext, an attacker may see the repeated - * ciphertexts and be able to deduce something about the plaintext. - */ - public boolean isRandomizedEncryptionRequired() { - return mRandomizedEncryptionRequired; - } - - /** - * Returns {@code true} if user authentication is required for this key to be used. - * - * @see #getUserAuthenticationValidityDurationSeconds() - */ - public boolean isUserAuthenticationRequired() { - return mUserAuthenticationRequired; - } - - /** - * Gets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. This has effect only if user authentication is required. - * - * @return duration in seconds or {@code -1} if authentication is required for every use of the - * key. - * - * @see #isUserAuthenticationRequired() - */ - public int getUserAuthenticationValidityDurationSeconds() { - return mUserAuthenticationValidityDurationSeconds; + public boolean isEncryptionRequired() { + return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0; } /** @@ -245,21 +110,13 @@ public final class KeyStoreParameter implements ProtectionParameter { * .setEncryptionRequired() * .build(); * </pre> + * + * @deprecated Use {@link KeyProtection.Builder} instead. */ + @Deprecated public final static class Builder { private final Context mContext; private int mFlags; - private Date mKeyValidityStart; - private Date mKeyValidityForOriginationEnd; - private Date mKeyValidityForConsumptionEnd; - private @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; - private @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; - private @KeyStoreKeyProperties.DigestEnum String[] mDigests; - private @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; - private boolean mRandomizedEncryptionRequired = true; - private boolean mUserAuthenticationRequired; - private int mUserAuthenticationValidityDurationSeconds = -1; /** * Creates a new instance of the {@code Builder} with the given @@ -267,7 +124,7 @@ public final class KeyStoreParameter implements ProtectionParameter { * some UI to ask the user to unlock or initialize the Android KeyStore * facility. */ - public Builder(Context context) { + public Builder(@NonNull Context context) { if (context == null) { throw new NullPointerException("context == null"); } @@ -275,11 +132,19 @@ public final class KeyStoreParameter implements ProtectionParameter { } /** - * Indicates that this key must be encrypted at rest on storage. Note - * that enabling this will require that the user enable a strong lock - * screen (e.g., PIN, password) before creating or using the generated - * key is successful. + * Sets whether this {@link java.security.KeyStore} entry must be encrypted at rest. + * Encryption at rest will protect the entry with the secure lock screen credential (e.g., + * password, PIN, or pattern). + * + * <p>Note that enabling this feature requires that the secure lock screen (e.g., password, + * PIN, pattern) is set up, otherwise setting the {@code KeyStore} entry will fail. + * Moreover, this entry will be deleted when the secure lock screen is disabled or reset + * (e.g., by the user or a Device Administrator). Finally, this entry cannot be used until + * the user unlocks the secure lock screen after boot. + * + * @see KeyguardManager#isDeviceSecure() */ + @NonNull public Builder setEncryptionRequired(boolean required) { if (required) { mFlags |= KeyStore.FLAG_ENCRYPTED; @@ -290,237 +155,16 @@ public final class KeyStoreParameter implements ProtectionParameter { } /** - * Sets the time instant before which the key is not yet valid. - * - * <p>By default, the key is valid at any instant. - * - * <p><b>NOTE: This has currently no effect on asymmetric key pairs. - * - * @see #setKeyValidityEnd(Date) - */ - public Builder setKeyValidityStart(Date startDate) { - mKeyValidityStart = startDate; - return this; - } - - /** - * Sets the time instant after which the key is no longer valid. - * - * <p>By default, the key is valid at any instant. - * - * <p><b>NOTE: This has currently no effect on asymmetric key pairs. - * - * @see #setKeyValidityStart(Date) - * @see #setKeyValidityForConsumptionEnd(Date) - * @see #setKeyValidityForOriginationEnd(Date) - */ - public Builder setKeyValidityEnd(Date endDate) { - setKeyValidityForOriginationEnd(endDate); - setKeyValidityForConsumptionEnd(endDate); - return this; - } - - /** - * Sets the time instant after which the key is no longer valid for encryption and signing. - * - * <p>By default, the key is valid at any instant. - * - * <p><b>NOTE: This has currently no effect on asymmetric key pairs. - * - * @see #setKeyValidityForConsumptionEnd(Date) - */ - public Builder setKeyValidityForOriginationEnd(Date endDate) { - mKeyValidityForOriginationEnd = endDate; - return this; - } - - /** - * Sets the time instant after which the key is no longer valid for decryption and - * verification. - * - * <p>By default, the key is valid at any instant. - * - * <p><b>NOTE: This has currently no effect on asymmetric key pairs. - * - * @see #setKeyValidityForOriginationEnd(Date) - */ - public Builder setKeyValidityForConsumptionEnd(Date endDate) { - mKeyValidityForConsumptionEnd = endDate; - return this; - } - - /** - * Sets the set of purposes for which the key can be used. - * - * <p>This must be specified for all keys. There is no default. - * - * <p><b>NOTE: This has currently no effect on asymmetric key pairs. - */ - public Builder setPurposes(@KeyStoreKeyProperties.PurposeEnum int purposes) { - mPurposes = purposes; - return this; - } - - /** - * Sets the set of padding schemes with which the key can be used when - * encrypting/decrypting. Attempts to use the key with any other padding scheme will be - * rejected. - * - * <p>This must be specified for keys which are used for encryption/decryption. - * - * <p><b>NOTE: This has currently no effect on asymmetric key pairs. - */ - public Builder setEncryptionPaddings( - @KeyStoreKeyProperties.EncryptionPaddingEnum String... paddings) { - mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings); - return this; - } - - /** - * Sets the set of padding schemes with which the key can be used when - * signing/verifying. Attempts to use the key with any other padding scheme will be - * rejected. - * - * <p>This must be specified for RSA keys which are used for signing/verification. - * - * <p><b>NOTE: This has currently no effect on asymmetric key pairs. - */ - public Builder setSignaturePaddings( - @KeyStoreKeyProperties.SignaturePaddingEnum String... paddings) { - mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(paddings); - return this; - } - - - /** - * Sets the set of digests with which the key can be used when signing/verifying or - * generating MACs. Attempts to use the key with any other digest will be rejected. - * - * <p>For HMAC keys, the default is the digest specified in {@link Key#getAlgorithm()}. For - * asymmetric signing keys this constraint must be specified. - * - * <p><b>NOTE: This has currently no effect on asymmetric key pairs. - */ - public Builder setDigests(@KeyStoreKeyProperties.DigestEnum String... digests) { - mDigests = ArrayUtils.cloneIfNotEmpty(digests); - return this; - } - - /** - * Sets the set of block modes with which the key can be used when encrypting/decrypting. - * Attempts to use the key with any other block modes will be rejected. - * - * <p>This must be specified for encryption/decryption keys. - * - * <p><b>NOTE: This has currently no effect on asymmetric key pairs. - */ - public Builder setBlockModes(@KeyStoreKeyProperties.BlockModeEnum String... blockModes) { - mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes); - return this; - } - - /** - * Sets whether encryption using this key must be sufficiently randomized to produce - * different ciphertexts for the same plaintext every time. The formal cryptographic - * property being required is <em>indistinguishability under chosen-plaintext attack - * ({@code IND-CPA})</em>. This property is important because it mitigates several classes - * of weaknesses due to which ciphertext may leak information about plaintext. For example, - * if a given plaintext always produces the same ciphertext, an attacker may see the - * repeated ciphertexts and be able to deduce something about the plaintext. - * - * <p>By default, {@code IND-CPA} is required. - * - * <p>When {@code IND-CPA} is required: - * <ul> - * <li>transformation which do not offer {@code IND-CPA}, such as symmetric ciphers using - * {@code ECB} mode or RSA encryption without padding, are prohibited;</li> - * <li>in transformations which use an IV, such as symmetric ciphers in {@code CBC}, - * {@code CTR}, and {@code GCM} block modes, caller-provided IVs are rejected when - * encrypting, to ensure that only random IVs are used.</li> - * - * <p>Before disabling this requirement, consider the following approaches instead: - * <ul> - * <li>If you are generating a random IV for encryption and then initializing a {@code} - * Cipher using the IV, the solution is to let the {@code Cipher} generate a random IV - * instead. This will occur if the {@code Cipher} is initialized for encryption without an - * IV. The IV can then be queried via {@link Cipher#getIV()}.</li> - * <li>If you are generating a non-random IV (e.g., an IV derived from something not fully - * random, such as the name of the file being encrypted, or transaction ID, or password, - * or a device identifier), consider changing your design to use a random IV which will then - * be provided in addition to the ciphertext to the entities which need to decrypt the - * ciphertext.</li> - * <li>If you are using RSA encryption without padding, consider switching to padding - * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li> - * </ul> - * - * <p><b>NOTE: This has currently no effect on asymmetric key pairs. - */ - public Builder setRandomizedEncryptionRequired(boolean required) { - mRandomizedEncryptionRequired = required; - return this; - } - - /** - * Sets whether user authentication is required to use this key. - * - * <p>By default, the key can be used without user authentication. - * - * <p>When user authentication is required, the user authorizes the use of the key by - * authenticating to this Android device using a subset of their secure lock screen - * credentials. Different authentication methods are used depending on whether the every - * use of the key must be authenticated (as specified by - * {@link #setUserAuthenticationValidityDurationSeconds(int)}). - * <a href="{@docRoot}training/articles/keystore.html#UserAuthentication">More - * information</a>. - * - * <p><b>NOTE: This has currently no effect on asymmetric key pairs. - * - * @see #setUserAuthenticationValidityDurationSeconds(int) - */ - public Builder setUserAuthenticationRequired(boolean required) { - mUserAuthenticationRequired = required; - return this; - } - - /** - * Sets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. This has effect only if user authentication is required. - * - * <p>By default, the user needs to authenticate for every use of the key. - * - * <p><b>NOTE: This has currently no effect on asymmetric key pairs. - * - * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for - * every use of the key. - * - * @see #setUserAuthenticationRequired(boolean) - */ - public Builder setUserAuthenticationValidityDurationSeconds(int seconds) { - mUserAuthenticationValidityDurationSeconds = seconds; - return this; - } - - /** * Builds the instance of the {@code KeyStoreParameter}. * * @throws IllegalArgumentException if a required field is missing * @return built instance of {@code KeyStoreParameter} */ + @NonNull public KeyStoreParameter build() { return new KeyStoreParameter( mContext, - mFlags, - mKeyValidityStart, - mKeyValidityForOriginationEnd, - mKeyValidityForConsumptionEnd, - mPurposes, - mEncryptionPaddings, - mSignaturePaddings, - mDigests, - mBlockModes, - mRandomizedEncryptionRequired, - mUserAuthenticationRequired, - mUserAuthenticationValidityDurationSeconds); + mFlags); } } } diff --git a/keystore/java/android/security/AndroidKeyPairGenerator.java b/keystore/java/android/security/keystore/AndroidKeyPairGeneratorSpi.java index 3f29c6a..8d3b421 100644 --- a/keystore/java/android/security/AndroidKeyPairGenerator.java +++ b/keystore/java/android/security/keystore/AndroidKeyPairGeneratorSpi.java @@ -14,7 +14,12 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; + +import android.annotation.NonNull; +import android.security.Credentials; +import android.security.KeyPairGeneratorSpec; +import android.security.KeyStore; import com.android.org.bouncycastle.x509.X509V3CertificateGenerator; import com.android.org.conscrypt.NativeConstants; @@ -36,6 +41,7 @@ import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAKeyGenParameterSpec; import java.security.spec.X509EncodedKeySpec; +import java.util.Locale; /** * Provides a way to create instances of a KeyPair which will be placed in the @@ -50,17 +56,17 @@ import java.security.spec.X509EncodedKeySpec; * * {@hide} */ -public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { +public abstract class AndroidKeyPairGeneratorSpi extends KeyPairGeneratorSpi { - public static class RSA extends AndroidKeyPairGenerator { + public static class RSA extends AndroidKeyPairGeneratorSpi { public RSA() { - super(KeyStoreKeyProperties.Algorithm.RSA); + super(KeyProperties.KEY_ALGORITHM_RSA); } } - public static class EC extends AndroidKeyPairGenerator { + public static class EC extends AndroidKeyPairGeneratorSpi { public EC() { - super(KeyStoreKeyProperties.Algorithm.EC); + super(KeyProperties.KEY_ALGORITHM_EC); } } @@ -80,18 +86,18 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { private final String mAlgorithm; - private android.security.KeyStore mKeyStore; + private KeyStore mKeyStore; - private KeyPairGeneratorSpec mSpec; - private @KeyStoreKeyProperties.AlgorithmEnum String mKeyAlgorithm; + private KeyGenParameterSpec mSpec; + private @KeyProperties.KeyAlgorithmEnum String mKeyAlgorithm; private int mKeyType; private int mKeySize; - protected AndroidKeyPairGenerator(@KeyStoreKeyProperties.AlgorithmEnum String algorithm) { + protected AndroidKeyPairGeneratorSpi(@KeyProperties.KeyAlgorithmEnum String algorithm) { mAlgorithm = algorithm; } - public @KeyStoreKeyProperties.AlgorithmEnum String getAlgorithm() { + @KeyProperties.KeyAlgorithmEnum String getAlgorithm() { return mAlgorithm; } @@ -113,15 +119,16 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { @Override public KeyPair generateKeyPair() { if (mKeyStore == null || mSpec == null) { - throw new IllegalStateException( - "Must call initialize with an android.security.KeyPairGeneratorSpec first"); + throw new IllegalStateException("Not initialized"); + } - if (((mSpec.getFlags() & KeyStore.FLAG_ENCRYPTED) != 0) + final int flags = mSpec.getFlags(); + if (((flags & KeyStore.FLAG_ENCRYPTED) != 0) && (mKeyStore.state() != KeyStore.State.UNLOCKED)) { throw new IllegalStateException( - "Android keystore must be in initialized and unlocked state " - + "if encryption is required"); + "Encryption at rest using secure lock screen credential requested for key pair" + + ", but the user has not yet entered the credential"); } final String alias = mSpec.getKeystoreAlias(); @@ -131,8 +138,9 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { byte[][] args = getArgsForKeyType(mKeyType, mSpec.getAlgorithmParameterSpec()); final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; + if (!mKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, mKeyType, mKeySize, - mSpec.getFlags(), args)) { + flags, args)) { throw new IllegalStateException("could not generate key in keystore"); } @@ -175,7 +183,7 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { } if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, certBytes, KeyStore.UID_SELF, - mSpec.getFlags())) { + flags)) { Credentials.deleteAllTypesForAlias(mKeyStore, alias); throw new IllegalStateException("Can't store certificate in AndroidKeyStore"); } @@ -188,16 +196,17 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { throws Exception { final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); certGen.setPublicKey(publicKey); - certGen.setSerialNumber(mSpec.getSerialNumber()); - certGen.setSubjectDN(mSpec.getSubjectDN()); - certGen.setIssuerDN(mSpec.getSubjectDN()); - certGen.setNotBefore(mSpec.getStartDate()); - certGen.setNotAfter(mSpec.getEndDate()); + certGen.setSerialNumber(mSpec.getCertificateSerialNumber()); + certGen.setSubjectDN(mSpec.getCertificateSubject()); + certGen.setIssuerDN(mSpec.getCertificateSubject()); + certGen.setNotBefore(mSpec.getCertificateNotBefore()); + certGen.setNotAfter(mSpec.getCertificateNotAfter()); certGen.setSignatureAlgorithm(getDefaultSignatureAlgorithmForKeyAlgorithm(mKeyAlgorithm)); return certGen.generate(privateKey); } - private @KeyStoreKeyProperties.AlgorithmEnum String getKeyAlgorithm(KeyPairGeneratorSpec spec) { + @NonNull + private @KeyProperties.KeyAlgorithmEnum String getKeyAlgorithm(KeyPairGeneratorSpec spec) { String result = spec.getKeyType(); if (result != null) { return result; @@ -249,10 +258,10 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { } private static String getDefaultSignatureAlgorithmForKeyAlgorithm( - @KeyStoreKeyProperties.AlgorithmEnum String algorithm) { - if (KeyStoreKeyProperties.Algorithm.RSA.equalsIgnoreCase(algorithm)) { + @KeyProperties.KeyAlgorithmEnum String algorithm) { + if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) { return "sha256WithRSA"; - } else if (KeyStoreKeyProperties.Algorithm.EC.equalsIgnoreCase(algorithm)) { + } else if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) { return "sha256WithECDSA"; } else { throw new IllegalArgumentException("Unsupported key type " + algorithm); @@ -281,14 +290,86 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { throws InvalidAlgorithmParameterException { if (params == null) { throw new InvalidAlgorithmParameterException( - "must supply params of type android.security.KeyPairGeneratorSpec"); - } else if (!(params instanceof KeyPairGeneratorSpec)) { + "Must supply params of type " + KeyGenParameterSpec.class.getName() + + " or " + KeyPairGeneratorSpec.class.getName()); + } + + String keyAlgorithm; + KeyGenParameterSpec spec; + if (params instanceof KeyPairGeneratorSpec) { + KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params; + try { + KeyGenParameterSpec.Builder specBuilder; + keyAlgorithm = getKeyAlgorithm(legacySpec).toUpperCase(Locale.US); + if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { + specBuilder = new KeyGenParameterSpec.Builder( + legacySpec.getKeystoreAlias(), + KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_MD5, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { + specBuilder = new KeyGenParameterSpec.Builder( + legacySpec.getKeystoreAlias(), + KeyProperties.PURPOSE_ENCRYPT + | KeyProperties.PURPOSE_DECRYPT + | KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_MD5, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + specBuilder.setSignaturePaddings( + KeyProperties.SIGNATURE_PADDING_RSA_PKCS1); + specBuilder.setBlockModes(KeyProperties.BLOCK_MODE_ECB); + specBuilder.setEncryptionPaddings( + KeyProperties.ENCRYPTION_PADDING_NONE, + KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1); + // Disable randomized encryption requirement to support encryption padding NONE + // above. + specBuilder.setRandomizedEncryptionRequired(false); + } else { + throw new InvalidAlgorithmParameterException( + "Unsupported key algorithm: " + keyAlgorithm); + } + + if (legacySpec.getKeySize() != -1) { + specBuilder.setKeySize(legacySpec.getKeySize()); + } + if (legacySpec.getAlgorithmParameterSpec() != null) { + specBuilder.setAlgorithmParameterSpec(legacySpec.getAlgorithmParameterSpec()); + } + specBuilder.setCertificateSubject(legacySpec.getSubjectDN()); + specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber()); + specBuilder.setCertificateNotBefore(legacySpec.getStartDate()); + specBuilder.setCertificateNotAfter(legacySpec.getEndDate()); + specBuilder.setEncryptionAtRestRequired(legacySpec.isEncryptionRequired()); + specBuilder.setUserAuthenticationRequired(false); + + spec = specBuilder.build(); + } catch (NullPointerException | IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException(e); + } + } else if (params instanceof KeyGenParameterSpec) { + spec = (KeyGenParameterSpec) params; + keyAlgorithm = getAlgorithm(); + } else { throw new InvalidAlgorithmParameterException( - "params must be of type android.security.KeyPairGeneratorSpec"); + "Unsupported params class: " + params.getClass().getName() + + ". Supported: " + KeyGenParameterSpec.class.getName() + + ", " + KeyPairGeneratorSpec.class); } - KeyPairGeneratorSpec spec = (KeyPairGeneratorSpec) params; - @KeyStoreKeyProperties.AlgorithmEnum String keyAlgorithm = getKeyAlgorithm(spec); int keyType = KeyStore.getKeyTypeForAlgorithm(keyAlgorithm); if (keyType == -1) { throw new InvalidAlgorithmParameterException( @@ -299,7 +380,7 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { keySize = getDefaultKeySize(keyType); if (keySize == -1) { throw new InvalidAlgorithmParameterException( - "Unsupported key algorithm: " + keyAlgorithm); + "Unsupported key algorithm: " + keyAlgorithm); } } checkCorrectParametersSpec(keyType, keySize, spec.getAlgorithmParameterSpec()); @@ -309,6 +390,6 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { mKeyType = keyType; mKeySize = keySize; mSpec = spec; - mKeyStore = android.security.KeyStore.getInstance(); + mKeyStore = KeyStore.getInstance(); } } diff --git a/keystore/java/android/security/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java index 45329cf..3774e36 100644 --- a/keystore/java/android/security/AndroidKeyStoreBCWorkaroundProvider.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; import java.security.Provider; @@ -40,9 +40,9 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider { // classes when this provider is instantiated and installed early on during each app's // initialization process. - private static final String PACKAGE_NAME = "android.security"; + private static final String PACKAGE_NAME = "android.security.keystore"; private static final String KEYSTORE_SECRET_KEY_CLASS_NAME = - PACKAGE_NAME + ".KeyStoreSecretKey"; + PACKAGE_NAME + ".AndroidKeyStoreSecretKey"; AndroidKeyStoreBCWorkaroundProvider() { super("AndroidKeyStoreBCWorkaround", @@ -50,25 +50,25 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider { "Android KeyStore security provider to work around Bouncy Castle"); // javax.crypto.Mac - putMacImpl("HmacSHA1", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA1"); - putMacImpl("HmacSHA224", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA224"); - putMacImpl("HmacSHA256", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA256"); - putMacImpl("HmacSHA384", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA384"); - putMacImpl("HmacSHA512", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA512"); + putMacImpl("HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA1"); + putMacImpl("HmacSHA224", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA224"); + putMacImpl("HmacSHA256", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA256"); + putMacImpl("HmacSHA384", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA384"); + putMacImpl("HmacSHA512", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA512"); // javax.crypto.Cipher putSymmetricCipherImpl("AES/ECB/NoPadding", - PACKAGE_NAME + ".KeyStoreCipherSpi$AES$ECB$NoPadding"); + PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$ECB$NoPadding"); putSymmetricCipherImpl("AES/ECB/PKCS7Padding", - PACKAGE_NAME + ".KeyStoreCipherSpi$AES$ECB$PKCS7Padding"); + PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$ECB$PKCS7Padding"); putSymmetricCipherImpl("AES/CBC/NoPadding", - PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CBC$NoPadding"); + PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$CBC$NoPadding"); putSymmetricCipherImpl("AES/CBC/PKCS7Padding", - PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CBC$PKCS7Padding"); + PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$CBC$PKCS7Padding"); putSymmetricCipherImpl("AES/CTR/NoPadding", - PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CTR$NoPadding"); + PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$CTR$NoPadding"); } private void putMacImpl(String algorithm, String implClass) { diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpi.java index bd601bc..27df5e7 100644 --- a/keystore/java/android/security/KeyStoreCipherSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpi.java @@ -14,12 +14,15 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; import android.os.IBinder; +import android.security.KeyStore; +import android.security.KeyStoreException; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; import android.security.keymaster.OperationResult; +import android.security.keystore.KeyProperties; import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; @@ -47,9 +50,10 @@ import javax.crypto.spec.IvParameterSpec; * * @hide */ -public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCryptoOperation { +public abstract class AndroidKeyStoreCipherSpi extends CipherSpi + implements KeyStoreCryptoOperation { - public abstract static class AES extends KeyStoreCipherSpi { + public abstract static class AES extends AndroidKeyStoreCipherSpi { protected AES(int keymasterBlockMode, int keymasterPadding, boolean ivUsed) { super(KeymasterDefs.KM_ALGORITHM_AES, keymasterBlockMode, @@ -119,7 +123,7 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after // doFinal finishes. protected boolean mEncrypting; - private KeyStoreSecretKey mKey; + private AndroidKeyStoreSecretKey mKey; private SecureRandom mRng; private boolean mFirstOperationInitiated; private byte[] mIv; @@ -146,7 +150,7 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry */ private Exception mCachedException; - protected KeyStoreCipherSpi( + protected AndroidKeyStoreCipherSpi( int keymasterAlgorithm, int keymasterBlockMode, int keymasterPadding, @@ -218,11 +222,11 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry } private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException { - if (!(key instanceof KeyStoreSecretKey)) { + if (!(key instanceof AndroidKeyStoreSecretKey)) { throw new InvalidKeyException( "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null")); } - mKey = (KeyStoreSecretKey) key; + mKey = (AndroidKeyStoreSecretKey) key; mRng = random; mIv = null; mFirstOperationInitiated = false; @@ -496,7 +500,7 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry if ((mIv != null) && (mIv.length > 0)) { try { AlgorithmParameters params = - AlgorithmParameters.getInstance(KeyStoreKeyProperties.Algorithm.AES); + AlgorithmParameters.getInstance(KeyProperties.KEY_ALGORITHM_AES); params.init(new IvParameterSpec(mIv)); return params; } catch (NoSuchAlgorithmException e) { diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java index 5089a25..b82a7f5 100644 --- a/keystore/java/android/security/KeyStoreHmacSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java @@ -14,9 +14,11 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; import android.os.IBinder; +import android.security.KeyStore; +import android.security.KeyStoreException; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; import android.security.keymaster.OperationResult; @@ -34,33 +36,33 @@ import javax.crypto.MacSpi; * * @hide */ -public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation { +public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation { - public static class HmacSHA1 extends KeyStoreHmacSpi { + public static class HmacSHA1 extends AndroidKeyStoreHmacSpi { public HmacSHA1() { super(KeymasterDefs.KM_DIGEST_SHA1); } } - public static class HmacSHA224 extends KeyStoreHmacSpi { + public static class HmacSHA224 extends AndroidKeyStoreHmacSpi { public HmacSHA224() { super(KeymasterDefs.KM_DIGEST_SHA_2_224); } } - public static class HmacSHA256 extends KeyStoreHmacSpi { + public static class HmacSHA256 extends AndroidKeyStoreHmacSpi { public HmacSHA256() { super(KeymasterDefs.KM_DIGEST_SHA_2_256); } } - public static class HmacSHA384 extends KeyStoreHmacSpi { + public static class HmacSHA384 extends AndroidKeyStoreHmacSpi { public HmacSHA384() { super(KeymasterDefs.KM_DIGEST_SHA_2_384); } } - public static class HmacSHA512 extends KeyStoreHmacSpi { + public static class HmacSHA512 extends AndroidKeyStoreHmacSpi { public HmacSHA512() { super(KeymasterDefs.KM_DIGEST_SHA_2_512); } @@ -71,14 +73,14 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp private final int mMacSizeBits; // Fields below are populated by engineInit and should be preserved after engineDoFinal. - private KeyStoreSecretKey mKey; + private AndroidKeyStoreSecretKey mKey; // Fields below are reset when engineDoFinal succeeds. private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer; private IBinder mOperationToken; private long mOperationHandle; - protected KeyStoreHmacSpi(int keymasterDigest) { + protected AndroidKeyStoreHmacSpi(int keymasterDigest) { mKeymasterDigest = keymasterDigest; mMacSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest); } @@ -109,11 +111,11 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp InvalidAlgorithmParameterException { if (key == null) { throw new InvalidKeyException("key == null"); - } else if (!(key instanceof KeyStoreSecretKey)) { + } else if (!(key instanceof AndroidKeyStoreSecretKey)) { throw new InvalidKeyException( "Only Android KeyStore secret keys supported. Key: " + key); } - mKey = (KeyStoreSecretKey) key; + mKey = (AndroidKeyStoreSecretKey) key; if (params != null) { throw new InvalidAlgorithmParameterException( diff --git a/keystore/java/android/security/KeyStoreKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreKey.java index 7a34829..6098e5c 100644 --- a/keystore/java/android/security/KeyStoreKey.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKey.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; import java.security.Key; @@ -23,11 +23,11 @@ import java.security.Key; * * @hide */ -public class KeyStoreKey implements Key { +public class AndroidKeyStoreKey implements Key { private final String mAlias; private final String mAlgorithm; - public KeyStoreKey(String alias, String algorithm) { + public AndroidKeyStoreKey(String alias, String algorithm) { mAlias = alias; mAlgorithm = algorithm; } diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java index 4b914c2..0821bf5 100644 --- a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java @@ -14,16 +14,23 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; +import android.security.Credentials; +import android.security.KeyStore; import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; + +import libcore.util.EmptyArray; import java.security.InvalidAlgorithmParameterException; import java.security.ProviderException; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; import java.util.Date; import javax.crypto.KeyGeneratorSpi; @@ -34,9 +41,9 @@ import javax.crypto.SecretKey; * * @hide */ -public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { +public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { - public static class AES extends KeyStoreKeyGeneratorSpi { + public static class AES extends AndroidKeyStoreKeyGeneratorSpi { public AES() { super(KeymasterDefs.KM_ALGORITHM_AES, 128); } @@ -53,7 +60,7 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { } } - protected static abstract class HmacBase extends KeyStoreKeyGeneratorSpi { + protected static abstract class HmacBase extends AndroidKeyStoreKeyGeneratorSpi { protected HmacBase(int keymasterDigest) { super(KeymasterDefs.KM_ALGORITHM_HMAC, keymasterDigest, @@ -96,21 +103,22 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { private final int mKeymasterDigest; private final int mDefaultKeySizeBits; - private KeyGeneratorSpec mSpec; + private KeyGenParameterSpec mSpec; private SecureRandom mRng; protected int mKeySizeBits; private int[] mKeymasterPurposes; private int[] mKeymasterBlockModes; private int[] mKeymasterPaddings; + private int[] mKeymasterDigests; - protected KeyStoreKeyGeneratorSpi( + protected AndroidKeyStoreKeyGeneratorSpi( int keymasterAlgorithm, int defaultKeySizeBits) { this(keymasterAlgorithm, -1, defaultKeySizeBits); } - protected KeyStoreKeyGeneratorSpi( + protected AndroidKeyStoreKeyGeneratorSpi( int keymasterAlgorithm, int keymasterDigest, int defaultKeySizeBits) { @@ -129,14 +137,14 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { @Override protected void engineInit(SecureRandom random) { - throw new UnsupportedOperationException("Cannot initialize without an " - + KeyGeneratorSpec.class.getName() + " parameter"); + throw new UnsupportedOperationException("Cannot initialize without a " + + KeyGenParameterSpec.class.getName() + " parameter"); } @Override protected void engineInit(int keySize, SecureRandom random) { throw new UnsupportedOperationException("Cannot initialize without a " - + KeyGeneratorSpec.class.getName() + " parameter"); + + KeyGenParameterSpec.class.getName() + " parameter"); } @Override @@ -146,11 +154,11 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { boolean success = false; try { - if ((params == null) || (!(params instanceof KeyGeneratorSpec))) { - throw new InvalidAlgorithmParameterException("Cannot initialize without an " - + KeyGeneratorSpec.class.getName() + " parameter"); + if ((params == null) || (!(params instanceof KeyGenParameterSpec))) { + throw new InvalidAlgorithmParameterException("Cannot initialize without a " + + KeyGenParameterSpec.class.getName() + " parameter"); } - KeyGeneratorSpec spec = (KeyGeneratorSpec) params; + KeyGenParameterSpec spec = (KeyGenParameterSpec) params; if (spec.getKeystoreAlias() == null) { throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided"); } @@ -168,13 +176,11 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { } try { - mKeymasterPurposes = - KeyStoreKeyProperties.Purpose.allToKeymaster(spec.getPurposes()); - mKeymasterPaddings = KeyStoreKeyProperties.EncryptionPadding.allToKeymaster( + mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes()); + mKeymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster( spec.getEncryptionPaddings()); - mKeymasterBlockModes = - KeyStoreKeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()); - if (((spec.getPurposes() & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) + mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()); + if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) && (spec.isRandomizedEncryptionRequired())) { for (int keymasterBlockMode : mKeymasterBlockModes) { if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible( @@ -182,14 +188,55 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { throw new InvalidAlgorithmParameterException( "Randomized encryption (IND-CPA) required but may be violated" + " by block mode: " - + KeyStoreKeyProperties.BlockMode.fromKeymaster( - keymasterBlockMode) - + ". See " + KeyGeneratorSpec.class.getName() + + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode) + + ". See " + KeyGenParameterSpec.class.getName() + " documentation."); } } } - } catch (IllegalArgumentException e) { + if (spec.isDigestsSpecified()) { + // Digest(s) explicitly specified in the spec + mKeymasterDigests = KeyProperties.Digest.allToKeymaster(spec.getDigests()); + if (mKeymasterDigest != -1) { + // Key algorithm implies a digest -- ensure it's specified in the spec as + // first digest. + if (!com.android.internal.util.ArrayUtils.contains( + mKeymasterDigests, mKeymasterDigest)) { + throw new InvalidAlgorithmParameterException( + "Digests specified in algorithm parameters (" + + Arrays.asList(spec.getDigests()) + ") must include " + + " the digest " + + KeyProperties.Digest.fromKeymaster(mKeymasterDigest) + + " implied by key algorithm"); + } + if (mKeymasterDigests[0] != mKeymasterDigest) { + // The first digest is not the one implied by the key algorithm. + // Swap the implied digest with the first one. + for (int i = 0; i < mKeymasterDigests.length; i++) { + if (mKeymasterDigests[i] == mKeymasterDigest) { + mKeymasterDigests[i] = mKeymasterDigests[0]; + mKeymasterDigests[0] = mKeymasterDigest; + break; + } + } + } + } + } else { + // No digest specified in the spec + if (mKeymasterDigest != -1) { + // Key algorithm implies a digest -- use that digest + mKeymasterDigests = new int[] {mKeymasterDigest}; + } else { + mKeymasterDigests = EmptyArray.INT; + } + } + if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { + if (mKeymasterDigests.length == 0) { + throw new InvalidAlgorithmParameterException( + "At least one digest algorithm must be specified"); + } + } + } catch (IllegalStateException | IllegalArgumentException e) { throw new InvalidAlgorithmParameterException(e); } @@ -212,29 +259,26 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { @Override protected SecretKey engineGenerateKey() { - KeyGeneratorSpec spec = mSpec; + KeyGenParameterSpec spec = mSpec; if (spec == null) { throw new IllegalStateException("Not initialized"); } - if ((spec.isEncryptionRequired()) + if ((spec.isEncryptionAtRestRequired()) && (mKeyStore.state() != KeyStore.State.UNLOCKED)) { throw new IllegalStateException( - "Android KeyStore must be in initialized and unlocked state if encryption is" - + " required"); + "Requested to import a key which must be encrypted at rest using secure lock" + + " screen credential, but the credential hasn't yet been entered by the user"); } KeymasterArguments args = new KeymasterArguments(); args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits); args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); - if (mKeymasterDigest != -1) { - args.addInt(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); - } args.addInts(KeymasterDefs.KM_TAG_PURPOSE, mKeymasterPurposes); args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockModes); args.addInts(KeymasterDefs.KM_TAG_PADDING, mKeymasterPaddings); + args.addInts(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests); KeymasterUtils.addUserAuthArgs(args, - spec.getContext(), spec.isUserAuthenticationRequired(), spec.getUserAuthenticationValidityDurationSeconds()); args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, @@ -247,7 +291,7 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { (spec.getKeyValidityForConsumptionEnd() != null) ? spec.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE)); - if (((spec.getPurposes() & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) + if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) && (!spec.isRandomizedEncryptionRequired())) { // Permit caller-provided IV when encrypting with this key args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); @@ -265,13 +309,13 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { throw new ProviderException( "Keystore operation failed", KeyStore.getKeyStoreException(errorCode)); } - String keyAlgorithmJCA; + @KeyProperties.KeyAlgorithmEnum String keyAlgorithmJCA; try { - keyAlgorithmJCA = KeyStoreKeyProperties.Algorithm.fromKeymasterSecretKeyAlgorithm( + keyAlgorithmJCA = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm( mKeymasterAlgorithm, mKeymasterDigest); } catch (IllegalArgumentException e) { throw new ProviderException("Failed to obtain JCA secret key algorithm name", e); } - return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmJCA); + return new AndroidKeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmJCA); } } diff --git a/keystore/java/android/security/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java index 257ab54..b20a122 100644 --- a/keystore/java/android/security/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java @@ -14,7 +14,9 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; + +import android.security.KeyStore; import java.security.Provider; import java.security.Security; @@ -38,25 +40,25 @@ public class AndroidKeyStoreProvider extends Provider { // Instead, they need to be offered by AndroidKeyStoreBCWorkaroundProvider. See its Javadoc // for details. - private static final String PACKAGE_NAME = "android.security"; + private static final String PACKAGE_NAME = "android.security.keystore"; public AndroidKeyStoreProvider() { super(PROVIDER_NAME, 1.0, "Android KeyStore security provider"); // java.security.KeyStore - put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStore"); + put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStoreSpi"); // java.security.KeyPairGenerator - put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyPairGenerator$EC"); - put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyPairGenerator$RSA"); + put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyPairGeneratorSpi$EC"); + put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyPairGeneratorSpi$RSA"); // javax.crypto.KeyGenerator - put("KeyGenerator.AES", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$AES"); - put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA1"); - put("KeyGenerator.HmacSHA224", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA224"); - put("KeyGenerator.HmacSHA256", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA256"); - put("KeyGenerator.HmacSHA384", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA384"); - put("KeyGenerator.HmacSHA512", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA512"); + put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES"); + put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA1"); + put("KeyGenerator.HmacSHA224", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA224"); + put("KeyGenerator.HmacSHA256", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA256"); + put("KeyGenerator.HmacSHA384", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA384"); + put("KeyGenerator.HmacSHA512", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA512"); // java.security.SecretKeyFactory putSecretKeyFactoryImpl("AES"); @@ -95,7 +97,7 @@ public class AndroidKeyStoreProvider extends Provider { } private void putSecretKeyFactoryImpl(String algorithm) { - put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".KeyStoreSecretKeyFactorySpi"); + put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreSecretKeyFactorySpi"); } /** diff --git a/keystore/java/android/security/KeyStoreSecretKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.java index ee25465..f75516b 100644 --- a/keystore/java/android/security/KeyStoreSecretKey.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKey.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; import javax.crypto.SecretKey; @@ -23,9 +23,9 @@ import javax.crypto.SecretKey; * * @hide */ -public class KeyStoreSecretKey extends KeyStoreKey implements SecretKey { +public class AndroidKeyStoreSecretKey extends AndroidKeyStoreKey implements SecretKey { - public KeyStoreSecretKey(String alias, String algorithm) { + public AndroidKeyStoreSecretKey(String alias, String algorithm) { super(alias, algorithm); } } diff --git a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java index 548296b..455f170 100644 --- a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java @@ -14,8 +14,10 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; +import android.security.Credentials; +import android.security.KeyStore; import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterDefs; @@ -37,7 +39,7 @@ import javax.crypto.spec.SecretKeySpec; * * @hide */ -public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { +public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { private final KeyStore mKeyStore = KeyStore.getInstance(); @@ -47,7 +49,7 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { if (keySpecClass == null) { throw new InvalidKeySpecException("keySpecClass == null"); } - if (!(key instanceof KeyStoreSecretKey)) { + if (!(key instanceof AndroidKeyStoreSecretKey)) { throw new InvalidKeySpecException("Only Android KeyStore secret keys supported: " + ((key != null) ? key.getClass().getName() : "null")); } @@ -55,10 +57,10 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { throw new InvalidKeySpecException( "Key material export of Android KeyStore keys is not supported"); } - if (!KeyStoreKeySpec.class.equals(keySpecClass)) { + if (!KeyInfo.class.equals(keySpecClass)) { throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName()); } - String keyAliasInKeystore = ((KeyStoreSecretKey) key).getAlias(); + String keyAliasInKeystore = ((AndroidKeyStoreSecretKey) key).getAlias(); String entryAlias; if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) { entryAlias = keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length()); @@ -75,22 +77,22 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { } boolean insideSecureHardware; - @KeyStoreKeyProperties.OriginEnum int origin; + @KeyProperties.OriginEnum int origin; int keySize; - @KeyStoreKeyProperties.PurposeEnum int purposes; + @KeyProperties.PurposeEnum int purposes; String[] encryptionPaddings; - @KeyStoreKeyProperties.DigestEnum String[] digests; - @KeyStoreKeyProperties.BlockModeEnum String[] blockModes; + @KeyProperties.DigestEnum String[] digests; + @KeyProperties.BlockModeEnum String[] blockModes; int keymasterSwEnforcedUserAuthenticators; int keymasterHwEnforcedUserAuthenticators; try { if (keyCharacteristics.hwEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) { insideSecureHardware = true; - origin = KeyStoreKeyProperties.Origin.fromKeymaster( + origin = KeyProperties.Origin.fromKeymaster( keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1)); } else if (keyCharacteristics.swEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) { insideSecureHardware = false; - origin = KeyStoreKeyProperties.Origin.fromKeymaster( + origin = KeyProperties.Origin.fromKeymaster( keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1)); } else { throw new InvalidKeySpecException("Key origin not available"); @@ -100,15 +102,14 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { throw new InvalidKeySpecException("Key size not available"); } keySize = keySizeInteger; - purposes = KeyStoreKeyProperties.Purpose.allFromKeymaster( + purposes = KeyProperties.Purpose.allFromKeymaster( keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PURPOSE)); List<String> encryptionPaddingsList = new ArrayList<String>(); for (int keymasterPadding : keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PADDING)) { - @KeyStoreKeyProperties.EncryptionPaddingEnum String jcaPadding; + @KeyProperties.EncryptionPaddingEnum String jcaPadding; try { - jcaPadding = - KeyStoreKeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding); + jcaPadding = KeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding); } catch (IllegalArgumentException e) { throw new InvalidKeySpecException( "Unsupported encryption padding: " + keymasterPadding); @@ -118,9 +119,9 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { encryptionPaddings = encryptionPaddingsList.toArray(new String[encryptionPaddingsList.size()]); - digests = KeyStoreKeyProperties.Digest.allFromKeymaster( + digests = KeyProperties.Digest.allFromKeymaster( keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST)); - blockModes = KeyStoreKeyProperties.BlockMode.allFromKeymaster( + blockModes = KeyProperties.BlockMode.allFromKeymaster( keyCharacteristics.getInts(KeymasterDefs.KM_TAG_BLOCK_MODE)); keymasterSwEnforcedUserAuthenticators = keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); @@ -154,7 +155,7 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { && (keymasterHwEnforcedUserAuthenticators != 0) && (keymasterSwEnforcedUserAuthenticators == 0); - return new KeyStoreKeySpec(entryAlias, + return new KeyInfo(entryAlias, insideSecureHardware, origin, keySize, diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java index e82ff6a..d6145a3 100644 --- a/keystore/java/android/security/AndroidKeyStore.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java @@ -14,16 +14,20 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; import com.android.org.conscrypt.OpenSSLEngine; import com.android.org.conscrypt.OpenSSLKeyHolder; import libcore.util.EmptyArray; +import android.security.Credentials; +import android.security.KeyStoreParameter; import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyProperties; +import android.security.keystore.KeyProtection; import android.util.Log; import java.io.ByteArrayInputStream; @@ -79,7 +83,7 @@ import javax.crypto.SecretKey; * * @hide */ -public class AndroidKeyStore extends KeyStoreSpi { +public class AndroidKeyStoreSpi extends KeyStoreSpi { public static final String NAME = "AndroidKeyStore"; private android.security.KeyStore mKeyStore; @@ -103,8 +107,9 @@ public class AndroidKeyStore extends KeyStoreSpi { keyAliasInKeystore, null, null, keyCharacteristics); if ((errorCode != KeymasterDefs.KM_ERROR_OK) && (errorCode != android.security.KeyStore.NO_ERROR)) { - throw new UnrecoverableKeyException("Failed to load information about key." - + " Error code: " + errorCode); + throw (UnrecoverableKeyException) + new UnrecoverableKeyException("Failed to load information about key") + .initCause(mKeyStore.getInvalidKeyException(alias, errorCode)); } int keymasterAlgorithm = @@ -128,17 +133,16 @@ public class AndroidKeyStore extends KeyStoreSpi { keymasterDigest = keymasterDigests.get(0); } - @KeyStoreKeyProperties.AlgorithmEnum String keyAlgorithmString; + @KeyProperties.KeyAlgorithmEnum String keyAlgorithmString; try { - keyAlgorithmString = - KeyStoreKeyProperties.Algorithm.fromKeymasterSecretKeyAlgorithm( - keymasterAlgorithm, keymasterDigest); + keyAlgorithmString = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm( + keymasterAlgorithm, keymasterDigest); } catch (IllegalArgumentException e) { throw (UnrecoverableKeyException) new UnrecoverableKeyException("Unsupported secret key type").initCause(e); } - return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmString); + return new AndroidKeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmString); } return null; @@ -269,7 +273,70 @@ public class AndroidKeyStore extends KeyStoreSpi { } private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain, - KeyStoreParameter params) throws KeyStoreException { + java.security.KeyStore.ProtectionParameter param) throws KeyStoreException { + KeyProtection spec; + if (param instanceof KeyStoreParameter) { + KeyStoreParameter legacySpec = (KeyStoreParameter) param; + try { + String keyAlgorithm = key.getAlgorithm(); + KeyProtection.Builder specBuilder; + if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { + specBuilder = + new KeyProtection.Builder( + KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY); + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_MD5, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { + specBuilder = + new KeyProtection.Builder( + KeyProperties.PURPOSE_ENCRYPT + | KeyProperties.PURPOSE_DECRYPT + | KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_MD5, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + specBuilder.setSignaturePaddings( + KeyProperties.SIGNATURE_PADDING_RSA_PKCS1); + specBuilder.setBlockModes(KeyProperties.BLOCK_MODE_ECB); + specBuilder.setEncryptionPaddings( + KeyProperties.ENCRYPTION_PADDING_NONE, + KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1); + // Disable randomized encryption requirement to support encryption padding NONE + // above. + specBuilder.setRandomizedEncryptionRequired(false); + } else { + throw new KeyStoreException("Unsupported key algorithm: " + keyAlgorithm); + } + specBuilder.setEncryptionAtRestRequired(legacySpec.isEncryptionRequired()); + specBuilder.setUserAuthenticationRequired(false); + + spec = specBuilder.build(); + } catch (NullPointerException | IllegalArgumentException e) { + throw new KeyStoreException("Unsupported protection parameter", e); + } + } else if (param instanceof KeyProtection) { + spec = (KeyProtection) param; + } else if (param != null) { + throw new KeyStoreException( + "Unsupported protection parameter class:" + param.getClass().getName() + + ". Supported: " + KeyStoreParameter.class.getName() + ", " + + KeyProtection.class.getName()); + } else { + spec = null; + } + byte[] keyBytes = null; final String pkeyAlias; @@ -382,7 +449,7 @@ public class AndroidKeyStore extends KeyStoreSpi { Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias); } - final int flags = (params == null) ? 0 : params.getFlags(); + final int flags = (spec == null) ? 0 : spec.getFlags(); if (shouldReplacePrivateKey && !mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes, @@ -401,12 +468,20 @@ public class AndroidKeyStore extends KeyStoreSpi { } } - private void setSecretKeyEntry(String entryAlias, SecretKey key, KeyStoreParameter params) + private void setSecretKeyEntry(String entryAlias, SecretKey key, + java.security.KeyStore.ProtectionParameter param) throws KeyStoreException { - if (key instanceof KeyStoreSecretKey) { + if ((param != null) && (!(param instanceof KeyProtection))) { + throw new KeyStoreException( + "Unsupported protection parameter class: " + param.getClass().getName() + + ". Supported: " + KeyProtection.class.getName()); + } + KeyProtection params = (KeyProtection) param; + + if (key instanceof AndroidKeyStoreSecretKey) { // KeyStore-backed secret key. It cannot be duplicated into another entry and cannot // overwrite its own entry. - String keyAliasInKeystore = ((KeyStoreSecretKey) key).getAlias(); + String keyAliasInKeystore = ((AndroidKeyStoreSecretKey) key).getAlias(); if (keyAliasInKeystore == null) { throw new KeyStoreException("KeyStore-backed secret key does not have an alias"); } @@ -452,10 +527,9 @@ public class AndroidKeyStore extends KeyStoreSpi { int keymasterAlgorithm; int keymasterDigest; try { - keymasterAlgorithm = KeyStoreKeyProperties.Algorithm.toKeymasterSecretKeyAlgorithm( - keyAlgorithmString); - keymasterDigest = - KeyStoreKeyProperties.Algorithm.toKeymasterDigest(keyAlgorithmString); + keymasterAlgorithm = + KeyProperties.KeyAlgorithm.toKeymasterSecretKeyAlgorithm(keyAlgorithmString); + keymasterDigest = KeyProperties.KeyAlgorithm.toKeymasterDigest(keyAlgorithmString); } catch (IllegalArgumentException e) { throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString); } @@ -466,7 +540,7 @@ public class AndroidKeyStore extends KeyStoreSpi { int[] keymasterDigests; if (params.isDigestsSpecified()) { // Digest(s) specified in parameters - keymasterDigests = KeyStoreKeyProperties.Digest.allToKeymaster(params.getDigests()); + keymasterDigests = KeyProperties.Digest.allToKeymaster(params.getDigests()); if (keymasterDigest != -1) { // Digest also specified in the JCA key algorithm name. if (!com.android.internal.util.ArrayUtils.contains( @@ -475,6 +549,21 @@ public class AndroidKeyStore extends KeyStoreSpi { + ". Key: " + keyAlgorithmString + ", parameter spec: " + Arrays.asList(params.getDigests())); } + // When the key is read back from keystore we reconstruct the JCA key algorithm + // name from the KM_TAG_ALGORITHM and the first KM_TAG_DIGEST. Thus we need to + // ensure that the digest reflected in the JCA key algorithm name is the first + // KM_TAG_DIGEST tag. + if (keymasterDigests[0] != keymasterDigest) { + // The first digest is not the one implied by the JCA key algorithm name. + // Swap the implied digest with the first one. + for (int i = 0; i < keymasterDigests.length; i++) { + if (keymasterDigests[i] == keymasterDigest) { + keymasterDigests[i] = keymasterDigests[0]; + keymasterDigests[0] = keymasterDigest; + break; + } + } + } } } else { // No digest specified in parameters @@ -493,33 +582,32 @@ public class AndroidKeyStore extends KeyStoreSpi { } } - @KeyStoreKeyProperties.PurposeEnum int purposes = params.getPurposes(); + @KeyProperties.PurposeEnum int purposes = params.getPurposes(); int[] keymasterBlockModes = - KeyStoreKeyProperties.BlockMode.allToKeymaster(params.getBlockModes()); - if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) + KeyProperties.BlockMode.allToKeymaster(params.getBlockModes()); + if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) && (params.isRandomizedEncryptionRequired())) { for (int keymasterBlockMode : keymasterBlockModes) { if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible(keymasterBlockMode)) { throw new KeyStoreException( "Randomized encryption (IND-CPA) required but may be violated by block" + " mode: " - + KeyStoreKeyProperties.BlockMode.fromKeymaster(keymasterBlockMode) - + ". See KeyStoreParameter documentation."); + + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode) + + ". See KeyProtection documentation."); } } } - for (int keymasterPurpose : KeyStoreKeyProperties.Purpose.allToKeymaster(purposes)) { + for (int keymasterPurpose : KeyProperties.Purpose.allToKeymaster(purposes)) { args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose); } args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes); if (params.getSignaturePaddings().length > 0) { throw new KeyStoreException("Signature paddings not supported for symmetric keys"); } - int[] keymasterPaddings = KeyStoreKeyProperties.EncryptionPadding.allToKeymaster( + int[] keymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster( params.getEncryptionPaddings()); args.addInts(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings); KeymasterUtils.addUserAuthArgs(args, - params.getContext(), params.isUserAuthenticationRequired(), params.getUserAuthenticationValidityDurationSeconds()); args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, @@ -535,7 +623,7 @@ public class AndroidKeyStore extends KeyStoreSpi { // TODO: Remove this once keymaster does not require us to specify the size of imported key. args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keyMaterial.length * 8); - if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) + if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) && (!params.isRandomizedEncryptionRequired())) { // Permit caller-provided IV when encrypting with this key args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); @@ -773,19 +861,12 @@ public class AndroidKeyStore extends KeyStoreSpi { return; } - if (param != null && !(param instanceof KeyStoreParameter)) { - throw new KeyStoreException( - "protParam should be android.security.KeyStoreParameter; was: " - + param.getClass().getName()); - } - if (entry instanceof PrivateKeyEntry) { PrivateKeyEntry prE = (PrivateKeyEntry) entry; - setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(), - (KeyStoreParameter) param); + setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(), param); } else if (entry instanceof SecretKeyEntry) { SecretKeyEntry secE = (SecretKeyEntry) entry; - setSecretKeyEntry(alias, secE.getSecretKey(), (KeyStoreParameter) param); + setSecretKeyEntry(alias, secE.getSecretKey(), param); } else { throw new KeyStoreException( "Entry must be a PrivateKeyEntry, SecretKeyEntry or TrustedCertificateEntry" diff --git a/keystore/java/android/security/ArrayUtils.java b/keystore/java/android/security/keystore/ArrayUtils.java index 2047d3f..81be384 100644 --- a/keystore/java/android/security/ArrayUtils.java +++ b/keystore/java/android/security/keystore/ArrayUtils.java @@ -1,11 +1,27 @@ -package android.security; +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; import libcore.util.EmptyArray; /** * @hide */ -abstract class ArrayUtils { +public abstract class ArrayUtils { private ArrayUtils() {} public static String[] nullToEmpty(String[] array) { diff --git a/keystore/java/android/security/KeyExpiredException.java b/keystore/java/android/security/keystore/KeyExpiredException.java index f58e48a..15b8d67 100644 --- a/keystore/java/android/security/KeyExpiredException.java +++ b/keystore/java/android/security/keystore/KeyExpiredException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; import java.security.InvalidKeyException; diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java new file mode 100644 index 0000000..f598482 --- /dev/null +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -0,0 +1,856 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; + +import android.app.KeyguardManager; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.text.TextUtils; +import android.security.KeyStore; + +import java.math.BigInteger; +import java.security.KeyPairGenerator; +import java.security.cert.Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Date; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.security.auth.x500.X500Principal; + +/** + * {@link AlgorithmParameterSpec} for initializing a {@link KeyPairGenerator} or a + * {@link KeyGenerator} of the <a href="{@docRoot}training/articles/keystore.html">Android Keystore + * system</a>. The spec determines whether user authentication is required for using the key, what + * uses the key is authorized for (e.g., only for signing -- decryption not permitted), whether the + * key should be encrypted at rest, the key's and validity start and end dates. + * + * <p>To generate an asymmetric key pair or a symmetric key, create an instance of this class using + * the {@link Builder}, initialize a {@code KeyPairGenerator} or a {@code KeyGenerator} of the + * desired key type (e.g., {@code EC} or {@code AES} -- see + * {@link KeyProperties}.{@code KEY_ALGORITHM} constants) from the {@code AndroidKeyStore} provider + * with the {@code KeyPairGeneratorSpec} instance, and then generate a key or key pair using + * {@link KeyPairGenerator#generateKeyPair()}. + * + * <p>The generated key pair or key will be returned by the generator and also stored in the Android + * Keystore system under the alias specified in this spec. To obtain the secret or private key from + * the Android KeyStore use {@link java.security.KeyStore#getKey(String, char[]) KeyStore.getKey(String, null)} + * or {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter) KeyStore.getEntry(String, null)}. + * To obtain the public key from the Android Keystore system use + * {@link java.security.KeyStore#getCertificate(String)} and then + * {@link Certificate#getPublicKey()}. + * + * <p>For asymmetric key pairs, a self-signed X.509 certificate will be also generated and stored in + * the Android KeyStore. This is because the {@link java.security.KeyStore} abstraction does not + * support storing key pairs without a certificate. The subject, serial number, and validity dates + * of the certificate can be customized in this spec. The self-signed certificate may be replaced at + * a later time by a certificate signed by a Certificate Authority (CA). + * + * <p>NOTE: The key material of the generated symmetric and private keys is not accessible. The key + * material of the public keys is accessible. + * + * <p><h3>Example: Asymmetric key pair</h3> + * The following example illustrates how to generate an EC key pair in the Android KeyStore system + * under alias {@code key1} authorized to be used only for signing using SHA-256, SHA-384, + * or SHA-512 digest and only if the user has been authenticated within the last five minutes. + * <pre> {@code + * KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( + * KeyProperties.KEY_ALGORITHM_EC, + * "AndroidKeyStore"); + * keyPairGenerator.initialize( + * new KeyGenParameterSpec.Builder("key1", + * KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) + * .setDigests(KeyProperties.DIGEST_SHA256 + * | KeyProperties.DIGEST_SHA384 + * | KeyProperties.DIGEST_SHA512) + * // Only permit this key to be used if the user authenticated + * // within the last five minutes. + * .setUserAuthenticationRequired(true) + * .setUserAuthenticationValidityDurationSeconds(5 * 60) + * .build()); + * KeyPair keyPair = keyPairGenerator.generateKeyPair(); + * + * // The key pair can also be obtained from the Android Keystore any time as follows: + * KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + * keyStore.load(null); + * PrivateKey privateKey = (PrivateKey) keyStore.getKey("key1", null); + * PublicKey publicKey = keyStore.getCertificate("key1").getPublicKey(); + * }</pre> + * + * <p><h3>Example: Symmetric key</h3> + * The following example illustrates how to generate an AES key in the Android KeyStore system under + * alias {@code key2} authorized to be used only for encryption/decryption in CTR mode. + * <pre> {@code + * KeyGenerator keyGenerator = KeyGenerator.getInstance( + * KeyProperties.KEY_ALGORITHM_HMAC_SHA256, + * "AndroidKeyStore"); + * keyGenerator.initialize( + * new KeyGenParameterSpec.Builder("key2", + * KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + * .setBlockModes(KeyProperties.BLOCK_MODE_CTR) + * .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + * .build()); + * SecretKey key = keyGenerator.generateKey(); + * + * // The key can also be obtained from the Android Keystore any time as follows: + * KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + * keyStore.load(null); + * key = (SecretKey) keyStore.getKey("key2", null); + * }</pre> + */ +public final class KeyGenParameterSpec implements AlgorithmParameterSpec { + + private static final X500Principal DEFAULT_CERT_SUBJECT = new X500Principal("CN=fake"); + private static final BigInteger DEFAULT_CERT_SERIAL_NUMBER = new BigInteger("1"); + private static final Date DEFAULT_CERT_NOT_BEFORE = new Date(0L); // Jan 1 1970 + private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048 + + private final String mKeystoreAlias; + private final int mKeySize; + private final AlgorithmParameterSpec mSpec; + private final X500Principal mCertificateSubject; + private final BigInteger mCertificateSerialNumber; + private final Date mCertificateNotBefore; + private final Date mCertificateNotAfter; + private final int mFlags; + private final Date mKeyValidityStart; + private final Date mKeyValidityForOriginationEnd; + private final Date mKeyValidityForConsumptionEnd; + private final @KeyProperties.PurposeEnum int mPurposes; + private final @KeyProperties.DigestEnum String[] mDigests; + private final @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private final @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; + private final @KeyProperties.BlockModeEnum String[] mBlockModes; + private final boolean mRandomizedEncryptionRequired; + private final boolean mUserAuthenticationRequired; + private final int mUserAuthenticationValidityDurationSeconds; + + /** + * @hide should be built with Builder + */ + public KeyGenParameterSpec( + String keyStoreAlias, + int keySize, + AlgorithmParameterSpec spec, + X500Principal certificateSubject, + BigInteger certificateSerialNumber, + Date certificateNotBefore, + Date certificateNotAfter, + int flags, + Date keyValidityStart, + Date keyValidityForOriginationEnd, + Date keyValidityForConsumptionEnd, + @KeyProperties.PurposeEnum int purposes, + @KeyProperties.DigestEnum String[] digests, + @KeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyProperties.SignaturePaddingEnum String[] signaturePaddings, + @KeyProperties.BlockModeEnum String[] blockModes, + boolean randomizedEncryptionRequired, + boolean userAuthenticationRequired, + int userAuthenticationValidityDurationSeconds) { + if (TextUtils.isEmpty(keyStoreAlias)) { + throw new IllegalArgumentException("keyStoreAlias must not be empty"); + } else if ((userAuthenticationValidityDurationSeconds < 0) + && (userAuthenticationValidityDurationSeconds != -1)) { + throw new IllegalArgumentException( + "userAuthenticationValidityDurationSeconds must not be negative"); + } + + if (certificateSubject == null) { + certificateSubject = DEFAULT_CERT_SUBJECT; + } + if (certificateNotBefore == null) { + certificateNotBefore = DEFAULT_CERT_NOT_BEFORE; + } + if (certificateNotAfter == null) { + certificateNotAfter = DEFAULT_CERT_NOT_AFTER; + } + if (certificateSerialNumber == null) { + certificateSerialNumber = DEFAULT_CERT_SERIAL_NUMBER; + } + + if (certificateNotAfter.before(certificateNotBefore)) { + throw new IllegalArgumentException("certificateNotAfter < certificateNotBefore"); + } + + mKeystoreAlias = keyStoreAlias; + mKeySize = keySize; + mSpec = spec; + mCertificateSubject = certificateSubject; + mCertificateSerialNumber = certificateSerialNumber; + mCertificateNotBefore = certificateNotBefore; + mCertificateNotAfter = certificateNotAfter; + mFlags = flags; + mKeyValidityStart = keyValidityStart; + mKeyValidityForOriginationEnd = keyValidityForOriginationEnd; + mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd; + mPurposes = purposes; + mDigests = ArrayUtils.cloneIfNotEmpty(digests); + mEncryptionPaddings = + ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings)); + mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(signaturePaddings)); + mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes)); + mRandomizedEncryptionRequired = randomizedEncryptionRequired; + mUserAuthenticationRequired = userAuthenticationRequired; + mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; + } + + /** + * Returns the alias that will be used in the {@code java.security.KeyStore} + * in conjunction with the {@code AndroidKeyStore}. + */ + public String getKeystoreAlias() { + return mKeystoreAlias; + } + + /** + * Returns the requested key size or {@code -1} if default size should be used. + */ + public int getKeySize() { + return mKeySize; + } + + /** + * Returns the {@link AlgorithmParameterSpec} that will be used for creation + * of the key pair. + */ + @NonNull + public AlgorithmParameterSpec getAlgorithmParameterSpec() { + return mSpec; + } + + /** + * Returns the subject distinguished name to be used on the X.509 certificate that will be put + * in the {@link java.security.KeyStore}. + */ + @NonNull + public X500Principal getCertificateSubject() { + return mCertificateSubject; + } + + /** + * Returns the serial number to be used on the X.509 certificate that will be put in the + * {@link java.security.KeyStore}. + */ + @NonNull + public BigInteger getCertificateSerialNumber() { + return mCertificateSerialNumber; + } + + /** + * Returns the start date to be used on the X.509 certificate that will be put in the + * {@link java.security.KeyStore}. + */ + @NonNull + public Date getCertificateNotBefore() { + return mCertificateNotBefore; + } + + /** + * Returns the end date to be used on the X.509 certificate that will be put in the + * {@link java.security.KeyStore}. + */ + @NonNull + public Date getCertificateNotAfter() { + return mCertificateNotAfter; + } + + /** + * @hide + */ + public int getFlags() { + return mFlags; + } + + /** + * Returns {@code true} if the key must be encrypted at rest. This will protect the key with the + * secure lock screen credential (e.g., password, PIN, or pattern). + * + * <p>Note that encrypting the key at rest requires that the secure lock screen (e.g., password, + * PIN, pattern) is set up, otherwise key generation will fail. Moreover, this key will be + * deleted when the secure lock screen is disabled or reset (e.g., by the user or a Device + * Administrator). Finally, this key cannot be used until the user unlocks the secure lock + * screen after boot. + * + * @see KeyguardManager#isDeviceSecure() + */ + public boolean isEncryptionAtRestRequired() { + return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0; + } + + /** + * Returns the time instant before which the key is not yet valid or {@code null} if not + * restricted. + */ + @Nullable + public Date getKeyValidityStart() { + return mKeyValidityStart; + } + + /** + * Returns the time instant after which the key is no longer valid for decryption and + * verification or {@code null} if not restricted. + */ + @Nullable + public Date getKeyValidityForConsumptionEnd() { + return mKeyValidityForConsumptionEnd; + } + + /** + * Returns the time instant after which the key is no longer valid for encryption and signing + * or {@code null} if not restricted. + */ + @Nullable + public Date getKeyValidityForOriginationEnd() { + return mKeyValidityForOriginationEnd; + } + + /** + * Returns the set of purposes (e.g., encrypt, decrypt, sign) for which the key can be used. + * Attempts to use the key for any other purpose will be rejected. + * + * <p>See {@link KeyProperties}.{@code PURPOSE} flags. + */ + public @KeyProperties.PurposeEnum int getPurposes() { + return mPurposes; + } + + /** + * Returns the set of digest algorithms (e.g., {@code SHA-256}, {@code SHA-384} with which the + * key can be used or {@code null} if not specified. + * + * <p>See {@link KeyProperties}.{@code DIGEST} constants. + * + * @throws IllegalStateException if this set has not been specified. + * + * @see #isDigestsSpecified() + */ + @NonNull + public @KeyProperties.DigestEnum String[] getDigests() { + if (mDigests == null) { + throw new IllegalStateException("Digests not specified"); + } + return ArrayUtils.cloneIfNotEmpty(mDigests); + } + + /** + * Returns {@code true} if the set of digest algorithms with which the key can be used has been + * specified. + * + * @see #getDigests() + */ + @NonNull + public boolean isDigestsSpecified() { + return mDigests != null; + } + + /** + * Returns the set of padding schemes (e.g., {@code PKCS7Padding}, {@code OEAPPadding}, + * {@code PKCS1Padding}, {@code NoPadding}) with which the key can be used when + * encrypting/decrypting. Attempts to use the key with any other padding scheme will be + * rejected. + * + * <p>See {@link KeyProperties}.{@code ENCRYPTION_PADDING} constants. + */ + @NonNull + public @KeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { + return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); + } + + /** + * Gets the set of padding schemes (e.g., {@code PSS}, {@code PKCS#1}) with which the key + * can be used when signing/verifying. Attempts to use the key with any other padding scheme + * will be rejected. + * + * <p>See {@link KeyProperties}.{@code SIGNATURE_PADDING} constants. + */ + @NonNull + public @KeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { + return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings); + } + + /** + * Gets the set of block modes (e.g., {@code CBC}, {@code CTR}) with which the key can be used + * when encrypting/decrypting. Attempts to use the key with any other block modes will be + * rejected. + * + * <p>See {@link KeyProperties}.{@code BLOCK_MODE} constants. + */ + @NonNull + public @KeyProperties.BlockModeEnum String[] getBlockModes() { + return ArrayUtils.cloneIfNotEmpty(mBlockModes); + } + + /** + * Returns {@code true} if encryption using this key must be sufficiently randomized to produce + * different ciphertexts for the same plaintext every time. The formal cryptographic property + * being required is <em>indistinguishability under chosen-plaintext attack ({@code + * IND-CPA})</em>. This property is important because it mitigates several classes of + * weaknesses due to which ciphertext may leak information about plaintext. For example, if a + * given plaintext always produces the same ciphertext, an attacker may see the repeated + * ciphertexts and be able to deduce something about the plaintext. + */ + public boolean isRandomizedEncryptionRequired() { + return mRandomizedEncryptionRequired; + } + + /** + * Returns {@code true} if user authentication is required for this key to be used. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @see #getUserAuthenticationValidityDurationSeconds() + */ + public boolean isUserAuthenticationRequired() { + return mUserAuthenticationRequired; + } + + /** + * Gets the duration of time (seconds) for which this key can be used after the user is + * successfully authenticated. This has effect only if user authentication is required. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. + * + * @see #isUserAuthenticationRequired() + */ + public int getUserAuthenticationValidityDurationSeconds() { + return mUserAuthenticationValidityDurationSeconds; + } + + /** + * Builder of {@link KeyGenParameterSpec} instances. + */ + public final static class Builder { + private final String mKeystoreAlias; + private @KeyProperties.PurposeEnum int mPurposes; + + private int mKeySize = -1; + private AlgorithmParameterSpec mSpec; + private X500Principal mCertificateSubject; + private BigInteger mCertificateSerialNumber; + private Date mCertificateNotBefore; + private Date mCertificateNotAfter; + private int mFlags; + private Date mKeyValidityStart; + private Date mKeyValidityForOriginationEnd; + private Date mKeyValidityForConsumptionEnd; + private @KeyProperties.DigestEnum String[] mDigests; + private @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; + private @KeyProperties.BlockModeEnum String[] mBlockModes; + private boolean mRandomizedEncryptionRequired = true; + private boolean mUserAuthenticationRequired; + private int mUserAuthenticationValidityDurationSeconds = -1; + + /** + * Creates a new instance of the {@code Builder}. + * + * @param keystoreAlias alias of the entry in which the generated key will appear in + * Android KeyStore. + * @param purposes set of purposes (e.g., encrypt, decrypt, sign) for which the key can be + * used. Attempts to use the key for any other purpose will be rejected. + * + * <p>If the set of purposes for which the key can be used does not contain + * {@link KeyProperties#PURPOSE_SIGN}, the self-signed certificate generated by + * {@link KeyPairGenerator} of {@code AndroidKeyStore} provider will contain an + * invalid signature. This is OK if the certificate is only used for obtaining the + * public key from Android KeyStore. + * + * <p><b>NOTE: The {@code purposes} parameter has currently no effect on asymmetric + * key pairs.</b> + * + * <p>See {@link KeyProperties}.{@code PURPOSE} flags. + */ + public Builder(@NonNull String keystoreAlias, @KeyProperties.PurposeEnum int purposes) { + if (keystoreAlias == null) { + throw new NullPointerException("keystoreAlias == null"); + } + mKeystoreAlias = keystoreAlias; + mPurposes = purposes; + } + + /** + * Sets the size (in bits) of the key to be generated. For instance, for RSA keys this sets + * the modulus size, for EC keys this selects a curve with a matching field size, and for + * symmetric keys this sets the size of the bitstring which is their key material. + * + * <p>The default key size is specific to each key algorithm. + */ + @NonNull + public Builder setKeySize(int keySize) { + if (keySize < 0) { + throw new IllegalArgumentException("keySize < 0"); + } + mKeySize = keySize; + return this; + } + + /** + * Sets the algorithm-specific key generation parameters. For example, for RSA keys this may + * be an instance of {@link java.security.spec.RSAKeyGenParameterSpec}. + */ + public Builder setAlgorithmParameterSpec(@NonNull AlgorithmParameterSpec spec) { + if (spec == null) { + throw new NullPointerException("spec == null"); + } + mSpec = spec; + return this; + } + + /** + * Sets the subject used for the self-signed certificate of the generated key pair. + * + * <p>By default, the subject is {@code CN=fake}. + */ + @NonNull + public Builder setCertificateSubject(@NonNull X500Principal subject) { + if (subject == null) { + throw new NullPointerException("subject == null"); + } + mCertificateSubject = subject; + return this; + } + + /** + * Sets the serial number used for the self-signed certificate of the generated key pair. + * + * <p>By default, the serial number is {@code 1}. + */ + @NonNull + public Builder setCertificateSerialNumber(@NonNull BigInteger serialNumber) { + if (serialNumber == null) { + throw new NullPointerException("serialNumber == null"); + } + mCertificateSerialNumber = serialNumber; + return this; + } + + /** + * Sets the start of the validity period for the self-signed certificate of the generated + * key pair. + * + * <p>By default, this date is {@code Jan 1 1970}. + */ + @NonNull + public Builder setCertificateNotBefore(@NonNull Date date) { + if (date == null) { + throw new NullPointerException("date == null"); + } + mCertificateNotBefore = date; + return this; + } + + /** + * Sets the end of the validity period for the self-signed certificate of the generated key + * pair. + * + * <p>By default, this date is {@code Jan 1 2048}. + */ + @NonNull + public Builder setCertificateNotAfter(@NonNull Date date) { + if (date == null) { + throw new NullPointerException("date == null"); + } + mCertificateNotAfter = date; + return this; + } + + /** + * Sets whether this key pair or key must be encrypted at rest. This will protect the key + * pair or key with the secure lock screen credential (e.g., password, PIN, or pattern). + * + * <p>Note that enabling this feature requires that the secure lock screen (e.g., password, + * PIN, pattern) is set up, otherwise key generation will fail. Moreover, this key will be + * deleted when the secure lock screen is disabled or reset (e.g., by the user or a Device + * Administrator). Finally, this key cannot be used until the user unlocks the secure lock + * screen after boot. + * + * @see KeyguardManager#isDeviceSecure() + */ + @NonNull + public Builder setEncryptionAtRestRequired(boolean required) { + if (required) { + mFlags |= KeyStore.FLAG_ENCRYPTED; + } else { + mFlags &= ~KeyStore.FLAG_ENCRYPTED; + } + return this; + } + + /** + * Sets the time instant before which the key is not yet valid. + * + * <p>By default, the key is valid at any instant. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * @see #setKeyValidityEnd(Date) + */ + @NonNull + public Builder setKeyValidityStart(Date startDate) { + mKeyValidityStart = startDate; + return this; + } + + /** + * Sets the time instant after which the key is no longer valid. + * + * <p>By default, the key is valid at any instant. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * @see #setKeyValidityStart(Date) + * @see #setKeyValidityForConsumptionEnd(Date) + * @see #setKeyValidityForOriginationEnd(Date) + */ + @NonNull + public Builder setKeyValidityEnd(Date endDate) { + setKeyValidityForOriginationEnd(endDate); + setKeyValidityForConsumptionEnd(endDate); + return this; + } + + /** + * Sets the time instant after which the key is no longer valid for encryption and signing. + * + * <p>By default, the key is valid at any instant. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * @see #setKeyValidityForConsumptionEnd(Date) + */ + @NonNull + public Builder setKeyValidityForOriginationEnd(Date endDate) { + mKeyValidityForOriginationEnd = endDate; + return this; + } + + /** + * Sets the time instant after which the key is no longer valid for decryption and + * verification. + * + * <p>By default, the key is valid at any instant. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * @see #setKeyValidityForOriginationEnd(Date) + */ + @NonNull + public Builder setKeyValidityForConsumptionEnd(Date endDate) { + mKeyValidityForConsumptionEnd = endDate; + return this; + } + + /** + * Sets the set of digests algorithms (e.g., {@code SHA-256}, {@code SHA-384}) with which + * the key can be used when signing/verifying. Attempts to use the key with any other digest + * algorithm will be rejected. + * + * <p>This must be specified for keys which are used for signing/verification. For HMAC + * keys, the set of digests defaults to the digest associated with the key algorithm (e.g., + * {@code SHA-256} for key algorithm {@code HmacSHA256} + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * @see KeyProperties.Digest + */ + @NonNull + public Builder setDigests(@KeyProperties.DigestEnum String... digests) { + mDigests = ArrayUtils.cloneIfNotEmpty(digests); + return this; + } + + /** + * Sets the set of padding schemes (e.g., {@code PKCS7Padding}, {@code OAEPPadding}, + * {@code PKCS1Padding}, {@code NoPadding}) with which the key can be used when + * encrypting/decrypting. Attempts to use the key with any other padding scheme will be + * rejected. + * + * <p>This must be specified for keys which are used for encryption/decryption. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * <p>See {@link KeyProperties}.{@code ENCRYPTION_PADDING} constants. + */ + @NonNull + public Builder setEncryptionPaddings( + @KeyProperties.EncryptionPaddingEnum String... paddings) { + mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings); + return this; + } + + /** + * Sets the set of padding schemes (e.g., {@code PSS}, {@code PKCS#1}) with which the key + * can be used when signing/verifying. Attempts to use the key with any other padding scheme + * will be rejected. + * + * <p>This must be specified for RSA keys which are used for signing/verification. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * <p>See {@link KeyProperties}.{@code SIGNATURE_PADDING} constants. + */ + @NonNull + public Builder setSignaturePaddings( + @KeyProperties.SignaturePaddingEnum String... paddings) { + mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(paddings); + return this; + } + + /** + * Sets the set of block modes (e.g., {@code CBC}, {@code CTR}, {@code ECB}) with which the + * key can be used when encrypting/decrypting. Attempts to use the key with any other block + * modes will be rejected. + * + * <p>This must be specified for encryption/decryption keys. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * <p>See {@link KeyProperties}.{@code BLOCK_MODE} constants. + */ + @NonNull + public Builder setBlockModes(@KeyProperties.BlockModeEnum String... blockModes) { + mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes); + return this; + } + + /** + * Sets whether encryption using this key must be sufficiently randomized to produce + * different ciphertexts for the same plaintext every time. The formal cryptographic + * property being required is <em>indistinguishability under chosen-plaintext attack + * ({@code IND-CPA})</em>. This property is important because it mitigates several classes + * of weaknesses due to which ciphertext may leak information about plaintext. For example, + * if a given plaintext always produces the same ciphertext, an attacker may see the + * repeated ciphertexts and be able to deduce something about the plaintext. + * + * <p>By default, {@code IND-CPA} is required. + * + * <p>When {@code IND-CPA} is required: + * <ul> + * <li>encryption/decryption transformation which do not offer {@code IND-CPA}, such as + * {@code ECB} with a symmetric encryption algorithm, or RSA encryption/decryption without + * padding, are prohibited;</li> + * <li>in block modes which use an IV, such as {@code CBC}, {@code CTR}, and {@code GCM}, + * caller-provided IVs are rejected when encrypting, to ensure that only random IVs are + * used.</li> + * </ul> + * + * <p>Before disabling this requirement, consider the following approaches instead: + * <ul> + * <li>If you are generating a random IV for encryption and then initializing a {@code} + * Cipher using the IV, the solution is to let the {@code Cipher} generate a random IV + * instead. This will occur if the {@code Cipher} is initialized for encryption without an + * IV. The IV can then be queried via {@link Cipher#getIV()}.</li> + * <li>If you are generating a non-random IV (e.g., an IV derived from something not fully + * random, such as the name of the file being encrypted, or transaction ID, or password, + * or a device identifier), consider changing your design to use a random IV which will then + * be provided in addition to the ciphertext to the entities which need to decrypt the + * ciphertext.</li> + * <li>If you are using RSA encryption without padding, consider switching to encryption + * padding schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li> + * </ul> + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + */ + @NonNull + public Builder setRandomizedEncryptionRequired(boolean required) { + mRandomizedEncryptionRequired = required; + return this; + } + + /** + * Sets whether user authentication is required to use this key. + * + * <p>By default, the key can be used without user authentication. + * + * <p>When user authentication is required, the user authorizes the use of the key by + * authenticating to this Android device using a subset of their secure lock screen + * credentials. Different authentication methods are used depending on whether the every + * use of the key must be authenticated (as specified by + * {@link #setUserAuthenticationValidityDurationSeconds(int)}). + * <a href="{@docRoot}training/articles/keystore.html#UserAuthentication">More + * information</a>. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * <p><b>NOTE: This has currently no effect.</b> + * + * @see #setUserAuthenticationValidityDurationSeconds(int) + */ + @NonNull + public Builder setUserAuthenticationRequired(boolean required) { + mUserAuthenticationRequired = required; + return this; + } + + /** + * Sets the duration of time (seconds) for which this key can be used after the user is + * successfully authenticated. This has effect only if user authentication is required. + * + * <p>By default, the user needs to authenticate for every use of the key. + * + * <p><b>NOTE: This has currently no effect.</b> + * + * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for + * every use of the key. + * + * @see #setUserAuthenticationRequired(boolean) + */ + @NonNull + public Builder setUserAuthenticationValidityDurationSeconds( + @IntRange(from = -1) int seconds) { + mUserAuthenticationValidityDurationSeconds = seconds; + return this; + } + + /** + * Builds an instance of {@code KeyGenParameterSpec}. + * + * @throws IllegalArgumentException if a required field is missing + */ + @NonNull + public KeyGenParameterSpec build() { + return new KeyGenParameterSpec( + mKeystoreAlias, + mKeySize, + mSpec, + mCertificateSubject, + mCertificateSerialNumber, + mCertificateNotBefore, + mCertificateNotAfter, + mFlags, + mKeyValidityStart, + mKeyValidityForOriginationEnd, + mKeyValidityForConsumptionEnd, + mPurposes, + mDigests, + mEncryptionPaddings, + mSignaturePaddings, + mBlockModes, + mRandomizedEncryptionRequired, + mUserAuthenticationRequired, + mUserAuthenticationValidityDurationSeconds); + } + } +} diff --git a/keystore/java/android/security/KeyStoreKeySpec.java b/keystore/java/android/security/keystore/KeyInfo.java index a630a0a..e4f921e 100644 --- a/keystore/java/android/security/KeyStoreKeySpec.java +++ b/keystore/java/android/security/keystore/KeyInfo.java @@ -14,28 +14,68 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.security.PrivateKey; import java.security.spec.KeySpec; import java.util.Date; +import javax.crypto.SecretKey; + /** * Information about a key from the <a href="{@docRoot}training/articles/keystore.html">Android - * KeyStore</a>. + * Keystore system</a>. This class describes whether the key material is available in + * plaintext outside of secure hardware, whether user authentication is required for using the key + * and whether this requirement is enforced by secure hardware, the key's origin, what uses the key + * is authorized for (e.g., only in {@code CBC} mode, or signing only), whether the key should be + * encrypted at rest, the key's and validity start and end dates. + * + * <p><h3>Example: Symmetric Key</h3> + * The following example illustrates how to obtain a {@code KeyInfo} describing the provided Android + * Keystore {@link SecretKey}. + * <pre> {@code + * SecretKey key = ...; // Android Keystore key + * + * SecretKeyFactory factory = SecretKeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore"); + * KeyInfo keyInfo; + * try { + * keyInfo = (KeyInfo) factory.getKeySpec(key, KeyInfo.class); + * } catch (InvalidKeySpecException e) { + * // Not an Android KeyStore key. + * } + * }</pre> + * + * <p><h3>Example: Private Key</h3> + * The following example illustrates how to obtain a {@code KeyInfo} describing the provided + * Android KeyStore {@link PrivateKey}. + * <pre> {@code + * PrivateKey key = ...; // Android KeyStore key + * + * KeyFactory factory = KeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore"); + * KeyInfo keyInfo; + * try { + * keyInfo = factory.getKeySpec(key, KeyInfo.class); + * } catch (InvalidKeySpecException e) { + * // Not an Android KeyStore key. + * } + * }</pre> */ -public class KeyStoreKeySpec implements KeySpec { +public class KeyInfo implements KeySpec { private final String mKeystoreAlias; private final int mKeySize; private final boolean mInsideSecureHardware; - private final @KeyStoreKeyProperties.OriginEnum int mOrigin; + private final @KeyProperties.OriginEnum int mOrigin; private final Date mKeyValidityStart; private final Date mKeyValidityForOriginationEnd; private final Date mKeyValidityForConsumptionEnd; - private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; - private final @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; - private final @KeyStoreKeyProperties.DigestEnum String[] mDigests; - private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; + private final @KeyProperties.PurposeEnum int mPurposes; + private final @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private final @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; + private final @KeyProperties.DigestEnum String[] mDigests; + private final @KeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; private final boolean mUserAuthenticationRequirementEnforcedBySecureHardware; @@ -43,18 +83,18 @@ public class KeyStoreKeySpec implements KeySpec { /** * @hide */ - KeyStoreKeySpec(String keystoreKeyAlias, + public KeyInfo(String keystoreKeyAlias, boolean insideSecureHardware, - @KeyStoreKeyProperties.OriginEnum int origin, + @KeyProperties.OriginEnum int origin, int keySize, Date keyValidityStart, Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, - @KeyStoreKeyProperties.PurposeEnum int purposes, - @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, - @KeyStoreKeyProperties.SignaturePaddingEnum String[] signaturePaddings, - @KeyStoreKeyProperties.DigestEnum String[] digests, - @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, + @KeyProperties.PurposeEnum int purposes, + @KeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyProperties.SignaturePaddingEnum String[] signaturePaddings, + @KeyProperties.DigestEnum String[] digests, + @KeyProperties.BlockModeEnum String[] blockModes, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds, boolean userAuthenticationRequirementEnforcedBySecureHardware) { @@ -95,9 +135,9 @@ public class KeyStoreKeySpec implements KeySpec { } /** - * Gets the origin of the key. + * Gets the origin of the key. See {@link KeyProperties}.{@code ORIGIN} constants. */ - public @KeyStoreKeyProperties.OriginEnum int getOrigin() { + public @KeyProperties.OriginEnum int getOrigin() { return mOrigin; } @@ -113,6 +153,7 @@ public class KeyStoreKeySpec implements KeySpec { * * @return instant or {@code null} if not restricted. */ + @Nullable public Date getKeyValidityStart() { return mKeyValidityStart; } @@ -122,6 +163,7 @@ public class KeyStoreKeySpec implements KeySpec { * * @return instant or {@code null} if not restricted. */ + @Nullable public Date getKeyValidityForConsumptionEnd() { return mKeyValidityForConsumptionEnd; } @@ -131,42 +173,65 @@ public class KeyStoreKeySpec implements KeySpec { * * @return instant or {@code null} if not restricted. */ + @Nullable public Date getKeyValidityForOriginationEnd() { return mKeyValidityForOriginationEnd; } /** - * Gets the set of purposes for which the key can be used. + * Gets the set of purposes (e.g., encrypt, decrypt, sign) for which the key can be used. + * Attempts to use the key for any other purpose will be rejected. + * + * <p>See {@link KeyProperties}.{@code PURPOSE} flags. */ - public @KeyStoreKeyProperties.PurposeEnum int getPurposes() { + public @KeyProperties.PurposeEnum int getPurposes() { return mPurposes; } /** - * Gets the set of block modes with which the key can be used. + * Gets the set of block modes (e.g., {@code CBC}, {@code CTR}) with which the key can be used + * when encrypting/decrypting. Attempts to use the key with any other block modes will be + * rejected. + * + * <p>See {@link KeyProperties}.{@code BLOCK_MODE} constants. */ - public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { + @NonNull + public @KeyProperties.BlockModeEnum String[] getBlockModes() { return ArrayUtils.cloneIfNotEmpty(mBlockModes); } /** - * Gets the set of padding modes with which the key can be used when encrypting/decrypting. + * Gets the set of padding schemes (e.g., {@code PKCS7Padding}, {@code PKCS1Padding}, + * {@code NoPadding}) with which the key can be used when encrypting/decrypting. Attempts to use + * the key with any other padding scheme will be rejected. + * + * <p>See {@link KeyProperties}.{@code ENCRYPTION_PADDING} constants. */ - public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { + @NonNull + public @KeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); } /** - * Gets the set of padding modes with which the key can be used when signing/verifying. + * Gets the set of padding schemes (e.g., {@code PSS}, {@code PKCS#1}) with which the key + * can be used when signing/verifying. Attempts to use the key with any other padding scheme + * will be rejected. + * + * <p>See {@link KeyProperties}.{@code SIGNATURE_PADDING} constants. */ - public @KeyStoreKeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { + @NonNull + public @KeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings); } /** - * Gets the set of digest algorithms with which the key can be used. + * Gets the set of digest algorithms (e.g., {@code SHA-256}, {@code SHA-384}) with which the key + * can be used. + * + * <p>See {@link KeyProperties}.{@code DIGEST} constants. */ - public @KeyStoreKeyProperties.DigestEnum String[] getDigests() { + @NonNull + public @KeyProperties.DigestEnum String[] getDigests() { return ArrayUtils.cloneIfNotEmpty(mDigests); } diff --git a/keystore/java/android/security/KeyNotYetValidException.java b/keystore/java/android/security/keystore/KeyNotYetValidException.java index 4ea27ef..2cec77d 100644 --- a/keystore/java/android/security/KeyNotYetValidException.java +++ b/keystore/java/android/security/keystore/KeyNotYetValidException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; import java.security.InvalidKeyException; diff --git a/keystore/java/android/security/KeyPermanentlyInvalidatedException.java b/keystore/java/android/security/keystore/KeyPermanentlyInvalidatedException.java index 229eab0..e320c9c 100644 --- a/keystore/java/android/security/KeyPermanentlyInvalidatedException.java +++ b/keystore/java/android/security/keystore/KeyPermanentlyInvalidatedException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; import java.security.InvalidKeyException; diff --git a/keystore/java/android/security/KeyStoreKeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index 1cf6a7a..e3c2d1d 100644 --- a/keystore/java/android/security/KeyStoreKeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -14,9 +14,11 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.StringDef; import android.security.keymaster.KeymasterDefs; @@ -24,93 +26,85 @@ import libcore.util.EmptyArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.security.Key; -import java.security.KeyFactory; -import java.security.KeyPairGenerator; import java.util.Collection; import java.util.Locale; -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.Mac; -import javax.crypto.SecretKeyFactory; - /** - * Properties of {@code AndroidKeyStore} keys. + * Properties of <a href="{@docRoot}training/articles/keystore.html">Android Keystore</a> keys. */ -public abstract class KeyStoreKeyProperties { - private KeyStoreKeyProperties() {} +public abstract class KeyProperties { + private KeyProperties() {} + /** + * @hide + */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, - value = {Purpose.ENCRYPT, Purpose.DECRYPT, Purpose.SIGN, Purpose.VERIFY}) + value = { + PURPOSE_ENCRYPT, + PURPOSE_DECRYPT, + PURPOSE_SIGN, + PURPOSE_VERIFY, + }) public @interface PurposeEnum {} /** - * Purposes of key. + * Purpose of key: encryption. */ - public static abstract class Purpose { - private Purpose() {} + public static final int PURPOSE_ENCRYPT = 1 << 0; - /** - * Purpose: encryption. - */ - public static final int ENCRYPT = 1 << 0; + /** + * Purpose of key: decryption. + */ + public static final int PURPOSE_DECRYPT = 1 << 1; - /** - * Purpose: decryption. - */ - public static final int DECRYPT = 1 << 1; + /** + * Purpose of key: signing or generating a Message Authentication Code (MAC). + */ + public static final int PURPOSE_SIGN = 1 << 2; - /** - * Purpose: signing. - */ - public static final int SIGN = 1 << 2; + /** + * Purpose of key: signature or Message Authentication Code (MAC) verification. + */ + public static final int PURPOSE_VERIFY = 1 << 3; - /** - * Purpose: signature verification. - */ - public static final int VERIFY = 1 << 3; + /** + * @hide + */ + public static abstract class Purpose { + private Purpose() {} - /** - * @hide - */ public static int toKeymaster(@PurposeEnum int purpose) { switch (purpose) { - case ENCRYPT: + case PURPOSE_ENCRYPT: return KeymasterDefs.KM_PURPOSE_ENCRYPT; - case DECRYPT: + case PURPOSE_DECRYPT: return KeymasterDefs.KM_PURPOSE_DECRYPT; - case SIGN: + case PURPOSE_SIGN: return KeymasterDefs.KM_PURPOSE_SIGN; - case VERIFY: + case PURPOSE_VERIFY: return KeymasterDefs.KM_PURPOSE_VERIFY; default: throw new IllegalArgumentException("Unknown purpose: " + purpose); } } - /** - * @hide - */ public static @PurposeEnum int fromKeymaster(int purpose) { switch (purpose) { case KeymasterDefs.KM_PURPOSE_ENCRYPT: - return ENCRYPT; + return PURPOSE_ENCRYPT; case KeymasterDefs.KM_PURPOSE_DECRYPT: - return DECRYPT; + return PURPOSE_DECRYPT; case KeymasterDefs.KM_PURPOSE_SIGN: - return SIGN; + return PURPOSE_SIGN; case KeymasterDefs.KM_PURPOSE_VERIFY: - return VERIFY; + return PURPOSE_VERIFY; default: throw new IllegalArgumentException("Unknown purpose: " + purpose); } } - /** - * @hide - */ + @NonNull public static int[] allToKeymaster(@PurposeEnum int purposes) { int[] result = getSetFlags(purposes); for (int i = 0; i < result.length; i++) { @@ -119,10 +113,7 @@ public abstract class KeyStoreKeyProperties { return result; } - /** - * @hide - */ - public static @PurposeEnum int allFromKeymaster(Collection<Integer> purposes) { + public static @PurposeEnum int allFromKeymaster(@NonNull Collection<Integer> purposes) { @PurposeEnum int result = 0; for (int keymasterPurpose : purposes) { result |= fromKeymaster(keymasterPurpose); @@ -131,59 +122,55 @@ public abstract class KeyStoreKeyProperties { } } + /** + * @hide + */ @Retention(RetentionPolicy.SOURCE) @StringDef({ - Algorithm.RSA, - Algorithm.EC, - Algorithm.AES, - Algorithm.HMAC_SHA1, - Algorithm.HMAC_SHA224, - Algorithm.HMAC_SHA256, - Algorithm.HMAC_SHA384, - Algorithm.HMAC_SHA512, + KEY_ALGORITHM_RSA, + KEY_ALGORITHM_EC, + KEY_ALGORITHM_AES, + KEY_ALGORITHM_HMAC_SHA1, + KEY_ALGORITHM_HMAC_SHA224, + KEY_ALGORITHM_HMAC_SHA256, + KEY_ALGORITHM_HMAC_SHA384, + KEY_ALGORITHM_HMAC_SHA512, }) - public @interface AlgorithmEnum {} + public @interface KeyAlgorithmEnum {} - /** - * Key algorithms. - * - * <p>These are standard names which can be used to obtain instances of {@link KeyGenerator}, - * {@link KeyPairGenerator}, {@link Cipher} (as part of the transformation string), {@link Mac}, - * {@link KeyFactory}, {@link SecretKeyFactory}. These are also the names used by - * {@link Key#getAlgorithm()}. - */ - public static abstract class Algorithm { - private Algorithm() {} + /** Rivest Shamir Adleman (RSA) key. */ + public static final String KEY_ALGORITHM_RSA = "RSA"; - /** Rivest Shamir Adleman (RSA) key. */ - public static final String RSA = "RSA"; + /** Elliptic Curve (EC) Cryptography key. */ + public static final String KEY_ALGORITHM_EC = "EC"; - /** Elliptic Curve (EC) key. */ - public static final String EC = "EC"; + /** Advanced Encryption Standard (AES) key. */ + public static final String KEY_ALGORITHM_AES = "AES"; - /** Advanced Encryption Standard (AES) key. */ - public static final String AES = "AES"; + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-1 as the hash. */ + public static final String KEY_ALGORITHM_HMAC_SHA1 = "HmacSHA1"; - /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-1 as the hash. */ - public static final String HMAC_SHA1 = "HmacSHA1"; + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-224 as the hash. */ + public static final String KEY_ALGORITHM_HMAC_SHA224 = "HmacSHA224"; - /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-224 as the hash. */ - public static final String HMAC_SHA224 = "HmacSHA224"; + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-256 as the hash. */ + public static final String KEY_ALGORITHM_HMAC_SHA256 = "HmacSHA256"; - /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-256 as the hash. */ - public static final String HMAC_SHA256 = "HmacSHA256"; + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-384 as the hash. */ + public static final String KEY_ALGORITHM_HMAC_SHA384 = "HmacSHA384"; - /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-384 as the hash. */ - public static final String HMAC_SHA384 = "HmacSHA384"; + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-512 as the hash. */ + public static final String KEY_ALGORITHM_HMAC_SHA512 = "HmacSHA512"; - /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-512 as the hash. */ - public static final String HMAC_SHA512 = "HmacSHA512"; + /** + * @hide + */ + public static abstract class KeyAlgorithm { + private KeyAlgorithm() {} - /** - * @hide - */ - static int toKeymasterSecretKeyAlgorithm(@AlgorithmEnum String algorithm) { - if (AES.equalsIgnoreCase(algorithm)) { + public static int toKeymasterSecretKeyAlgorithm( + @NonNull @KeyAlgorithmEnum String algorithm) { + if (KEY_ALGORITHM_AES.equalsIgnoreCase(algorithm)) { return KeymasterDefs.KM_ALGORITHM_AES; } else if (algorithm.toUpperCase(Locale.US).startsWith("HMAC")) { return KeymasterDefs.KM_ALGORITHM_HMAC; @@ -193,10 +180,8 @@ public abstract class KeyStoreKeyProperties { } } - /** - * @hide - */ - static @AlgorithmEnum String fromKeymasterSecretKeyAlgorithm( + @NonNull + public static @KeyAlgorithmEnum String fromKeymasterSecretKeyAlgorithm( int keymasterAlgorithm, int keymasterDigest) { switch (keymasterAlgorithm) { case KeymasterDefs.KM_ALGORITHM_AES: @@ -204,26 +189,26 @@ public abstract class KeyStoreKeyProperties { throw new IllegalArgumentException("Digest not supported for AES key: " + Digest.fromKeymaster(keymasterDigest)); } - return AES; + return KEY_ALGORITHM_AES; case KeymasterDefs.KM_ALGORITHM_HMAC: switch (keymasterDigest) { case KeymasterDefs.KM_DIGEST_SHA1: - return HMAC_SHA1; + return KEY_ALGORITHM_HMAC_SHA1; case KeymasterDefs.KM_DIGEST_SHA_2_224: - return HMAC_SHA224; + return KEY_ALGORITHM_HMAC_SHA224; case KeymasterDefs.KM_DIGEST_SHA_2_256: - return HMAC_SHA256; + return KEY_ALGORITHM_HMAC_SHA256; case KeymasterDefs.KM_DIGEST_SHA_2_384: - return HMAC_SHA384; + return KEY_ALGORITHM_HMAC_SHA384; case KeymasterDefs.KM_DIGEST_SHA_2_512: - return HMAC_SHA512; + return KEY_ALGORITHM_HMAC_SHA512; default: throw new IllegalArgumentException("Unsupported HMAC digest: " + Digest.fromKeymaster(keymasterDigest)); } default: throw new IllegalArgumentException( - "Unsupported algorithm: " + keymasterAlgorithm); + "Unsupported key algorithm: " + keymasterAlgorithm); } } @@ -232,7 +217,7 @@ public abstract class KeyStoreKeyProperties { * * @return keymaster digest or {@code -1} if the algorithm does not involve a digest. */ - static int toKeymasterDigest(@AlgorithmEnum String algorithm) { + public static int toKeymasterDigest(@NonNull @KeyAlgorithmEnum String algorithm) { String algorithmUpper = algorithm.toUpperCase(Locale.US); if (algorithmUpper.startsWith("HMAC")) { String digestUpper = algorithmUpper.substring("HMAC".length()); @@ -257,72 +242,69 @@ public abstract class KeyStoreKeyProperties { } } + /** + * @hide + */ @Retention(RetentionPolicy.SOURCE) @StringDef({ - BlockMode.ECB, - BlockMode.CBC, - BlockMode.CTR, - BlockMode.GCM, + BLOCK_MODE_ECB, + BLOCK_MODE_CBC, + BLOCK_MODE_CTR, + BLOCK_MODE_GCM, }) public @interface BlockModeEnum {} - /** - * Block modes that can be used when encrypting/decrypting using a key. - */ - public static abstract class BlockMode { - private BlockMode() {} + /** Electronic Codebook (ECB) block mode. */ + public static final String BLOCK_MODE_ECB = "ECB"; - /** Electronic Codebook (ECB) block mode. */ - public static final String ECB = "ECB"; + /** Cipher Block Chaining (CBC) block mode. */ + public static final String BLOCK_MODE_CBC = "CBC"; - /** Cipher Block Chaining (CBC) block mode. */ - public static final String CBC = "CBC"; + /** Counter (CTR) block mode. */ + public static final String BLOCK_MODE_CTR = "CTR"; - /** Counter (CTR) block mode. */ - public static final String CTR = "CTR"; + /** Galois/Counter Mode (GCM) block mode. */ + public static final String BLOCK_MODE_GCM = "GCM"; - /** Galois/Counter Mode (GCM) block mode. */ - public static final String GCM = "GCM"; + /** + * @hide + */ + public static abstract class BlockMode { + private BlockMode() {} - /** - * @hide - */ - static int toKeymaster(@BlockModeEnum String blockMode) { - if (ECB.equalsIgnoreCase(blockMode)) { + public static int toKeymaster(@NonNull @BlockModeEnum String blockMode) { + if (BLOCK_MODE_ECB.equalsIgnoreCase(blockMode)) { return KeymasterDefs.KM_MODE_ECB; - } else if (CBC.equalsIgnoreCase(blockMode)) { + } else if (BLOCK_MODE_CBC.equalsIgnoreCase(blockMode)) { return KeymasterDefs.KM_MODE_CBC; - } else if (CTR.equalsIgnoreCase(blockMode)) { + } else if (BLOCK_MODE_CTR.equalsIgnoreCase(blockMode)) { return KeymasterDefs.KM_MODE_CTR; - } else if (GCM.equalsIgnoreCase(blockMode)) { + } else if (BLOCK_MODE_GCM.equalsIgnoreCase(blockMode)) { return KeymasterDefs.KM_MODE_GCM; } else { throw new IllegalArgumentException("Unsupported block mode: " + blockMode); } } - /** - * @hide - */ - static @BlockModeEnum String fromKeymaster(int blockMode) { + @NonNull + public static @BlockModeEnum String fromKeymaster(int blockMode) { switch (blockMode) { case KeymasterDefs.KM_MODE_ECB: - return ECB; + return BLOCK_MODE_ECB; case KeymasterDefs.KM_MODE_CBC: - return CBC; + return BLOCK_MODE_CBC; case KeymasterDefs.KM_MODE_CTR: - return CTR; + return BLOCK_MODE_CTR; case KeymasterDefs.KM_MODE_GCM: - return GCM; + return BLOCK_MODE_GCM; default: throw new IllegalArgumentException("Unsupported block mode: " + blockMode); } } - /** - * @hide - */ - static @BlockModeEnum String[] allFromKeymaster(Collection<Integer> blockModes) { + @NonNull + public static @BlockModeEnum String[] allFromKeymaster( + @NonNull Collection<Integer> blockModes) { if ((blockModes == null) || (blockModes.isEmpty())) { return EmptyArray.STRING; } @@ -335,10 +317,7 @@ public abstract class KeyStoreKeyProperties { return result; } - /** - * @hide - */ - static int[] allToKeymaster(@BlockModeEnum String[] blockModes) { + public static int[] allToKeymaster(@Nullable @BlockModeEnum String[] blockModes) { if ((blockModes == null) || (blockModes.length == 0)) { return EmptyArray.INT; } @@ -350,52 +329,52 @@ public abstract class KeyStoreKeyProperties { } } + /** + * @hide + */ @Retention(RetentionPolicy.SOURCE) @StringDef({ - EncryptionPadding.NONE, - EncryptionPadding.PKCS7, - EncryptionPadding.RSA_PKCS1, - EncryptionPadding.RSA_OAEP, + ENCRYPTION_PADDING_NONE, + ENCRYPTION_PADDING_PKCS7, + ENCRYPTION_PADDING_RSA_PKCS1, + ENCRYPTION_PADDING_RSA_OAEP, }) public @interface EncryptionPaddingEnum {} /** - * Padding schemes for encryption/decryption. + * No encryption padding. */ - public static abstract class EncryptionPadding { - private EncryptionPadding() {} + public static final String ENCRYPTION_PADDING_NONE = "NoPadding"; - /** - * No padding. - */ - public static final String NONE = "NoPadding"; + /** + * PKCS#7 encryption padding scheme. + */ + public static final String ENCRYPTION_PADDING_PKCS7 = "PKCS7Padding"; - /** - * PKCS#7 padding. - */ - public static final String PKCS7 = "PKCS7Padding"; + /** + * RSA PKCS#1 v1.5 padding scheme for encryption. + */ + public static final String ENCRYPTION_PADDING_RSA_PKCS1 = "PKCS1Padding"; - /** - * RSA PKCS#1 v1.5 padding for encryption/decryption. - */ - public static final String RSA_PKCS1 = "PKCS1Padding"; + /** + * RSA Optimal Asymmetric Encryption Padding (OAEP) scheme. + */ + public static final String ENCRYPTION_PADDING_RSA_OAEP = "OAEPPadding"; - /** - * RSA Optimal Asymmetric Encryption Padding (OAEP). - */ - public static final String RSA_OAEP = "OAEPPadding"; + /** + * @hide + */ + public static abstract class EncryptionPadding { + private EncryptionPadding() {} - /** - * @hide - */ - static int toKeymaster(@EncryptionPaddingEnum String padding) { - if (NONE.equalsIgnoreCase(padding)) { + public static int toKeymaster(@NonNull @EncryptionPaddingEnum String padding) { + if (ENCRYPTION_PADDING_NONE.equalsIgnoreCase(padding)) { return KeymasterDefs.KM_PAD_NONE; - } else if (PKCS7.equalsIgnoreCase(padding)) { + } else if (ENCRYPTION_PADDING_PKCS7.equalsIgnoreCase(padding)) { return KeymasterDefs.KM_PAD_PKCS7; - } else if (RSA_PKCS1.equalsIgnoreCase(padding)) { + } else if (ENCRYPTION_PADDING_RSA_PKCS1.equalsIgnoreCase(padding)) { return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT; - } else if (RSA_OAEP.equalsIgnoreCase(padding)) { + } else if (ENCRYPTION_PADDING_RSA_OAEP.equalsIgnoreCase(padding)) { return KeymasterDefs.KM_PAD_RSA_OAEP; } else { throw new IllegalArgumentException( @@ -403,29 +382,25 @@ public abstract class KeyStoreKeyProperties { } } - /** - * @hide - */ - static @EncryptionPaddingEnum String fromKeymaster(int padding) { + @NonNull + public static @EncryptionPaddingEnum String fromKeymaster(int padding) { switch (padding) { case KeymasterDefs.KM_PAD_NONE: - return NONE; + return ENCRYPTION_PADDING_NONE; case KeymasterDefs.KM_PAD_PKCS7: - return PKCS7; + return ENCRYPTION_PADDING_PKCS7; case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT: - return RSA_PKCS1; + return ENCRYPTION_PADDING_RSA_PKCS1; case KeymasterDefs.KM_PAD_RSA_OAEP: - return RSA_OAEP; + return ENCRYPTION_PADDING_RSA_OAEP; default: throw new IllegalArgumentException( "Unsupported encryption padding: " + padding); } } - /** - * @hide - */ - static int[] allToKeymaster(@EncryptionPaddingEnum String[] paddings) { + @NonNull + public static int[] allToKeymaster(@Nullable @EncryptionPaddingEnum String[] paddings) { if ((paddings == null) || (paddings.length == 0)) { return EmptyArray.INT; } @@ -437,37 +412,34 @@ public abstract class KeyStoreKeyProperties { } } + /** + * @hide + */ @Retention(RetentionPolicy.SOURCE) @StringDef({ - SignaturePadding.RSA_PKCS1, - SignaturePadding.RSA_PSS, + SIGNATURE_PADDING_RSA_PKCS1, + SIGNATURE_PADDING_RSA_PSS, }) public @interface SignaturePaddingEnum {} /** - * Padding schemes for signing/verification. + * RSA PKCS#1 v1.5 padding for signatures. */ - public static abstract class SignaturePadding { - private SignaturePadding() {} + public static final String SIGNATURE_PADDING_RSA_PKCS1 = "PKCS1"; - /** - * RSA PKCS#1 v1.5 padding for signatures. - */ - public static final String RSA_PKCS1 = "PKCS1"; + /** + * RSA PKCS#1 v2.1 Probabilistic Signature Scheme (PSS) padding. + */ + public static final String SIGNATURE_PADDING_RSA_PSS = "PSS"; - /** - * RSA PKCS#1 v2.1 Probabilistic Signature Scheme (PSS) padding. - */ - public static final String RSA_PSS = "PSS"; + static abstract class SignaturePadding { + private SignaturePadding() {} - /** - * @hide - */ - static int toKeymaster(@SignaturePaddingEnum String padding) { + static int toKeymaster(@NonNull @SignaturePaddingEnum String padding) { switch (padding.toUpperCase(Locale.US)) { - case RSA_PKCS1: + case SIGNATURE_PADDING_RSA_PKCS1: return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN; - case RSA_PSS: + case SIGNATURE_PADDING_RSA_PSS: return KeymasterDefs.KM_PAD_RSA_PSS; default: throw new IllegalArgumentException( @@ -475,24 +447,20 @@ public abstract class KeyStoreKeyProperties { } } - /** - * @hide - */ + @NonNull static @SignaturePaddingEnum String fromKeymaster(int padding) { switch (padding) { case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN: - return RSA_PKCS1; + return SIGNATURE_PADDING_RSA_PKCS1; case KeymasterDefs.KM_PAD_RSA_PSS: - return RSA_PSS; + return SIGNATURE_PADDING_RSA_PSS; default: throw new IllegalArgumentException("Unsupported signature padding: " + padding); } } - /** - * @hide - */ - static int[] allToKeymaster(@SignaturePaddingEnum String[] paddings) { + @NonNull + static int[] allToKeymaster(@Nullable @SignaturePaddingEnum String[] paddings) { if ((paddings == null) || (paddings.length == 0)) { return EmptyArray.INT; } @@ -504,112 +472,107 @@ public abstract class KeyStoreKeyProperties { } } + /** + * @hide + */ @Retention(RetentionPolicy.SOURCE) @StringDef({ - Digest.NONE, - Digest.MD5, - Digest.SHA1, - Digest.SHA224, - Digest.SHA256, - Digest.SHA384, - Digest.SHA512, + DIGEST_NONE, + DIGEST_MD5, + DIGEST_SHA1, + DIGEST_SHA224, + DIGEST_SHA256, + DIGEST_SHA384, + DIGEST_SHA512, }) public @interface DigestEnum {} /** - * Digests that can be used with a key when signing or generating Message Authentication - * Codes (MACs). + * No digest: sign/authenticate the raw message. */ - public static abstract class Digest { - private Digest() {} + public static final String DIGEST_NONE = "NONE"; - /** - * No digest: sign/authenticate the raw message. - */ - public static final String NONE = "NONE"; + /** + * MD5 digest. + */ + public static final String DIGEST_MD5 = "MD5"; - /** - * MD5 digest. - */ - public static final String MD5 = "MD5"; + /** + * SHA-1 digest. + */ + public static final String DIGEST_SHA1 = "SHA-1"; - /** - * SHA-1 digest. - */ - public static final String SHA1 = "SHA-1"; + /** + * SHA-2 224 (aka SHA-224) digest. + */ + public static final String DIGEST_SHA224 = "SHA-224"; - /** - * SHA-2 224 (aka SHA-224) digest. - */ - public static final String SHA224 = "SHA-224"; + /** + * SHA-2 256 (aka SHA-256) digest. + */ + public static final String DIGEST_SHA256 = "SHA-256"; - /** - * SHA-2 256 (aka SHA-256) digest. - */ - public static final String SHA256 = "SHA-256"; + /** + * SHA-2 384 (aka SHA-384) digest. + */ + public static final String DIGEST_SHA384 = "SHA-384"; - /** - * SHA-2 384 (aka SHA-384) digest. - */ - public static final String SHA384 = "SHA-384"; + /** + * SHA-2 512 (aka SHA-512) digest. + */ + public static final String DIGEST_SHA512 = "SHA-512"; - /** - * SHA-2 512 (aka SHA-512) digest. - */ - public static final String SHA512 = "SHA-512"; + /** + * @hide + */ + public static abstract class Digest { + private Digest() {} - /** - * @hide - */ - static int toKeymaster(@DigestEnum String digest) { + public static int toKeymaster(@NonNull @DigestEnum String digest) { switch (digest.toUpperCase(Locale.US)) { - case SHA1: + case DIGEST_SHA1: return KeymasterDefs.KM_DIGEST_SHA1; - case SHA224: + case DIGEST_SHA224: return KeymasterDefs.KM_DIGEST_SHA_2_224; - case SHA256: + case DIGEST_SHA256: return KeymasterDefs.KM_DIGEST_SHA_2_256; - case SHA384: + case DIGEST_SHA384: return KeymasterDefs.KM_DIGEST_SHA_2_384; - case SHA512: + case DIGEST_SHA512: return KeymasterDefs.KM_DIGEST_SHA_2_512; - case NONE: + case DIGEST_NONE: return KeymasterDefs.KM_DIGEST_NONE; - case MD5: + case DIGEST_MD5: return KeymasterDefs.KM_DIGEST_MD5; default: throw new IllegalArgumentException("Unsupported digest algorithm: " + digest); } } - /** - * @hide - */ - static @DigestEnum String fromKeymaster(int digest) { + @NonNull + public static @DigestEnum String fromKeymaster(int digest) { switch (digest) { case KeymasterDefs.KM_DIGEST_NONE: - return NONE; + return DIGEST_NONE; case KeymasterDefs.KM_DIGEST_MD5: - return MD5; + return DIGEST_MD5; case KeymasterDefs.KM_DIGEST_SHA1: - return SHA1; + return DIGEST_SHA1; case KeymasterDefs.KM_DIGEST_SHA_2_224: - return SHA224; + return DIGEST_SHA224; case KeymasterDefs.KM_DIGEST_SHA_2_256: - return SHA256; + return DIGEST_SHA256; case KeymasterDefs.KM_DIGEST_SHA_2_384: - return SHA384; + return DIGEST_SHA384; case KeymasterDefs.KM_DIGEST_SHA_2_512: - return SHA512; + return DIGEST_SHA512; default: throw new IllegalArgumentException("Unsupported digest algorithm: " + digest); } } - /** - * @hide - */ - static @DigestEnum String[] allFromKeymaster(Collection<Integer> digests) { + @NonNull + public static @DigestEnum String[] allFromKeymaster(@NonNull Collection<Integer> digests) { if (digests.isEmpty()) { return EmptyArray.STRING; } @@ -622,10 +585,8 @@ public abstract class KeyStoreKeyProperties { return result; } - /** - * @hide - */ - static int[] allToKeymaster(@DigestEnum String[] digests) { + @NonNull + public static int[] allToKeymaster(@Nullable @DigestEnum String[] digests) { if ((digests == null) || (digests.length == 0)) { return EmptyArray.INT; } @@ -639,39 +600,43 @@ public abstract class KeyStoreKeyProperties { } } + /** + * @hide + */ @Retention(RetentionPolicy.SOURCE) - @IntDef({Origin.GENERATED, Origin.IMPORTED, Origin.UNKNOWN}) + @IntDef({ + ORIGIN_GENERATED, + ORIGIN_IMPORTED, + ORIGIN_UNKNOWN, + }) public @interface OriginEnum {} + /** Key was generated inside AndroidKeyStore. */ + public static final int ORIGIN_GENERATED = 1 << 0; + + /** Key was imported into AndroidKeyStore. */ + public static final int ORIGIN_IMPORTED = 1 << 1; + /** - * Origin of the key. + * Origin of the key is unknown. This can occur only for keys backed by an old TEE-backed + * implementation which does not record origin information. + */ + public static final int ORIGIN_UNKNOWN = 1 << 2; + + /** + * @hide */ public static abstract class Origin { private Origin() {} - /** Key was generated inside AndroidKeyStore. */ - public static final int GENERATED = 1 << 0; - - /** Key was imported into AndroidKeyStore. */ - public static final int IMPORTED = 1 << 1; - - /** - * Origin of the key is unknown. This can occur only for keys backed by an old TEE-backed - * implementation which does not record origin information. - */ - public static final int UNKNOWN = 1 << 2; - - /** - * @hide - */ public static @OriginEnum int fromKeymaster(int origin) { switch (origin) { case KeymasterDefs.KM_ORIGIN_GENERATED: - return GENERATED; + return ORIGIN_GENERATED; case KeymasterDefs.KM_ORIGIN_IMPORTED: - return IMPORTED; + return ORIGIN_IMPORTED; case KeymasterDefs.KM_ORIGIN_UNKNOWN: - return UNKNOWN; + return ORIGIN_UNKNOWN; default: throw new IllegalArgumentException("Unknown origin: " + origin); } diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java new file mode 100644 index 0000000..48fdd98 --- /dev/null +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -0,0 +1,606 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.KeyguardManager; +import android.security.KeyStore; + +import java.security.Key; +import java.security.KeyStore.ProtectionParameter; +import java.security.cert.Certificate; +import java.util.Date; + +import javax.crypto.Cipher; + +/** + * Specification of how a key or key pair is secured when imported into the + * <a href="{@docRoot}training/articles/keystore.html">Android KeyStore facility</a>. This class + * specifies parameters such as whether user authentication is required for using the key, what uses + * the key is authorized for (e.g., only in {@code CTR} mode, or only for signing -- decryption not + * permitted), whether the key should be encrypted at rest, the key's and validity start and end + * dates. + * + * <p>To import a key or key pair into the Android KeyStore, create an instance of this class using + * the {@link Builder} and pass the instance into {@link java.security.KeyStore#setEntry(String, java.security.KeyStore.Entry, ProtectionParameter) KeyStore.setEntry} + * with the key or key pair being imported. + * + * <p>To obtain the secret/symmetric or private key from the Android KeyStore use + * {@link java.security.KeyStore#getKey(String, char[]) KeyStore.getKey(String, null)} or + * {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter) KeyStore.getEntry(String, null)}. + * To obtain the public key from the Android KeyStore use + * {@link java.security.KeyStore#getCertificate(String)} and then + * {@link Certificate#getPublicKey()}. + * + * <p>NOTE: The key material of keys stored in the Android KeyStore is not accessible. + * + * <p><h3>Example: Symmetric Key</h3> + * The following example illustrates how to import an AES key into the Android KeyStore under alias + * {@code key1} authorized to be used only for encryption/decryption in CBC mode with PKCS#7 + * padding. The key must export its key material via {@link Key#getEncoded()} in {@code RAW} format. + * <pre> {@code + * SecretKey key = ...; // AES key + * + * KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + * keyStore.load(null); + * keyStore.setEntry( + * "key1", + * new KeyStore.SecretKeyEntry(key), + * new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + * .setBlockMode(KeyProperties.BLOCK_MODE_CBC) + * .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) + * .build()); + * // Key imported, obtain a reference to it. + * SecretKey keyStoreKey = (SecretKey) keyStore.getKey("key1", null); + * // The original key can now be thrown away. + * }</pre> + * + * <p><h3>Example: Asymmetric Key Pair</h3> + * The following example illustrates how to import an EC key pair into the Android KeyStore under + * alias {@code key2} authorized to be used only for signing with SHA-256 digest and only if + * the user has been authenticated within the last ten minutes. Both the private and the public key + * must export their key material via {@link Key#getEncoded()} in {@code PKCS#8} and {@code X.509} + * format respectively. + * <pre> {@code + * PrivateKey privateKey = ...; // EC private key + * Certificate[] certChain = ...; // Certificate chain with the first certificate + * // containing the corresponding EC public key. + * + * KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + * keyStore.load(null); + * keyStore.setEntry( + * "key2", + * new KeyStore.PrivateKeyEntry(privateKey, certChain), + * new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN) + * .setDigests(KeyProperties.DIGEST_SHA256) + * // Only permit this key to be used if the user + * // authenticated within the last ten minutes. + * .setUserAuthenticationRequired(true) + * .setUserAuthenticationValidityDurationSeconds(10 * 60) + * .build()); + * // Key pair imported, obtain a reference to it. + * PrivateKey keyStorePrivateKey = (PrivateKey) keyStore.getKey("key2", null); + * PublicKey publicKey = keyStore.getCertificate("key2").getPublicKey(); + * // The original private key can now be thrown away. + * }</pre> + */ +public final class KeyProtection implements ProtectionParameter { + private final int mFlags; + private final Date mKeyValidityStart; + private final Date mKeyValidityForOriginationEnd; + private final Date mKeyValidityForConsumptionEnd; + private final @KeyProperties.PurposeEnum int mPurposes; + private final @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private final @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; + private final @KeyProperties.DigestEnum String[] mDigests; + private final @KeyProperties.BlockModeEnum String[] mBlockModes; + private final boolean mRandomizedEncryptionRequired; + private final boolean mUserAuthenticationRequired; + private final int mUserAuthenticationValidityDurationSeconds; + + private KeyProtection( + int flags, + Date keyValidityStart, + Date keyValidityForOriginationEnd, + Date keyValidityForConsumptionEnd, + @KeyProperties.PurposeEnum int purposes, + @KeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyProperties.SignaturePaddingEnum String[] signaturePaddings, + @KeyProperties.DigestEnum String[] digests, + @KeyProperties.BlockModeEnum String[] blockModes, + boolean randomizedEncryptionRequired, + boolean userAuthenticationRequired, + int userAuthenticationValidityDurationSeconds) { + if ((userAuthenticationValidityDurationSeconds < 0) + && (userAuthenticationValidityDurationSeconds != -1)) { + throw new IllegalArgumentException( + "userAuthenticationValidityDurationSeconds must not be negative"); + } + + mFlags = flags; + mKeyValidityStart = keyValidityStart; + mKeyValidityForOriginationEnd = keyValidityForOriginationEnd; + mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd; + mPurposes = purposes; + mEncryptionPaddings = + ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings)); + mSignaturePaddings = + ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(signaturePaddings)); + mDigests = ArrayUtils.cloneIfNotEmpty(digests); + mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes)); + mRandomizedEncryptionRequired = randomizedEncryptionRequired; + mUserAuthenticationRequired = userAuthenticationRequired; + mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; + } + + /** + * @hide + */ + public int getFlags() { + return mFlags; + } + + /** + * Returns {@code true} if the {@link java.security.KeyStore} entry must be encrypted at rest. + * This will protect the entry with the secure lock screen credential (e.g., password, PIN, or + * pattern). + */ + public boolean isEncryptionAtRestRequired() { + return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0; + } + + /** + * Gets the time instant before which the key is not yet valid. + * + * @return instant or {@code null} if not restricted. + */ + @Nullable + public Date getKeyValidityStart() { + return mKeyValidityStart; + } + + /** + * Gets the time instant after which the key is no long valid for decryption and verification. + * + * @return instant or {@code null} if not restricted. + */ + @Nullable + public Date getKeyValidityForConsumptionEnd() { + return mKeyValidityForConsumptionEnd; + } + + /** + * Gets the time instant after which the key is no long valid for encryption and signing. + * + * @return instant or {@code null} if not restricted. + */ + @Nullable + public Date getKeyValidityForOriginationEnd() { + return mKeyValidityForOriginationEnd; + } + + /** + * Gets the set of purposes (e.g., encrypt, decrypt, sign) for which the key can be used. + * Attempts to use the key for any other purpose will be rejected. + * + * <p>See {@link KeyProperties}.{@code PURPOSE} flags. + */ + public @KeyProperties.PurposeEnum int getPurposes() { + return mPurposes; + } + + /** + * Gets the set of padding schemes (e.g., {@code PKCS7Padding}, {@code PKCS1Padding}, + * {@code NoPadding}) with which the key can be used when encrypting/decrypting. Attempts to use + * the key with any other padding scheme will be rejected. + * + * <p>See {@link KeyProperties}.{@code ENCRYPTION_PADDING} constants. + */ + @NonNull + public @KeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { + return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); + } + + /** + * Gets the set of padding schemes (e.g., {@code PSS}, {@code PKCS#1}) with which the key + * can be used when signing/verifying. Attempts to use the key with any other padding scheme + * will be rejected. + * + * <p>See {@link KeyProperties}.{@code SIGNATURE_PADDING} constants. + */ + @NonNull + public @KeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { + return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings); + } + + /** + * Gets the set of digest algorithms (e.g., {@code SHA-256}, {@code SHA-384}) with which the key + * can be used. + * + * <p>See {@link KeyProperties}.{@code DIGEST} constants. + * + * @throws IllegalStateException if this set has not been specified. + * + * @see #isDigestsSpecified() + */ + @NonNull + public @KeyProperties.DigestEnum String[] getDigests() { + if (mDigests == null) { + throw new IllegalStateException("Digests not specified"); + } + return ArrayUtils.cloneIfNotEmpty(mDigests); + } + + /** + * Returns {@code true} if the set of digest algorithms with which the key can be used has been + * specified. + * + * @see #getDigests() + */ + public boolean isDigestsSpecified() { + return mDigests != null; + } + + /** + * Gets the set of block modes (e.g., {@code CBC}, {@code CTR}) with which the key can be used + * when encrypting/decrypting. Attempts to use the key with any other block modes will be + * rejected. + * + * <p>See {@link KeyProperties}.{@code BLOCK_MODE} constants. + */ + @NonNull + public @KeyProperties.BlockModeEnum String[] getBlockModes() { + return ArrayUtils.cloneIfNotEmpty(mBlockModes); + } + + /** + * Returns {@code true} if encryption using this key must be sufficiently randomized to produce + * different ciphertexts for the same plaintext every time. The formal cryptographic property + * being required is <em>indistinguishability under chosen-plaintext attack ({@code + * IND-CPA})</em>. This property is important because it mitigates several classes of + * weaknesses due to which ciphertext may leak information about plaintext. For example, if a + * given plaintext always produces the same ciphertext, an attacker may see the repeated + * ciphertexts and be able to deduce something about the plaintext. + */ + public boolean isRandomizedEncryptionRequired() { + return mRandomizedEncryptionRequired; + } + + /** + * Returns {@code true} if user authentication is required for this key to be used. + * + * @see #getUserAuthenticationValidityDurationSeconds() + */ + public boolean isUserAuthenticationRequired() { + return mUserAuthenticationRequired; + } + + /** + * Gets the duration of time (seconds) for which this key can be used after the user is + * successfully authenticated. This has effect only if user authentication is required. + * + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. + * + * @see #isUserAuthenticationRequired() + */ + public int getUserAuthenticationValidityDurationSeconds() { + return mUserAuthenticationValidityDurationSeconds; + } + + /** + * Builder of {@link KeyProtection} instances. + */ + public final static class Builder { + private @KeyProperties.PurposeEnum int mPurposes; + + private int mFlags; + private Date mKeyValidityStart; + private Date mKeyValidityForOriginationEnd; + private Date mKeyValidityForConsumptionEnd; + private @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; + private @KeyProperties.DigestEnum String[] mDigests; + private @KeyProperties.BlockModeEnum String[] mBlockModes; + private boolean mRandomizedEncryptionRequired = true; + private boolean mUserAuthenticationRequired; + private int mUserAuthenticationValidityDurationSeconds = -1; + + /** + * Creates a new instance of the {@code Builder}. + * + * @param purposes set of purposes (e.g., encrypt, decrypt, sign) for which the key can be + * used. Attempts to use the key for any other purpose will be rejected. + * + * <p><b>NOTE: The {@code purposes} parameter has currently no effect on asymmetric + * key pairs.</b> + * + * <p>See {@link KeyProperties}.{@code PURPOSE} flags. + */ + public Builder(@KeyProperties.PurposeEnum int purposes) { + mPurposes = purposes; + } + + /** + * Sets whether this {@link java.security.KeyStore} entry must be encrypted at rest. + * Encryption at rest will protect the entry with the secure lock screen credential (e.g., + * password, PIN, or pattern). + * + * <p>Note that enabling this feature requires that the secure lock screen (e.g., password, + * PIN, pattern) is set up, otherwise setting the {@code KeyStore} entry will fail. + * Moreover, this entry will be deleted when the secure lock screen is disabled or reset + * (e.g., by the user or a Device Administrator). Finally, this entry cannot be used until + * the user unlocks the secure lock screen after boot. + * + * @see KeyguardManager#isDeviceSecure() + */ + @NonNull + public Builder setEncryptionAtRestRequired(boolean required) { + if (required) { + mFlags |= KeyStore.FLAG_ENCRYPTED; + } else { + mFlags &= ~KeyStore.FLAG_ENCRYPTED; + } + return this; + } + + /** + * Sets the time instant before which the key is not yet valid. + * + * <p>By default, the key is valid at any instant. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * @see #setKeyValidityEnd(Date) + */ + @NonNull + public Builder setKeyValidityStart(Date startDate) { + mKeyValidityStart = startDate; + return this; + } + + /** + * Sets the time instant after which the key is no longer valid. + * + * <p>By default, the key is valid at any instant. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * @see #setKeyValidityStart(Date) + * @see #setKeyValidityForConsumptionEnd(Date) + * @see #setKeyValidityForOriginationEnd(Date) + */ + @NonNull + public Builder setKeyValidityEnd(Date endDate) { + setKeyValidityForOriginationEnd(endDate); + setKeyValidityForConsumptionEnd(endDate); + return this; + } + + /** + * Sets the time instant after which the key is no longer valid for encryption and signing. + * + * <p>By default, the key is valid at any instant. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * @see #setKeyValidityForConsumptionEnd(Date) + */ + @NonNull + public Builder setKeyValidityForOriginationEnd(Date endDate) { + mKeyValidityForOriginationEnd = endDate; + return this; + } + + /** + * Sets the time instant after which the key is no longer valid for decryption and + * verification. + * + * <p>By default, the key is valid at any instant. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * @see #setKeyValidityForOriginationEnd(Date) + */ + @NonNull + public Builder setKeyValidityForConsumptionEnd(Date endDate) { + mKeyValidityForConsumptionEnd = endDate; + return this; + } + + /** + * Sets the set of padding schemes (e.g., {@code OAEPPadding}, {@code PKCS7Padding}, + * {@code NoPadding}) with which the key can be used when encrypting/decrypting. Attempts to + * use the key with any other padding scheme will be rejected. + * + * <p>This must be specified for keys which are used for encryption/decryption. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * <p>See {@link KeyProperties}.{@code ENCRYPTION_PADDING} constants. + */ + @NonNull + public Builder setEncryptionPaddings( + @KeyProperties.EncryptionPaddingEnum String... paddings) { + mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings); + return this; + } + + /** + * Sets the set of padding schemes (e.g., {@code PSS}, {@code PKCS#1}) with which the key + * can be used when signing/verifying. Attempts to use the key with any other padding scheme + * will be rejected. + * + * <p>This must be specified for RSA keys which are used for signing/verification. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * <p>See {@link KeyProperties}.{@code SIGNATURE_PADDING} constants. + */ + @NonNull + public Builder setSignaturePaddings( + @KeyProperties.SignaturePaddingEnum String... paddings) { + mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(paddings); + return this; + } + + /** + * Sets the set of digest algorithms (e.g., {@code SHA-256}, {@code SHA-384}) with which the + * key can be used when signing/verifying or generating MACs. Attempts to use the key with + * any other digest algorithm will be rejected. + * + * <p>For HMAC keys, the default is the digest algorithm specified in + * {@link Key#getAlgorithm()}. For asymmetric signing keys the set of digest algorithms + * must be specified. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * <p>See {@link KeyProperties}.{@code DIGEST} constants. + */ + @NonNull + public Builder setDigests(@KeyProperties.DigestEnum String... digests) { + mDigests = ArrayUtils.cloneIfNotEmpty(digests); + return this; + } + + /** + * Sets the set of block modes (e.g., {@code CBC}, {@code CTR}, {@code ECB}) with which the + * key can be used when encrypting/decrypting. Attempts to use the key with any other block + * modes will be rejected. + * + * <p>This must be specified for encryption/decryption keys. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * <p>See {@link KeyProperties}.{@code BLOCK_MODE} constants. + */ + @NonNull + public Builder setBlockModes(@KeyProperties.BlockModeEnum String... blockModes) { + mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes); + return this; + } + + /** + * Sets whether encryption using this key must be sufficiently randomized to produce + * different ciphertexts for the same plaintext every time. The formal cryptographic + * property being required is <em>indistinguishability under chosen-plaintext attack + * ({@code IND-CPA})</em>. This property is important because it mitigates several classes + * of weaknesses due to which ciphertext may leak information about plaintext. For example, + * if a given plaintext always produces the same ciphertext, an attacker may see the + * repeated ciphertexts and be able to deduce something about the plaintext. + * + * <p>By default, {@code IND-CPA} is required. + * + * <p>When {@code IND-CPA} is required: + * <ul> + * <li>transformation which do not offer {@code IND-CPA}, such as symmetric ciphers using + * {@code ECB} mode or RSA encryption without padding, are prohibited;</li> + * <li>in transformations which use an IV, such as symmetric ciphers in {@code CBC}, + * {@code CTR}, and {@code GCM} block modes, caller-provided IVs are rejected when + * encrypting, to ensure that only random IVs are used.</li> + * + * <p>Before disabling this requirement, consider the following approaches instead: + * <ul> + * <li>If you are generating a random IV for encryption and then initializing a {@code} + * Cipher using the IV, the solution is to let the {@code Cipher} generate a random IV + * instead. This will occur if the {@code Cipher} is initialized for encryption without an + * IV. The IV can then be queried via {@link Cipher#getIV()}.</li> + * <li>If you are generating a non-random IV (e.g., an IV derived from something not fully + * random, such as the name of the file being encrypted, or transaction ID, or password, + * or a device identifier), consider changing your design to use a random IV which will then + * be provided in addition to the ciphertext to the entities which need to decrypt the + * ciphertext.</li> + * <li>If you are using RSA encryption without padding, consider switching to padding + * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li> + * </ul> + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + */ + @NonNull + public Builder setRandomizedEncryptionRequired(boolean required) { + mRandomizedEncryptionRequired = required; + return this; + } + + /** + * Sets whether user authentication is required to use this key. + * + * <p>By default, the key can be used without user authentication. + * + * <p>When user authentication is required, the user authorizes the use of the key by + * authenticating to this Android device using a subset of their secure lock screen + * credentials. Different authentication methods are used depending on whether the every + * use of the key must be authenticated (as specified by + * {@link #setUserAuthenticationValidityDurationSeconds(int)}). + * <a href="{@docRoot}training/articles/keystore.html#UserAuthentication">More + * information</a>. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * @see #setUserAuthenticationValidityDurationSeconds(int) + */ + @NonNull + public Builder setUserAuthenticationRequired(boolean required) { + mUserAuthenticationRequired = required; + return this; + } + + /** + * Sets the duration of time (seconds) for which this key can be used after the user is + * successfully authenticated. This has effect only if user authentication is required. + * + * <p>By default, the user needs to authenticate for every use of the key. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * + * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for + * every use of the key. + * + * @see #setUserAuthenticationRequired(boolean) + */ + @NonNull + public Builder setUserAuthenticationValidityDurationSeconds( + @IntRange(from = -1) int seconds) { + mUserAuthenticationValidityDurationSeconds = seconds; + return this; + } + + /** + * Builds an instance of {@link KeyProtection}. + * + * @throws IllegalArgumentException if a required field is missing + */ + @NonNull + public KeyProtection build() { + return new KeyProtection( + mFlags, + mKeyValidityStart, + mKeyValidityForOriginationEnd, + mKeyValidityForConsumptionEnd, + mPurposes, + mEncryptionPaddings, + mSignaturePaddings, + mDigests, + mBlockModes, + mRandomizedEncryptionRequired, + mUserAuthenticationRequired, + mUserAuthenticationValidityDurationSeconds); + } + } +} diff --git a/keystore/java/android/security/KeyStoreConnectException.java b/keystore/java/android/security/keystore/KeyStoreConnectException.java index 885f1f7..e008976 100644 --- a/keystore/java/android/security/KeyStoreConnectException.java +++ b/keystore/java/android/security/keystore/KeyStoreConnectException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; import java.security.ProviderException; diff --git a/keystore/java/android/security/KeyStoreCryptoOperation.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java index c5cf211..2c709ae 100644 --- a/keystore/java/android/security/KeyStoreCryptoOperation.java +++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java @@ -14,7 +14,9 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; + +import android.security.KeyStore; /** * Cryptographic operation backed by {@link KeyStore}. diff --git a/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java index 0619199..7d57e5f 100644 --- a/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java +++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java @@ -14,9 +14,11 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; import android.os.IBinder; +import android.security.KeyStore; +import android.security.KeyStoreException; import android.security.keymaster.OperationResult; import libcore.util.EmptyArray; diff --git a/keystore/java/android/security/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java index 311278b..6ae76f1 100644 --- a/keystore/java/android/security/KeyStoreCryptoOperationUtils.java +++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; +import android.security.KeyStore; import android.security.keymaster.KeymasterDefs; import java.security.GeneralSecurityException; @@ -40,7 +41,7 @@ abstract class KeyStoreCryptoOperationUtils { * the {@code init} method should succeed. */ static InvalidKeyException getInvalidKeyExceptionForInit( - KeyStore keyStore, KeyStoreKey key, int beginOpResultCode) { + KeyStore keyStore, AndroidKeyStoreKey key, int beginOpResultCode) { if (beginOpResultCode == KeyStore.NO_ERROR) { return null; } @@ -68,8 +69,8 @@ abstract class KeyStoreCryptoOperationUtils { * in response to {@code KeyStore.begin} operation or {@code null} if the {@code init} method * should succeed. */ - static GeneralSecurityException getExceptionForCipherInit( - KeyStore keyStore, KeyStoreKey key, int beginOpResultCode) { + public static GeneralSecurityException getExceptionForCipherInit( + KeyStore keyStore, AndroidKeyStoreKey key, int beginOpResultCode) { if (beginOpResultCode == KeyStore.NO_ERROR) { return null; } diff --git a/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java index df67ae7..e7529e1 100644 --- a/keystore/java/android/security/KeymasterUtils.java +++ b/keystore/java/android/security/keystore/KeymasterUtils.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; -import android.content.Context; import android.hardware.fingerprint.FingerprintManager; +import android.security.GateKeeper; +import android.security.KeyStore; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; @@ -73,7 +74,6 @@ public abstract class KeymasterUtils { * use of the key needs authorization. */ public static void addUserAuthArgs(KeymasterArguments args, - Context context, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds) { if (!userAuthenticationRequired) { @@ -85,7 +85,7 @@ public abstract class KeymasterUtils { // Every use of this key needs to be authorized by the user. This currently means // fingerprint-only auth. FingerprintManager fingerprintManager = - context.getSystemService(FingerprintManager.class); + KeyStore.getApplicationContext().getSystemService(FingerprintManager.class); if ((fingerprintManager == null) || (!fingerprintManager.isHardwareDetected())) { throw new IllegalStateException( "This device does not support keys which require authentication for every" diff --git a/keystore/java/android/security/UserNotAuthenticatedException.java b/keystore/java/android/security/keystore/UserNotAuthenticatedException.java index 2954fa7..21f861c 100644 --- a/keystore/java/android/security/UserNotAuthenticatedException.java +++ b/keystore/java/android/security/keystore/UserNotAuthenticatedException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.security; +package android.security.keystore; import java.security.InvalidKeyException; |
