/*
 * 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 SignatureSpi implementations of Android KeyStore backed ciphers.
@hide
/** * 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 engineInitSign/engineInitVerify and is invalidated when engineSign/engineVerify succeeds and on some error conditions in between.
/** * 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 engineSign or engineVerify. Once such an exception is encountered, engineUpdate starts ignoring input data.
/** * 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 key is not suitable.
/** * 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.

Subclasses storing additional state should override this method, reset the additional state, and then chain to superclass.

/** * 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 engineInit completed successfully.

Subclasses storing additional post-init state should override this method, reset the additional state, and then chain to superclass.

/** * 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

This implementation returns a working streamer.

/** * 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 true if this signature is initialized for signing, false if this signature is initialized for verification.
/** * 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 finish operation when generating a signature.

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 R, whereas for the RSA signature with PKCS#1 padding this should be 0.

/** * 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 begin operation.
Params:
  • keymasterArgs – keystore/keymaster arguments to be populated with algorithm-specific parameters.
/** * 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); }