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

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 parameters); }