blob: 96da1e00ade83f01f9db7ad983e017304f96cbbc [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.keystore2;
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.hardware.security.keymint.KeyParameter;
import android.security.KeyStoreException;
import android.security.KeyStoreOperation;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.ArrayUtils;
import android.security.keystore.KeyStoreCryptoOperation;
import libcore.util.EmptyArray;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.ProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.SignatureSpi;
import java.util.ArrayList;
import java.util.List;
/**
* Base class for {@link SignatureSpi} implementations of Android KeyStore backed ciphers.
*
* @hide
*/
abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
implements KeyStoreCryptoOperation {
private static final String TAG = "AndroidKeyStoreSignatureSpiBase";
// 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;
/**
* Object representing 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 KeyStoreOperation mOperation;
/**
* The operation challenge is required when an operation needs user authorization.
* The challenge is subjected to an authenticator, e.g., Gatekeeper or a biometric
* authenticator, and included in the authentication token minted by this authenticator.
* It may be null, if the operation does not require authorization.
*/
private long mOperationChallenge;
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;
/**
* This signature object is used for public key operations, i.e, signatrue verification.
* The Android Keystore backend does not perform public key operations and defers to the
* Highest priority provider.
*/
private Signature mSignature;
AndroidKeyStoreSignatureSpiBase() {
mOperation = null;
mOperationChallenge = 0;
mSigning = false;
mKey = null;
appRandom = null;
mMessageStreamer = null;
mCachedException = null;
mSignature = null;
}
@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();
try {
mSignature = Signature.getInstance(getAlgorithm());
} catch (NoSuchAlgorithmException e) {
throw new InvalidKeyException(e);
}
mSignature.initVerify(publicKey);
}
/**
* 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;
}
private void abortOperation() {
KeyStoreCryptoOperationUtils.abortOperation(mOperation);
mOperation = null;
}
/**
* 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() {
abortOperation();
mOperationChallenge = 0;
mSigning = false;
mKey = null;
appRandom = null;
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() {
abortOperation();
mOperationChallenge = 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");
}
List<KeyParameter> parameters = new ArrayList<>();
addAlgorithmSpecificParametersToBegin(parameters);
int purpose = mSigning ? KeymasterDefs.KM_PURPOSE_SIGN : KeymasterDefs.KM_PURPOSE_VERIFY;
parameters.add(KeyStore2ParameterUtils.makeEnum(KeymasterDefs.KM_TAG_PURPOSE, purpose));
try {
mOperation = mKey.getSecurityLevel().createOperation(
mKey.getKeyIdDescriptor(),
parameters);
} catch (KeyStoreException keyStoreException) {
throw KeyStoreCryptoOperationUtils.getInvalidKeyException(
mKey, keyStoreException);
}
// Now we check if we got an operation challenge. This indicates that user authorization
// is required. And if we got a challenge we check if the authorization can possibly
// succeed.
mOperationChallenge = KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge(
mOperation, mKey);
mMessageStreamer = createMainDataStreamer(mOperation);
}
/**
* 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(
@NonNull KeyStoreOperation operation) {
return new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
operation));
}
@Override
public final long getOperationHandle() {
return mOperationChallenge;
}
@Override
protected final void engineUpdate(byte[] b, int off, int len) throws SignatureException {
if (mSignature != null) {
mSignature.update(b, off, len);
return;
}
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
} catch (InvalidKeyException | KeyStoreException e) {
throw new SignatureException(e);
}
resetWhilePreservingInitState();
return signature;
}
@Override
protected final boolean engineVerify(byte[] signature) throws SignatureException {
if (mSignature != null) {
return mSignature.verify(signature);
}
throw new IllegalStateException("Not initialised.");
}
@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();
}
/**
* Implementations need to report the algorithm they implement so that we can delegate to the
* highest priority provider.
* @return Algorithm string.
*/
protected abstract String getAlgorithm();
/**
* 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 parameters keystore/keymaster arguments to be populated with algorithm-specific
* parameters.
*/
protected abstract void addAlgorithmSpecificParametersToBegin(
@NonNull List<KeyParameter> parameters);
}