content: /*
   * 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);
  }
  