blob: da47b6bde69af129fc4bda7b1e11cf1fe5e47309 [file] [log] [blame]
/*
* 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);
}