diff options
Diffstat (limited to 'keystore')
32 files changed, 1574 insertions, 696 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 059d8e6..f482bf0 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -29,15 +29,14 @@ import android.os.Looper; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; -import android.security.keystore.KeyInfo; +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.KeyFactory; 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; @@ -47,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; /** @@ -90,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 */ @@ -372,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(); 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 893771a..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 { @@ -710,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/AndroidKeyStoreAuthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java new file mode 100644 index 0000000..f412743 --- /dev/null +++ b/keystore/java/android/security/keystore/AndroidKeyStoreAuthenticatedAESCipherSpi.java @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.security.KeyStore; +import android.security.KeyStoreException; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keymaster.OperationResult; +import android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.Stream; + +import libcore.util.EmptyArray; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.ProviderException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.util.Arrays; + +import javax.crypto.CipherSpi; +import javax.crypto.spec.GCMParameterSpec; + +/** + * Base class for Android Keystore authenticated AES {@link CipherSpi} implementations. + * + * @hide + */ +abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase { + + abstract static class GCM extends AndroidKeyStoreAuthenticatedAESCipherSpi { + private static final int MIN_SUPPORTED_TAG_LENGTH_BITS = 96; + private static final int MAX_SUPPORTED_TAG_LENGTH_BITS = 128; + private static final int DEFAULT_TAG_LENGTH_BITS = 128; + private static final int IV_LENGTH_BYTES = 12; + + private int mTagLengthBits = DEFAULT_TAG_LENGTH_BITS; + + GCM(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_GCM, keymasterPadding); + } + + @Override + protected final void resetAll() { + mTagLengthBits = DEFAULT_TAG_LENGTH_BITS; + super.resetAll(); + } + + @Override + protected final void resetWhilePreservingInitState() { + super.resetWhilePreservingInitState(); + } + + @Override + protected final void initAlgorithmSpecificParameters() throws InvalidKeyException { + if (!isEncrypting()) { + throw new InvalidKeyException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + } + + @Override + protected final void initAlgorithmSpecificParameters(AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + // IV is used + if (params == null) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException( + "GCMParameterSpec must be provided when decrypting"); + } + return; + } + if (!(params instanceof GCMParameterSpec)) { + throw new InvalidAlgorithmParameterException("Only GCMParameterSpec supported"); + } + GCMParameterSpec spec = (GCMParameterSpec) params; + byte[] iv = spec.getIV(); + if (iv == null) { + throw new InvalidAlgorithmParameterException("Null IV in GCMParameterSpec"); + } else if (iv.length != IV_LENGTH_BYTES) { + throw new InvalidAlgorithmParameterException("Unsupported IV length: " + + iv.length + " bytes. Only " + IV_LENGTH_BYTES + + " bytes long IV supported"); + } + int tagLengthBits = spec.getTLen(); + if ((tagLengthBits < MIN_SUPPORTED_TAG_LENGTH_BITS) + || (tagLengthBits > MAX_SUPPORTED_TAG_LENGTH_BITS) + || ((tagLengthBits % 8) != 0)) { + throw new InvalidAlgorithmParameterException( + "Unsupported tag length: " + tagLengthBits + " bits" + + ". Supported lengths: 96, 104, 112, 120, 128"); + } + setIv(iv); + mTagLengthBits = tagLengthBits; + } + + @Override + protected final void initAlgorithmSpecificParameters(AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + if (params == null) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ". Use GCMParameterSpec or GCM AlgorithmParameters to provide it."); + } + return; + } + + GCMParameterSpec spec; + try { + spec = params.getParameterSpec(GCMParameterSpec.class); + } catch (InvalidParameterSpecException e) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV and tag length required when" + + " decrypting, but not found in parameters: " + params, e); + } + setIv(null); + return; + } + initAlgorithmSpecificParameters(spec); + } + + @Nullable + @Override + protected final AlgorithmParameters engineGetParameters() { + byte[] iv = getIv(); + if ((iv != null) && (iv.length > 0)) { + try { + AlgorithmParameters params = AlgorithmParameters.getInstance("GCM"); + params.init(new GCMParameterSpec(mTagLengthBits, iv)); + return params; + } catch (NoSuchAlgorithmException e) { + throw new ProviderException( + "Failed to obtain GCM AlgorithmParameters", e); + } catch (InvalidParameterSpecException e) { + throw new ProviderException( + "Failed to initialize GCM AlgorithmParameters", e); + } + } + return null; + } + + @NonNull + @Override + protected KeyStoreCryptoOperationStreamer createMainDataStreamer( + KeyStore keyStore, IBinder operationToken) { + KeyStoreCryptoOperationStreamer streamer = new KeyStoreCryptoOperationChunkedStreamer( + new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( + keyStore, operationToken)); + if (isEncrypting()) { + return streamer; + } else { + // When decrypting, to avoid leaking unauthenticated plaintext, do not return any + // plaintext before ciphertext is authenticated by KeyStore.finish. + return new BufferAllOutputUntilDoFinalStreamer(streamer); + } + } + + @NonNull + @Override + protected final KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer( + KeyStore keyStore, IBinder operationToken) { + return new KeyStoreCryptoOperationChunkedStreamer( + new AdditionalAuthenticationDataStream(keyStore, operationToken)); + } + + @Override + protected final int getAdditionalEntropyAmountForBegin() { + if ((getIv() == null) && (isEncrypting())) { + // IV will need to be generated + return IV_LENGTH_BYTES; + } + + return 0; + } + + @Override + protected final int getAdditionalEntropyAmountForFinish() { + return 0; + } + + @Override + protected final void addAlgorithmSpecificParametersToBegin( + @NonNull KeymasterArguments keymasterArgs) { + super.addAlgorithmSpecificParametersToBegin(keymasterArgs); + keymasterArgs.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mTagLengthBits); + } + + protected final int getTagLengthBits() { + return mTagLengthBits; + } + + public static final class NoPadding extends GCM { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + + @Override + protected final int engineGetOutputSize(int inputLen) { + int tagLengthBytes = (getTagLengthBits() + 7) / 8; + long result; + if (isEncrypting()) { + result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen + + tagLengthBytes; + } else { + result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen + - tagLengthBytes; + } + if (result < 0) { + return 0; + } else if (result > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + return (int) result; + } + } + } + + private static final int BLOCK_SIZE_BYTES = 16; + + private final int mKeymasterBlockMode; + private final int mKeymasterPadding; + + private byte[] mIv; + + /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */ + private boolean mIvHasBeenUsed; + + AndroidKeyStoreAuthenticatedAESCipherSpi( + int keymasterBlockMode, + int keymasterPadding) { + mKeymasterBlockMode = keymasterBlockMode; + mKeymasterPadding = keymasterPadding; + } + + @Override + protected void resetAll() { + mIv = null; + mIvHasBeenUsed = false; + super.resetAll(); + } + + @Override + protected final void initKey(int opmode, Key key) throws InvalidKeyException { + if (!(key instanceof AndroidKeyStoreSecretKey)) { + throw new InvalidKeyException( + "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null")); + } + if (!KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(key.getAlgorithm())) { + throw new InvalidKeyException( + "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " + + KeyProperties.KEY_ALGORITHM_AES + " supported"); + } + setKey((AndroidKeyStoreSecretKey) key); + } + + @Override + protected void addAlgorithmSpecificParametersToBegin( + @NonNull KeymasterArguments keymasterArgs) { + if ((isEncrypting()) && (mIvHasBeenUsed)) { + // IV is being reused for encryption: this violates security best practices. + throw new IllegalStateException( + "IV has already been used. Reusing IV in encryption mode violates security best" + + " practices."); + } + + keymasterArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES); + keymasterArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode); + keymasterArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding); + if (mIv != null) { + keymasterArgs.addBlob(KeymasterDefs.KM_TAG_NONCE, mIv); + } + } + + @Override + protected final void loadAlgorithmSpecificParametersFromBeginResult( + @NonNull KeymasterArguments keymasterArgs) { + mIvHasBeenUsed = true; + + // NOTE: Keymaster doesn't always return an IV, even if it's used. + byte[] returnedIv = keymasterArgs.getBlob(KeymasterDefs.KM_TAG_NONCE, null); + if ((returnedIv != null) && (returnedIv.length == 0)) { + returnedIv = null; + } + + if (mIv == null) { + mIv = returnedIv; + } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) { + throw new ProviderException("IV in use differs from provided IV"); + } + } + + @Override + protected final int engineGetBlockSize() { + return BLOCK_SIZE_BYTES; + } + + @Override + protected final byte[] engineGetIV() { + return ArrayUtils.cloneIfNotEmpty(mIv); + } + + protected void setIv(byte[] iv) { + mIv = iv; + } + + protected byte[] getIv() { + return mIv; + } + + /** + * {@link KeyStoreCryptoOperationStreamer} which buffers all output until {@code doFinal} from + * which it returns all output in one go, provided {@code doFinal} succeeds. + */ + private static class BufferAllOutputUntilDoFinalStreamer + implements KeyStoreCryptoOperationStreamer { + + private final KeyStoreCryptoOperationStreamer mDelegate; + private ByteArrayOutputStream mBufferedOutput = new ByteArrayOutputStream(); + private long mProducedOutputSizeBytes; + + private BufferAllOutputUntilDoFinalStreamer(KeyStoreCryptoOperationStreamer delegate) { + mDelegate = delegate; + } + + @Override + public byte[] update(byte[] input, int inputOffset, int inputLength) + throws KeyStoreException { + byte[] output = mDelegate.update(input, inputOffset, inputLength); + if (output != null) { + try { + mBufferedOutput.write(output); + } catch (IOException e) { + throw new ProviderException("Failed to buffer output", e); + } + } + return EmptyArray.BYTE; + } + + @Override + public byte[] doFinal(byte[] input, int inputOffset, int inputLength, + byte[] additionalEntropy) throws KeyStoreException { + byte[] output = mDelegate.doFinal(input, inputOffset, inputLength, additionalEntropy); + if (output != null) { + try { + mBufferedOutput.write(output); + } catch (IOException e) { + throw new ProviderException("Failed to buffer output", e); + } + } + byte[] result = mBufferedOutput.toByteArray(); + mBufferedOutput.reset(); + mProducedOutputSizeBytes += result.length; + return result; + } + + @Override + public long getConsumedInputSizeBytes() { + return mDelegate.getConsumedInputSizeBytes(); + } + + @Override + public long getProducedOutputSizeBytes() { + return mProducedOutputSizeBytes; + } + } + + /** + * Additional Authentication Data (AAD) stream via a KeyStore streaming operation. This stream + * sends AAD into the KeyStore. + */ + private static class AdditionalAuthenticationDataStream implements Stream { + + private final KeyStore mKeyStore; + private final IBinder mOperationToken; + + private AdditionalAuthenticationDataStream(KeyStore keyStore, IBinder operationToken) { + mKeyStore = keyStore; + mOperationToken = operationToken; + } + + @Override + public OperationResult update(byte[] input) { + KeymasterArguments keymasterArgs = new KeymasterArguments(); + keymasterArgs.addBlob(KeymasterDefs.KM_TAG_ASSOCIATED_DATA, input); + + // KeyStore does not reflect AAD in inputConsumed, but users of Stream rely on this + // field. We fix this discrepancy here. KeyStore.update contract is that all of AAD + // has been consumed if the method succeeds. + OperationResult result = mKeyStore.update(mOperationToken, keymasterArgs, null); + if (result.resultCode == KeyStore.NO_ERROR) { + result = new OperationResult( + result.resultCode, + result.token, + result.operationHandle, + input.length, // inputConsumed + result.output, + result.outParams); + } + return result; + } + + @Override + public OperationResult finish(byte[] additionalEntropy) { + if ((additionalEntropy != null) && (additionalEntropy.length > 0)) { + throw new ProviderException("AAD stream does not support additional entropy"); + } + return new OperationResult( + KeyStore.NO_ERROR, + mOperationToken, + 0, // operation handle -- nobody cares about this being returned from finish + 0, // inputConsumed + EmptyArray.BYTE, // output + new KeymasterArguments() // additional params returned by finish + ); + } + } +}
\ No newline at end of file diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java index f37cf07..06b76fa 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java @@ -93,6 +93,9 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider { putSymmetricCipherImpl("AES/CTR/NoPadding", PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CTR$NoPadding"); + putSymmetricCipherImpl("AES/GCM/NoPadding", + PACKAGE_NAME + ".AndroidKeyStoreAuthenticatedAESCipherSpi$GCM$NoPadding"); + putAsymmetricCipherImpl("RSA/ECB/NoPadding", PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$NoPadding"); put("Alg.Alias.Cipher.RSA/None/NoPadding", "RSA/ECB/NoPadding"); diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java index 19375a2..fc53451 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java @@ -26,6 +26,8 @@ import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; import android.security.keymaster.OperationResult; +import libcore.util.EmptyArray; + import java.nio.ByteBuffer; import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; @@ -78,6 +80,8 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor private IBinder mOperationToken; private long mOperationHandle; private KeyStoreCryptoOperationStreamer mMainDataStreamer; + private KeyStoreCryptoOperationStreamer mAdditionalAuthenticationDataStreamer; + private boolean mAdditionalAuthenticationDataStreamerClosed; /** * Encountered exception which could not be immediately thrown because it was encountered inside @@ -189,6 +193,8 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor mOperationToken = null; mOperationHandle = 0; mMainDataStreamer = null; + mAdditionalAuthenticationDataStreamer = null; + mAdditionalAuthenticationDataStreamerClosed = false; mCachedException = null; } @@ -209,6 +215,8 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor mOperationToken = null; mOperationHandle = 0; mMainDataStreamer = null; + mAdditionalAuthenticationDataStreamer = null; + mAdditionalAuthenticationDataStreamerClosed = false; mCachedException = null; } @@ -273,6 +281,9 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor loadAlgorithmSpecificParametersFromBeginResult(opResult.outParams); mMainDataStreamer = createMainDataStreamer(mKeyStore, opResult.token); + mAdditionalAuthenticationDataStreamer = + createAdditionalAuthenticationDataStreamer(mKeyStore, opResult.token); + mAdditionalAuthenticationDataStreamerClosed = false; } /** @@ -289,6 +300,20 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor keyStore, operationToken)); } + /** + * Creates a streamer which sends Additional Authentication Data (AAD) into the KeyStore. + * + * <p>This implementation returns {@code null}. + * + * @returns stream or {@code null} if AAD is not supported by this cipher. + */ + @Nullable + protected KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer( + @SuppressWarnings("unused") KeyStore keyStore, + @SuppressWarnings("unused") IBinder operationToken) { + return null; + } + @Override protected final byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { if (mCachedException != null) { @@ -307,6 +332,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor byte[] output; try { + flushAAD(); output = mMainDataStreamer.update(input, inputOffset, inputLen); } catch (KeyStoreException e) { mCachedException = e; @@ -320,6 +346,25 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor return output; } + private void flushAAD() throws KeyStoreException { + if ((mAdditionalAuthenticationDataStreamer != null) + && (!mAdditionalAuthenticationDataStreamerClosed)) { + byte[] output; + try { + output = mAdditionalAuthenticationDataStreamer.doFinal( + EmptyArray.BYTE, 0, 0, + null // no additional entropy needed flushing AAD + ); + } finally { + mAdditionalAuthenticationDataStreamerClosed = true; + } + if ((output != null) && (output.length > 0)) { + throw new ProviderException( + "AAD update unexpectedly returned data: " + output.length + " bytes"); + } + } + } + @Override protected final int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException { @@ -344,12 +389,64 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor @Override protected final void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) { - super.engineUpdateAAD(input, inputOffset, inputLen); + if (mCachedException != null) { + return; + } + + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + mCachedException = e; + return; + } + + if (mAdditionalAuthenticationDataStreamerClosed) { + throw new IllegalStateException( + "AAD can only be provided before Cipher.update is invoked"); + } + + if (mAdditionalAuthenticationDataStreamer == null) { + throw new IllegalStateException("This cipher does not support AAD"); + } + + byte[] output; + try { + output = mAdditionalAuthenticationDataStreamer.update(input, inputOffset, inputLen); + } catch (KeyStoreException e) { + mCachedException = e; + return; + } + + if ((output != null) && (output.length > 0)) { + throw new ProviderException("AAD update unexpectedly produced output: " + + output.length + " bytes"); + } } @Override protected final void engineUpdateAAD(ByteBuffer src) { - super.engineUpdateAAD(src); + if (src == null) { + throw new IllegalArgumentException("src == null"); + } + if (!src.hasRemaining()) { + return; + } + + byte[] input; + int inputOffset; + int inputLen; + if (src.hasArray()) { + input = src.array(); + inputOffset = src.arrayOffset() + src.position(); + inputLen = src.remaining(); + src.position(src.limit()); + } else { + input = new byte[src.remaining()]; + inputOffset = 0; + inputLen = input.length; + src.get(input); + } + super.engineUpdateAAD(input, inputOffset, inputLen); } @Override @@ -368,7 +465,11 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor byte[] output; try { - output = mMainDataStreamer.doFinal(input, inputOffset, inputLen); + flushAAD(); + 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: @@ -612,6 +713,20 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor return mKeyStore; } + protected final long getConsumedInputSizeBytes() { + if (mMainDataStreamer == null) { + throw new IllegalStateException("Not initialized"); + } + return mMainDataStreamer.getConsumedInputSizeBytes(); + } + + protected final long getProducedOutputSizeBytes() { + if (mMainDataStreamer == null) { + throw new IllegalStateException("Not initialized"); + } + return mMainDataStreamer.getProducedOutputSizeBytes(); + } + // The methods below need to be implemented by subclasses. /** @@ -667,21 +782,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/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java index 69155a8..af05578 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -20,7 +20,6 @@ import android.annotation.Nullable; import android.security.Credentials; import android.security.KeyPairGeneratorSpec; import android.security.KeyStore; -import android.security.keymaster.ExportResult; import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; @@ -44,29 +43,24 @@ 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.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.ECGenParameterSpec; -import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAKeyGenParameterSpec; -import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -110,8 +104,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato /* EC */ private static final int EC_DEFAULT_KEY_SIZE = 256; - private static final int EC_MIN_KEY_SIZE = 192; - private static final int EC_MAX_KEY_SIZE = 521; /* RSA */ private static final int RSA_DEFAULT_KEY_SIZE = 2048; @@ -121,16 +113,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato 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>(); + private static final List<Integer> SUPPORTED_EC_NIST_CURVE_SIZES = new ArrayList<Integer>(); 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); @@ -146,7 +135,12 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato SUPPORTED_EC_NIST_CURVE_NAMES.addAll(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.keySet()); Collections.sort(SUPPORTED_EC_NIST_CURVE_NAMES); + + SUPPORTED_EC_NIST_CURVE_SIZES.addAll( + new HashSet<Integer>(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.values())); + Collections.sort(SUPPORTED_EC_NIST_CURVE_SIZES); } + private final int mOriginalKeymasterAlgorithm; private KeyStore mKeyStore; @@ -220,14 +214,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato 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); + // 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( @@ -236,19 +224,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato | 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); + // 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, - KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1); + KeyProperties.ENCRYPTION_PADDING_NONE); // Disable randomized encryption requirement to support encryption // padding NONE above. specBuilder.setRandomizedEncryptionRequired(false); @@ -431,24 +413,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato args.addInts(KeymasterDefs.KM_TAG_PADDING, mKeymasterSignaturePaddings); args.addInts(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests); - // TODO: Remove the digest and padding NONE workaround below once Android Keystore returns - // keys which are backed by AndroidKeyStoreBCWorkaround provider instead of Conscrypt. The - // workaround is needed because Conscrypt (via keystore-engine) uses old KeyStore API which - // translates into digest NONE and padding NONE in the new API. keystore-engine cannot be - // updated to pass in the correct padding and digest values because it uses - // OpenSSL/BoringSSL engine which performs digesting and padding prior before invoking - // KeyStore API. - if (!com.android.internal.util.ArrayUtils.contains( - mKeymasterDigests, KeymasterDefs.KM_DIGEST_NONE)) { - args.addInt(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE); - } - if ((!com.android.internal.util.ArrayUtils.contains( - mKeymasterSignaturePaddings, KeymasterDefs.KM_PAD_NONE)) - && (!com.android.internal.util.ArrayUtils.contains( - mKeymasterEncryptionPaddings, KeymasterDefs.KM_PAD_NONE))) { - args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE); - } - KeymasterUtils.addUserAuthArgs(args, mSpec.isUserAuthenticationRequired(), mSpec.getUserAuthenticationValidityDurationSeconds()); @@ -483,40 +447,23 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato "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 ProviderException("Failed to obtain generated private key", e); - } - - ExportResult exportResult = - mKeyStore.exportKey( - privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null); - if (exportResult == null) { - throw new KeyStoreConnectException(); - } else if (exportResult.resultCode != KeyStore.NO_ERROR) { - throw new ProviderException( - "Failed to obtain X.509 form of generated public key", - KeyStore.getKeyStoreException(exportResult.resultCode)); + result = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore( + mKeyStore, privateKeyAlias); + } catch (UnrecoverableKeyException e) { + throw new ProviderException("Failed to load generated key pair from keystore", e); } - final byte[] pubKeyBytes = exportResult.exportData; - final PublicKey pubKey; - try { - final KeyFactory keyFact = KeyFactory.getInstance(mJcaKeyAlgorithm); - pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes)); - } catch (NoSuchAlgorithmException e) { + if (!mJcaKeyAlgorithm.equalsIgnoreCase(result.getPrivate().getAlgorithm())) { throw new ProviderException( - "Failed to obtain " + mJcaKeyAlgorithm + " KeyFactory", e); - } catch (InvalidKeySpecException e) { - throw new ProviderException("Invalid X.509 encoding of generated public key", e); + "Generated key pair algorithm does not match requested algorithm: " + + result.getPrivate().getAlgorithm() + " vs " + mJcaKeyAlgorithm); } final X509Certificate cert; try { - cert = generateSelfSignedCertificate(privKey, pubKey); + cert = generateSelfSignedCertificate(result.getPrivate(), result.getPublic()); } catch (Exception e) { throw new ProviderException("Failed to generate self-signed certificate", e); } @@ -539,7 +486,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato KeyStore.getKeyStoreException(insertErrorCode)); } - KeyPair result = new KeyPair(pubKey, privKey); success = true; return result; } finally { @@ -651,9 +597,9 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato 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); + if (!SUPPORTED_EC_NIST_CURVE_SIZES.contains(keySize)) { + throw new InvalidAlgorithmParameterException("Unsupported EC key size: " + + keySize + " bits. Supported: " + SUPPORTED_EC_NIST_CURVE_SIZES); } break; case KeymasterDefs.KM_ALGORITHM_RSA: @@ -677,7 +623,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato int keySizeBits, KeyGenParameterSpec spec) { // Constraints: - // 1. Key must be authorized for signing. + // 1. Key must be authorized for signing without user authentication. // 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 @@ -689,6 +635,10 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato // Key not authorized for signing return null; } + if (spec.isUserAuthenticationRequired()) { + // Key not authorized for use without user authentication + return null; + } if (!spec.isDigestsSpecified()) { // Key not authorized for any digests -- can't sign return null; @@ -744,6 +694,36 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } 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; + } + Set<Integer> availableKeymasterDigests = getAvailableKeymasterSignatureDigests( spec.getDigests(), AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests()); 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 d33692a..38e216d 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java @@ -99,6 +99,11 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase } @Override + protected final int getAdditionalEntropyAmountForFinish() { + return 0; + } + + @Override @NonNull protected KeyStoreCryptoOperationStreamer createMainDataStreamer( KeyStore keyStore, IBinder operationToken) { @@ -124,6 +129,7 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase private final KeyStoreCryptoOperationStreamer mDelegate; private final int mModulusSizeBytes; private final ByteArrayOutputStream mInputBuffer = new ByteArrayOutputStream(); + private long mConsumedInputSizeBytes; private ZeroPaddingEncryptionStreamer( KeyStoreCryptoOperationStreamer delegate, @@ -137,14 +143,17 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase throws KeyStoreException { if (inputLength > 0) { mInputBuffer.write(input, inputOffset, inputLength); + mConsumedInputSizeBytes += inputLength; } return EmptyArray.BYTE; } @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) { + mConsumedInputSizeBytes += inputLength; mInputBuffer.write(input, inputOffset, inputLength); } byte[] bufferedInput = mInputBuffer.toByteArray(); @@ -165,7 +174,17 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase "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); + } + + @Override + public long getConsumedInputSizeBytes() { + return mConsumedInputSizeBytes; + } + + @Override + public long getProducedOutputSizeBytes() { + return mDelegate.getProducedOutputSizeBytes(); } } } @@ -207,6 +226,11 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase @Override protected final int getAdditionalEntropyAmountForBegin() { + return 0; + } + + @Override + protected final int getAdditionalEntropyAmountForFinish() { return (isEncrypting()) ? getModulusSizeBytes() : 0; } } @@ -361,6 +385,11 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase @Override protected final int getAdditionalEntropyAmountForBegin() { + return 0; + } + + @Override + protected final int getAdditionalEntropyAmountForFinish() { return (isEncrypting()) ? mDigestOutputSizeBytes : 0; } } 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 c03be63..3bd9d1d 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java @@ -16,9 +16,6 @@ package android.security.keystore; -import com.android.org.conscrypt.OpenSSLEngine; -import com.android.org.conscrypt.OpenSSLKeyHolder; - import libcore.util.EmptyArray; import android.security.Credentials; @@ -35,7 +32,6 @@ 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; @@ -45,6 +41,7 @@ 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; @@ -92,59 +88,17 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { 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 != 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 @@ -188,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; @@ -214,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>(); @@ -279,14 +247,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { 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); + // 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( @@ -294,19 +256,13 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { | 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); + // 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, - KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1); + KeyProperties.ENCRYPTION_PADDING_NONE); // Disable randomized encryption requirement to support encryption padding NONE // above. specBuilder.setRandomizedEncryptionRequired(false); @@ -406,9 +362,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } final String pkeyAlias; - if (key instanceof OpenSSLKeyHolder) { - pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias(); - } else if (key instanceof AndroidKeyStorePrivateKey) { + if (key instanceof AndroidKeyStorePrivateKey) { pkeyAlias = ((AndroidKeyStoreKey) key).getAlias(); } else { pkeyAlias = null; @@ -851,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>(); @@ -868,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; } } @@ -893,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; } } @@ -954,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..6c53c6a 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package android.security.keystore; import android.annotation.NonNull; @@ -210,6 +226,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 19ff9c7..8d4bfcd 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -59,9 +59,19 @@ 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. * + * <p>Instances of this class are immutable. + * * <p><h3>Example: Asymmetric key pair</h3> * The following example illustrates how to generate an EC key pair in the Android KeyStore system * under alias {@code key1} authorized to be used only for signing using SHA-256, SHA-384, @@ -71,11 +81,12 @@ import javax.security.auth.x500.X500Principal; * KeyProperties.KEY_ALGORITHM_EC, * "AndroidKeyStore"); * keyPairGenerator.initialize( - * new KeyGenParameterSpec.Builder("key1", + * new KeyGenParameterSpec.Builder( + * "key1", * KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) - * .setDigests(KeyProperties.DIGEST_SHA256 - * | KeyProperties.DIGEST_SHA384 - * | KeyProperties.DIGEST_SHA512) + * .setDigests(KeyProperties.DIGEST_SHA256, + * KeyProperties.DIGEST_SHA384, + * KeyProperties.DIGEST_SHA512) * // Only permit this key to be used if the user authenticated * // within the last five minutes. * .setUserAuthenticationRequired(true) @@ -92,16 +103,17 @@ import javax.security.auth.x500.X500Principal; * * <p><h3>Example: Symmetric key</h3> * The following example illustrates how to generate an AES key in the Android KeyStore system under - * alias {@code key2} authorized to be used only for encryption/decryption in CTR mode. + * alias {@code key2} authorized to be used only for encryption/decryption in CBC mode with PKCS#7 + * padding. * <pre> {@code * KeyGenerator keyGenerator = KeyGenerator.getInstance( - * KeyProperties.KEY_ALGORITHM_HMAC_SHA256, + * KeyProperties.KEY_ALGORITHM_AES, * "AndroidKeyStore"); * keyGenerator.initialize( * new KeyGenParameterSpec.Builder("key2", * KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) - * .setBlockModes(KeyProperties.BLOCK_MODE_CTR) - * .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + * .setBlockModes(KeyProperties.BLOCK_MODE_CBC) + * .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) * .build()); * SecretKey key = keyGenerator.generateKey(); * @@ -161,10 +173,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { int userAuthenticationValidityDurationSeconds) { if (TextUtils.isEmpty(keyStoreAlias)) { throw new IllegalArgumentException("keyStoreAlias must not be empty"); - } else if ((userAuthenticationValidityDurationSeconds < 0) - && (userAuthenticationValidityDurationSeconds != -1)) { - throw new IllegalArgumentException( - "userAuthenticationValidityDurationSeconds must not be negative"); } if (certificateSubject == null) { @@ -189,11 +197,11 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { mSpec = spec; mCertificateSubject = certificateSubject; mCertificateSerialNumber = certificateSerialNumber; - mCertificateNotBefore = certificateNotBefore; - mCertificateNotAfter = certificateNotAfter; - mKeyValidityStart = keyValidityStart; - mKeyValidityForOriginationEnd = keyValidityForOriginationEnd; - mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd; + mCertificateNotBefore = Utils.cloneIfNotNull(certificateNotBefore); + mCertificateNotAfter = Utils.cloneIfNotNull(certificateNotAfter); + mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart); + mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd); + mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd); mPurposes = purposes; mDigests = ArrayUtils.cloneIfNotEmpty(digests); mEncryptionPaddings = @@ -209,6 +217,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * Returns the alias that will be used in the {@code java.security.KeyStore} * in conjunction with the {@code AndroidKeyStore}. */ + @NonNull public String getKeystoreAlias() { return mKeystoreAlias; } @@ -223,10 +232,10 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { } /** - * Returns the {@link AlgorithmParameterSpec} that will be used for creation - * of the key pair. + * Returns the key algorithm-specific {@link AlgorithmParameterSpec} that will be used for + * creation of the key or {@code null} if algorithm-specific defaults should be used. */ - @NonNull + @Nullable public AlgorithmParameterSpec getAlgorithmParameterSpec() { return mSpec; } @@ -255,7 +264,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { */ @NonNull public Date getCertificateNotBefore() { - return mCertificateNotBefore; + return Utils.cloneIfNotNull(mCertificateNotBefore); } /** @@ -264,7 +273,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { */ @NonNull public Date getCertificateNotAfter() { - return mCertificateNotAfter; + return Utils.cloneIfNotNull(mCertificateNotAfter); } /** @@ -273,7 +282,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { */ @Nullable public Date getKeyValidityStart() { - return mKeyValidityStart; + return Utils.cloneIfNotNull(mKeyValidityStart); } /** @@ -282,7 +291,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { */ @Nullable public Date getKeyValidityForConsumptionEnd() { - return mKeyValidityForConsumptionEnd; + return Utils.cloneIfNotNull(mKeyValidityForConsumptionEnd); } /** @@ -291,7 +300,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { */ @Nullable public Date getKeyValidityForOriginationEnd() { - return mKeyValidityForOriginationEnd; + return Utils.cloneIfNotNull(mKeyValidityForOriginationEnd); } /** @@ -403,7 +412,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * restricted. * * @return duration in seconds or {@code -1} if authentication is required for every use of the - * key. + * key. * * @see #isUserAuthenticationRequired() */ @@ -439,7 +448,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * Creates a new instance of the {@code Builder}. * * @param keystoreAlias alias of the entry in which the generated key will appear in - * Android KeyStore. + * Android KeyStore. Must not be empty. * @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. * @@ -449,14 +458,13 @@ 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) { if (keystoreAlias == null) { throw new NullPointerException("keystoreAlias == null"); + } else if (keystoreAlias.isEmpty()) { + throw new IllegalArgumentException("keystoreAlias must not be empty"); } mKeystoreAlias = keystoreAlias; mPurposes = purposes; @@ -483,7 +491,11 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { /** * Sets the algorithm-specific key generation parameters. For example, for RSA keys this may - * be an instance of {@link java.security.spec.RSAKeyGenParameterSpec}. + * be an instance of {@link java.security.spec.RSAKeyGenParameterSpec} whereas for EC keys + * this may be an instance of {@link java.security.spec.ECGenParameterSpec}. + * + * <p>These key generation parameters must match other explicitly set parameters (if any), + * such as key size. */ public Builder setAlgorithmParameterSpec(@NonNull AlgorithmParameterSpec spec) { if (spec == null) { @@ -532,7 +544,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { if (date == null) { throw new NullPointerException("date == null"); } - mCertificateNotBefore = date; + mCertificateNotBefore = Utils.cloneIfNotNull(date); return this; } @@ -547,7 +559,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { if (date == null) { throw new NullPointerException("date == null"); } - mCertificateNotAfter = date; + mCertificateNotAfter = Utils.cloneIfNotNull(date); return this; } @@ -556,13 +568,11 @@ 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 public Builder setKeyValidityStart(Date startDate) { - mKeyValidityStart = startDate; + mKeyValidityStart = Utils.cloneIfNotNull(startDate); return this; } @@ -571,8 +581,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) @@ -589,13 +597,11 @@ 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 public Builder setKeyValidityForOriginationEnd(Date endDate) { - mKeyValidityForOriginationEnd = endDate; + mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(endDate); return this; } @@ -605,13 +611,11 @@ 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 public Builder setKeyValidityForConsumptionEnd(Date endDate) { - mKeyValidityForConsumptionEnd = endDate; + mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(endDate); return this; } @@ -622,11 +626,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) { @@ -642,7 +649,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. */ @@ -660,8 +671,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 @@ -678,8 +687,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 @@ -723,8 +730,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) { @@ -748,8 +753,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 @@ -764,8 +767,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. * @@ -774,14 +775,15 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { @NonNull public Builder setUserAuthenticationValidityDurationSeconds( @IntRange(from = -1) int seconds) { + if (seconds < -1) { + throw new IllegalArgumentException("seconds must be -1 or larger"); + } mUserAuthenticationValidityDurationSeconds = seconds; return this; } /** * Builds an instance of {@code KeyGenParameterSpec}. - * - * @throws IllegalArgumentException if a required field is missing */ @NonNull public KeyGenParameterSpec build() { diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java index e4f921e..03b4100 100644 --- a/keystore/java/android/security/keystore/KeyInfo.java +++ b/keystore/java/android/security/keystore/KeyInfo.java @@ -33,6 +33,8 @@ import javax.crypto.SecretKey; * is authorized for (e.g., only in {@code CBC} mode, or signing only), whether the key should be * encrypted at rest, the key's and validity start and end dates. * + * <p>Instances of this class are immutable. + * * <p><h3>Example: Symmetric Key</h3> * The following example illustrates how to obtain a {@code KeyInfo} describing the provided Android * Keystore {@link SecretKey}. @@ -102,9 +104,9 @@ public class KeyInfo implements KeySpec { mInsideSecureHardware = insideSecureHardware; mOrigin = origin; mKeySize = keySize; - mKeyValidityStart = keyValidityStart; - mKeyValidityForOriginationEnd = keyValidityForOriginationEnd; - mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd; + mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart); + mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd); + mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd); mPurposes = purposes; mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings)); @@ -155,7 +157,7 @@ public class KeyInfo implements KeySpec { */ @Nullable public Date getKeyValidityStart() { - return mKeyValidityStart; + return Utils.cloneIfNotNull(mKeyValidityStart); } /** @@ -165,7 +167,7 @@ public class KeyInfo implements KeySpec { */ @Nullable public Date getKeyValidityForConsumptionEnd() { - return mKeyValidityForConsumptionEnd; + return Utils.cloneIfNotNull(mKeyValidityForConsumptionEnd); } /** @@ -175,7 +177,7 @@ public class KeyInfo implements KeySpec { */ @Nullable public Date getKeyValidityForOriginationEnd() { - return mKeyValidityForOriginationEnd; + return Utils.cloneIfNotNull(mKeyValidityForOriginationEnd); } /** diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index 5af4181..f9fe176 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -368,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"; @@ -514,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"; diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index f52a193..1e0611c 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -47,6 +47,8 @@ import javax.crypto.Cipher; * * <p>NOTE: The key material of keys stored in the Android KeyStore is not accessible. * + * <p>Instances of this class are immutable. + * * <p><h3>Example: Symmetric Key</h3> * The following example illustrates how to import an AES key into the Android KeyStore under alias * {@code key1} authorized to be used only for encryption/decryption in CBC mode with PKCS#7 @@ -122,15 +124,9 @@ public final class KeyProtection implements ProtectionParameter { boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds) { - if ((userAuthenticationValidityDurationSeconds < 0) - && (userAuthenticationValidityDurationSeconds != -1)) { - throw new IllegalArgumentException( - "userAuthenticationValidityDurationSeconds must not be negative"); - } - - mKeyValidityStart = keyValidityStart; - mKeyValidityForOriginationEnd = keyValidityForOriginationEnd; - mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd; + mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart); + mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd); + mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd); mPurposes = purposes; mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings)); @@ -150,7 +146,7 @@ public final class KeyProtection implements ProtectionParameter { */ @Nullable public Date getKeyValidityStart() { - return mKeyValidityStart; + return Utils.cloneIfNotNull(mKeyValidityStart); } /** @@ -160,7 +156,7 @@ public final class KeyProtection implements ProtectionParameter { */ @Nullable public Date getKeyValidityForConsumptionEnd() { - return mKeyValidityForConsumptionEnd; + return Utils.cloneIfNotNull(mKeyValidityForConsumptionEnd); } /** @@ -170,7 +166,7 @@ public final class KeyProtection implements ProtectionParameter { */ @Nullable public Date getKeyValidityForOriginationEnd() { - return mKeyValidityForOriginationEnd; + return Utils.cloneIfNotNull(mKeyValidityForOriginationEnd); } /** @@ -305,9 +301,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,13 +312,11 @@ 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 public Builder setKeyValidityStart(Date startDate) { - mKeyValidityStart = startDate; + mKeyValidityStart = Utils.cloneIfNotNull(startDate); return this; } @@ -334,8 +325,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,13 +341,11 @@ 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 public Builder setKeyValidityForOriginationEnd(Date endDate) { - mKeyValidityForOriginationEnd = endDate; + mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(endDate); return this; } @@ -368,13 +355,11 @@ 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 public Builder setKeyValidityForConsumptionEnd(Date endDate) { - mKeyValidityForConsumptionEnd = endDate; + mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(endDate); return this; } @@ -385,7 +370,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 +392,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 +410,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 +430,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 +471,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 +491,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 +505,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. * @@ -531,6 +513,9 @@ public final class KeyProtection implements ProtectionParameter { @NonNull public Builder setUserAuthenticationValidityDurationSeconds( @IntRange(from = -1) int seconds) { + if (seconds < -1) { + throw new IllegalArgumentException("seconds must be -1 or larger"); + } mUserAuthenticationValidityDurationSeconds = seconds; return this; } diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java index 47b4996..894d52a 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. @@ -73,6 +73,8 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS private byte[] mBuffered = EmptyArray.BYTE; private int mBufferedOffset; private int mBufferedLength; + private long mConsumedInputSizeBytes; + private long mProducedOutputSizeBytes; public KeyStoreCryptoOperationChunkedStreamer(Stream operation) { this(operation, DEFAULT_MAX_CHUNK_SIZE); @@ -119,6 +121,7 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS // Update input array references to reflect that some of its bytes are now in mBuffered. inputOffset += inputBytesInChunk; inputLength -= inputBytesInChunk; + mConsumedInputSizeBytes += inputBytesInChunk; OperationResult opResult = mKeyStoreStream.update(chunk); if (opResult == null) { @@ -167,9 +170,10 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS } } else { // No more output will be produced in this loop + byte[] result; if (bufferedOutput == null) { // No previously buffered output - return opResult.output; + result = opResult.output; } else { // There was some previously buffered output try { @@ -177,22 +181,27 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS } catch (IOException e) { throw new IllegalStateException("Failed to buffer output", e); } - return bufferedOutput.toByteArray(); + result = bufferedOutput.toByteArray(); } + mProducedOutputSizeBytes += result.length; + return result; } } } + byte[] result; if (bufferedOutput == null) { // No output produced - return EmptyArray.BYTE; + result = EmptyArray.BYTE; } else { - return bufferedOutput.toByteArray(); + result = bufferedOutput.toByteArray(); } + mProducedOutputSizeBytes += result.length; + return result; } @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,20 +213,17 @@ 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) { throw KeyStore.getKeyStoreException(opResult.resultCode); } + mProducedOutputSizeBytes += opResult.output.length; return ArrayUtils.concat(output, opResult.output); } - /** - * Passes all of buffered input into the the KeyStore operation (via the {@code update} - * operation) and returns output. - */ public byte[] flush() throws KeyStoreException { if (mBufferedLength <= 0) { return EmptyArray.BYTE; @@ -243,7 +249,19 @@ class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationS + " . Provided: " + chunk.length + ", consumed: " + opResult.inputConsumed); } - return (opResult.output != null) ? opResult.output : EmptyArray.BYTE; + byte[] result = (opResult.output != null) ? opResult.output : EmptyArray.BYTE; + mProducedOutputSizeBytes += result.length; + return result; + } + + @Override + public long getConsumedInputSizeBytes() { + return mConsumedInputSizeBytes; + } + + @Override + public long getProducedOutputSizeBytes() { + return mProducedOutputSizeBytes; } /** @@ -268,8 +286,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..897bd71 100644 --- a/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java +++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationStreamer.java @@ -28,12 +28,15 @@ 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; + long getConsumedInputSizeBytes(); + long getProducedOutputSizeBytes(); } diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java index 0639d49..4b37d90 100644 --- a/keystore/java/android/security/keystore/KeymasterUtils.java +++ b/keystore/java/android/security/keystore/KeymasterUtils.java @@ -101,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/java/android/security/keystore/Utils.java b/keystore/java/android/security/keystore/Utils.java new file mode 100644 index 0000000..9bec682 --- /dev/null +++ b/keystore/java/android/security/keystore/Utils.java @@ -0,0 +1,32 @@ +/* + * 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.util.Date; + +/** + * Assorted utility methods. + * + * @hide + */ +abstract class Utils { + private Utils() {} + + static Date cloneIfNotNull(Date value) { + return (value != null) ? (Date) value.clone() : null; + } +} diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java index 44fb826..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 * diff --git a/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java b/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java index 8488acd..e5c15c5 100644 --- a/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java +++ b/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java @@ -26,17 +26,21 @@ 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; @@ -158,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") @@ -328,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()); @@ -357,7 +402,10 @@ 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); @@ -368,6 +416,8 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { 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 336fa40..c3b731b 100644 --- a/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java +++ b/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java @@ -19,38 +19,31 @@ 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; import android.security.KeyStoreParameter; -import android.security.keymaster.ExportResult; -import android.security.keymaster.KeymasterDefs; import android.test.AndroidTestCase; 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; @@ -1203,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, @@ -1263,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 { @@ -1287,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 { @@ -1926,31 +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); - } - - ExportResult exportResult = - keyStore.exportKey(privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null); - assertEquals(KeyStore.NO_ERROR, exportResult.resultCode); - final byte[] pubKeyBytes = exportResult.exportData; - - 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); @@ -1958,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; } |