| /* |
| * 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.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.ByteBuffer; |
| import java.security.InvalidKeyException; |
| import java.security.InvalidParameterException; |
| import java.security.PrivateKey; |
| import java.security.ProviderException; |
| import java.security.PublicKey; |
| import java.security.SecureRandom; |
| import java.security.SignatureException; |
| import java.security.SignatureSpi; |
| |
| /** |
| * Base class for {@link SignatureSpi} implementations of Android KeyStore backed ciphers. |
| * |
| * @hide |
| */ |
| abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi |
| implements KeyStoreCryptoOperation { |
| private final KeyStore mKeyStore; |
| |
| // Fields below are populated by SignatureSpi.engineInitSign/engineInitVerify and KeyStore.begin |
| // and should be preserved after SignatureSpi.engineSign/engineVerify finishes. |
| private boolean mSigning; |
| private AndroidKeyStoreKey mKey; |
| |
| /** |
| * Token referencing this operation inside keystore service. It is initialized by |
| * {@code engineInitSign}/{@code engineInitVerify} and is invalidated when |
| * {@code engineSign}/{@code engineVerify} succeeds and on some error conditions in between. |
| */ |
| private IBinder mOperationToken; |
| private long mOperationHandle; |
| private KeyStoreCryptoOperationStreamer mMessageStreamer; |
| |
| /** |
| * 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 engineSign} or {@code engineVerify}. Once such an exception is encountered, |
| * {@code engineUpdate} starts ignoring input data. |
| */ |
| private Exception mCachedException; |
| |
| AndroidKeyStoreSignatureSpiBase() { |
| mKeyStore = KeyStore.getInstance(); |
| } |
| |
| @Override |
| protected final void engineInitSign(PrivateKey key) throws InvalidKeyException { |
| engineInitSign(key, null); |
| } |
| |
| @Override |
| protected final void engineInitSign(PrivateKey privateKey, SecureRandom random) |
| throws InvalidKeyException { |
| resetAll(); |
| |
| boolean success = false; |
| try { |
| if (privateKey == null) { |
| throw new InvalidKeyException("Unsupported key: null"); |
| } |
| AndroidKeyStoreKey keystoreKey; |
| if (privateKey instanceof AndroidKeyStorePrivateKey) { |
| keystoreKey = (AndroidKeyStoreKey) privateKey; |
| } else { |
| throw new InvalidKeyException("Unsupported private key type: " + privateKey); |
| } |
| mSigning = true; |
| initKey(keystoreKey); |
| appRandom = random; |
| ensureKeystoreOperationInitialized(); |
| success = true; |
| } finally { |
| if (!success) { |
| resetAll(); |
| } |
| } |
| } |
| |
| @Override |
| protected final void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { |
| resetAll(); |
| |
| boolean success = false; |
| try { |
| if (publicKey == null) { |
| throw new InvalidKeyException("Unsupported key: null"); |
| } |
| AndroidKeyStoreKey keystoreKey; |
| if (publicKey instanceof AndroidKeyStorePublicKey) { |
| keystoreKey = (AndroidKeyStorePublicKey) publicKey; |
| } else { |
| throw new InvalidKeyException("Unsupported public key type: " + publicKey); |
| } |
| mSigning = false; |
| initKey(keystoreKey); |
| appRandom = null; |
| ensureKeystoreOperationInitialized(); |
| success = true; |
| } finally { |
| if (!success) { |
| resetAll(); |
| } |
| } |
| } |
| |
| /** |
| * Configures this signature instance to use the provided key. |
| * |
| * @throws InvalidKeyException if the {@code key} is not suitable. |
| */ |
| @CallSuper |
| protected void initKey(AndroidKeyStoreKey key) throws InvalidKeyException { |
| mKey = key; |
| } |
| |
| /** |
| * 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) { |
| mOperationToken = null; |
| mKeyStore.abort(operationToken); |
| } |
| mSigning = false; |
| mKey = null; |
| appRandom = null; |
| mOperationToken = null; |
| mOperationHandle = 0; |
| mMessageStreamer = null; |
| 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) { |
| mOperationToken = null; |
| mKeyStore.abort(operationToken); |
| } |
| mOperationHandle = 0; |
| mMessageStreamer = null; |
| mCachedException = null; |
| } |
| |
| private void ensureKeystoreOperationInitialized() throws InvalidKeyException { |
| if (mMessageStreamer != null) { |
| return; |
| } |
| if (mCachedException != null) { |
| return; |
| } |
| if (mKey == null) { |
| throw new IllegalStateException("Not initialized"); |
| } |
| |
| KeymasterArguments keymasterInputArgs = new KeymasterArguments(); |
| addAlgorithmSpecificParametersToBegin(keymasterInputArgs); |
| |
| 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, |
| null, // no additional entropy for begin -- only finish might need some |
| 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. |
| InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit( |
| mKeyStore, mKey, opResult.resultCode); |
| if (e != null) { |
| throw e; |
| } |
| |
| if (mOperationToken == null) { |
| throw new ProviderException("Keystore returned null operation token"); |
| } |
| if (mOperationHandle == 0) { |
| throw new ProviderException("Keystore returned invalid operation handle"); |
| } |
| |
| mMessageStreamer = createMainDataStreamer(mKeyStore, opResult.token); |
| } |
| |
| /** |
| * Creates a streamer which sends the message to be signed/verified into the provided KeyStore |
| * |
| * <p>This implementation returns a working streamer. |
| */ |
| @NonNull |
| protected KeyStoreCryptoOperationStreamer createMainDataStreamer( |
| KeyStore keyStore, IBinder operationToken) { |
| return new KeyStoreCryptoOperationChunkedStreamer( |
| new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( |
| keyStore, operationToken)); |
| } |
| |
| @Override |
| public final long getOperationHandle() { |
| return mOperationHandle; |
| } |
| |
| @Override |
| protected final void engineUpdate(byte[] b, int off, int len) throws SignatureException { |
| if (mCachedException != null) { |
| throw new SignatureException(mCachedException); |
| } |
| |
| try { |
| ensureKeystoreOperationInitialized(); |
| } catch (InvalidKeyException e) { |
| throw new SignatureException(e); |
| } |
| |
| if (len == 0) { |
| return; |
| } |
| |
| byte[] output; |
| try { |
| output = mMessageStreamer.update(b, off, len); |
| } catch (KeyStoreException e) { |
| throw new SignatureException(e); |
| } |
| |
| if (output.length != 0) { |
| throw new ProviderException( |
| "Update operation unexpectedly produced output: " + output.length + " bytes"); |
| } |
| } |
| |
| @Override |
| protected final void engineUpdate(byte b) throws SignatureException { |
| engineUpdate(new byte[] {b}, 0, 1); |
| } |
| |
| @Override |
| protected final void engineUpdate(ByteBuffer input) { |
| byte[] b; |
| int off; |
| int len = input.remaining(); |
| if (input.hasArray()) { |
| b = input.array(); |
| off = input.arrayOffset() + input.position(); |
| input.position(input.limit()); |
| } else { |
| b = new byte[len]; |
| off = 0; |
| input.get(b); |
| } |
| |
| try { |
| engineUpdate(b, off, len); |
| } catch (SignatureException e) { |
| mCachedException = e; |
| } |
| } |
| |
| @Override |
| protected final int engineSign(byte[] out, int outOffset, int outLen) |
| throws SignatureException { |
| return super.engineSign(out, outOffset, outLen); |
| } |
| |
| @Override |
| protected final byte[] engineSign() throws SignatureException { |
| if (mCachedException != null) { |
| throw new SignatureException(mCachedException); |
| } |
| |
| byte[] signature; |
| try { |
| ensureKeystoreOperationInitialized(); |
| |
| byte[] additionalEntropy = |
| KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( |
| appRandom, getAdditionalEntropyAmountForSign()); |
| signature = mMessageStreamer.doFinal( |
| EmptyArray.BYTE, 0, 0, |
| null, // no signature provided -- it'll be generated by this invocation |
| additionalEntropy); |
| } catch (InvalidKeyException | KeyStoreException e) { |
| throw new SignatureException(e); |
| } |
| |
| resetWhilePreservingInitState(); |
| return signature; |
| } |
| |
| @Override |
| protected final boolean engineVerify(byte[] signature) throws SignatureException { |
| if (mCachedException != null) { |
| throw new SignatureException(mCachedException); |
| } |
| |
| try { |
| ensureKeystoreOperationInitialized(); |
| } catch (InvalidKeyException e) { |
| throw new SignatureException(e); |
| } |
| |
| boolean verified; |
| try { |
| byte[] output = mMessageStreamer.doFinal( |
| EmptyArray.BYTE, 0, 0, |
| signature, |
| null // no additional entropy needed -- verification is deterministic |
| ); |
| if (output.length != 0) { |
| throw new ProviderException( |
| "Signature verification unexpected produced output: " + output.length |
| + " bytes"); |
| } |
| verified = true; |
| } catch (KeyStoreException e) { |
| switch (e.getErrorCode()) { |
| case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED: |
| verified = false; |
| break; |
| default: |
| throw new SignatureException(e); |
| } |
| } |
| |
| resetWhilePreservingInitState(); |
| return verified; |
| } |
| |
| @Override |
| protected final boolean engineVerify(byte[] sigBytes, int offset, int len) |
| throws SignatureException { |
| return engineVerify(ArrayUtils.subarray(sigBytes, offset, len)); |
| } |
| |
| @Deprecated |
| @Override |
| protected final Object engineGetParameter(String param) throws InvalidParameterException { |
| throw new InvalidParameterException(); |
| } |
| |
| @Deprecated |
| @Override |
| protected final void engineSetParameter(String param, Object value) |
| throws InvalidParameterException { |
| throw new InvalidParameterException(); |
| } |
| |
| protected final KeyStore getKeyStore() { |
| return mKeyStore; |
| } |
| |
| /** |
| * Returns {@code true} if this signature is initialized for signing, {@code false} if this |
| * signature is initialized for verification. |
| */ |
| protected final boolean isSigning() { |
| return mSigning; |
| } |
| |
| // The methods below need to be implemented by subclasses. |
| |
| /** |
| * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's |
| * {@code finish} operation when generating a signature. |
| * |
| * <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 getAdditionalEntropyAmountForSign(); |
| |
| /** |
| * 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); |
| } |