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