diff options
Diffstat (limited to 'keystore')
30 files changed, 1970 insertions, 952 deletions
diff --git a/keystore/java/android/security/EcIesParameterSpec.java b/keystore/java/android/security/EcIesParameterSpec.java deleted file mode 100644 index 1cd8784..0000000 --- a/keystore/java/android/security/EcIesParameterSpec.java +++ /dev/null @@ -1,272 +0,0 @@ -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; -import java.security.spec.AlgorithmParameterSpec; - -import javax.crypto.Cipher; -import javax.crypto.Mac; - -/** - * {@link AlgorithmParameterSpec} for ECIES (Integrated Encryption Scheme using Elliptic Curve - * cryptography) based on {@code ISO/IEC 18033-2}. - * - * <p>ECIES is a hybrid authenticated encryption scheme. Encryption is performed using an Elliptic - * Curve (EC) public key. The resulting ciphertext can be decrypted only using the corresponding EC - * private key. The scheme is called hybrid because the EC key is only used to securely encapsulate - * symmetric key material. Encryption of plaintext and authentication of the corresponding - * ciphertext is performed using symmetric cryptography. - * - * <p>Encryption using ECIES consists of two stages: - * <ol> - * <li>Key Encapsulation Mechanism (KEM) randomly generates symmetric key material and securely - * encapsulates it in the output so that it can be extracted by the KEM when decrypting. - * Encapsulated key material is represented in the output as an EC point.</li> - * <li>The above symmetric key material is used by Data Encapsulation Mechanism (DEM) to encrypt the - * provided plaintext and authenticate the ciphertext. The resulting authenticated ciphertext is - * then output. When decrypting, the DEM first authenticates the ciphertext and, only if it - * authenticates, decrypts the ciphertext and outputs the plaintext.</li> - * </ol> - * - * <p>Details of KEM: - * <ul> - * <li>Only curves with cofactor of {@code 1} are supported.</li> - * <li>{@code CheckMode}, {@code OldCofactorMode}, {@code CofactorMode}, and {@code SingleHashMode} - * are {@code 0}. - * <li>Point format is specified by {@link #getKemPointFormat()}.</li> - * <li>KDF algorithm is specified by {@link #getKemKdfAlgorithm()}.</li> - * </ul> - * - * <p>Details of DEM: - * <ul> - * <li>Only DEM1-like mechanism is supported, with its symmetric cipher (SC) specified by - * {@link #getDemCipherTransformation()} (e.g., {@code AES/CBC/NoPadding} for standard DEM1) and - * MAC algorithm specified by {@link #getDemMacAlgorithm()} (e.g., {@code HmacSHA1} for standard - * DEM1).</li> - * </ul> - */ -public class EcIesParameterSpec implements AlgorithmParameterSpec { - - /** - * @hide - */ - @Retention(RetentionPolicy.SOURCE) - @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; - - /** - * 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 final int POINT_FORMAT_UNCOMPRESSED = 0; - - /** - * 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( - POINT_FORMAT_COMPRESSED, - "HKDFwithSHA256", - "AES/GCM/NoPadding", - 128, - null, - 0); - - private final @PointFormatEnum int mKemPointFormat; - private final String mKemKdfAlgorithm; - private final String mDemCipherTransformation; - private final int mDemCipherKeySize; - private final String mDemMacAlgorithm; - private final int mDemMacKeySize; - - private EcIesParameterSpec( - @PointFormatEnum int kemPointFormat, - String kemKdfAlgorithm, - String demCipherTransformation, - int demCipherKeySize, - String demMacAlgorithm, - int demMacKeySize) { - mKemPointFormat = kemPointFormat; - mKemKdfAlgorithm = kemKdfAlgorithm; - mDemCipherTransformation = demCipherTransformation; - mDemCipherKeySize = demCipherKeySize; - mDemMacAlgorithm = demMacAlgorithm; - mDemMacKeySize = demMacKeySize; - } - - /** - * Returns KEM EC point wire format or {@link #POINT_FORMAT_UNSPECIFIED} if not specified. - */ - public @PointFormatEnum int getKemPointFormat() { - return mKemPointFormat; - } - - /** - * Returns KEM KDF algorithm (e.g., {@code HKDFwithSHA256} or {@code KDF1withSHA1}) or - * {@code null} if not specified. - */ - @Nullable - public String getKemKdfAlgorithm() { - return mKemKdfAlgorithm; - } - - /** - * Returns DEM {@link Cipher} transformation (e.g., {@code AES/GCM/NoPadding} or - * {@code AES/CBC/PKCS7Padding}) or {@code null} if not specified. - * - * @see Cipher#getInstance(String) - * @see #getDemCipherKeySize() - */ - @Nullable - public String getDemCipherTransformation() { - return mDemCipherTransformation; - } - - /** - * Returns DEM {@link Cipher} key size in bits. - * - * @see #getDemCipherTransformation() - */ - public int getDemCipherKeySize() { - return mDemCipherKeySize; - } - - /** - * Returns DEM {@link Mac} algorithm (e.g., {@code HmacSHA256} or {@code HmacSHA1}) or - * {@code null} if not specified. - * - * @see Mac#getInstance(String) - * @see #getDemMacKeySize() - */ - @Nullable - public String getDemMacAlgorithm() { - return mDemMacAlgorithm; - } - - /** - * Returns DEM {@link Mac} key size in bits. - * - * @see #getDemCipherTransformation() - */ - public int getDemMacKeySize() { - return mDemMacKeySize; - } - - /** - * Builder of {@link EcIesParameterSpec}. - */ - public static class Builder { - private @PointFormatEnum int mKemPointFormat = POINT_FORMAT_UNSPECIFIED; - private String mKemKdfAlgorithm; - private String mDemCipherTransformation; - private int mDemCipherKeySize = 128; - private String mDemMacAlgorithm; - private int mDemMacKeySize = -1; - - /** - * Sets KEM EC point wire format. - */ - public Builder setKemPointFormat(@PointFormatEnum int pointFormat) { - mKemPointFormat = pointFormat; - return this; - } - - /** - * Sets KEM KDF algorithm. For example, {@code HKDFwithSHA256}, {@code KDF2withSHA256}, or - * {@code KDF1withSHA1}. - */ - @NonNull - public Builder setKemKdfAlgorithm(@Nullable String algorithm) { - mKemKdfAlgorithm = algorithm; - return this; - } - - /** - * Sets DEM {@link Cipher} transformation. For example, {@code AES/GCM/NoPadding}, - * {@code AES/CBC/PKCS7Padding} or {@code AES/CTR/NoPadding}. - * - * @see Cipher#getInstance(String) - */ - @NonNull - public Builder setDemCipherTransformation(@Nullable String transformation) { - mDemCipherTransformation = transformation; - return this; - } - - /** - * Returns DEM {@link Cipher} key size in bits. - * - * <p>The default value is {@code 128} bits. - * - * @see #setDemCipherTransformation(String) - */ - @NonNull - public Builder setDemCipherKeySize(int sizeBits) { - mDemCipherKeySize = sizeBits; - return this; - } - - /** - * Sets DEM {@link Mac} algorithm. For example, {@code HmacSHA256} or {@code HmacSHA1}. - * - * @see Mac#getInstance(String) - */ - @NonNull - public Builder setDemMacAlgorithm(@Nullable String algorithm) { - mDemMacAlgorithm = algorithm; - return this; - } - - /** - * Sets DEM {@link Mac} key size in bits. - * - * <p>By default, {@code Mac} key size is the same as the {@code Cipher} key size. - * - * @see #setDemCipherKeySize(int) - */ - @NonNull - public Builder setDemMacKeySize(int sizeBits) { - mDemMacKeySize = sizeBits; - return this; - } - - /** - * 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( - mKemPointFormat, - mKemKdfAlgorithm, - mDemCipherTransformation, - mDemCipherKeySize, - mDemMacAlgorithm, - demMacKeySize - ); - } - } -} diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index 817b7c9..f482bf0 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -29,13 +29,14 @@ import android.os.Looper; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.security.keystore.AndroidKeyStoreProvider; import android.security.keystore.KeyProperties; import java.io.ByteArrayInputStream; import java.io.Closeable; -import java.security.InvalidKeyException; import java.security.Principal; import java.security.PrivateKey; +import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; @@ -45,7 +46,6 @@ import java.util.Locale; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; -import com.android.org.conscrypt.OpenSSLEngine; import com.android.org.conscrypt.TrustedCertificateStore; /** @@ -88,8 +88,6 @@ import com.android.org.conscrypt.TrustedCertificateStore; // TODO reference intent for credential installation when public public final class KeyChain { - private static final String TAG = "KeyChain"; - /** * @hide Also used by KeyChainService implementation */ @@ -370,15 +368,14 @@ public final class KeyChain { if (keyId == null) { throw new KeyChainException("keystore had a problem"); } - - final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore"); - return engine.getPrivateKeyById(keyId); + return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore( + KeyStore.getInstance(), keyId); } catch (RemoteException e) { throw new KeyChainException(e); } catch (RuntimeException e) { // only certain RuntimeExceptions can be propagated across the IKeyChainService call throw new KeyChainException(e); - } catch (InvalidKeyException e) { + } catch (UnrecoverableKeyException e) { throw new KeyChainException(e); } finally { keyChainConnection.close(); @@ -442,7 +439,20 @@ public final class KeyChain { * imported or generated. This can be used to tell if there is special * hardware support that can be used to bind keys to the device in a way * that makes it non-exportable. + * + * @deprecated Whether the key is bound to the secure hardware is known only + * once the key has been imported. To find out, use: + * <pre>{@code + * PrivateKey key = ...; // private key from KeyChain + * + * KeyFactory keyFactory = + * KeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore"); + * KeyInfo keyInfo = keyFactory.getKeySpec(key, KeyInfo.class); + * if (keyInfo.isInsideSecureHardware()) { + * // The key is bound to the secure hardware of this Android + * }}</pre> */ + @Deprecated public static boolean isBoundKeyAlgorithm( @NonNull @KeyProperties.KeyAlgorithmEnum String algorithm) { if (!isKeyAlgorithmSupported(algorithm)) { diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java index efbce41..d849317 100644 --- a/keystore/java/android/security/KeyPairGeneratorSpec.java +++ b/keystore/java/android/security/KeyPairGeneratorSpec.java @@ -331,7 +331,9 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { if (keyType == null) { throw new NullPointerException("keyType == null"); } else { - if (KeyStore.getKeyTypeForAlgorithm(keyType) == -1) { + try { + KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(keyType); + } catch (IllegalArgumentException e) { throw new NoSuchAlgorithmException("Unsupported key type: " + keyType); } } diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 1a05104..6a08368 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -19,7 +19,6 @@ package android.security; import android.app.ActivityThread; import android.app.Application; import android.app.KeyguardManager; -import com.android.org.conscrypt.NativeConstants; import android.content.Context; import android.hardware.fingerprint.FingerprintManager; @@ -38,7 +37,6 @@ 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; @@ -110,15 +108,10 @@ public class KeyStore { } public static Context getApplicationContext() { - ActivityThread activityThread = ActivityThread.currentActivityThread(); - if (activityThread == null) { - throw new IllegalStateException( - "Failed to obtain application Context: no ActivityThread"); - } - Application application = activityThread.getApplication(); + Application application = ActivityThread.currentApplication(); if (application == null) { throw new IllegalStateException( - "Failed to obtain application Context: no Application"); + "Failed to obtain application Context from ActivityThread"); } return application; } @@ -136,16 +129,6 @@ public class KeyStore { return mToken; } - public static int getKeyTypeForAlgorithm(@KeyProperties.KeyAlgorithmEnum String keyType) { - if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyType)) { - return NativeConstants.EVP_PKEY_RSA; - } else if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyType)) { - return NativeConstants.EVP_PKEY_EC; - } else { - return -1; - } - } - public State state(int userId) { final int ret; try { @@ -181,11 +164,15 @@ public class KeyStore { } public boolean put(String key, byte[] value, int uid, int flags) { + return insert(key, value, uid, flags) == NO_ERROR; + } + + public int insert(String key, byte[] value, int uid, int flags) { try { - return mBinder.insert(key, value, uid, flags) == NO_ERROR; + return mBinder.insert(key, value, uid, flags); } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); - return false; + return SYSTEM_ERROR; } } @@ -231,14 +218,6 @@ public class KeyStore { return list(prefix, UID_SELF); } - public String[] saw(String prefix, int uid) { - return list(prefix, uid); - } - - public String[] saw(String prefix) { - return saw(prefix, UID_SELF); - } - public boolean reset() { try { return mBinder.reset() == NO_ERROR; @@ -328,23 +307,6 @@ public class KeyStore { } } - public byte[] getPubkey(String key) { - try { - return mBinder.get_pubkey(key); - } catch (RemoteException e) { - Log.w(TAG, "Cannot connect to keystore", e); - return null; - } - } - - public boolean delKey(String key, int uid) { - return delete(key, uid); - } - - public boolean delKey(String key) { - return delKey(key, UID_SELF); - } - public byte[] sign(String key, byte[] data) { try { return mBinder.sign(key, data); @@ -408,7 +370,7 @@ public class KeyStore { } } - // TODO remove this when it's removed from Settings + // TODO: remove this when it's removed from Settings public boolean isHardwareBacked() { return isHardwareBacked("RSA"); } @@ -731,16 +693,13 @@ public class KeyStore { } private long getFingerprintOnlySid() { - FingerprintManager fingerprintManager = - mContext.getSystemService(FingerprintManager.class); + FingerprintManager fingerprintManager = mContext.getSystemService(FingerprintManager.class); if (fingerprintManager == null) { return 0; } - if (!fingerprintManager.isHardwareDetected()) { - return 0; - } - + // TODO: Restore USE_FINGERPRINT permission check in + // FingerprintManager.getAuthenticatorId once the ID is no longer needed here. return fingerprintManager.getAuthenticatorId(); } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java index e555cc0..f37cf07 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java @@ -245,4 +245,12 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider { put("Signature." + algorithm + " SupportedKeyClasses", KEYSTORE_PRIVATE_KEY_CLASS_NAME + "|" + KEYSTORE_PUBLIC_KEY_CLASS_NAME); } + + public static String[] getSupportedEcdsaSignatureDigests() { + return new String[] {"NONE", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"}; + } + + public static String[] getSupportedRsaSignatureWithPkcs1PaddingDigests() { + return new String[] {"NONE", "MD5", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"}; + } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java index fd9bdb8..d2d5850 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java @@ -31,11 +31,18 @@ import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.InvalidParameterException; import java.security.Key; +import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.security.ProviderException; +import java.security.PublicKey; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; import javax.crypto.AEADBadTagException; import javax.crypto.BadPaddingException; @@ -43,7 +50,10 @@ import javax.crypto.Cipher; import javax.crypto.CipherSpi; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; import javax.crypto.ShortBufferException; +import javax.crypto.spec.SecretKeySpec; /** * Base class for {@link CipherSpi} implementations of Android KeyStore backed ciphers. @@ -56,6 +66,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after // doFinal finishes. private boolean mEncrypting; + private int mKeymasterPurposeOverride = -1; private AndroidKeyStoreKey mKey; private SecureRandom mRng; @@ -139,11 +150,18 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor } private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException { - if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.DECRYPT_MODE)) { - throw new UnsupportedOperationException( - "Only ENCRYPT and DECRYPT modes supported. Mode: " + opmode); + switch (opmode) { + case Cipher.ENCRYPT_MODE: + case Cipher.WRAP_MODE: + mEncrypting = true; + break; + case Cipher.DECRYPT_MODE: + case Cipher.UNWRAP_MODE: + mEncrypting = false; + break; + default: + throw new InvalidParameterException("Unsupported opmode: " + opmode); } - mEncrypting = opmode == Cipher.ENCRYPT_MODE; initKey(opmode, key); if (mKey == null) { throw new ProviderException("initKey did not initialize the key"); @@ -165,6 +183,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor mKeyStore.abort(operationToken); } mEncrypting = false; + mKeymasterPurposeOverride = -1; mKey = null; mRng = null; mOperationToken = null; @@ -210,9 +229,16 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( mRng, getAdditionalEntropyAmountForBegin()); + int purpose; + if (mKeymasterPurposeOverride != -1) { + purpose = mKeymasterPurposeOverride; + } else { + purpose = mEncrypting + ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT; + } OperationResult opResult = mKeyStore.begin( mKey.getAlias(), - mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT, + purpose, true, // permit aborting this operation if keystore runs out of resources keymasterInputArgs, additionalEntropy); @@ -342,15 +368,18 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor byte[] output; try { - output = mMainDataStreamer.doFinal(input, inputOffset, inputLen); + byte[] additionalEntropy = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, getAdditionalEntropyAmountForFinish()); + output = mMainDataStreamer.doFinal(input, inputOffset, inputLen, additionalEntropy); } catch (KeyStoreException e) { switch (e.getErrorCode()) { case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH: - throw new IllegalBlockSizeException(); + throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT: - throw new BadPaddingException(); + throw (BadPaddingException) new BadPaddingException().initCause(e); case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED: - throw new AEADBadTagException(); + throw (AEADBadTagException) new AEADBadTagException().initCause(e); default: throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); } @@ -386,13 +415,139 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor @Override protected final byte[] engineWrap(Key key) throws IllegalBlockSizeException, InvalidKeyException { - return super.engineWrap(key); + if (mKey == null) { + throw new IllegalStateException("Not initilized"); + } + + if (!isEncrypting()) { + throw new IllegalStateException( + "Cipher must be initialized in Cipher.WRAP_MODE to wrap keys"); + } + + if (key == null) { + throw new NullPointerException("key == null"); + } + byte[] encoded = null; + if (key instanceof SecretKey) { + if ("RAW".equalsIgnoreCase(key.getFormat())) { + encoded = key.getEncoded(); + } + if (encoded == null) { + try { + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(key.getAlgorithm()); + SecretKeySpec spec = + (SecretKeySpec) keyFactory.getKeySpec( + (SecretKey) key, SecretKeySpec.class); + encoded = spec.getEncoded(); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to wrap key because it does not export its key material", + e); + } + } + } else if (key instanceof PrivateKey) { + if ("PKCS8".equalsIgnoreCase(key.getFormat())) { + encoded = key.getEncoded(); + } + if (encoded == null) { + try { + KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm()); + PKCS8EncodedKeySpec spec = + keyFactory.getKeySpec(key, PKCS8EncodedKeySpec.class); + encoded = spec.getEncoded(); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to wrap key because it does not export its key material", + e); + } + } + } else if (key instanceof PublicKey) { + if ("X.509".equalsIgnoreCase(key.getFormat())) { + encoded = key.getEncoded(); + } + if (encoded == null) { + try { + KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm()); + X509EncodedKeySpec spec = + keyFactory.getKeySpec(key, X509EncodedKeySpec.class); + encoded = spec.getEncoded(); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to wrap key because it does not export its key material", + e); + } + } + } else { + throw new InvalidKeyException("Unsupported key type: " + key.getClass().getName()); + } + + if (encoded == null) { + throw new InvalidKeyException( + "Failed to wrap key because it does not export its key material"); + } + + try { + return engineDoFinal(encoded, 0, encoded.length); + } catch (BadPaddingException e) { + throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); + } } @Override protected final Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException { - return super.engineUnwrap(wrappedKey, wrappedKeyAlgorithm, wrappedKeyType); + if (mKey == null) { + throw new IllegalStateException("Not initilized"); + } + + if (isEncrypting()) { + throw new IllegalStateException( + "Cipher must be initialized in Cipher.WRAP_MODE to wrap keys"); + } + + if (wrappedKey == null) { + throw new NullPointerException("wrappedKey == null"); + } + + byte[] encoded; + try { + encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length); + } catch (IllegalBlockSizeException | BadPaddingException e) { + throw new InvalidKeyException("Failed to unwrap key", e); + } + + switch (wrappedKeyType) { + case Cipher.SECRET_KEY: + { + return new SecretKeySpec(encoded, wrappedKeyAlgorithm); + // break; + } + case Cipher.PRIVATE_KEY: + { + KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm); + try { + return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded)); + } catch (InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to create private key from its PKCS#8 encoded form", e); + } + // break; + } + case Cipher.PUBLIC_KEY: + { + KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm); + try { + return keyFactory.generatePublic(new X509EncodedKeySpec(encoded)); + } catch (InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to create public key from its X.509 encoded form", e); + } + // break; + } + default: + throw new InvalidParameterException( + "Unsupported wrappedKeyType: " + wrappedKeyType); + } } @Override @@ -437,6 +592,17 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor } /** + * Overrides the default purpose/type of the crypto operation. + */ + protected final void setKeymasterPurposeOverride(int keymasterPurpose) { + mKeymasterPurposeOverride = keymasterPurpose; + } + + protected final int getKeymasterPurposeOverride() { + return mKeymasterPurposeOverride; + } + + /** * Returns {@code true} if this cipher is initialized for encryption, {@code false} if this * cipher is initialized for decryption. */ @@ -504,21 +670,37 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor /** * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's - * {@code begin} operation. + * {@code begin} operation. This amount of entropy is typically what's consumed to generate + * random parameters, such as IV. * - * <p>For decryption, this should be {@code 0} because decryption should not be consuming any - * entropy. For encryption, this value should match (or exceed) the amount of Shannon entropy of - * the ciphertext produced by this cipher assuming the key, the plaintext, and all explicitly - * provided parameters to {@code Cipher.init} are known. For example, for AES CBC encryption - * with an explicitly provided IV this should be {@code 0}, whereas for the case where IV is - * generated by the KeyStore's {@code begin} operation this should be {@code 16}. For RSA with - * OAEP this should be the size of the OAEP hash output. For RSA with PKCS#1 padding this should - * be the size of the padding string or could be raised (for simplicity) to the size of the - * modulus. + * <p>For decryption, the return value should be {@code 0} because decryption should not be + * consuming any entropy. For encryption, the value combined with + * {@link #getAdditionalEntropyAmountForFinish()} should match (or exceed) the amount of Shannon + * entropy of the ciphertext produced by this cipher assuming the key, the plaintext, and all + * explicitly provided parameters to {@code Cipher.init} are known. For example, for AES CBC + * encryption with an explicitly provided IV the return value should be {@code 0}, whereas for + * the case where IV is generated by the KeyStore's {@code begin} operation it should be + * {@code 16}. */ protected abstract int getAdditionalEntropyAmountForBegin(); /** + * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's + * {@code finish} operation. This amount of entropy is typically what's consumed by encryption + * padding scheme. + * + * <p>For decryption, the return value should be {@code 0} because decryption should not be + * consuming any entropy. For encryption, the value combined with + * {@link #getAdditionalEntropyAmountForBegin()} should match (or exceed) the amount of Shannon + * entropy of the ciphertext produced by this cipher assuming the key, the plaintext, and all + * explicitly provided parameters to {@code Cipher.init} are known. For example, for RSA with + * OAEP the return value should be the size of the OAEP hash output. For RSA with PKCS#1 padding + * the return value should be the size of the padding string or could be raised (for simplicity) + * to the size of the modulus. + */ + protected abstract int getAdditionalEntropyAmountForFinish(); + + /** * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation. * * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreECDSASignatureSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreECDSASignatureSpi.java index 335da07..d19a766 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreECDSASignatureSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreECDSASignatureSpi.java @@ -117,7 +117,7 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature } @Override - protected int getAdditionalEntropyAmountForBegin() { - return (isSigning()) ? mGroupSizeBytes : 0; + protected int getAdditionalEntropyAmountForSign() { + return mGroupSizeBytes; } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreECPrivateKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreECPrivateKey.java new file mode 100644 index 0000000..5dbcd68 --- /dev/null +++ b/keystore/java/android/security/keystore/AndroidKeyStoreECPrivateKey.java @@ -0,0 +1,40 @@ +/* + * 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 java.security.PrivateKey; +import java.security.interfaces.ECKey; +import java.security.spec.ECParameterSpec; + +/** + * EC private key (instance of {@link PrivateKey} and {@link ECKey}) backed by keystore. + * + * @hide + */ +public class AndroidKeyStoreECPrivateKey extends AndroidKeyStorePrivateKey implements ECKey { + private final ECParameterSpec mParams; + + public AndroidKeyStoreECPrivateKey(String alias, ECParameterSpec params) { + super(alias, KeyProperties.KEY_ALGORITHM_EC); + mParams = params; + } + + @Override + public ECParameterSpec getParams() { + return mParams; + } +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java index f31c06d..f7c184c 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreHmacSpi.java @@ -232,7 +232,10 @@ public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreC byte[] result; try { - result = mChunkedStreamer.doFinal(null, 0, 0); + result = mChunkedStreamer.doFinal( + null, 0, 0, + null // no additional entropy needed -- HMAC is deterministic + ); } catch (KeyStoreException e) { throw new ProviderException("Keystore operation failed", e); } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreKey.java index 1751aa5..e76802f 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKey.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKey.java @@ -52,4 +52,42 @@ public class AndroidKeyStoreKey implements Key { // This key does not export its key material return null; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mAlgorithm == null) ? 0 : mAlgorithm.hashCode()); + result = prime * result + ((mAlias == null) ? 0 : mAlias.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + AndroidKeyStoreKey other = (AndroidKeyStoreKey) obj; + if (mAlgorithm == null) { + if (other.mAlgorithm != null) { + return false; + } + } else if (!mAlgorithm.equals(other.mAlgorithm)) { + return false; + } + if (mAlias == null) { + if (other.mAlias != null) { + return false; + } + } else if (!mAlias.equals(other.mAlias)) { + return false; + } + return true; + } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java index 4d6178f..688936c 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java @@ -179,11 +179,15 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes()); mKeymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster( spec.getEncryptionPaddings()); + if (spec.getSignaturePaddings().length > 0) { + throw new InvalidAlgorithmParameterException( + "Signature paddings not supported for symmetric key algorithms"); + } mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()); if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) && (spec.isRandomizedEncryptionRequired())) { for (int keymasterBlockMode : mKeymasterBlockModes) { - if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible( + if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto( keymasterBlockMode)) { throw new InvalidAlgorithmParameterException( "Randomized encryption (IND-CPA) required but may be violated" diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java index 7b5ca3a..b93424d 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -16,32 +16,60 @@ package android.security.keystore; -import android.annotation.NonNull; +import android.annotation.Nullable; import android.security.Credentials; import android.security.KeyPairGeneratorSpec; import android.security.KeyStore; - +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; + +import com.android.org.bouncycastle.asn1.ASN1EncodableVector; +import com.android.org.bouncycastle.asn1.ASN1InputStream; +import com.android.org.bouncycastle.asn1.ASN1Integer; +import com.android.org.bouncycastle.asn1.ASN1ObjectIdentifier; +import com.android.org.bouncycastle.asn1.DERBitString; +import com.android.org.bouncycastle.asn1.DERInteger; +import com.android.org.bouncycastle.asn1.DERNull; +import com.android.org.bouncycastle.asn1.DERSequence; +import com.android.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import com.android.org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import com.android.org.bouncycastle.asn1.x509.Certificate; +import com.android.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import com.android.org.bouncycastle.asn1.x509.TBSCertificate; +import com.android.org.bouncycastle.asn1.x509.Time; +import com.android.org.bouncycastle.asn1.x509.V3TBSCertificateGenerator; +import com.android.org.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import com.android.org.bouncycastle.jce.X509Principal; +import com.android.org.bouncycastle.jce.provider.X509CertificateObject; import com.android.org.bouncycastle.x509.X509V3CertificateGenerator; -import com.android.org.conscrypt.NativeConstants; -import com.android.org.conscrypt.OpenSSLEngine; +import libcore.util.EmptyArray; + +import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyPairGeneratorSpi; -import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; +import java.security.ProviderException; import java.security.PublicKey; import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; -import java.security.spec.InvalidKeySpecException; +import java.security.spec.ECGenParameterSpec; import java.security.spec.RSAKeyGenParameterSpec; -import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.Set; /** * Provides a way to create instances of a KeyPair which will be placed in the @@ -60,13 +88,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato public static class RSA extends AndroidKeyStoreKeyPairGeneratorSpi { public RSA() { - super(KeyProperties.KEY_ALGORITHM_RSA); + super(KeymasterDefs.KM_ALGORITHM_RSA); } } public static class EC extends AndroidKeyStoreKeyPairGeneratorSpi { public EC() { - super(KeyProperties.KEY_ALGORITHM_EC); + super(KeymasterDefs.KM_ALGORITHM_EC); } } @@ -84,39 +112,285 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato private static final int RSA_MIN_KEY_SIZE = 512; private static final int RSA_MAX_KEY_SIZE = 8192; - private final String mAlgorithm; + private static final Map<String, Integer> SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE = + new HashMap<String, Integer>(); + private static final List<String> SUPPORTED_EC_NIST_CURVE_NAMES = new ArrayList<String>(); + static { + // Aliases for NIST P-192 + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-192", 192); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp192r1", 192); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("prime192v1", 192); + + // Aliases for NIST P-224 + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-224", 224); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp224r1", 224); + + // Aliases for NIST P-256 + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-256", 256); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp256r1", 256); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("prime256v1", 256); + + // Aliases for NIST P-384 + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-384", 384); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp384r1", 384); + + // Aliases for NIST P-521 + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-521", 521); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp521r1", 521); + + SUPPORTED_EC_NIST_CURVE_NAMES.addAll(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.keySet()); + Collections.sort(SUPPORTED_EC_NIST_CURVE_NAMES); + } + + private final int mOriginalKeymasterAlgorithm; private KeyStore mKeyStore; private KeyGenParameterSpec mSpec; + + private String mEntryAlias; private boolean mEncryptionAtRestRequired; - private @KeyProperties.KeyAlgorithmEnum String mKeyAlgorithm; - private int mKeyType; - private int mKeySize; + private @KeyProperties.KeyAlgorithmEnum String mJcaKeyAlgorithm; + private int mKeymasterAlgorithm = -1; + private int mKeySizeBits; + private SecureRandom mRng; + + private int[] mKeymasterPurposes; + private int[] mKeymasterBlockModes; + private int[] mKeymasterEncryptionPaddings; + private int[] mKeymasterSignaturePaddings; + private int[] mKeymasterDigests; - protected AndroidKeyStoreKeyPairGeneratorSpi(@KeyProperties.KeyAlgorithmEnum String algorithm) { - mAlgorithm = algorithm; + private long mRSAPublicExponent; + + protected AndroidKeyStoreKeyPairGeneratorSpi(int keymasterAlgorithm) { + mOriginalKeymasterAlgorithm = keymasterAlgorithm; } - @KeyProperties.KeyAlgorithmEnum String getAlgorithm() { - return mAlgorithm; + @Override + public void initialize(int keysize, SecureRandom random) { + throw new IllegalArgumentException( + KeyGenParameterSpec.class.getName() + " or " + KeyPairGeneratorSpec.class.getName() + + " required to initialize this KeyPairGenerator"); + } + + @Override + public void initialize(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + if (params == null) { + throw new InvalidAlgorithmParameterException( + "Must supply params of type " + KeyGenParameterSpec.class.getName() + + " or " + KeyPairGeneratorSpec.class.getName()); + } + + KeyGenParameterSpec spec; + boolean encryptionAtRestRequired = false; + int keymasterAlgorithm = mOriginalKeymasterAlgorithm; + if (params instanceof KeyGenParameterSpec) { + spec = (KeyGenParameterSpec) params; + } else if (params instanceof KeyPairGeneratorSpec) { + // Legacy/deprecated spec + KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params; + try { + KeyGenParameterSpec.Builder specBuilder; + String specKeyAlgorithm = legacySpec.getKeyType(); + if (specKeyAlgorithm != null) { + // Spec overrides the generator's default key algorithm + try { + keymasterAlgorithm = + KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm( + specKeyAlgorithm); + } catch (IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException( + "Invalid key type in parameters", e); + } + } + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + specBuilder = new KeyGenParameterSpec.Builder( + legacySpec.getKeystoreAlias(), + KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + // Authorized to be used with any digest (including no digest). + specBuilder.setDigests(KeyProperties.DIGEST_NONE); + break; + case KeymasterDefs.KM_ALGORITHM_RSA: + specBuilder = new KeyGenParameterSpec.Builder( + legacySpec.getKeystoreAlias(), + KeyProperties.PURPOSE_ENCRYPT + | KeyProperties.PURPOSE_DECRYPT + | KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + // Authorized to be used with any digest (including no digest). + specBuilder.setDigests(KeyProperties.DIGEST_NONE); + specBuilder.setSignaturePaddings( + KeyProperties.SIGNATURE_PADDING_RSA_PKCS1); + // Authorized to be used with any padding (including no padding). + specBuilder.setEncryptionPaddings( + KeyProperties.ENCRYPTION_PADDING_NONE); + // Disable randomized encryption requirement to support encryption + // padding NONE above. + specBuilder.setRandomizedEncryptionRequired(false); + break; + default: + throw new ProviderException( + "Unsupported algorithm: " + mKeymasterAlgorithm); + } + + 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()); + encryptionAtRestRequired = legacySpec.isEncryptionRequired(); + specBuilder.setUserAuthenticationRequired(false); + + spec = specBuilder.build(); + } catch (NullPointerException | IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException(e); + } + } else { + throw new InvalidAlgorithmParameterException( + "Unsupported params class: " + params.getClass().getName() + + ". Supported: " + KeyGenParameterSpec.class.getName() + + ", " + KeyPairGeneratorSpec.class.getName()); + } + + mEntryAlias = spec.getKeystoreAlias(); + mSpec = spec; + mKeymasterAlgorithm = keymasterAlgorithm; + mEncryptionAtRestRequired = encryptionAtRestRequired; + mKeySizeBits = spec.getKeySize(); + initAlgorithmSpecificParameters(); + if (mKeySizeBits == -1) { + mKeySizeBits = getDefaultKeySize(keymasterAlgorithm); + } + checkValidKeySize(keymasterAlgorithm, mKeySizeBits); + + if (spec.getKeystoreAlias() == null) { + throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided"); + } + + String jcaKeyAlgorithm; + try { + jcaKeyAlgorithm = KeyProperties.KeyAlgorithm.fromKeymasterAsymmetricKeyAlgorithm( + keymasterAlgorithm); + mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes()); + mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()); + mKeymasterEncryptionPaddings = KeyProperties.EncryptionPadding.allToKeymaster( + spec.getEncryptionPaddings()); + mKeymasterSignaturePaddings = KeyProperties.SignaturePadding.allToKeymaster( + spec.getSignaturePaddings()); + if (spec.isDigestsSpecified()) { + mKeymasterDigests = KeyProperties.Digest.allToKeymaster(spec.getDigests()); + } else { + mKeymasterDigests = EmptyArray.INT; + } + } catch (IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException(e); + } + + mJcaKeyAlgorithm = jcaKeyAlgorithm; + mRng = random; + mKeyStore = KeyStore.getInstance(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + private void resetAll() { + mEntryAlias = null; + mJcaKeyAlgorithm = null; + mKeymasterAlgorithm = -1; + mKeymasterPurposes = null; + mKeymasterBlockModes = null; + mKeymasterEncryptionPaddings = null; + mKeymasterSignaturePaddings = null; + mKeymasterDigests = null; + mKeySizeBits = 0; + mSpec = null; + mRSAPublicExponent = -1; + mEncryptionAtRestRequired = false; + mRng = null; + mKeyStore = null; + } + + private void initAlgorithmSpecificParameters() throws InvalidAlgorithmParameterException { + AlgorithmParameterSpec algSpecificSpec = mSpec.getAlgorithmParameterSpec(); + switch (mKeymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_RSA: + { + BigInteger publicExponent = null; + if (algSpecificSpec instanceof RSAKeyGenParameterSpec) { + RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) algSpecificSpec; + if (mKeySizeBits == -1) { + mKeySizeBits = rsaSpec.getKeysize(); + } else if (mKeySizeBits != rsaSpec.getKeysize()) { + throw new InvalidAlgorithmParameterException("RSA key size must match " + + " between " + mSpec + " and " + algSpecificSpec + + ": " + mKeySizeBits + " vs " + rsaSpec.getKeysize()); + } + publicExponent = rsaSpec.getPublicExponent(); + } else if (algSpecificSpec != null) { + throw new InvalidAlgorithmParameterException( + "RSA may only use RSAKeyGenParameterSpec"); + } + if (publicExponent == null) { + publicExponent = RSAKeyGenParameterSpec.F4; + } + if (publicExponent.compareTo(BigInteger.ZERO) < 1) { + throw new InvalidAlgorithmParameterException( + "RSA public exponent must be positive: " + publicExponent); + } + if (publicExponent.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) { + throw new InvalidAlgorithmParameterException( + "Unsupported RSA public exponent: " + publicExponent + + ". Only exponents <= " + Long.MAX_VALUE + " supported"); + } + mRSAPublicExponent = publicExponent.longValue(); + break; + } + case KeymasterDefs.KM_ALGORITHM_EC: + if (algSpecificSpec instanceof ECGenParameterSpec) { + ECGenParameterSpec ecSpec = (ECGenParameterSpec) algSpecificSpec; + String curveName = ecSpec.getName(); + Integer ecSpecKeySizeBits = SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.get( + curveName.toLowerCase(Locale.US)); + if (ecSpecKeySizeBits == null) { + throw new InvalidAlgorithmParameterException( + "Unsupported EC curve name: " + curveName + + ". Supported: " + SUPPORTED_EC_NIST_CURVE_NAMES); + } + if (mKeySizeBits == -1) { + mKeySizeBits = ecSpecKeySizeBits; + } else if (mKeySizeBits != ecSpecKeySizeBits) { + throw new InvalidAlgorithmParameterException("EC key size must match " + + " between " + mSpec + " and " + algSpecificSpec + + ": " + mKeySizeBits + " vs " + ecSpecKeySizeBits); + } + } else if (algSpecificSpec != null) { + throw new InvalidAlgorithmParameterException( + "EC may only use ECGenParameterSpec"); + } + break; + default: + throw new ProviderException("Unsupported algorithm: " + mKeymasterAlgorithm); + } } - /** - * Generate a KeyPair which is backed by the Android keystore service. You - * must call {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)} - * with an {@link KeyPairGeneratorSpec} as the {@code params} - * argument before calling this otherwise an {@code IllegalStateException} - * will be thrown. - * <p> - * This will create an entry in the Android keystore service with a - * self-signed certificate using the {@code params} specified in the - * {@code initialize(params)} call. - * - * @throws IllegalStateException when called before calling - * {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)} - * @see java.security.KeyPairGeneratorSpi#generateKeyPair() - */ @Override public KeyPair generateKeyPair() { if (mKeyStore == null || mSpec == null) { @@ -131,72 +405,127 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato + ", but the user has not yet entered the credential"); } - final String alias = mSpec.getKeystoreAlias(); - - byte[][] args = getArgsForKeyType(mKeyType, mSpec.getAlgorithmParameterSpec()); - - final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; - + KeymasterArguments args = new KeymasterArguments(); + args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits); + args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); + args.addInts(KeymasterDefs.KM_TAG_PURPOSE, mKeymasterPurposes); + args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockModes); + args.addInts(KeymasterDefs.KM_TAG_PADDING, mKeymasterEncryptionPaddings); + args.addInts(KeymasterDefs.KM_TAG_PADDING, mKeymasterSignaturePaddings); + args.addInts(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests); + + KeymasterUtils.addUserAuthArgs(args, + mSpec.isUserAuthenticationRequired(), + mSpec.getUserAuthenticationValidityDurationSeconds()); + args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, + (mSpec.getKeyValidityStart() != null) + ? mSpec.getKeyValidityStart() : new Date(0)); + args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + (mSpec.getKeyValidityForOriginationEnd() != null) + ? mSpec.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE)); + args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + (mSpec.getKeyValidityForConsumptionEnd() != null) + ? mSpec.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE)); + addAlgorithmSpecificParameters(args); + + byte[] additionalEntropy = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, (mKeySizeBits + 7) / 8); + + final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + mEntryAlias; boolean success = false; try { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); - if (!mKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, mKeyType, mKeySize, - flags, args)) { - throw new IllegalStateException("could not generate key in keystore"); + Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias); + KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); + int errorCode = mKeyStore.generateKey( + privateKeyAlias, + args, + additionalEntropy, + flags, + resultingKeyCharacteristics); + if (errorCode != KeyStore.NO_ERROR) { + throw new ProviderException( + "Failed to generate key pair", KeyStore.getKeyStoreException(errorCode)); } - final PrivateKey privKey; - final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore"); + KeyPair result; try { - privKey = engine.getPrivateKeyById(privateKeyAlias); - } catch (InvalidKeyException e) { - throw new RuntimeException("Can't get key", e); + result = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore( + mKeyStore, privateKeyAlias); + } catch (UnrecoverableKeyException e) { + throw new ProviderException("Failed to load generated key pair from keystore", e); } - final byte[] pubKeyBytes = mKeyStore.getPubkey(privateKeyAlias); - - final PublicKey pubKey; - try { - final KeyFactory keyFact = KeyFactory.getInstance(mKeyAlgorithm); - pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes)); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("Can't instantiate key generator", e); - } catch (InvalidKeySpecException e) { - throw new IllegalStateException("keystore returned invalid key encoding", e); + if (!mJcaKeyAlgorithm.equalsIgnoreCase(result.getPrivate().getAlgorithm())) { + throw new ProviderException( + "Generated key pair algorithm does not match requested algorithm: " + + result.getPrivate().getAlgorithm() + " vs " + mJcaKeyAlgorithm); } final X509Certificate cert; try { - cert = generateCertificate(privKey, pubKey); + cert = generateSelfSignedCertificate(result.getPrivate(), result.getPublic()); } catch (Exception e) { - throw new IllegalStateException("Can't generate certificate", e); + throw new ProviderException("Failed to generate self-signed certificate", e); } byte[] certBytes; try { certBytes = cert.getEncoded(); } catch (CertificateEncodingException e) { - throw new IllegalStateException("Can't get encoding of certificate", e); + throw new ProviderException( + "Failed to obtain encoded form of self-signed certificate", e); } - if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, certBytes, KeyStore.UID_SELF, - flags)) { - throw new IllegalStateException("Can't store certificate in AndroidKeyStore"); + int insertErrorCode = mKeyStore.insert( + Credentials.USER_CERTIFICATE + mEntryAlias, + certBytes, + KeyStore.UID_SELF, + flags); + if (insertErrorCode != KeyStore.NO_ERROR) { + throw new ProviderException("Failed to store self-signed certificate", + KeyStore.getKeyStoreException(insertErrorCode)); } - KeyPair result = new KeyPair(pubKey, privKey); success = true; return result; } finally { if (!success) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); + Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias); } } } + private void addAlgorithmSpecificParameters(KeymasterArguments keymasterArgs) { + switch (mKeymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_RSA: + keymasterArgs.addLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, mRSAPublicExponent); + break; + case KeymasterDefs.KM_ALGORITHM_EC: + break; + default: + throw new ProviderException("Unsupported algorithm: " + mKeymasterAlgorithm); + } + } + + private X509Certificate generateSelfSignedCertificate( + PrivateKey privateKey, PublicKey publicKey) throws Exception { + String signatureAlgorithm = + getCertificateSignatureAlgorithm(mKeymasterAlgorithm, mKeySizeBits, mSpec); + if (signatureAlgorithm == null) { + // Key cannot be used to sign a certificate + return generateSelfSignedCertificateWithFakeSignature(publicKey); + } else { + // Key can be used to sign a certificate + return generateSelfSignedCertificateWithValidSignature( + privateKey, publicKey, signatureAlgorithm); + } + } + @SuppressWarnings("deprecation") - private X509Certificate generateCertificate(PrivateKey privateKey, PublicKey publicKey) - throws Exception { + private X509Certificate generateSelfSignedCertificateWithValidSignature( + PrivateKey privateKey, PublicKey publicKey, String signatureAlgorithm) + throws Exception { final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); certGen.setPublicKey(publicKey); certGen.setSerialNumber(mSpec.getCertificateSerialNumber()); @@ -204,198 +533,253 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato certGen.setIssuerDN(mSpec.getCertificateSubject()); certGen.setNotBefore(mSpec.getCertificateNotBefore()); certGen.setNotAfter(mSpec.getCertificateNotAfter()); - certGen.setSignatureAlgorithm(getDefaultSignatureAlgorithmForKeyAlgorithm(mKeyAlgorithm)); + certGen.setSignatureAlgorithm(signatureAlgorithm); return certGen.generate(privateKey); } - @NonNull - private @KeyProperties.KeyAlgorithmEnum String getKeyAlgorithm(KeyPairGeneratorSpec spec) { - String result = spec.getKeyType(); - if (result != null) { - return result; + @SuppressWarnings("deprecation") + private X509Certificate generateSelfSignedCertificateWithFakeSignature( + PublicKey publicKey) throws Exception { + V3TBSCertificateGenerator tbsGenerator = new V3TBSCertificateGenerator(); + ASN1ObjectIdentifier sigAlgOid; + AlgorithmIdentifier sigAlgId; + byte[] signature; + switch (mKeymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + sigAlgOid = X9ObjectIdentifiers.ecdsa_with_SHA256; + sigAlgId = new AlgorithmIdentifier(sigAlgOid); + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(new DERInteger(0)); + v.add(new DERInteger(0)); + signature = new DERSequence().getEncoded(); + break; + case KeymasterDefs.KM_ALGORITHM_RSA: + sigAlgOid = PKCSObjectIdentifiers.sha256WithRSAEncryption; + sigAlgId = new AlgorithmIdentifier(sigAlgOid, DERNull.INSTANCE); + signature = new byte[1]; + break; + default: + throw new ProviderException("Unsupported key algorithm: " + mKeymasterAlgorithm); } - return getAlgorithm(); - } - private static int getDefaultKeySize(int keyType) { - if (keyType == NativeConstants.EVP_PKEY_EC) { - return EC_DEFAULT_KEY_SIZE; - } else if (keyType == NativeConstants.EVP_PKEY_RSA) { - return RSA_DEFAULT_KEY_SIZE; + try (ASN1InputStream publicKeyInfoIn = new ASN1InputStream(publicKey.getEncoded())) { + tbsGenerator.setSubjectPublicKeyInfo( + SubjectPublicKeyInfo.getInstance(publicKeyInfoIn.readObject())); } - return -1; + tbsGenerator.setSerialNumber(new ASN1Integer(mSpec.getCertificateSerialNumber())); + X509Principal subject = + new X509Principal(mSpec.getCertificateSubject().getEncoded()); + tbsGenerator.setSubject(subject); + tbsGenerator.setIssuer(subject); + tbsGenerator.setStartDate(new Time(mSpec.getCertificateNotBefore())); + tbsGenerator.setEndDate(new Time(mSpec.getCertificateNotAfter())); + tbsGenerator.setSignature(sigAlgId); + TBSCertificate tbsCertificate = tbsGenerator.generateTBSCertificate(); + + ASN1EncodableVector result = new ASN1EncodableVector(); + result.add(tbsCertificate); + result.add(sigAlgId); + result.add(new DERBitString(signature)); + return new X509CertificateObject(Certificate.getInstance(new DERSequence(result))); } - private static void checkValidKeySize(String keyAlgorithm, int keyType, int keySize) - throws InvalidAlgorithmParameterException { - if (keyType == NativeConstants.EVP_PKEY_EC) { - if (keySize < EC_MIN_KEY_SIZE || keySize > EC_MAX_KEY_SIZE) { - throw new InvalidAlgorithmParameterException("EC keys must be >= " - + EC_MIN_KEY_SIZE + " and <= " + EC_MAX_KEY_SIZE); - } - } else if (keyType == NativeConstants.EVP_PKEY_RSA) { - if (keySize < RSA_MIN_KEY_SIZE || keySize > RSA_MAX_KEY_SIZE) { - throw new InvalidAlgorithmParameterException("RSA keys must be >= " - + RSA_MIN_KEY_SIZE + " and <= " + RSA_MAX_KEY_SIZE); - } - } else { - throw new InvalidAlgorithmParameterException( - "Unsupported key algorithm: " + keyAlgorithm); + private static int getDefaultKeySize(int keymasterAlgorithm) { + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + return EC_DEFAULT_KEY_SIZE; + case KeymasterDefs.KM_ALGORITHM_RSA: + return RSA_DEFAULT_KEY_SIZE; + default: + throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm); } } - private static void checkCorrectParametersSpec(int keyType, int keySize, - AlgorithmParameterSpec spec) throws InvalidAlgorithmParameterException { - if (keyType == NativeConstants.EVP_PKEY_RSA && spec != null) { - if (spec instanceof RSAKeyGenParameterSpec) { - RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) spec; - if (keySize != -1 && keySize != rsaSpec.getKeysize()) { - throw new InvalidAlgorithmParameterException("RSA key size must match: " - + keySize + " vs " + rsaSpec.getKeysize()); + private static void checkValidKeySize(int keymasterAlgorithm, int keySize) + throws InvalidAlgorithmParameterException { + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + if (keySize < EC_MIN_KEY_SIZE || keySize > EC_MAX_KEY_SIZE) { + throw new InvalidAlgorithmParameterException("EC key size must be >= " + + EC_MIN_KEY_SIZE + " and <= " + EC_MAX_KEY_SIZE); } - } else { - throw new InvalidAlgorithmParameterException( - "RSA may only use RSAKeyGenParameterSpec"); - } - } - } - - private static String getDefaultSignatureAlgorithmForKeyAlgorithm( - @KeyProperties.KeyAlgorithmEnum String algorithm) { - if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) { - return "sha256WithRSA"; - } else if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) { - return "sha256WithECDSA"; - } else { - throw new IllegalArgumentException("Unsupported key type " + algorithm); - } - } - - private static byte[][] getArgsForKeyType(int keyType, AlgorithmParameterSpec spec) { - switch (keyType) { - case NativeConstants.EVP_PKEY_RSA: - if (spec instanceof RSAKeyGenParameterSpec) { - RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) spec; - return new byte[][] { rsaSpec.getPublicExponent().toByteArray() }; + break; + case KeymasterDefs.KM_ALGORITHM_RSA: + if (keySize < RSA_MIN_KEY_SIZE || keySize > RSA_MAX_KEY_SIZE) { + throw new InvalidAlgorithmParameterException("RSA key size must be >= " + + RSA_MIN_KEY_SIZE + " and <= " + RSA_MAX_KEY_SIZE); } break; + default: + throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm); } - return null; - } - - @Override - public void initialize(int keysize, SecureRandom random) { - throw new IllegalArgumentException( - "cannot specify keysize with AndroidKeyStore KeyPairGenerator"); } - @Override - public void initialize(AlgorithmParameterSpec params, SecureRandom random) - throws InvalidAlgorithmParameterException { - if (params == null) { - throw new InvalidAlgorithmParameterException( - "Must supply params of type " + KeyGenParameterSpec.class.getName() - + " or " + KeyPairGeneratorSpec.class.getName()); + /** + * Returns the {@code Signature} algorithm to be used for signing a certificate using the + * specified key or {@code null} if the key cannot be used for signing a certificate. + */ + @Nullable + private static String getCertificateSignatureAlgorithm( + int keymasterAlgorithm, + int keySizeBits, + KeyGenParameterSpec spec) { + // Constraints: + // 1. Key must be authorized for signing. + // 2. Signature digest must be one of key's authorized digests. + // 3. For RSA keys, the digest output size must not exceed modulus size minus space needed + // for RSA PKCS#1 signature padding (about 29 bytes: minimum 10 bytes of padding + 15--19 + // bytes overhead for encoding digest OID and digest value in DER). + // 4. For EC keys, the there is no point in using a digest whose output size is longer than + // key/field size because the digest will be truncated to that size. + + if ((spec.getPurposes() & KeyProperties.PURPOSE_SIGN) == 0) { + // Key not authorized for signing + return null; } - - String keyAlgorithm; - KeyGenParameterSpec spec; - boolean encryptionAtRestRequired = false; - 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 (!spec.isDigestsSpecified()) { + // Key not authorized for any digests -- can't sign + return null; + } + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + { + Set<Integer> availableKeymasterDigests = getAvailableKeymasterSignatureDigests( + spec.getDigests(), + AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests()); + + int bestKeymasterDigest = -1; + int bestDigestOutputSizeBits = -1; + for (int keymasterDigest : availableKeymasterDigests) { + int outputSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest); + if (outputSizeBits == keySizeBits) { + // Perfect match -- use this digest + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + break; + } + // Not a perfect match -- check against the best digest so far + if (bestKeymasterDigest == -1) { + // First digest tested -- definitely the best so far + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } else { + // Prefer output size to be as close to key size as possible, with output + // sizes larger than key size preferred to those smaller than key size. + if (bestDigestOutputSizeBits < keySizeBits) { + // Output size of the best digest so far is smaller than key size. + // Anything larger is a win. + if (outputSizeBits > bestDigestOutputSizeBits) { + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } + } else { + // Output size of the best digest so far is larger than key size. + // Anything smaller is a win, as long as it's not smaller than key size. + if ((outputSizeBits < bestDigestOutputSizeBits) + && (outputSizeBits >= keySizeBits)) { + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } + } + } + } + if (bestKeymasterDigest == -1) { + return null; + } + return KeyProperties.Digest.fromKeymasterToSignatureAlgorithmDigest( + bestKeymasterDigest) + "WithECDSA"; + } + case KeymasterDefs.KM_ALGORITHM_RSA: + { + // Check whether this key is authorized for PKCS#1 signature padding. + // We use Bouncy Castle to generate self-signed RSA certificates. Bouncy Castle + // only supports RSA certificates signed using PKCS#1 padding scheme. The key needs + // to be authorized for PKCS#1 padding or padding NONE which means any padding. + boolean pkcs1SignaturePaddingSupported = false; + for (int keymasterPadding : KeyProperties.SignaturePadding.allToKeymaster( + spec.getSignaturePaddings())) { + if ((keymasterPadding == KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN) + || (keymasterPadding == KeymasterDefs.KM_PAD_NONE)) { + pkcs1SignaturePaddingSupported = true; + break; + } + } + if (!pkcs1SignaturePaddingSupported) { + // Keymaster doesn't distinguish between encryption padding NONE and signature + // padding NONE. In the Android Keystore API only encryption padding NONE is + // exposed. + for (int keymasterPadding : KeyProperties.EncryptionPadding.allToKeymaster( + spec.getEncryptionPaddings())) { + if (keymasterPadding == KeymasterDefs.KM_PAD_NONE) { + pkcs1SignaturePaddingSupported = true; + break; + } + } + } + if (!pkcs1SignaturePaddingSupported) { + // Key not authorized for PKCS#1 signature padding -- can't sign + return null; } - if (legacySpec.getKeySize() != -1) { - specBuilder.setKeySize(legacySpec.getKeySize()); + Set<Integer> availableKeymasterDigests = getAvailableKeymasterSignatureDigests( + spec.getDigests(), + AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests()); + + // The amount of space available for the digest is less than modulus size because + // padding must be at least 10 bytes long, and then there's also the 15--19 + // bytes overhead for encoding digest OID and digest value in DER. + int maxDigestOutputSizeBits = keySizeBits - 29 * 8; + int bestKeymasterDigest = -1; + int bestDigestOutputSizeBits = -1; + for (int keymasterDigest : availableKeymasterDigests) { + int outputSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest); + if (outputSizeBits > maxDigestOutputSizeBits) { + // Digest too long (signature generation will fail) -- skip + continue; + } + if (bestKeymasterDigest == -1) { + // First digest tested -- definitely the best so far + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } else { + // The longer the better + if (outputSizeBits > bestDigestOutputSizeBits) { + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } + } } - if (legacySpec.getAlgorithmParameterSpec() != null) { - specBuilder.setAlgorithmParameterSpec(legacySpec.getAlgorithmParameterSpec()); + if (bestKeymasterDigest == -1) { + return null; } - specBuilder.setCertificateSubject(legacySpec.getSubjectDN()); - specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber()); - specBuilder.setCertificateNotBefore(legacySpec.getStartDate()); - specBuilder.setCertificateNotAfter(legacySpec.getEndDate()); - encryptionAtRestRequired = legacySpec.isEncryptionRequired(); - specBuilder.setUserAuthenticationRequired(false); - - spec = specBuilder.build(); - } catch (NullPointerException | IllegalArgumentException e) { - throw new InvalidAlgorithmParameterException(e); + return KeyProperties.Digest.fromKeymasterToSignatureAlgorithmDigest( + bestKeymasterDigest) + "WithRSA"; } - } else if (params instanceof KeyGenParameterSpec) { - spec = (KeyGenParameterSpec) params; - keyAlgorithm = getAlgorithm(); - } else { - throw new InvalidAlgorithmParameterException( - "Unsupported params class: " + params.getClass().getName() - + ". Supported: " + KeyGenParameterSpec.class.getName() - + ", " + KeyPairGeneratorSpec.class); + default: + throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm); } + } - int keyType = KeyStore.getKeyTypeForAlgorithm(keyAlgorithm); - if (keyType == -1) { - throw new InvalidAlgorithmParameterException( - "Unsupported key algorithm: " + keyAlgorithm); + private static Set<Integer> getAvailableKeymasterSignatureDigests( + @KeyProperties.DigestEnum String[] authorizedKeyDigests, + @KeyProperties.DigestEnum String[] supportedSignatureDigests) { + Set<Integer> authorizedKeymasterKeyDigests = new HashSet<Integer>(); + for (int keymasterDigest : KeyProperties.Digest.allToKeymaster(authorizedKeyDigests)) { + authorizedKeymasterKeyDigests.add(keymasterDigest); } - int keySize = spec.getKeySize(); - if (keySize == -1) { - keySize = getDefaultKeySize(keyType); - if (keySize == -1) { - throw new InvalidAlgorithmParameterException( - "Unsupported key algorithm: " + keyAlgorithm); - } + Set<Integer> supportedKeymasterSignatureDigests = new HashSet<Integer>(); + for (int keymasterDigest + : KeyProperties.Digest.allToKeymaster(supportedSignatureDigests)) { + supportedKeymasterSignatureDigests.add(keymasterDigest); + } + if (authorizedKeymasterKeyDigests.contains(KeymasterDefs.KM_DIGEST_NONE)) { + // Key is authorized to be used with any digest + return supportedKeymasterSignatureDigests; + } else { + // Key is authorized to be used only with specific digests. + Set<Integer> result = new HashSet<Integer>(supportedKeymasterSignatureDigests); + result.retainAll(authorizedKeymasterKeyDigests); + return result; } - checkCorrectParametersSpec(keyType, keySize, spec.getAlgorithmParameterSpec()); - checkValidKeySize(keyAlgorithm, keyType, keySize); - - mKeyAlgorithm = keyAlgorithm; - mKeyType = keyType; - mKeySize = keySize; - mSpec = spec; - mEncryptionAtRestRequired = encryptionAtRestRequired; - mKeyStore = KeyStore.getInstance(); } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java index cb270bb..967319a 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java @@ -16,11 +16,28 @@ package android.security.keystore; +import android.annotation.NonNull; import android.security.KeyStore; +import android.security.keymaster.ExportResult; +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterDefs; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; import java.security.Provider; +import java.security.ProviderException; +import java.security.PublicKey; import java.security.Security; import java.security.Signature; +import java.security.UnrecoverableKeyException; +import java.security.interfaces.ECKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.List; import javax.crypto.Cipher; import javax.crypto.Mac; @@ -146,4 +163,145 @@ public class AndroidKeyStoreProvider extends Provider { } return ((KeyStoreCryptoOperation) spi).getOperationHandle(); } + + @NonNull + public static AndroidKeyStorePublicKey getAndroidKeyStorePublicKey( + @NonNull String alias, + @NonNull @KeyProperties.KeyAlgorithmEnum String keyAlgorithm, + @NonNull byte[] x509EncodedForm) { + PublicKey publicKey; + try { + KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm); + publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedForm)); + } catch (NoSuchAlgorithmException e) { + throw new ProviderException( + "Failed to obtain " + keyAlgorithm + " KeyFactory", e); + } catch (InvalidKeySpecException e) { + throw new ProviderException("Invalid X.509 encoding of public key", e); + } + if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { + return new AndroidKeyStoreECPublicKey(alias, (ECPublicKey) publicKey); + } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { + return new AndroidKeyStoreRSAPublicKey(alias, (RSAPublicKey) publicKey); + } else { + throw new ProviderException("Unsupported Android Keystore public key algorithm: " + + keyAlgorithm); + } + } + + @NonNull + public static AndroidKeyStorePrivateKey getAndroidKeyStorePrivateKey( + @NonNull AndroidKeyStorePublicKey publicKey) { + String keyAlgorithm = publicKey.getAlgorithm(); + if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { + return new AndroidKeyStoreECPrivateKey( + publicKey.getAlias(), ((ECKey) publicKey).getParams()); + } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { + return new AndroidKeyStoreRSAPrivateKey( + publicKey.getAlias(), ((RSAKey) publicKey).getModulus()); + } else { + throw new ProviderException("Unsupported Android Keystore public key algorithm: " + + keyAlgorithm); + } + } + + @NonNull + public static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore( + @NonNull KeyStore keyStore, @NonNull String privateKeyAlias) + throws UnrecoverableKeyException { + KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); + int errorCode = keyStore.getKeyCharacteristics( + privateKeyAlias, null, null, keyCharacteristics); + if (errorCode != KeyStore.NO_ERROR) { + throw (UnrecoverableKeyException) + new UnrecoverableKeyException("Failed to obtain information about private key") + .initCause(KeyStore.getKeyStoreException(errorCode)); + } + ExportResult exportResult = keyStore.exportKey( + privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null); + if (exportResult.resultCode != KeyStore.NO_ERROR) { + throw (UnrecoverableKeyException) + new UnrecoverableKeyException("Failed to obtain X.509 form of public key") + .initCause(KeyStore.getKeyStoreException(errorCode)); + } + final byte[] x509EncodedPublicKey = exportResult.exportData; + + int keymasterAlgorithm = keyCharacteristics.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1); + if (keymasterAlgorithm == -1) { + throw new UnrecoverableKeyException("Key algorithm unknown"); + } + + String jcaKeyAlgorithm; + try { + jcaKeyAlgorithm = KeyProperties.KeyAlgorithm.fromKeymasterAsymmetricKeyAlgorithm( + keymasterAlgorithm); + } catch (IllegalArgumentException e) { + throw (UnrecoverableKeyException) + new UnrecoverableKeyException("Failed to load private key") + .initCause(e); + } + + return AndroidKeyStoreProvider.getAndroidKeyStorePublicKey( + privateKeyAlias, jcaKeyAlgorithm, x509EncodedPublicKey); + } + + @NonNull + public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore( + @NonNull KeyStore keyStore, @NonNull String privateKeyAlias) + throws UnrecoverableKeyException { + AndroidKeyStorePublicKey publicKey = + loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias); + AndroidKeyStorePrivateKey privateKey = + AndroidKeyStoreProvider.getAndroidKeyStorePrivateKey(publicKey); + return new KeyPair(publicKey, privateKey); + } + + @NonNull + public static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore( + @NonNull KeyStore keyStore, @NonNull String privateKeyAlias) + throws UnrecoverableKeyException { + KeyPair keyPair = loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias); + return (AndroidKeyStorePrivateKey) keyPair.getPrivate(); + } + + @NonNull + public static AndroidKeyStoreSecretKey loadAndroidKeyStoreSecretKeyFromKeystore( + @NonNull KeyStore keyStore, @NonNull String secretKeyAlias) + throws UnrecoverableKeyException { + KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); + int errorCode = keyStore.getKeyCharacteristics( + secretKeyAlias, null, null, keyCharacteristics); + if (errorCode != KeyStore.NO_ERROR) { + throw (UnrecoverableKeyException) + new UnrecoverableKeyException("Failed to obtain information about key") + .initCause(KeyStore.getKeyStoreException(errorCode)); + } + + int keymasterAlgorithm = keyCharacteristics.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1); + if (keymasterAlgorithm == -1) { + throw new UnrecoverableKeyException("Key algorithm unknown"); + } + + List<Integer> keymasterDigests = + keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST); + int keymasterDigest; + if (keymasterDigests.isEmpty()) { + keymasterDigest = -1; + } else { + // More than one digest can be permitted for this key. Use the first one to form the + // JCA key algorithm name. + keymasterDigest = keymasterDigests.get(0); + } + + @KeyProperties.KeyAlgorithmEnum String keyAlgorithmString; + try { + keyAlgorithmString = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm( + keymasterAlgorithm, keymasterDigest); + } catch (IllegalArgumentException e) { + throw (UnrecoverableKeyException) + new UnrecoverableKeyException("Unsupported secret key type").initCause(e); + } + + return new AndroidKeyStoreSecretKey(secretKeyAlias, keyAlgorithmString); + } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java b/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java index 8133d46..9fea30d 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java +++ b/keystore/java/android/security/keystore/AndroidKeyStorePublicKey.java @@ -17,6 +17,7 @@ package android.security.keystore; import java.security.PublicKey; +import java.util.Arrays; /** * {@link PublicKey} backed by Android Keystore. @@ -41,4 +42,30 @@ public class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implements Publ public byte[] getEncoded() { return ArrayUtils.cloneIfNotEmpty(mEncoded); } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + Arrays.hashCode(mEncoded); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + AndroidKeyStorePublicKey other = (AndroidKeyStorePublicKey) obj; + if (!Arrays.equals(mEncoded, other.mEncoded)) { + return false; + } + return true; + } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java index f70c323..6abdf19 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java @@ -60,6 +60,13 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase } @Override + protected boolean isEncryptingUsingPrivateKeyPermitted() { + // RSA encryption with no padding using private key is is a way to implement raw RSA + // signatures. We have to support this. + return true; + } + + @Override protected void initAlgorithmSpecificParameters() throws InvalidKeyException {} @Override @@ -92,6 +99,11 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase } @Override + protected final int getAdditionalEntropyAmountForFinish() { + return 0; + } + + @Override @NonNull protected KeyStoreCryptoOperationStreamer createMainDataStreamer( KeyStore keyStore, IBinder operationToken) { @@ -135,7 +147,8 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase } @Override - public byte[] doFinal(byte[] input, int inputOffset, int inputLength) + public byte[] doFinal(byte[] input, int inputOffset, int inputLength, + byte[] additionalEntropy) throws KeyStoreException { if (inputLength > 0) { mInputBuffer.write(input, inputOffset, inputLength); @@ -152,10 +165,13 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase paddedInput.length - bufferedInput.length, bufferedInput.length); } else { - // No need to pad input - paddedInput = bufferedInput; + // RI throws BadPaddingException in this scenario. INVALID_ARGUMENT below will + // be translated into BadPaddingException. + throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_ARGUMENT, + "Message size (" + bufferedInput.length + " bytes) must be smaller than" + + " modulus (" + mModulusSizeBytes + " bytes)"); } - return mDelegate.doFinal(paddedInput, 0, paddedInput.length); + return mDelegate.doFinal(paddedInput, 0, paddedInput.length, additionalEntropy); } } } @@ -197,6 +213,11 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase @Override protected final int getAdditionalEntropyAmountForBegin() { + return 0; + } + + @Override + protected final int getAdditionalEntropyAmountForFinish() { return (isEncrypting()) ? getModulusSizeBytes() : 0; } } @@ -351,6 +372,11 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase @Override protected final int getAdditionalEntropyAmountForBegin() { + return 0; + } + + @Override + protected final int getAdditionalEntropyAmountForFinish() { return (isEncrypting()) ? mDigestOutputSizeBytes : 0; } } @@ -412,14 +438,46 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase } if (keystoreKey instanceof PrivateKey) { - if ((opmode != Cipher.DECRYPT_MODE) && (opmode != Cipher.UNWRAP_MODE)) { - throw new InvalidKeyException("Private key cannot be used with opmode: " + opmode - + ". Only DECRYPT_MODE and UNWRAP_MODE supported"); + // Private key + switch (opmode) { + case Cipher.DECRYPT_MODE: + case Cipher.UNWRAP_MODE: + // Permitted + break; + case Cipher.ENCRYPT_MODE: + if (!isEncryptingUsingPrivateKeyPermitted()) { + throw new InvalidKeyException( + "RSA private keys cannot be used with Cipher.ENCRYPT_MODE" + + ". Only RSA public keys supported for this mode"); + } + // JCA doesn't provide a way to generate raw RSA signatures (with arbitrary + // padding). Thus, encrypting with private key is used instead. + setKeymasterPurposeOverride(KeymasterDefs.KM_PURPOSE_SIGN); + break; + case Cipher.WRAP_MODE: + throw new InvalidKeyException( + "RSA private keys cannot be used with Cipher.WRAP_MODE" + + ". Only RSA public keys supported for this mode"); + // break; + default: + throw new InvalidKeyException( + "RSA private keys cannot be used with opmode: " + opmode); } } else { - if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.WRAP_MODE)) { - throw new InvalidKeyException("Public key cannot be used with opmode: " + opmode - + ". Only ENCRYPT_MODE and WRAP_MODE supported"); + // Public key + switch (opmode) { + case Cipher.ENCRYPT_MODE: + case Cipher.WRAP_MODE: + // Permitted + break; + case Cipher.DECRYPT_MODE: + case Cipher.UNWRAP_MODE: + throw new InvalidKeyException("RSA public keys cannot be used with opmode: " + + opmode + ". Only RSA private keys supported for this opmode."); + // break; + default: + throw new InvalidKeyException( + "RSA public keys cannot be used with opmode: " + opmode); } } @@ -438,6 +496,10 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase setKey(keystoreKey); } + protected boolean isEncryptingUsingPrivateKeyPermitted() { + return false; + } + @Override protected final void resetAll() { mModulusSizeBytes = -1; @@ -454,6 +516,13 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase @NonNull KeymasterArguments keymasterArgs) { keymasterArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA); keymasterArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding); + int purposeOverride = getKeymasterPurposeOverride(); + if ((purposeOverride != -1) + && ((purposeOverride == KeymasterDefs.KM_PURPOSE_SIGN) + || (purposeOverride == KeymasterDefs.KM_PURPOSE_VERIFY))) { + // Keymaster sign/verify requires digest to be specified. For raw sign/verify it's NONE. + keymasterArgs.addInt(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE); + } } @Override diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSAPrivateKey.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSAPrivateKey.java new file mode 100644 index 0000000..179ffd8 --- /dev/null +++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSAPrivateKey.java @@ -0,0 +1,41 @@ +/* + * 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 java.math.BigInteger; +import java.security.PrivateKey; +import java.security.interfaces.RSAKey; + +/** + * RSA private key (instance of {@link PrivateKey} and {@link RSAKey}) backed by keystore. + * + * @hide + */ +public class AndroidKeyStoreRSAPrivateKey extends AndroidKeyStorePrivateKey implements RSAKey { + + private final BigInteger mModulus; + + public AndroidKeyStoreRSAPrivateKey(String alias, BigInteger modulus) { + super(alias, KeyProperties.KEY_ALGORITHM_RSA); + mModulus = modulus; + } + + @Override + public BigInteger getModulus() { + return mModulus; + } +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSASignatureSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSASignatureSpi.java index 898336d..954b71a 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreRSASignatureSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSASignatureSpi.java @@ -36,7 +36,7 @@ abstract class AndroidKeyStoreRSASignatureSpi extends AndroidKeyStoreSignatureSp } @Override - protected final int getAdditionalEntropyAmountForBegin() { + protected final int getAdditionalEntropyAmountForSign() { // No entropy required for this deterministic signature scheme. return 0; } @@ -92,8 +92,8 @@ abstract class AndroidKeyStoreRSASignatureSpi extends AndroidKeyStoreSignatureSp } @Override - protected final int getAdditionalEntropyAmountForBegin() { - return (isSigning()) ? SALT_LENGTH_BYTES : 0; + protected final int getAdditionalEntropyAmountForSign() { + return SALT_LENGTH_BYTES; } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSignatureSpiBase.java b/keystore/java/android/security/keystore/AndroidKeyStoreSignatureSpiBase.java index 4c4062f..5cdcc41 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSignatureSpiBase.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSignatureSpiBase.java @@ -25,7 +25,7 @@ import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; import android.security.keymaster.OperationResult; -import com.android.org.conscrypt.util.EmptyArray; +import libcore.util.EmptyArray; import java.nio.ByteBuffer; import java.security.InvalidKeyException; @@ -198,15 +198,14 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi KeymasterArguments keymasterInputArgs = new KeymasterArguments(); addAlgorithmSpecificParametersToBegin(keymasterInputArgs); - byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( - appRandom, getAdditionalEntropyAmountForBegin()); OperationResult opResult = mKeyStore.begin( mKey.getAlias(), mSigning ? KeymasterDefs.KM_PURPOSE_SIGN : KeymasterDefs.KM_PURPOSE_VERIFY, true, // permit aborting this operation if keystore runs out of resources keymasterInputArgs, - additionalEntropy); + null // no additional entropy for begin -- only finish might need some + ); if (opResult == null) { throw new KeyStoreConnectException(); } @@ -311,7 +310,11 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi byte[] signature; try { ensureKeystoreOperationInitialized(); - signature = mMessageStreamer.doFinal(EmptyArray.BYTE, 0, 0); + + byte[] additionalEntropy = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + appRandom, getAdditionalEntropyAmountForSign()); + signature = mMessageStreamer.doFinal(EmptyArray.BYTE, 0, 0, additionalEntropy); } catch (InvalidKeyException | KeyStoreException e) { throw new SignatureException(e); } @@ -388,15 +391,14 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi /** * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's - * {@code begin} operation. + * {@code finish} operation when generating a signature. * - * <p>For signature verification, this should be {@code 0} because verification should not be - * consuming any entropy. For signature generation, this value should match (or exceed) the - * amount of Shannon entropy of the produced signature assuming the key and the message are - * known. For example, for ECDSA signature this should be the size of {@code R}, whereas for the - * RSA signature with PKCS#1 padding this should be {@code 0}. + * <p>This value should match (or exceed) the amount of Shannon entropy of the produced + * signature assuming the key and the message are known. For example, for ECDSA signature this + * should be the size of {@code R}, whereas for the RSA signature with PKCS#1 padding this + * should be {@code 0}. */ - protected abstract int getAdditionalEntropyAmountForBegin(); + protected abstract int getAdditionalEntropyAmountForSign(); /** * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation. diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java index 05ddef6..3bd9d1d 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java @@ -16,12 +16,10 @@ 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.KeyStore; import android.security.KeyStoreParameter; import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterArguments; @@ -34,17 +32,16 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyStore.Entry; import java.security.KeyStore.PrivateKeyEntry; import java.security.KeyStore.ProtectionParameter; -import java.security.KeyStore; import java.security.KeyStore.SecretKeyEntry; import java.security.KeyStoreException; import java.security.KeyStoreSpi; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; +import java.security.PublicKey; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; @@ -59,7 +56,6 @@ import java.util.Date; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; -import java.util.List; import java.util.Set; import javax.crypto.SecretKey; @@ -86,66 +82,23 @@ import javax.crypto.SecretKey; public class AndroidKeyStoreSpi extends KeyStoreSpi { public static final String NAME = "AndroidKeyStore"; - private android.security.KeyStore mKeyStore; + private KeyStore mKeyStore; @Override public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException { if (isPrivateKeyEntry(alias)) { - final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore"); - try { - return engine.getPrivateKeyById(Credentials.USER_PRIVATE_KEY + alias); - } catch (InvalidKeyException e) { - UnrecoverableKeyException t = new UnrecoverableKeyException("Can't get key"); - t.initCause(e); - throw t; - } + String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; + return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore( + mKeyStore, privateKeyAlias); } else if (isSecretKeyEntry(alias)) { - KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); - String keyAliasInKeystore = Credentials.USER_SECRET_KEY + alias; - int errorCode = mKeyStore.getKeyCharacteristics( - keyAliasInKeystore, null, null, keyCharacteristics); - if ((errorCode != KeymasterDefs.KM_ERROR_OK) - && (errorCode != android.security.KeyStore.NO_ERROR)) { - throw (UnrecoverableKeyException) - new UnrecoverableKeyException("Failed to load information about key") - .initCause(mKeyStore.getInvalidKeyException(alias, errorCode)); - } - - int keymasterAlgorithm = - keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1); - if (keymasterAlgorithm == -1) { - keymasterAlgorithm = - keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1); - } - if (keymasterAlgorithm == -1) { - throw new UnrecoverableKeyException("Key algorithm unknown"); - } - - List<Integer> keymasterDigests = - keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST); - int keymasterDigest; - if (keymasterDigests.isEmpty()) { - keymasterDigest = -1; - } else { - // More than one digest can be permitted for this key. Use the first one to form the - // JCA key algorithm name. - keymasterDigest = keymasterDigests.get(0); - } - - @KeyProperties.KeyAlgorithmEnum String keyAlgorithmString; - try { - keyAlgorithmString = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm( - keymasterAlgorithm, keymasterDigest); - } catch (IllegalArgumentException e) { - throw (UnrecoverableKeyException) - new UnrecoverableKeyException("Unsupported secret key type").initCause(e); - } - - return new AndroidKeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmString); + String secretKeyAlias = Credentials.USER_SECRET_KEY + alias; + return AndroidKeyStoreProvider.loadAndroidKeyStoreSecretKeyFromKeystore( + mKeyStore, secretKeyAlias); + } else { + // Key not found + return null; } - - return null; } @Override @@ -189,22 +142,36 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { byte[] certificate = mKeyStore.get(Credentials.USER_CERTIFICATE + alias); if (certificate != null) { - return toCertificate(certificate); + return wrapIntoKeyStoreCertificate( + Credentials.USER_PRIVATE_KEY + alias, toCertificate(certificate)); } certificate = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); if (certificate != null) { - return toCertificate(certificate); + return wrapIntoKeyStoreCertificate( + Credentials.USER_PRIVATE_KEY + alias, toCertificate(certificate)); } return null; } + /** + * Wraps the provided cerificate into {@link KeyStoreX509Certificate} so that the public key + * returned by the certificate contains information about the alias of the private key in + * keystore. This is needed so that Android Keystore crypto operations using public keys can + * find out which key alias to use. These operations cannot work without an alias. + */ + private static KeyStoreX509Certificate wrapIntoKeyStoreCertificate( + String privateKeyAlias, X509Certificate certificate) { + return (certificate != null) + ? new KeyStoreX509Certificate(privateKeyAlias, certificate) : null; + } + private static X509Certificate toCertificate(byte[] bytes) { try { final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - return (X509Certificate) certFactory - .generateCertificate(new ByteArrayInputStream(bytes)); + return (X509Certificate) certFactory.generateCertificate( + new ByteArrayInputStream(bytes)); } catch (CertificateException e) { Log.w(NAME, "Couldn't parse certificate in keystore", e); return null; @@ -215,8 +182,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { private static Collection<X509Certificate> toCertificates(byte[] bytes) { try { final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - return (Collection<X509Certificate>) certFactory - .generateCertificates(new ByteArrayInputStream(bytes)); + return (Collection<X509Certificate>) certFactory.generateCertificates( + new ByteArrayInputStream(bytes)); } catch (CertificateException e) { Log.w(NAME, "Couldn't parse certificates in keystore", e); return new ArrayList<X509Certificate>(); @@ -272,107 +239,60 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } } + private static KeyProtection getLegacyKeyProtectionParameter(PrivateKey key) + throws KeyStoreException { + String keyAlgorithm = key.getAlgorithm(); + KeyProtection.Builder specBuilder; + if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { + specBuilder = + new KeyProtection.Builder( + KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY); + // Authorized to be used with any digest (including no digest). + specBuilder.setDigests(KeyProperties.DIGEST_NONE); + } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { + specBuilder = + new KeyProtection.Builder( + KeyProperties.PURPOSE_ENCRYPT + | KeyProperties.PURPOSE_DECRYPT + | KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + // Authorized to be used with any digest (including no digest). + specBuilder.setDigests(KeyProperties.DIGEST_NONE); + specBuilder.setSignaturePaddings( + KeyProperties.SIGNATURE_PADDING_RSA_PKCS1); + // Authorized to be used with any padding (including no padding). + specBuilder.setEncryptionPaddings( + KeyProperties.ENCRYPTION_PADDING_NONE); + // Disable randomized encryption requirement to support encryption padding NONE + // above. + specBuilder.setRandomizedEncryptionRequired(false); + } else { + throw new KeyStoreException("Unsupported key algorithm: " + keyAlgorithm); + } + specBuilder.setUserAuthenticationRequired(false); + + return specBuilder.build(); + } + private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain, java.security.KeyStore.ProtectionParameter param) throws KeyStoreException { int flags = 0; KeyProtection spec; - if (param instanceof KeyStoreParameter) { + if (param == null) { + spec = getLegacyKeyProtectionParameter(key); + } else if (param instanceof KeyStoreParameter) { + spec = getLegacyKeyProtectionParameter(key); 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); - } - if (legacySpec.isEncryptionRequired()) { - flags = android.security.KeyStore.FLAG_ENCRYPTED; - } - specBuilder.setUserAuthenticationRequired(false); - - spec = specBuilder.build(); - } catch (NullPointerException | IllegalArgumentException e) { - throw new KeyStoreException("Unsupported protection parameter", e); + if (legacySpec.isEncryptionRequired()) { + flags = KeyStore.FLAG_ENCRYPTED; } } else if (param instanceof KeyProtection) { spec = (KeyProtection) param; - } else if (param != null) { + } else { 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; - if (key instanceof OpenSSLKeyHolder) { - pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias(); - } else { - pkeyAlias = null; - } - - final boolean shouldReplacePrivateKey; - if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) { - final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length()); - if (!alias.equals(keySubalias)) { - throw new KeyStoreException("Can only replace keys with same alias: " + alias - + " != " + keySubalias); - } - - shouldReplacePrivateKey = false; - } else { - // Make sure the PrivateKey format is the one we support. - final String keyFormat = key.getFormat(); - if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) { - throw new KeyStoreException( - "Only PrivateKeys that can be encoded into PKCS#8 are supported"); - } - - // Make sure we can actually encode the key. - keyBytes = key.getEncoded(); - if (keyBytes == null) { - throw new KeyStoreException("PrivateKey has no encoding"); - } - - shouldReplacePrivateKey = true; + + ". Supported: " + KeyProtection.class.getName() + ", " + + KeyStoreParameter.class.getName()); } // Make sure the chain exists since this is a PrivateKey @@ -400,7 +320,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { try { userCertBytes = x509chain[0].getEncoded(); } catch (CertificateEncodingException e) { - throw new KeyStoreException("Couldn't encode certificate #1", e); + throw new KeyStoreException("Failed to encode certificate #0", e); } /* @@ -421,7 +341,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { certsBytes[i] = x509chain[i + 1].getEncoded(); totalCertLength += certsBytes[i].length; } catch (CertificateEncodingException e) { - throw new KeyStoreException("Can't encode Certificate #" + i, e); + throw new KeyStoreException("Failed to encode certificate #" + i, e); } } @@ -441,31 +361,148 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { chainBytes = null; } - /* - * Make sure we clear out all the appropriate types before trying to - * write. - */ - if (shouldReplacePrivateKey) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); + final String pkeyAlias; + if (key instanceof AndroidKeyStorePrivateKey) { + pkeyAlias = ((AndroidKeyStoreKey) key).getAlias(); + } else { + pkeyAlias = null; + } + + byte[] pkcs8EncodedPrivateKeyBytes; + KeymasterArguments importArgs; + final boolean shouldReplacePrivateKey; + if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) { + final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length()); + if (!alias.equals(keySubalias)) { + throw new KeyStoreException("Can only replace keys with same alias: " + alias + + " != " + keySubalias); + } + shouldReplacePrivateKey = false; + importArgs = null; + pkcs8EncodedPrivateKeyBytes = null; } else { - Credentials.deleteCertificateTypesForAlias(mKeyStore, alias); - Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias); - } - - if (shouldReplacePrivateKey - && !mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes, - android.security.KeyStore.UID_SELF, flags)) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); - throw new KeyStoreException("Couldn't put private key in keystore"); - } else if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertBytes, - android.security.KeyStore.UID_SELF, flags)) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); - throw new KeyStoreException("Couldn't put certificate #1 in keystore"); - } else if (chainBytes != null - && !mKeyStore.put(Credentials.CA_CERTIFICATE + alias, chainBytes, - android.security.KeyStore.UID_SELF, flags)) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); - throw new KeyStoreException("Couldn't put certificate chain in keystore"); + shouldReplacePrivateKey = true; + // Make sure the PrivateKey format is the one we support. + final String keyFormat = key.getFormat(); + if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) { + throw new KeyStoreException( + "Unsupported private key export format: " + keyFormat + + ". Only private keys which export their key material in PKCS#8 format are" + + " supported."); + } + + // Make sure we can actually encode the key. + pkcs8EncodedPrivateKeyBytes = key.getEncoded(); + if (pkcs8EncodedPrivateKeyBytes == null) { + throw new KeyStoreException("Private key did not export any key material"); + } + + importArgs = new KeymasterArguments(); + try { + importArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, + KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm( + key.getAlgorithm())); + @KeyProperties.PurposeEnum int purposes = spec.getPurposes(); + importArgs.addInts(KeymasterDefs.KM_TAG_PURPOSE, + KeyProperties.Purpose.allToKeymaster(purposes)); + if (spec.isDigestsSpecified()) { + importArgs.addInts(KeymasterDefs.KM_TAG_DIGEST, + KeyProperties.Digest.allToKeymaster(spec.getDigests())); + } + + importArgs.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, + KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes())); + int[] keymasterEncryptionPaddings = + KeyProperties.EncryptionPadding.allToKeymaster( + spec.getEncryptionPaddings()); + if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) + && (spec.isRandomizedEncryptionRequired())) { + for (int keymasterPadding : keymasterEncryptionPaddings) { + if (!KeymasterUtils + .isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto( + keymasterPadding)) { + throw new KeyStoreException( + "Randomized encryption (IND-CPA) required but is violated by" + + " encryption padding mode: " + + KeyProperties.EncryptionPadding.fromKeymaster( + keymasterPadding) + + ". See KeyProtection documentation."); + } + } + } + importArgs.addInts(KeymasterDefs.KM_TAG_PADDING, keymasterEncryptionPaddings); + importArgs.addInts(KeymasterDefs.KM_TAG_PADDING, + KeyProperties.SignaturePadding.allToKeymaster(spec.getSignaturePaddings())); + KeymasterUtils.addUserAuthArgs(importArgs, + spec.isUserAuthenticationRequired(), + spec.getUserAuthenticationValidityDurationSeconds()); + importArgs.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, + (spec.getKeyValidityStart() != null) + ? spec.getKeyValidityStart() : new Date(0)); + importArgs.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + (spec.getKeyValidityForOriginationEnd() != null) + ? spec.getKeyValidityForOriginationEnd() + : new Date(Long.MAX_VALUE)); + importArgs.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + (spec.getKeyValidityForConsumptionEnd() != null) + ? spec.getKeyValidityForConsumptionEnd() + : new Date(Long.MAX_VALUE)); + } catch (IllegalArgumentException e) { + throw new KeyStoreException("Invalid parameter", e); + } + } + + + boolean success = false; + try { + // Store the private key, if necessary + if (shouldReplacePrivateKey) { + // Delete the stored private key and any related entries before importing the + // provided key + Credentials.deleteAllTypesForAlias(mKeyStore, alias); + KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); + int errorCode = mKeyStore.importKey( + Credentials.USER_PRIVATE_KEY + alias, + importArgs, + KeymasterDefs.KM_KEY_FORMAT_PKCS8, + pkcs8EncodedPrivateKeyBytes, + flags, + resultingKeyCharacteristics); + if (errorCode != KeyStore.NO_ERROR) { + throw new KeyStoreException("Failed to store private key", + KeyStore.getKeyStoreException(errorCode)); + } + } else { + // Keep the stored private key around -- delete all other entry types + Credentials.deleteCertificateTypesForAlias(mKeyStore, alias); + Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias); + } + + // Store the leaf certificate + int errorCode = mKeyStore.insert(Credentials.USER_CERTIFICATE + alias, userCertBytes, + KeyStore.UID_SELF, flags); + if (errorCode != KeyStore.NO_ERROR) { + throw new KeyStoreException("Failed to store certificate #0", + KeyStore.getKeyStoreException(errorCode)); + } + + // Store the certificate chain + errorCode = mKeyStore.insert(Credentials.CA_CERTIFICATE + alias, chainBytes, + KeyStore.UID_SELF, flags); + if (errorCode != KeyStore.NO_ERROR) { + throw new KeyStoreException("Failed to store certificate chain", + KeyStore.getKeyStoreException(errorCode)); + } + success = true; + } finally { + if (!success) { + if (shouldReplacePrivateKey) { + Credentials.deleteAllTypesForAlias(mKeyStore, alias); + } else { + Credentials.deleteCertificateTypesForAlias(mKeyStore, alias); + Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias); + } + } } } @@ -589,7 +626,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0) && (params.isRandomizedEncryptionRequired())) { for (int keymasterBlockMode : keymasterBlockModes) { - if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible(keymasterBlockMode)) { + if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto( + keymasterBlockMode)) { throw new KeyStoreException( "Randomized encryption (IND-CPA) required but may be violated by block" + " mode: " @@ -598,9 +636,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } } } - for (int keymasterPurpose : KeyProperties.Purpose.allToKeymaster(purposes)) { - args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose); - } + args.addInts(KeymasterDefs.KM_TAG_PURPOSE, KeyProperties.Purpose.allToKeymaster(purposes)); args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes); if (params.getSignaturePaddings().length > 0) { throw new KeyStoreException("Signature paddings not supported for symmetric keys"); @@ -636,7 +672,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { keyMaterial, 0, // flags new KeyCharacteristics()); - if (errorCode != android.security.KeyStore.NO_ERROR) { + if (errorCode != KeyStore.NO_ERROR) { throw new KeyStoreException("Failed to import secret key. Keystore error code: " + errorCode); } @@ -667,7 +703,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded, - android.security.KeyStore.UID_SELF, android.security.KeyStore.FLAG_NONE)) { + KeyStore.UID_SELF, KeyStore.FLAG_NONE)) { throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?"); } } @@ -685,7 +721,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } private Set<String> getUniqueAliases() { - final String[] rawAliases = mKeyStore.saw(""); + final String[] rawAliases = mKeyStore.list(""); if (rawAliases == null) { return new HashSet<String>(); } @@ -769,6 +805,19 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { if (cert == null) { return null; } + if (!"X.509".equalsIgnoreCase(cert.getType())) { + // Only X.509 certificates supported + return null; + } + byte[] targetCertBytes; + try { + targetCertBytes = cert.getEncoded(); + } catch (CertificateEncodingException e) { + return null; + } + if (targetCertBytes == null) { + return null; + } final Set<String> nonCaEntries = new HashSet<String>(); @@ -778,7 +827,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { * equivalent to the USER_CERTIFICATE prefix for the Android keystore * convention. */ - final String[] certAliases = mKeyStore.saw(Credentials.USER_CERTIFICATE); + final String[] certAliases = mKeyStore.list(Credentials.USER_CERTIFICATE); if (certAliases != null) { for (String alias : certAliases) { final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias); @@ -786,10 +835,9 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { continue; } - final Certificate c = toCertificate(certBytes); nonCaEntries.add(alias); - if (cert.equals(c)) { + if (Arrays.equals(certBytes, targetCertBytes)) { return alias; } } @@ -799,7 +847,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { * Look at all the TrustedCertificateEntry types. Skip all the * PrivateKeyEntry we looked at above. */ - final String[] caAliases = mKeyStore.saw(Credentials.CA_CERTIFICATE); + final String[] caAliases = mKeyStore.list(Credentials.CA_CERTIFICATE); if (certAliases != null) { for (String alias : caAliases) { if (nonCaEntries.contains(alias)) { @@ -811,9 +859,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { continue; } - final Certificate c = - toCertificate(mKeyStore.get(Credentials.CA_CERTIFICATE + alias)); - if (cert.equals(c)) { + if (Arrays.equals(certBytes, targetCertBytes)) { return alias; } } @@ -840,7 +886,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } // Unfortunate name collision. - mKeyStore = android.security.KeyStore.getInstance(); + mKeyStore = KeyStore.getInstance(); } @Override @@ -852,8 +898,9 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { Credentials.deleteAllTypesForAlias(mKeyStore, alias); - if (entry instanceof KeyStore.TrustedCertificateEntry) { - KeyStore.TrustedCertificateEntry trE = (KeyStore.TrustedCertificateEntry) entry; + if (entry instanceof java.security.KeyStore.TrustedCertificateEntry) { + java.security.KeyStore.TrustedCertificateEntry trE = + (java.security.KeyStore.TrustedCertificateEntry) entry; engineSetCertificateEntry(alias, trE.getTrustedCertificate()); return; } @@ -871,4 +918,25 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } } + /** + * {@link X509Certificate} which returns {@link AndroidKeyStorePublicKey} from + * {@link #getPublicKey()}. This is so that crypto operations on these public keys contain + * can find out which keystore private key entry to use. This is needed so that Android Keystore + * crypto operations using public keys can find out which key alias to use. These operations + * require an alias. + */ + static class KeyStoreX509Certificate extends DelegatingX509Certificate { + private final String mPrivateKeyAlias; + KeyStoreX509Certificate(String privateKeyAlias, X509Certificate delegate) { + super(delegate); + mPrivateKeyAlias = privateKeyAlias; + } + + @Override + public PublicKey getPublicKey() { + PublicKey original = super.getPublicKey(); + return AndroidKeyStoreProvider.getAndroidKeyStorePublicKey( + mPrivateKeyAlias, original.getAlgorithm(), original.getEncoded()); + } + } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java index 47cd1d1..76804a9 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java @@ -210,6 +210,11 @@ class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSp } @Override + protected final int getAdditionalEntropyAmountForFinish() { + return 0; + } + + @Override protected final void addAlgorithmSpecificParametersToBegin( @NonNull KeymasterArguments keymasterArgs) { if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) { diff --git a/keystore/java/android/security/keystore/DelegatingX509Certificate.java b/keystore/java/android/security/keystore/DelegatingX509Certificate.java new file mode 100644 index 0000000..03d202f --- /dev/null +++ b/keystore/java/android/security/keystore/DelegatingX509Certificate.java @@ -0,0 +1,212 @@ +/* + * 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 java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Principal; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import javax.security.auth.x500.X500Principal; + +class DelegatingX509Certificate extends X509Certificate { + private final X509Certificate mDelegate; + + DelegatingX509Certificate(X509Certificate delegate) { + mDelegate = delegate; + } + + @Override + public Set<String> getCriticalExtensionOIDs() { + return mDelegate.getCriticalExtensionOIDs(); + } + + @Override + public byte[] getExtensionValue(String oid) { + return mDelegate.getExtensionValue(oid); + } + + @Override + public Set<String> getNonCriticalExtensionOIDs() { + return mDelegate.getNonCriticalExtensionOIDs(); + } + + @Override + public boolean hasUnsupportedCriticalExtension() { + return mDelegate.hasUnsupportedCriticalExtension(); + } + + @Override + public void checkValidity() throws CertificateExpiredException, + CertificateNotYetValidException { + mDelegate.checkValidity(); + } + + @Override + public void checkValidity(Date date) throws CertificateExpiredException, + CertificateNotYetValidException { + mDelegate.checkValidity(date); + } + + @Override + public int getBasicConstraints() { + return mDelegate.getBasicConstraints(); + } + + @Override + public Principal getIssuerDN() { + return mDelegate.getIssuerDN(); + } + + @Override + public boolean[] getIssuerUniqueID() { + return mDelegate.getIssuerUniqueID(); + } + + @Override + public boolean[] getKeyUsage() { + return mDelegate.getKeyUsage(); + } + + @Override + public Date getNotAfter() { + return mDelegate.getNotAfter(); + } + + @Override + public Date getNotBefore() { + return mDelegate.getNotBefore(); + } + + @Override + public BigInteger getSerialNumber() { + return mDelegate.getSerialNumber(); + } + + @Override + public String getSigAlgName() { + return mDelegate.getSigAlgName(); + } + + @Override + public String getSigAlgOID() { + return mDelegate.getSigAlgOID(); + } + + @Override + public byte[] getSigAlgParams() { + return mDelegate.getSigAlgParams(); + } + + @Override + public byte[] getSignature() { + return mDelegate.getSignature(); + } + + @Override + public Principal getSubjectDN() { + return mDelegate.getSubjectDN(); + } + + @Override + public boolean[] getSubjectUniqueID() { + return mDelegate.getSubjectUniqueID(); + } + + @Override + public byte[] getTBSCertificate() throws CertificateEncodingException { + return mDelegate.getTBSCertificate(); + } + + @Override + public int getVersion() { + return mDelegate.getVersion(); + } + + @Override + public byte[] getEncoded() throws CertificateEncodingException { + return mDelegate.getEncoded(); + } + + @Override + public PublicKey getPublicKey() { + return mDelegate.getPublicKey(); + } + + @Override + public String toString() { + return mDelegate.toString(); + } + + @Override + public void verify(PublicKey key) + throws CertificateException, + NoSuchAlgorithmException, + InvalidKeyException, + NoSuchProviderException, + SignatureException { + mDelegate.verify(key); + } + + @Override + public void verify(PublicKey key, String sigProvider) + throws CertificateException, + NoSuchAlgorithmException, + InvalidKeyException, + NoSuchProviderException, + SignatureException { + mDelegate.verify(key, sigProvider); + } + + @Override + public List<String> getExtendedKeyUsage() throws CertificateParsingException { + return mDelegate.getExtendedKeyUsage(); + } + + @Override + public Collection<List<?>> getIssuerAlternativeNames() throws CertificateParsingException { + return mDelegate.getIssuerAlternativeNames(); + } + + @Override + public X500Principal getIssuerX500Principal() { + return mDelegate.getIssuerX500Principal(); + } + + @Override + public Collection<List<?>> getSubjectAlternativeNames() throws CertificateParsingException { + return mDelegate.getSubjectAlternativeNames(); + } + + @Override + public X500Principal getSubjectX500Principal() { + return mDelegate.getSubjectX500Principal(); + } +} diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index d861302..4c0631f 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -59,6 +59,14 @@ import javax.security.auth.x500.X500Principal; * 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: If a private key is not authorized to sign the self-signed certificate, then the + * certificate will be created with an invalid signature which will not verify. Such a certificate + * is still useful because it provides access to the public key. To generate a valid + * signature for the certificate the key needs to be authorized for + * {@link KeyProperties#PURPOSE_SIGN}, a suitable digest or {@link KeyProperties#DIGEST_NONE}, and + * {@link KeyProperties#SIGNATURE_PADDING_RSA_PKCS1} or + * {@link KeyProperties#ENCRYPTION_PADDING_NONE}. + * * <p>NOTE: The key material of the generated symmetric and private keys is not accessible. The key * material of the public keys is accessible. * @@ -214,7 +222,9 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { } /** - * Returns the requested key size or {@code -1} if default size should be used. + * Returns the requested key size. If {@code -1}, the size should be looked up from + * {@link #getAlgorithmParameterSpec()}, if provided, otherwise an algorithm-specific default + * size should be used. */ public int getKeySize() { return mKeySize; @@ -447,9 +457,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * 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) { @@ -465,7 +472,10 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * 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. + * <p>The default key size is specific to each key algorithm. If key size is not set + * via this method, it should be looked up from the algorithm-specific parameters (if any) + * provided via + * {@link #setAlgorithmParameterSpec(AlgorithmParameterSpec) setAlgorithmParameterSpec}. */ @NonNull public Builder setKeySize(int keySize) { @@ -551,8 +561,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * * <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 @@ -566,8 +574,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * * <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) @@ -584,8 +590,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * * <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 @@ -600,8 +604,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * * <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 @@ -617,11 +619,14 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * * <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} + * {@code SHA-256} for key algorithm {@code HmacSHA256}). * - * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> + * <p>For private keys used for TLS/SSL client or server authentication it is usually + * necessary to authorize the use of no digest ({@link KeyProperties#DIGEST_NONE}). This is + * because TLS/SSL stacks typically generate the necessary digest(s) themselves and then use + * a private key to sign it. * - * @see KeyProperties.Digest + * <p>See {@link KeyProperties}.{@code DIGEST} constants. */ @NonNull public Builder setDigests(@KeyProperties.DigestEnum String... digests) { @@ -637,7 +642,11 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * * <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>For RSA private keys used by TLS/SSL servers to authenticate themselves to clients it + * is usually necessary to authorize the use of no/any padding + * ({@link KeyProperties#ENCRYPTION_PADDING_NONE}). This is because RSA decryption is + * required by some cipher suites, and some stacks request decryption using no padding + * whereas others request PKCS#1 padding. * * <p>See {@link KeyProperties}.{@code ENCRYPTION_PADDING} constants. */ @@ -655,8 +664,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * * <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 @@ -673,8 +680,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * * <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 @@ -718,8 +723,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * <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) { @@ -743,8 +746,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * <p>This restriction applies only to private key operations. Public key operations are not * restricted. * - * <p><b>NOTE: This has currently no effect on asymmetric key pairs.</b> - * * @see #setUserAuthenticationValidityDurationSeconds(int) */ @NonNull @@ -759,8 +760,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * * <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. * diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index e3c2d1d..f9fe176 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -168,6 +168,31 @@ public abstract class KeyProperties { public static abstract class KeyAlgorithm { private KeyAlgorithm() {} + public static int toKeymasterAsymmetricKeyAlgorithm( + @NonNull @KeyAlgorithmEnum String algorithm) { + if (KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) { + return KeymasterDefs.KM_ALGORITHM_EC; + } else if (KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) { + return KeymasterDefs.KM_ALGORITHM_RSA; + } else { + throw new IllegalArgumentException("Unsupported key algorithm: " + algorithm); + } + } + + @NonNull + public static @KeyAlgorithmEnum String fromKeymasterAsymmetricKeyAlgorithm( + int keymasterAlgorithm) { + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + return KEY_ALGORITHM_EC; + case KeymasterDefs.KM_ALGORITHM_RSA: + return KEY_ALGORITHM_RSA; + default: + throw new IllegalArgumentException( + "Unsupported key algorithm: " + keymasterAlgorithm); + } + } + public static int toKeymasterSecretKeyAlgorithm( @NonNull @KeyAlgorithmEnum String algorithm) { if (KEY_ALGORITHM_AES.equalsIgnoreCase(algorithm)) { @@ -343,6 +368,9 @@ public abstract class KeyProperties { /** * No encryption padding. + * + * <p><b>NOTE</b>: If a key is authorized to be used with no padding, then it can be used with + * any padding scheme, both for encryption and signing. */ public static final String ENCRYPTION_PADDING_NONE = "NoPadding"; @@ -489,6 +517,9 @@ public abstract class KeyProperties { /** * No digest: sign/authenticate the raw message. + * + * <p><b>NOTE</b>: If a key is authorized to be used with no digest, then it can be used with + * any digest. */ public static final String DIGEST_NONE = "NONE"; @@ -572,6 +603,28 @@ public abstract class KeyProperties { } @NonNull + public static @DigestEnum String fromKeymasterToSignatureAlgorithmDigest(int digest) { + switch (digest) { + case KeymasterDefs.KM_DIGEST_NONE: + return "NONE"; + case KeymasterDefs.KM_DIGEST_MD5: + return "MD5"; + case KeymasterDefs.KM_DIGEST_SHA1: + return "SHA1"; + case KeymasterDefs.KM_DIGEST_SHA_2_224: + return "SHA224"; + case KeymasterDefs.KM_DIGEST_SHA_2_256: + return "SHA256"; + case KeymasterDefs.KM_DIGEST_SHA_2_384: + return "SHA384"; + case KeymasterDefs.KM_DIGEST_SHA_2_512: + return "SHA512"; + default: + throw new IllegalArgumentException("Unsupported digest algorithm: " + digest); + } + } + + @NonNull public static @DigestEnum String[] allFromKeymaster(@NonNull Collection<Integer> digests) { if (digests.isEmpty()) { return EmptyArray.STRING; diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index f52a193..432fc12 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -305,9 +305,6 @@ public final class KeyProtection implements ProtectionParameter { * @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) { @@ -319,8 +316,6 @@ public final class KeyProtection implements ProtectionParameter { * * <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 @@ -334,8 +329,6 @@ public final class KeyProtection implements ProtectionParameter { * * <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) @@ -352,8 +345,6 @@ public final class KeyProtection implements ProtectionParameter { * * <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 @@ -368,8 +359,6 @@ public final class KeyProtection implements ProtectionParameter { * * <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 @@ -385,7 +374,11 @@ public final class KeyProtection implements ProtectionParameter { * * <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>For RSA private keys used by TLS/SSL servers to authenticate themselves to clients it + * is usually necessary to authorize the use of no/any padding + * ({@link KeyProperties#ENCRYPTION_PADDING_NONE}). This is because RSA decryption is + * required by some cipher suites, and some stacks request decryption using no padding + * whereas others request PKCS#1 padding. * * <p>See {@link KeyProperties}.{@code ENCRYPTION_PADDING} constants. */ @@ -403,8 +396,6 @@ public final class KeyProtection implements ProtectionParameter { * * <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 @@ -423,7 +414,10 @@ public final class KeyProtection implements ProtectionParameter { * {@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>For private keys used for TLS/SSL client or server authentication it is usually + * necessary to authorize the use of no digest ({@link KeyProperties#DIGEST_NONE}). This is + * because TLS/SSL stacks typically generate the necessary digest(s) themselves and then use + * a private key to sign it. * * <p>See {@link KeyProperties}.{@code DIGEST} constants. */ @@ -440,8 +434,6 @@ public final class KeyProtection implements ProtectionParameter { * * <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 @@ -483,8 +475,6 @@ public final class KeyProtection implements ProtectionParameter { * <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) { @@ -505,8 +495,6 @@ public final class KeyProtection implements ProtectionParameter { * <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 @@ -521,8 +509,6 @@ public final class KeyProtection implements ProtectionParameter { * * <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. * diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java index 47b4996..9957e79 100644 --- a/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java +++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java @@ -35,8 +35,8 @@ import java.io.IOException; * amount of data in one go because the operations are marshalled via Binder. Secondly, the update * operation may consume less data than provided, in which case the caller has to buffer the * remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and - * {@link #doFinal(byte[], int, int) doFinal} operations which can be used to conveniently implement - * various JCA crypto primitives. + * {@link #doFinal(byte[], int, int, byte[]) doFinal} operations which can be used to conveniently + * implement various JCA crypto primitives. * * <p>Bidirectional chunked streaming of data via a KeyStore crypto operation is abstracted away as * a {@link Stream} to avoid having this class deal with operation tokens and occasional additional @@ -60,7 +60,7 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS * Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't * be reached. */ - OperationResult finish(); + OperationResult finish(byte[] additionalEntropy); } // Binder buffer is about 1MB, but it's shared between all active transactions of the process. @@ -192,7 +192,7 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS } @Override - public byte[] doFinal(byte[] input, int inputOffset, int inputLength) + public byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] additionalEntropy) throws KeyStoreException { if (inputLength == 0) { // No input provided -- simplify the rest of the code @@ -204,7 +204,7 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS byte[] output = update(input, inputOffset, inputLength); output = ArrayUtils.concat(output, flush()); - OperationResult opResult = mKeyStoreStream.finish(); + OperationResult opResult = mKeyStoreStream.finish(additionalEntropy); if (opResult == null) { throw new KeyStoreConnectException(); } else if (opResult.resultCode != KeyStore.NO_ERROR) { @@ -268,8 +268,8 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS } @Override - public OperationResult finish() { - return mKeyStore.finish(mOperationToken, null, null); + public OperationResult finish(byte[] additionalEntropy) { + return mKeyStore.finish(mOperationToken, null, null, additionalEntropy); } } } diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java index 2fb8f20..1c6de2d 100644 --- a/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java +++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java @@ -28,12 +28,13 @@ import android.security.KeyStoreException; * amount of data in one go because the operations are marshalled via Binder. Secondly, the update * operation may consume less data than provided, in which case the caller has to buffer the * remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and - * {@link #doFinal(byte[], int, int) doFinal} operations which can be used to conveniently implement - * various JCA crypto primitives. + * {@link #doFinal(byte[], int, int, byte[]) doFinal} operations which can be used to conveniently + * implement various JCA crypto primitives. * * @hide */ interface KeyStoreCryptoOperationStreamer { byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException; - byte[] doFinal(byte[] input, int inputOffset, int inputLength) throws KeyStoreException; + byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] additionalEntropy) + throws KeyStoreException; } diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java index e7529e1..4b37d90 100644 --- a/keystore/java/android/security/keystore/KeymasterUtils.java +++ b/keystore/java/android/security/keystore/KeymasterUtils.java @@ -50,7 +50,8 @@ public abstract class KeymasterUtils { } } - public static boolean isKeymasterBlockModeIndCpaCompatible(int keymasterBlockMode) { + public static boolean isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto( + int keymasterBlockMode) { switch (keymasterBlockMode) { case KeymasterDefs.KM_MODE_ECB: return false; @@ -63,6 +64,20 @@ public abstract class KeymasterUtils { } } + public static boolean isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto( + int keymasterPadding) { + switch (keymasterPadding) { + case KeymasterDefs.KM_PAD_NONE: + return false; + case KeymasterDefs.KM_PAD_RSA_OAEP: + case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT: + return true; + default: + throw new IllegalArgumentException( + "Unsupported encryption padding scheme: " + keymasterPadding); + } + } + /** * Adds keymaster arguments to express the key's authorization policy supported by user * authentication. @@ -86,13 +101,10 @@ public abstract class KeymasterUtils { // fingerprint-only auth. FingerprintManager fingerprintManager = KeyStore.getApplicationContext().getSystemService(FingerprintManager.class); - if ((fingerprintManager == null) || (!fingerprintManager.isHardwareDetected())) { - throw new IllegalStateException( - "This device does not support keys which require authentication for every" - + " use -- this requires fingerprint authentication which is not" - + " available on this device"); - } - long fingerprintOnlySid = fingerprintManager.getAuthenticatorId(); + // TODO: Restore USE_FINGERPRINT permission check in + // FingerprintManager.getAuthenticatorId once the ID is no longer needed here. + long fingerprintOnlySid = + (fingerprintManager != null) ? fingerprintManager.getAuthenticatorId() : 0; if (fingerprintOnlySid == 0) { throw new IllegalStateException( "At least one fingerprint must be enrolled to create keys requiring user" diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java index e048ec9..0b60c62 100644 --- a/keystore/tests/src/android/security/KeyStoreTest.java +++ b/keystore/tests/src/android/security/KeyStoreTest.java @@ -20,7 +20,6 @@ import android.app.Activity; import android.os.Binder; import android.os.IBinder; import android.os.Process; -import android.os.ServiceManager; import android.security.keymaster.ExportResult; import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterArguments; @@ -34,13 +33,9 @@ import android.test.suitebuilder.annotation.MediumTest; import com.android.org.conscrypt.NativeConstants; import java.nio.charset.StandardCharsets; import java.util.Arrays; -import java.util.Date; import java.util.HashSet; import java.security.spec.RSAKeyGenParameterSpec; -import android.util.Log; -import android.util.Base64; - /** * Junit / Instrumentation test case for KeyStore class * @@ -276,8 +271,8 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID)); } - public void testSaw() throws Exception { - String[] emptyResult = mKeyStore.saw(TEST_KEYNAME); + public void testList() throws Exception { + String[] emptyResult = mKeyStore.list(TEST_KEYNAME); assertNotNull(emptyResult); assertEquals(0, emptyResult.length); @@ -285,26 +280,26 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED); mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED); - String[] results = mKeyStore.saw(TEST_KEYNAME); + String[] results = mKeyStore.list(TEST_KEYNAME); assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()), TEST_KEYNAME2.substring(TEST_KEYNAME.length()))), new HashSet(Arrays.asList(results))); } - public void testSaw_ungrantedUid_Bluetooth() throws Exception { - String[] results1 = mKeyStore.saw(TEST_KEYNAME, Process.BLUETOOTH_UID); + public void testList_ungrantedUid_Bluetooth() throws Exception { + String[] results1 = mKeyStore.list(TEST_KEYNAME, Process.BLUETOOTH_UID); assertEquals(0, results1.length); mKeyStore.onUserPasswordChanged(TEST_PASSWD); mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED); mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED); - String[] results2 = mKeyStore.saw(TEST_KEYNAME, Process.BLUETOOTH_UID); + String[] results2 = mKeyStore.list(TEST_KEYNAME, Process.BLUETOOTH_UID); assertEquals(0, results2.length); } - public void testSaw_grantedUid_Wifi() throws Exception { - String[] results1 = mKeyStore.saw(TEST_KEYNAME, Process.WIFI_UID); + public void testList_grantedUid_Wifi() throws Exception { + String[] results1 = mKeyStore.list(TEST_KEYNAME, Process.WIFI_UID); assertNotNull(results1); assertEquals(0, results1.length); @@ -312,14 +307,14 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED); mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED); - String[] results2 = mKeyStore.saw(TEST_KEYNAME, Process.WIFI_UID); + String[] results2 = mKeyStore.list(TEST_KEYNAME, Process.WIFI_UID); assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()), TEST_KEYNAME2.substring(TEST_KEYNAME.length()))), new HashSet(Arrays.asList(results2))); } - public void testSaw_grantedUid_Vpn() throws Exception { - String[] results1 = mKeyStore.saw(TEST_KEYNAME, Process.VPN_UID); + public void testList_grantedUid_Vpn() throws Exception { + String[] results1 = mKeyStore.list(TEST_KEYNAME, Process.VPN_UID); assertNotNull(results1); assertEquals(0, results1.length); @@ -327,7 +322,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, Process.VPN_UID, KeyStore.FLAG_ENCRYPTED); mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, Process.VPN_UID, KeyStore.FLAG_ENCRYPTED); - String[] results2 = mKeyStore.saw(TEST_KEYNAME, Process.VPN_UID); + String[] results2 = mKeyStore.list(TEST_KEYNAME, Process.VPN_UID); assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()), TEST_KEYNAME2.substring(TEST_KEYNAME.length()))), new HashSet(Arrays.asList(results2))); diff --git a/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java b/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java index cad4e54..e5c15c5 100644 --- a/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java +++ b/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java @@ -18,22 +18,29 @@ package android.security.keystore; import android.security.Credentials; import android.security.KeyPairGeneratorSpec; +import android.security.KeyStore; +import android.security.keymaster.ExportResult; +import android.security.keymaster.KeymasterDefs; import android.test.AndroidTestCase; import java.io.ByteArrayInputStream; import java.math.BigInteger; import java.security.KeyPair; +import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.security.interfaces.ECKey; import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.RSAKeyGenParameterSpec; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; import javax.security.auth.x500.X500Principal; @@ -78,7 +85,7 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111")); assertTrue(mAndroidKeyStore.isUnlocked()); - String[] aliases = mAndroidKeyStore.saw(""); + String[] aliases = mAndroidKeyStore.list(""); assertNotNull(aliases); assertEquals(0, aliases.length); } @@ -155,6 +162,26 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { } public void testKeyPairGenerator_GenerateKeyPair_EC_Unencrypted_Success() throws Exception { + KeyPairGenerator generator = KeyPairGenerator.getInstance("EC", "AndroidKeyStore"); + generator.initialize(new KeyGenParameterSpec.Builder( + TEST_ALIAS_1, + KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) + .setCertificateSubject(TEST_DN_1) + .setCertificateSerialNumber(TEST_SERIAL_1) + .setCertificateNotBefore(NOW) + .setCertificateNotAfter(NOW_PLUS_10_YEARS) + .setDigests(KeyProperties.DIGEST_SHA256) + .build()); + + final KeyPair pair = generator.generateKeyPair(); + assertNotNull("The KeyPair returned should not be null", pair); + + assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 256, null, TEST_DN_1, TEST_SERIAL_1, NOW, + NOW_PLUS_10_YEARS); + } + + public void testKeyPairGenerator_Legacy_GenerateKeyPair_EC_Unencrypted_Success() + throws Exception { mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext()) .setAlias(TEST_ALIAS_1) .setKeyType("EC") @@ -325,19 +352,40 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { assertNotNull("The PrivateKey for the KeyPair should be not null", privKey); assertEquals(keyType, privKey.getAlgorithm()); + if ("EC".equalsIgnoreCase(keyType)) { + assertTrue("EC private key must be instanceof ECKey: " + privKey.getClass().getName(), + privKey instanceof ECKey); + assertEquals("Private and public key must have the same EC parameters", + ((ECKey) pubKey).getParams(), ((ECKey) privKey).getParams()); + } else if ("RSA".equalsIgnoreCase(keyType)) { + assertTrue("RSA private key must be instance of RSAKey: " + + privKey.getClass().getName(), + privKey instanceof RSAKey); + assertEquals("Private and public key must have the same RSA modulus", + ((RSAKey) pubKey).getModulus(), ((RSAKey) privKey).getModulus()); + } + final byte[] userCertBytes = mAndroidKeyStore.get(Credentials.USER_CERTIFICATE + alias); assertNotNull("The user certificate should exist for the generated entry", userCertBytes); final CertificateFactory cf = CertificateFactory.getInstance("X.509"); - final Certificate userCert = cf - .generateCertificate(new ByteArrayInputStream(userCertBytes)); + final Certificate userCert = + cf.generateCertificate(new ByteArrayInputStream(userCertBytes)); assertTrue("Certificate should be in X.509 format", userCert instanceof X509Certificate); final X509Certificate x509userCert = (X509Certificate) userCert; + assertEquals( + "Public key used to sign certificate should have the same algorithm as in KeyPair", + pubKey.getAlgorithm(), x509userCert.getPublicKey().getAlgorithm()); + assertEquals("PublicKey used to sign certificate should match one returned in KeyPair", - pubKey, x509userCert.getPublicKey()); + pubKey, + AndroidKeyStoreProvider.getAndroidKeyStorePublicKey( + Credentials.USER_PRIVATE_KEY + alias, + x509userCert.getPublicKey().getAlgorithm(), + x509userCert.getPublicKey().getEncoded())); assertEquals("The Subject DN should be the one passed into the params", dn, x509userCert.getSubjectDN()); @@ -354,14 +402,22 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { assertDateEquals("The notAfter date should be the one passed into the params", end, x509userCert.getNotAfter()); + // Assert that the cert's signature verifies using the public key from generated KeyPair x509userCert.verify(pubKey); + // Assert that the cert's signature verifies using the public key from the cert itself. + x509userCert.verify(x509userCert.getPublicKey()); final byte[] caCerts = mAndroidKeyStore.get(Credentials.CA_CERTIFICATE + alias); assertNull("A list of CA certificates should not exist for the generated entry", caCerts); - final byte[] pubKeyBytes = mAndroidKeyStore.getPubkey(Credentials.USER_PRIVATE_KEY + alias); + ExportResult exportResult = mAndroidKeyStore.exportKey( + Credentials.USER_PRIVATE_KEY + alias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null); + assertEquals(KeyStore.NO_ERROR, exportResult.resultCode); + final byte[] pubKeyBytes = exportResult.exportData; assertNotNull("The keystore should return the public key for the generated key", pubKeyBytes); + assertTrue("Public key X.509 format should be as expected", + Arrays.equals(pubKey.getEncoded(), pubKeyBytes)); } private static void assertDateEquals(String message, Date date1, Date date2) throws Exception { diff --git a/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java b/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java index 2d4e4a0..c3b731b 100644 --- a/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java +++ b/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java @@ -19,7 +19,6 @@ package android.security.keystore; import com.android.org.bouncycastle.x509.X509V3CertificateGenerator; import com.android.org.conscrypt.NativeConstants; -import com.android.org.conscrypt.OpenSSLEngine; import android.security.Credentials; import android.security.KeyStore; @@ -30,25 +29,21 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.math.BigInteger; -import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyFactory; +import java.security.KeyPair; import java.security.KeyStore.Entry; import java.security.KeyStore.PrivateKeyEntry; import java.security.KeyStore.TrustedCertificateEntry; import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.interfaces.RSAPrivateKey; -import java.security.spec.InvalidKeySpecException; +import java.security.interfaces.ECKey; +import java.security.interfaces.RSAKey; import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import java.util.Collection; import java.util.Date; @@ -742,7 +737,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111")); assertTrue(mAndroidKeyStore.isUnlocked()); - assertEquals(0, mAndroidKeyStore.saw("").length); + assertEquals(0, mAndroidKeyStore.list("").length); } private void assertAliases(final String[] expectedAliases) throws KeyStoreException { @@ -1201,14 +1196,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase { private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, PrivateKey expectedKey, Certificate expectedCert, Collection<Certificate> expectedChain) throws Exception { - if (expectedKey instanceof ECPrivateKey) { + if (expectedKey instanceof ECKey) { assertEquals("Returned PrivateKey should be what we inserted", - ((ECPrivateKey) expectedKey).getParams().getCurve(), - ((ECPublicKey) keyEntry.getCertificate().getPublicKey()).getParams().getCurve()); - } else if (expectedKey instanceof RSAPrivateKey) { + ((ECKey) expectedKey).getParams().getCurve(), + ((ECKey) keyEntry.getCertificate().getPublicKey()).getParams().getCurve()); + } else if (expectedKey instanceof RSAKey) { assertEquals("Returned PrivateKey should be what we inserted", - ((RSAPrivateKey) expectedKey).getModulus(), - ((RSAPrivateKey) keyEntry.getPrivateKey()).getModulus()); + ((RSAKey) expectedKey).getModulus(), + ((RSAKey) keyEntry.getPrivateKey()).getModulus()); } assertEquals("Returned Certificate should be what we inserted", expectedCert, @@ -1261,15 +1256,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase { Key key = mKeyStore.getKey(TEST_ALIAS_1, null); assertNotNull("Key should exist", key); - assertTrue("Should be a RSAPrivateKey", key instanceof RSAPrivateKey); - - RSAPrivateKey actualKey = (RSAPrivateKey) key; + assertTrue("Should be a PrivateKey", key instanceof PrivateKey); + assertTrue("Should be a RSAKey", key instanceof RSAKey); KeyFactory keyFact = KeyFactory.getInstance("RSA"); PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); assertEquals("Inserted key should be same as retrieved key", - ((RSAPrivateKey) expectedKey).getModulus(), actualKey.getModulus()); + ((RSAKey) expectedKey).getModulus(), ((RSAKey) key).getModulus()); } public void testKeyStore_GetKey_NoPassword_Unencrypted_Success() throws Exception { @@ -1285,15 +1279,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase { Key key = mKeyStore.getKey(TEST_ALIAS_1, null); assertNotNull("Key should exist", key); - assertTrue("Should be a RSAPrivateKey", key instanceof RSAPrivateKey); - - RSAPrivateKey actualKey = (RSAPrivateKey) key; + assertTrue("Should be a PrivateKey", key instanceof PrivateKey); + assertTrue("Should be a RSAKey", key instanceof RSAKey); KeyFactory keyFact = KeyFactory.getInstance("RSA"); PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); assertEquals("Inserted key should be same as retrieved key", - ((RSAPrivateKey) expectedKey).getModulus(), actualKey.getModulus()); + ((RSAKey) expectedKey).getModulus(), ((RSAKey) key).getModulus()); } public void testKeyStore_GetKey_Certificate_Encrypted_Failure() throws Exception { @@ -1924,28 +1917,11 @@ public class AndroidKeyStoreTest extends AndroidTestCase { Date notAfter) throws Exception { final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; - final PrivateKey privKey; - final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore"); - try { - privKey = engine.getPrivateKeyById(privateKeyAlias); - } catch (InvalidKeyException e) { - throw new RuntimeException("Can't get key", e); - } - - final byte[] pubKeyBytes = keyStore.getPubkey(privateKeyAlias); - - final PublicKey pubKey; - try { - final KeyFactory keyFact = KeyFactory.getInstance("RSA"); - pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes)); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("Can't instantiate RSA key generator", e); - } catch (InvalidKeySpecException e) { - throw new IllegalStateException("keystore returned invalid key encoding", e); - } + KeyPair keyPair = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore( + keyStore, privateKeyAlias); final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); - certGen.setPublicKey(pubKey); + certGen.setPublicKey(keyPair.getPublic()); certGen.setSerialNumber(serialNumber); certGen.setSubjectDN(subjectDN); certGen.setIssuerDN(subjectDN); @@ -1953,7 +1929,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { certGen.setNotAfter(notAfter); certGen.setSignatureAlgorithm("sha1WithRSA"); - final X509Certificate cert = certGen.generate(privKey); + final X509Certificate cert = certGen.generate(keyPair.getPrivate()); return cert; } |
