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.NonNull;
  import android.annotation.Nullable;
  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 android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.Stream;
  
  import libcore.util.EmptyArray;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.security.AlgorithmParameters;
  import java.security.InvalidAlgorithmParameterException;
  import java.security.InvalidKeyException;
  import java.security.Key;
  import java.security.NoSuchAlgorithmException;
  import java.security.ProviderException;
  import java.security.spec.AlgorithmParameterSpec;
  import java.security.spec.InvalidParameterSpecException;
  import java.util.Arrays;
  
  import javax.crypto.CipherSpi;
  import javax.crypto.spec.GCMParameterSpec;
  
  /**
   * Base class for Android Keystore authenticated AES {@link CipherSpi} implementations.
   *
   * @hide
   */
  abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase {
  
      abstract static class GCM extends AndroidKeyStoreAuthenticatedAESCipherSpi {
          static final int MIN_SUPPORTED_TAG_LENGTH_BITS = 96;
          private static final int MAX_SUPPORTED_TAG_LENGTH_BITS = 128;
          private static final int DEFAULT_TAG_LENGTH_BITS = 128;
          private static final int IV_LENGTH_BYTES = 12;
  
          private int mTagLengthBits = DEFAULT_TAG_LENGTH_BITS;
  
          GCM(int keymasterPadding) {
              super(KeymasterDefs.KM_MODE_GCM, keymasterPadding);
          }
  
          @Override
          protected final void resetAll() {
              mTagLengthBits = DEFAULT_TAG_LENGTH_BITS;
              super.resetAll();
          }
  
          @Override
          protected final void resetWhilePreservingInitState() {
              super.resetWhilePreservingInitState();
          }
  
          @Override
          protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {
              if (!isEncrypting()) {
                  throw new InvalidKeyException("IV required when decrypting"
                          + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
              }
          }
  
          @Override
          protected final void initAlgorithmSpecificParameters(AlgorithmParameterSpec params)
                  throws InvalidAlgorithmParameterException {
              // IV is used
              if (params == null) {
                  if (!isEncrypting()) {
                      // IV must be provided by the caller
                      throw new InvalidAlgorithmParameterException(
                              "GCMParameterSpec must be provided when decrypting");
                  }
                  return;
              }
              if (!(params instanceof GCMParameterSpec)) {
                  throw new InvalidAlgorithmParameterException("Only GCMParameterSpec supported");
              }
              GCMParameterSpec spec = (GCMParameterSpec) params;
              byte[] iv = spec.getIV();
              if (iv == null) {
                  throw new InvalidAlgorithmParameterException("Null IV in GCMParameterSpec");
              } else if (iv.length != IV_LENGTH_BYTES) {
                  throw new InvalidAlgorithmParameterException("Unsupported IV length: "
                          + iv.length + " bytes. Only " + IV_LENGTH_BYTES
                          + " bytes long IV supported");
              }
              int tagLengthBits = spec.getTLen();
              if ((tagLengthBits < MIN_SUPPORTED_TAG_LENGTH_BITS)
                      || (tagLengthBits > MAX_SUPPORTED_TAG_LENGTH_BITS)
                      || ((tagLengthBits % 8) != 0)) {
                  throw new InvalidAlgorithmParameterException(
                          "Unsupported tag length: " + tagLengthBits + " bits"
                          + ". Supported lengths: 96, 104, 112, 120, 128");
              }
              setIv(iv);
              mTagLengthBits = tagLengthBits;
          }
  
          @Override
          protected final void initAlgorithmSpecificParameters(AlgorithmParameters params)
                  throws InvalidAlgorithmParameterException {
              if (params == null) {
                  if (!isEncrypting()) {
                      // IV must be provided by the caller
                      throw new InvalidAlgorithmParameterException("IV required when decrypting"
                              + ". Use GCMParameterSpec or GCM AlgorithmParameters to provide it.");
                  }
                  return;
              }
  
              if (!"GCM".equalsIgnoreCase(params.getAlgorithm())) {
                  throw new InvalidAlgorithmParameterException(
                          "Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm()
                          + ". Supported: GCM");
              }
  
              GCMParameterSpec spec;
              try {
                  spec = params.getParameterSpec(GCMParameterSpec.class);
              } catch (InvalidParameterSpecException e) {
                  if (!isEncrypting()) {
                      // IV must be provided by the caller
                      throw new InvalidAlgorithmParameterException("IV and tag length required when"
                              + " decrypting, but not found in parameters: " + params, e);
                  }
                  setIv(null);
                  return;
              }
              initAlgorithmSpecificParameters(spec);
          }
  
          @Nullable
          @Override
          protected final AlgorithmParameters engineGetParameters() {
              byte[] iv = getIv();
              if ((iv != null) && (iv.length > 0)) {
                  try {
                      AlgorithmParameters params = AlgorithmParameters.getInstance("GCM");
                      params.init(new GCMParameterSpec(mTagLengthBits, iv));
                      return params;
                  } catch (NoSuchAlgorithmException e) {
                      throw new ProviderException(
                              "Failed to obtain GCM AlgorithmParameters", e);
                  } catch (InvalidParameterSpecException e) {
                      throw new ProviderException(
                              "Failed to initialize GCM AlgorithmParameters", e);
                  }
              }
              return null;
          }
  
          @NonNull
          @Override
          protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
                  KeyStore keyStore, IBinder operationToken) {
              KeyStoreCryptoOperationStreamer streamer = new KeyStoreCryptoOperationChunkedStreamer(
                      new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
                              keyStore, operationToken));
              if (isEncrypting()) {
                  return streamer;
              } else {
                  // When decrypting, to avoid leaking unauthenticated plaintext, do not return any
                  // plaintext before ciphertext is authenticated by KeyStore.finish.
                  return new BufferAllOutputUntilDoFinalStreamer(streamer);
              }
          }
  
          @NonNull
          @Override
          protected final KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer(
                  KeyStore keyStore, IBinder operationToken) {
              return new KeyStoreCryptoOperationChunkedStreamer(
                      new AdditionalAuthenticationDataStream(keyStore, operationToken));
          }
  
          @Override
          protected final int getAdditionalEntropyAmountForBegin() {
              if ((getIv() == null) && (isEncrypting())) {
                  // IV will need to be generated
                  return IV_LENGTH_BYTES;
              }
  
              return 0;
          }
  
          @Override
          protected final int getAdditionalEntropyAmountForFinish() {
              return 0;
          }
  
          @Override
          protected final void addAlgorithmSpecificParametersToBegin(
                  @NonNull KeymasterArguments keymasterArgs) {
              super.addAlgorithmSpecificParametersToBegin(keymasterArgs);
              keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mTagLengthBits);
          }
  
          protected final int getTagLengthBits() {
              return mTagLengthBits;
          }
  
          public static final class NoPadding extends GCM {
              public NoPadding() {
                  super(KeymasterDefs.KM_PAD_NONE);
              }
  
              @Override
              protected final int engineGetOutputSize(int inputLen) {
                  int tagLengthBytes = (getTagLengthBits() + 7) / 8;
                  long result;
                  if (isEncrypting()) {
                      result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen
                              + tagLengthBytes;
                  } else {
                      result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen
                              - tagLengthBytes;
                  }
                  if (result < 0) {
                      return 0;
                  } else if (result > Integer.MAX_VALUE) {
                      return Integer.MAX_VALUE;
                  }
                  return (int) result;
              }
          }
      }
  
      private static final int BLOCK_SIZE_BYTES = 16;
  
      private final int mKeymasterBlockMode;
      private final int mKeymasterPadding;
  
      private byte[] mIv;
  
      /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
      private boolean mIvHasBeenUsed;
  
      AndroidKeyStoreAuthenticatedAESCipherSpi(
              int keymasterBlockMode,
              int keymasterPadding) {
          mKeymasterBlockMode = keymasterBlockMode;
          mKeymasterPadding = keymasterPadding;
      }
  
      @Override
      protected void resetAll() {
          mIv = null;
          mIvHasBeenUsed = false;
          super.resetAll();
      }
  
      @Override
      protected final void initKey(int opmode, Key key) throws InvalidKeyException {
          if (!(key instanceof AndroidKeyStoreSecretKey)) {
              throw new InvalidKeyException(
                      "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
          }
          if (!KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(key.getAlgorithm())) {
              throw new InvalidKeyException(
                      "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " +
                      KeyProperties.KEY_ALGORITHM_AES + " supported");
          }
          setKey((AndroidKeyStoreSecretKey) key);
      }
  
      @Override
      protected void addAlgorithmSpecificParametersToBegin(
              @NonNull KeymasterArguments keymasterArgs) {
          if ((isEncrypting()) && (mIvHasBeenUsed)) {
              // IV is being reused for encryption: this violates security best practices.
              throw new IllegalStateException(
                      "IV has already been used. Reusing IV in encryption mode violates security best"
                      + " practices.");
          }
  
          keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
          keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
          keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
          if (mIv != null) {
              keymasterArgs.addBytes(KeymasterDefs.KM_TAG_NONCE, mIv);
          }
      }
  
      @Override
      protected final void loadAlgorithmSpecificParametersFromBeginResult(
              @NonNull KeymasterArguments keymasterArgs) {
          mIvHasBeenUsed = true;
  
          // NOTE: Keymaster doesn't always return an IV, even if it's used.
          byte[] returnedIv = keymasterArgs.getBytes(KeymasterDefs.KM_TAG_NONCE, null);
          if ((returnedIv != null) && (returnedIv.length == 0)) {
              returnedIv = null;
          }
  
          if (mIv == null) {
              mIv = returnedIv;
          } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
              throw new ProviderException("IV in use differs from provided IV");
          }
      }
  
      @Override
      protected final int engineGetBlockSize() {
          return BLOCK_SIZE_BYTES;
      }
  
      @Override
      protected final byte[] engineGetIV() {
          return ArrayUtils.cloneIfNotEmpty(mIv);
      }
  
      protected void setIv(byte[] iv) {
          mIv = iv;
      }
  
      protected byte[] getIv() {
          return mIv;
      }
  
      /**
       * {@link KeyStoreCryptoOperationStreamer} which buffers all output until {@code doFinal} from
       * which it returns all output in one go, provided {@code doFinal} succeeds.
       */
      private static class BufferAllOutputUntilDoFinalStreamer
          implements KeyStoreCryptoOperationStreamer {
  
          private final KeyStoreCryptoOperationStreamer mDelegate;
          private ByteArrayOutputStream mBufferedOutput = new ByteArrayOutputStream();
          private long mProducedOutputSizeBytes;
  
          private BufferAllOutputUntilDoFinalStreamer(KeyStoreCryptoOperationStreamer delegate) {
              mDelegate = delegate;
          }
  
          @Override
          public byte[] update(byte[] input, int inputOffset, int inputLength)
                  throws KeyStoreException {
              byte[] output = mDelegate.update(input, inputOffset, inputLength);
              if (output != null) {
                  try {
                      mBufferedOutput.write(output);
                  } catch (IOException e) {
                      throw new ProviderException("Failed to buffer output", e);
                  }
              }
              return EmptyArray.BYTE;
          }
  
          @Override
          public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
                  byte[] signature, byte[] additionalEntropy) throws KeyStoreException {
              byte[] output = mDelegate.doFinal(input, inputOffset, inputLength, signature,
                      additionalEntropy);
              if (output != null) {
                  try {
                      mBufferedOutput.write(output);
                  } catch (IOException e) {
                      throw new ProviderException("Failed to buffer output", e);
                  }
              }
              byte[] result = mBufferedOutput.toByteArray();
              mBufferedOutput.reset();
              mProducedOutputSizeBytes += result.length;
              return result;
          }
  
          @Override
          public long getConsumedInputSizeBytes() {
              return mDelegate.getConsumedInputSizeBytes();
          }
  
          @Override
          public long getProducedOutputSizeBytes() {
              return mProducedOutputSizeBytes;
          }
      }
  
      /**
       * Additional Authentication Data (AAD) stream via a KeyStore streaming operation. This stream
       * sends AAD into the KeyStore.
       */
      private static class AdditionalAuthenticationDataStream implements Stream {
  
          private final KeyStore mKeyStore;
          private final IBinder mOperationToken;
  
          private AdditionalAuthenticationDataStream(KeyStore keyStore, IBinder operationToken) {
              mKeyStore = keyStore;
              mOperationToken = operationToken;
          }
  
          @Override
          public OperationResult update(byte[] input) {
              KeymasterArguments keymasterArgs = new KeymasterArguments();
              keymasterArgs.addBytes(KeymasterDefs.KM_TAG_ASSOCIATED_DATA, input);
  
              // KeyStore does not reflect AAD in inputConsumed, but users of Stream rely on this
              // field. We fix this discrepancy here. KeyStore.update contract is that all of AAD
              // has been consumed if the method succeeds.
              OperationResult result = mKeyStore.update(mOperationToken, keymasterArgs, null);
              if (result.resultCode == KeyStore.NO_ERROR) {
                  result = new OperationResult(
                          result.resultCode,
                          result.token,
                          result.operationHandle,
                          input.length, // inputConsumed
                          result.output,
                          result.outParams);
              }
              return result;
          }
  
          @Override
          public OperationResult finish(byte[] signature, byte[] additionalEntropy) {
              if ((additionalEntropy != null) && (additionalEntropy.length > 0)) {
                  throw new ProviderException("AAD stream does not support additional entropy");
              }
              return new OperationResult(
                      KeyStore.NO_ERROR,
                      mOperationToken,
                      0, // operation handle -- nobody cares about this being returned from finish
                      0, // inputConsumed
                      EmptyArray.BYTE, // output
                      new KeymasterArguments() // additional params returned by finish
                      );
          }
      }
  }