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.os.IBinder;
  import android.security.KeyStore;
  import android.security.KeyStoreException;
  import android.security.keymaster.KeyCharacteristics;
  import android.security.keymaster.KeymasterArguments;
  import android.security.keymaster.KeymasterDefs;
  
  import libcore.util.EmptyArray;
  
  import java.io.ByteArrayOutputStream;
  import java.security.InvalidKeyException;
  import java.security.SignatureSpi;
  
  /**
   * Base class for {@link SignatureSpi} providing Android KeyStore backed ECDSA signatures.
   *
   * @hide
   */
  abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignatureSpiBase {
  
      public final static class NONE extends AndroidKeyStoreECDSASignatureSpi {
          public NONE() {
              super(KeymasterDefs.KM_DIGEST_NONE);
          }
  
          @Override
          protected KeyStoreCryptoOperationStreamer createMainDataStreamer(KeyStore keyStore,
                  IBinder operationToken) {
              return new TruncateToFieldSizeMessageStreamer(
                      super.createMainDataStreamer(keyStore, operationToken),
                      getGroupSizeBits());
          }
  
          /**
           * Streamer which buffers all input, then truncates it to field size, and then sends it into
           * KeyStore via the provided delegate streamer.
           */
          private static class TruncateToFieldSizeMessageStreamer
                  implements KeyStoreCryptoOperationStreamer {
  
              private final KeyStoreCryptoOperationStreamer mDelegate;
              private final int mGroupSizeBits;
              private final ByteArrayOutputStream mInputBuffer = new ByteArrayOutputStream();
              private long mConsumedInputSizeBytes;
  
              private TruncateToFieldSizeMessageStreamer(
                      KeyStoreCryptoOperationStreamer delegate,
                      int groupSizeBits) {
                  mDelegate = delegate;
                  mGroupSizeBits = groupSizeBits;
              }
  
              @Override
              public byte[] update(byte[] input, int inputOffset, int inputLength)
                      throws KeyStoreException {
                  if (inputLength > 0) {
                      mInputBuffer.write(input, inputOffset, inputLength);
                      mConsumedInputSizeBytes += inputLength;
                  }
                  return EmptyArray.BYTE;
              }
  
              @Override
              public byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature,
                      byte[] additionalEntropy) throws KeyStoreException {
                  if (inputLength > 0) {
                      mConsumedInputSizeBytes += inputLength;
                      mInputBuffer.write(input, inputOffset, inputLength);
                  }
  
                  byte[] bufferedInput = mInputBuffer.toByteArray();
                  mInputBuffer.reset();
                  // Truncate input at field size (bytes)
                  return mDelegate.doFinal(bufferedInput,
                          0,
                          Math.min(bufferedInput.length, ((mGroupSizeBits + 7) / 8)),
                          signature, additionalEntropy);
              }
  
              @Override
              public long getConsumedInputSizeBytes() {
                  return mConsumedInputSizeBytes;
              }
  
              @Override
              public long getProducedOutputSizeBytes() {
                  return mDelegate.getProducedOutputSizeBytes();
              }
          }
      }
  
      public final static class SHA1 extends AndroidKeyStoreECDSASignatureSpi {
          public SHA1() {
              super(KeymasterDefs.KM_DIGEST_SHA1);
          }
      }
  
      public final static class SHA224 extends AndroidKeyStoreECDSASignatureSpi {
          public SHA224() {
              super(KeymasterDefs.KM_DIGEST_SHA_2_224);
          }
      }
  
      public final static class SHA256 extends AndroidKeyStoreECDSASignatureSpi {
          public SHA256() {
              super(KeymasterDefs.KM_DIGEST_SHA_2_256);
          }
      }
  
      public final static class SHA384 extends AndroidKeyStoreECDSASignatureSpi {
          public SHA384() {
              super(KeymasterDefs.KM_DIGEST_SHA_2_384);
          }
      }
  
      public final static class SHA512 extends AndroidKeyStoreECDSASignatureSpi {
          public SHA512() {
              super(KeymasterDefs.KM_DIGEST_SHA_2_512);
          }
      }
  
      private final int mKeymasterDigest;
  
      private int mGroupSizeBits = -1;
  
      AndroidKeyStoreECDSASignatureSpi(int keymasterDigest) {
          mKeymasterDigest = keymasterDigest;
      }
  
      @Override
      protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
          if (!KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(key.getAlgorithm())) {
              throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
                      + ". Only" + KeyProperties.KEY_ALGORITHM_EC + " supported");
          }
  
          KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
          int errorCode = getKeyStore().getKeyCharacteristics(
                  key.getAlias(), null, null, key.getUid(), keyCharacteristics);
          if (errorCode != KeyStore.NO_ERROR) {
              throw getKeyStore().getInvalidKeyException(key.getAlias(), key.getUid(), errorCode);
          }
          long keySizeBits = keyCharacteristics.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, -1);
          if (keySizeBits == -1) {
              throw new InvalidKeyException("Size of key not known");
          } else if (keySizeBits > Integer.MAX_VALUE) {
              throw new InvalidKeyException("Key too large: " + keySizeBits + " bits");
          }
          mGroupSizeBits = (int) keySizeBits;
  
          super.initKey(key);
      }
  
      @Override
      protected final void resetAll() {
          mGroupSizeBits = -1;
          super.resetAll();
      }
  
      @Override
      protected final void resetWhilePreservingInitState() {
          super.resetWhilePreservingInitState();
      }
  
      @Override
      protected final void addAlgorithmSpecificParametersToBegin(
              @NonNull KeymasterArguments keymasterArgs) {
          keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_EC);
          keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
      }
  
      @Override
      protected final int getAdditionalEntropyAmountForSign() {
          return (mGroupSizeBits + 7) / 8;
      }
  
      protected final int getGroupSizeBits() {
          if (mGroupSizeBits == -1) {
              throw new IllegalStateException("Not initialized");
          }
          return mGroupSizeBits;
      }
  }
  