| /* |
| * 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.CallSuper; |
| 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 libcore.util.EmptyArray; |
| |
| import java.nio.BufferOverflowException; |
| import java.nio.ByteBuffer; |
| import java.security.AlgorithmParameters; |
| import java.security.GeneralSecurityException; |
| import java.security.InvalidAlgorithmParameterException; |
| import java.security.InvalidKeyException; |
| import java.security.InvalidParameterException; |
| import java.security.Key; |
| import java.security.KeyFactory; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.PrivateKey; |
| import java.security.ProviderException; |
| import java.security.PublicKey; |
| import java.security.SecureRandom; |
| import java.security.spec.AlgorithmParameterSpec; |
| import java.security.spec.InvalidKeySpecException; |
| import java.security.spec.PKCS8EncodedKeySpec; |
| import java.security.spec.X509EncodedKeySpec; |
| |
| import javax.crypto.AEADBadTagException; |
| import javax.crypto.BadPaddingException; |
| import javax.crypto.Cipher; |
| import javax.crypto.CipherSpi; |
| import javax.crypto.IllegalBlockSizeException; |
| import javax.crypto.NoSuchPaddingException; |
| import javax.crypto.SecretKey; |
| import javax.crypto.SecretKeyFactory; |
| import javax.crypto.ShortBufferException; |
| import javax.crypto.spec.SecretKeySpec; |
| |
| /** |
| * Base class for {@link CipherSpi} implementations of Android KeyStore backed ciphers. |
| * |
| * @hide |
| */ |
| abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStoreCryptoOperation { |
| private final KeyStore mKeyStore; |
| |
| // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after |
| // doFinal finishes. |
| private boolean mEncrypting; |
| private int mKeymasterPurposeOverride = -1; |
| private AndroidKeyStoreKey mKey; |
| private SecureRandom mRng; |
| |
| /** |
| * Token referencing this operation inside keystore service. It is initialized by |
| * {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and on some error |
| * conditions in between. |
| */ |
| 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 |
| * a method that does not throw checked exception. This exception will be thrown from |
| * {@code engineDoFinal}. Once such an exception is encountered, {@code engineUpdate} and |
| * {@code engineDoFinal} start ignoring input data. |
| */ |
| private Exception mCachedException; |
| |
| AndroidKeyStoreCipherSpiBase() { |
| mKeyStore = KeyStore.getInstance(); |
| } |
| |
| @Override |
| protected final void engineInit(int opmode, Key key, SecureRandom random) |
| throws InvalidKeyException { |
| resetAll(); |
| |
| boolean success = false; |
| try { |
| init(opmode, key, random); |
| initAlgorithmSpecificParameters(); |
| try { |
| ensureKeystoreOperationInitialized(); |
| } catch (InvalidAlgorithmParameterException e) { |
| throw new InvalidKeyException(e); |
| } |
| success = true; |
| } finally { |
| if (!success) { |
| resetAll(); |
| } |
| } |
| } |
| |
| @Override |
| protected final void engineInit(int opmode, Key key, AlgorithmParameters params, |
| SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { |
| resetAll(); |
| |
| boolean success = false; |
| try { |
| init(opmode, key, random); |
| initAlgorithmSpecificParameters(params); |
| ensureKeystoreOperationInitialized(); |
| success = true; |
| } finally { |
| if (!success) { |
| resetAll(); |
| } |
| } |
| } |
| |
| @Override |
| protected final void engineInit(int opmode, Key key, AlgorithmParameterSpec params, |
| SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { |
| resetAll(); |
| |
| boolean success = false; |
| try { |
| init(opmode, key, random); |
| initAlgorithmSpecificParameters(params); |
| ensureKeystoreOperationInitialized(); |
| success = true; |
| } finally { |
| if (!success) { |
| resetAll(); |
| } |
| } |
| } |
| |
| private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException { |
| switch (opmode) { |
| case Cipher.ENCRYPT_MODE: |
| case Cipher.WRAP_MODE: |
| mEncrypting = true; |
| break; |
| case Cipher.DECRYPT_MODE: |
| case Cipher.UNWRAP_MODE: |
| mEncrypting = false; |
| break; |
| default: |
| throw new InvalidParameterException("Unsupported opmode: " + opmode); |
| } |
| initKey(opmode, key); |
| if (mKey == null) { |
| throw new ProviderException("initKey did not initialize the key"); |
| } |
| mRng = random; |
| } |
| |
| /** |
| * Resets this cipher to its pristine pre-init state. This must be equivalent to obtaining a new |
| * cipher instance. |
| * |
| * <p>Subclasses storing additional state should override this method, reset the additional |
| * state, and then chain to superclass. |
| */ |
| @CallSuper |
| protected void resetAll() { |
| IBinder operationToken = mOperationToken; |
| if (operationToken != null) { |
| mKeyStore.abort(operationToken); |
| } |
| mEncrypting = false; |
| mKeymasterPurposeOverride = -1; |
| mKey = null; |
| mRng = null; |
| mOperationToken = null; |
| mOperationHandle = 0; |
| mMainDataStreamer = null; |
| mAdditionalAuthenticationDataStreamer = null; |
| mAdditionalAuthenticationDataStreamerClosed = false; |
| mCachedException = null; |
| } |
| |
| /** |
| * Resets this cipher while preserving the initialized state. This must be equivalent to |
| * rolling back the cipher's state to just after the most recent {@code engineInit} completed |
| * successfully. |
| * |
| * <p>Subclasses storing additional post-init state should override this method, reset the |
| * additional state, and then chain to superclass. |
| */ |
| @CallSuper |
| protected void resetWhilePreservingInitState() { |
| IBinder operationToken = mOperationToken; |
| if (operationToken != null) { |
| mKeyStore.abort(operationToken); |
| } |
| mOperationToken = null; |
| mOperationHandle = 0; |
| mMainDataStreamer = null; |
| mAdditionalAuthenticationDataStreamer = null; |
| mAdditionalAuthenticationDataStreamerClosed = false; |
| mCachedException = null; |
| } |
| |
| private void ensureKeystoreOperationInitialized() throws InvalidKeyException, |
| InvalidAlgorithmParameterException { |
| if (mMainDataStreamer != null) { |
| return; |
| } |
| if (mCachedException != null) { |
| return; |
| } |
| if (mKey == null) { |
| throw new IllegalStateException("Not initialized"); |
| } |
| |
| KeymasterArguments keymasterInputArgs = new KeymasterArguments(); |
| addAlgorithmSpecificParametersToBegin(keymasterInputArgs); |
| byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( |
| mRng, getAdditionalEntropyAmountForBegin()); |
| |
| int purpose; |
| if (mKeymasterPurposeOverride != -1) { |
| purpose = mKeymasterPurposeOverride; |
| } else { |
| purpose = mEncrypting |
| ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT; |
| } |
| OperationResult opResult = mKeyStore.begin( |
| mKey.getAlias(), |
| purpose, |
| true, // permit aborting this operation if keystore runs out of resources |
| keymasterInputArgs, |
| additionalEntropy, |
| mKey.getUid()); |
| if (opResult == null) { |
| throw new KeyStoreConnectException(); |
| } |
| |
| // Store operation token and handle regardless of the error code returned by KeyStore to |
| // ensure that the operation gets aborted immediately if the code below throws an exception. |
| mOperationToken = opResult.token; |
| mOperationHandle = opResult.operationHandle; |
| |
| // If necessary, throw an exception due to KeyStore operation having failed. |
| GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit( |
| mKeyStore, mKey, opResult.resultCode); |
| if (e != null) { |
| if (e instanceof InvalidKeyException) { |
| throw (InvalidKeyException) e; |
| } else if (e instanceof InvalidAlgorithmParameterException) { |
| throw (InvalidAlgorithmParameterException) e; |
| } else { |
| throw new ProviderException("Unexpected exception type", e); |
| } |
| } |
| |
| if (mOperationToken == null) { |
| throw new ProviderException("Keystore returned null operation token"); |
| } |
| if (mOperationHandle == 0) { |
| throw new ProviderException("Keystore returned invalid operation handle"); |
| } |
| |
| loadAlgorithmSpecificParametersFromBeginResult(opResult.outParams); |
| mMainDataStreamer = createMainDataStreamer(mKeyStore, opResult.token); |
| mAdditionalAuthenticationDataStreamer = |
| createAdditionalAuthenticationDataStreamer(mKeyStore, opResult.token); |
| mAdditionalAuthenticationDataStreamerClosed = false; |
| } |
| |
| /** |
| * Creates a streamer which sends plaintext/ciphertext into the provided KeyStore and receives |
| * the corresponding ciphertext/plaintext from the KeyStore. |
| * |
| * <p>This implementation returns a working streamer. |
| */ |
| @NonNull |
| protected KeyStoreCryptoOperationStreamer createMainDataStreamer( |
| KeyStore keyStore, IBinder operationToken) { |
| return new KeyStoreCryptoOperationChunkedStreamer( |
| new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( |
| keyStore, operationToken)); |
| } |
| |
| /** |
| * Creates a streamer which sends Additional Authentication Data (AAD) into the KeyStore. |
| * |
| * <p>This implementation returns {@code null}. |
| * |
| * @return 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) { |
| return null; |
| } |
| try { |
| ensureKeystoreOperationInitialized(); |
| } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { |
| mCachedException = e; |
| return null; |
| } |
| |
| if (inputLen == 0) { |
| return null; |
| } |
| |
| byte[] output; |
| try { |
| flushAAD(); |
| output = mMainDataStreamer.update(input, inputOffset, inputLen); |
| } catch (KeyStoreException e) { |
| mCachedException = e; |
| return null; |
| } |
| |
| if (output.length == 0) { |
| return null; |
| } |
| |
| return output; |
| } |
| |
| private void flushAAD() throws KeyStoreException { |
| if ((mAdditionalAuthenticationDataStreamer != null) |
| && (!mAdditionalAuthenticationDataStreamerClosed)) { |
| byte[] output; |
| try { |
| output = mAdditionalAuthenticationDataStreamer.doFinal( |
| EmptyArray.BYTE, 0, 0, |
| null, // no signature |
| 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 { |
| byte[] outputCopy = engineUpdate(input, inputOffset, inputLen); |
| if (outputCopy == null) { |
| return 0; |
| } |
| int outputAvailable = output.length - outputOffset; |
| if (outputCopy.length > outputAvailable) { |
| throw new ShortBufferException("Output buffer too short. Produced: " |
| + outputCopy.length + ", available: " + outputAvailable); |
| } |
| System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); |
| return outputCopy.length; |
| } |
| |
| @Override |
| protected final int engineUpdate(ByteBuffer input, ByteBuffer output) |
| throws ShortBufferException { |
| if (input == null) { |
| throw new NullPointerException("input == null"); |
| } |
| if (output == null) { |
| throw new NullPointerException("output == null"); |
| } |
| |
| int inputSize = input.remaining(); |
| byte[] outputArray; |
| if (input.hasArray()) { |
| outputArray = |
| engineUpdate( |
| input.array(), input.arrayOffset() + input.position(), inputSize); |
| input.position(input.position() + inputSize); |
| } else { |
| byte[] inputArray = new byte[inputSize]; |
| input.get(inputArray); |
| outputArray = engineUpdate(inputArray, 0, inputSize); |
| } |
| |
| int outputSize = (outputArray != null) ? outputArray.length : 0; |
| if (outputSize > 0) { |
| int outputBufferAvailable = output.remaining(); |
| try { |
| output.put(outputArray); |
| } catch (BufferOverflowException e) { |
| throw new ShortBufferException( |
| "Output buffer too small. Produced: " + outputSize + ", available: " |
| + outputBufferAvailable); |
| } |
| } |
| return outputSize; |
| } |
| |
| @Override |
| protected final void engineUpdateAAD(byte[] input, int inputOffset, int 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) { |
| 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); |
| } |
| engineUpdateAAD(input, inputOffset, inputLen); |
| } |
| |
| @Override |
| protected final byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) |
| throws IllegalBlockSizeException, BadPaddingException { |
| if (mCachedException != null) { |
| throw (IllegalBlockSizeException) |
| new IllegalBlockSizeException().initCause(mCachedException); |
| } |
| |
| try { |
| ensureKeystoreOperationInitialized(); |
| } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { |
| throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); |
| } |
| |
| byte[] output; |
| try { |
| flushAAD(); |
| byte[] additionalEntropy = |
| KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( |
| mRng, getAdditionalEntropyAmountForFinish()); |
| output = mMainDataStreamer.doFinal( |
| input, inputOffset, inputLen, |
| null, // no signature involved |
| additionalEntropy); |
| } catch (KeyStoreException e) { |
| switch (e.getErrorCode()) { |
| case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH: |
| throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); |
| case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT: |
| throw (BadPaddingException) new BadPaddingException().initCause(e); |
| case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED: |
| throw (AEADBadTagException) new AEADBadTagException().initCause(e); |
| default: |
| throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); |
| } |
| } |
| |
| resetWhilePreservingInitState(); |
| return output; |
| } |
| |
| @Override |
| protected final int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, |
| int outputOffset) throws ShortBufferException, IllegalBlockSizeException, |
| BadPaddingException { |
| byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen); |
| if (outputCopy == null) { |
| return 0; |
| } |
| int outputAvailable = output.length - outputOffset; |
| if (outputCopy.length > outputAvailable) { |
| throw new ShortBufferException("Output buffer too short. Produced: " |
| + outputCopy.length + ", available: " + outputAvailable); |
| } |
| System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); |
| return outputCopy.length; |
| } |
| |
| @Override |
| protected final int engineDoFinal(ByteBuffer input, ByteBuffer output) |
| throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { |
| if (input == null) { |
| throw new NullPointerException("input == null"); |
| } |
| if (output == null) { |
| throw new NullPointerException("output == null"); |
| } |
| |
| int inputSize = input.remaining(); |
| byte[] outputArray; |
| if (input.hasArray()) { |
| outputArray = |
| engineDoFinal( |
| input.array(), input.arrayOffset() + input.position(), inputSize); |
| input.position(input.position() + inputSize); |
| } else { |
| byte[] inputArray = new byte[inputSize]; |
| input.get(inputArray); |
| outputArray = engineDoFinal(inputArray, 0, inputSize); |
| } |
| |
| int outputSize = (outputArray != null) ? outputArray.length : 0; |
| if (outputSize > 0) { |
| int outputBufferAvailable = output.remaining(); |
| try { |
| output.put(outputArray); |
| } catch (BufferOverflowException e) { |
| throw new ShortBufferException( |
| "Output buffer too small. Produced: " + outputSize + ", available: " |
| + outputBufferAvailable); |
| } |
| } |
| return outputSize; |
| } |
| |
| @Override |
| protected final byte[] engineWrap(Key key) |
| throws IllegalBlockSizeException, InvalidKeyException { |
| if (mKey == null) { |
| throw new IllegalStateException("Not initilized"); |
| } |
| |
| if (!isEncrypting()) { |
| throw new IllegalStateException( |
| "Cipher must be initialized in Cipher.WRAP_MODE to wrap keys"); |
| } |
| |
| if (key == null) { |
| throw new NullPointerException("key == null"); |
| } |
| byte[] encoded = null; |
| if (key instanceof SecretKey) { |
| if ("RAW".equalsIgnoreCase(key.getFormat())) { |
| encoded = key.getEncoded(); |
| } |
| if (encoded == null) { |
| try { |
| SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(key.getAlgorithm()); |
| SecretKeySpec spec = |
| (SecretKeySpec) keyFactory.getKeySpec( |
| (SecretKey) key, SecretKeySpec.class); |
| encoded = spec.getEncoded(); |
| } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { |
| throw new InvalidKeyException( |
| "Failed to wrap key because it does not export its key material", |
| e); |
| } |
| } |
| } else if (key instanceof PrivateKey) { |
| if ("PKCS8".equalsIgnoreCase(key.getFormat())) { |
| encoded = key.getEncoded(); |
| } |
| if (encoded == null) { |
| try { |
| KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm()); |
| PKCS8EncodedKeySpec spec = |
| keyFactory.getKeySpec(key, PKCS8EncodedKeySpec.class); |
| encoded = spec.getEncoded(); |
| } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { |
| throw new InvalidKeyException( |
| "Failed to wrap key because it does not export its key material", |
| e); |
| } |
| } |
| } else if (key instanceof PublicKey) { |
| if ("X.509".equalsIgnoreCase(key.getFormat())) { |
| encoded = key.getEncoded(); |
| } |
| if (encoded == null) { |
| try { |
| KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm()); |
| X509EncodedKeySpec spec = |
| keyFactory.getKeySpec(key, X509EncodedKeySpec.class); |
| encoded = spec.getEncoded(); |
| } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { |
| throw new InvalidKeyException( |
| "Failed to wrap key because it does not export its key material", |
| e); |
| } |
| } |
| } else { |
| throw new InvalidKeyException("Unsupported key type: " + key.getClass().getName()); |
| } |
| |
| if (encoded == null) { |
| throw new InvalidKeyException( |
| "Failed to wrap key because it does not export its key material"); |
| } |
| |
| try { |
| return engineDoFinal(encoded, 0, encoded.length); |
| } catch (BadPaddingException e) { |
| throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); |
| } |
| } |
| |
| @Override |
| protected final Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, |
| int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException { |
| if (mKey == null) { |
| throw new IllegalStateException("Not initilized"); |
| } |
| |
| if (isEncrypting()) { |
| throw new IllegalStateException( |
| "Cipher must be initialized in Cipher.WRAP_MODE to wrap keys"); |
| } |
| |
| if (wrappedKey == null) { |
| throw new NullPointerException("wrappedKey == null"); |
| } |
| |
| byte[] encoded; |
| try { |
| encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length); |
| } catch (IllegalBlockSizeException | BadPaddingException e) { |
| throw new InvalidKeyException("Failed to unwrap key", e); |
| } |
| |
| switch (wrappedKeyType) { |
| case Cipher.SECRET_KEY: |
| { |
| return new SecretKeySpec(encoded, wrappedKeyAlgorithm); |
| // break; |
| } |
| case Cipher.PRIVATE_KEY: |
| { |
| KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm); |
| try { |
| return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded)); |
| } catch (InvalidKeySpecException e) { |
| throw new InvalidKeyException( |
| "Failed to create private key from its PKCS#8 encoded form", e); |
| } |
| // break; |
| } |
| case Cipher.PUBLIC_KEY: |
| { |
| KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm); |
| try { |
| return keyFactory.generatePublic(new X509EncodedKeySpec(encoded)); |
| } catch (InvalidKeySpecException e) { |
| throw new InvalidKeyException( |
| "Failed to create public key from its X.509 encoded form", e); |
| } |
| // break; |
| } |
| default: |
| throw new InvalidParameterException( |
| "Unsupported wrappedKeyType: " + wrappedKeyType); |
| } |
| } |
| |
| @Override |
| protected final void engineSetMode(String mode) throws NoSuchAlgorithmException { |
| // This should never be invoked because all algorithms registered with the AndroidKeyStore |
| // provide explicitly specify block mode. |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| protected final void engineSetPadding(String arg0) throws NoSuchPaddingException { |
| // This should never be invoked because all algorithms registered with the AndroidKeyStore |
| // provide explicitly specify padding mode. |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| protected final int engineGetKeySize(Key key) throws InvalidKeyException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @CallSuper |
| @Override |
| public void finalize() throws Throwable { |
| try { |
| IBinder operationToken = mOperationToken; |
| if (operationToken != null) { |
| mKeyStore.abort(operationToken); |
| } |
| } finally { |
| super.finalize(); |
| } |
| } |
| |
| @Override |
| public final long getOperationHandle() { |
| return mOperationHandle; |
| } |
| |
| protected final void setKey(@NonNull AndroidKeyStoreKey key) { |
| mKey = key; |
| } |
| |
| /** |
| * Overrides the default purpose/type of the crypto operation. |
| */ |
| protected final void setKeymasterPurposeOverride(int keymasterPurpose) { |
| mKeymasterPurposeOverride = keymasterPurpose; |
| } |
| |
| protected final int getKeymasterPurposeOverride() { |
| return mKeymasterPurposeOverride; |
| } |
| |
| /** |
| * Returns {@code true} if this cipher is initialized for encryption, {@code false} if this |
| * cipher is initialized for decryption. |
| */ |
| protected final boolean isEncrypting() { |
| return mEncrypting; |
| } |
| |
| @NonNull |
| protected final KeyStore getKeyStore() { |
| 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(); |
| } |
| |
| static String opmodeToString(int opmode) { |
| switch (opmode) { |
| case Cipher.ENCRYPT_MODE: |
| return "ENCRYPT_MODE"; |
| case Cipher.DECRYPT_MODE: |
| return "DECRYPT_MODE"; |
| case Cipher.WRAP_MODE: |
| return "WRAP_MODE"; |
| case Cipher.UNWRAP_MODE: |
| return "UNWRAP_MODE"; |
| default: |
| return String.valueOf(opmode); |
| } |
| } |
| |
| // The methods below need to be implemented by subclasses. |
| |
| /** |
| * Initializes this cipher with the provided key. |
| * |
| * @throws InvalidKeyException if the {@code key} is not suitable for this cipher in the |
| * specified {@code opmode}. |
| * |
| * @see #setKey(AndroidKeyStoreKey) |
| */ |
| protected abstract void initKey(int opmode, @Nullable Key key) throws InvalidKeyException; |
| |
| /** |
| * Returns algorithm-specific parameters used by this cipher or {@code null} if no |
| * algorithm-specific parameters are used. |
| */ |
| @Nullable |
| @Override |
| protected abstract AlgorithmParameters engineGetParameters(); |
| |
| /** |
| * Invoked by {@code engineInit} to initialize algorithm-specific parameters when no additional |
| * initialization parameters were provided. |
| * |
| * @throws InvalidKeyException if this cipher cannot be configured based purely on the provided |
| * key and needs additional parameters to be provided to {@code Cipher.init}. |
| */ |
| protected abstract void initAlgorithmSpecificParameters() throws InvalidKeyException; |
| |
| /** |
| * Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional |
| * parameters were provided. |
| * |
| * @param params additional algorithm parameters or {@code null} if not specified. |
| * |
| * @throws InvalidAlgorithmParameterException if there is insufficient information to configure |
| * this cipher or if the provided parameters are not suitable for this cipher. |
| */ |
| protected abstract void initAlgorithmSpecificParameters( |
| @Nullable AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException; |
| |
| /** |
| * Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional |
| * parameters were provided. |
| * |
| * @param params additional algorithm parameters or {@code null} if not specified. |
| * |
| * @throws InvalidAlgorithmParameterException if there is insufficient information to configure |
| * this cipher or if the provided parameters are not suitable for this cipher. |
| */ |
| protected abstract void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params) |
| throws InvalidAlgorithmParameterException; |
| |
| /** |
| * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's |
| * {@code begin} operation. This amount of entropy is typically what's consumed to generate |
| * random parameters, such as IV. |
| * |
| * <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 |
| * parameters. |
| */ |
| protected abstract void addAlgorithmSpecificParametersToBegin( |
| @NonNull KeymasterArguments keymasterArgs); |
| |
| /** |
| * Invoked to obtain algorithm-specific parameters from the result of the KeyStore's |
| * {@code begin} operation. |
| * |
| * <p>Some parameters, such as IV, are not required to be provided to {@code Cipher.init}. Such |
| * parameters, if not provided, must be generated by KeyStore and returned to the user of |
| * {@code Cipher} and potentially reused after {@code doFinal}. |
| * |
| * @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin} |
| * operation. |
| */ |
| protected abstract void loadAlgorithmSpecificParametersFromBeginResult( |
| @NonNull KeymasterArguments keymasterArgs); |
| } |