content: /*
   * Copyright (C) 2017 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 com.android.server.locksettings;
  
  import android.security.keystore.KeyProperties;
  import android.security.keystore.KeyProtection;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.security.InvalidAlgorithmParameterException;
  import java.security.InvalidKeyException;
  import java.security.KeyStore;
  import java.security.KeyStoreException;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.security.SecureRandom;
  import java.security.UnrecoverableKeyException;
  import java.security.cert.CertificateException;
  import java.security.spec.InvalidParameterSpecException;
  import java.util.Arrays;
  
  import javax.crypto.BadPaddingException;
  import javax.crypto.Cipher;
  import javax.crypto.IllegalBlockSizeException;
  import javax.crypto.KeyGenerator;
  import javax.crypto.NoSuchPaddingException;
  import javax.crypto.SecretKey;
  import javax.crypto.spec.GCMParameterSpec;
  import javax.crypto.spec.SecretKeySpec;
  
  public class SyntheticPasswordCrypto {
      private static final int PROFILE_KEY_IV_SIZE = 12;
      private static final int DEFAULT_TAG_LENGTH_BITS = 128;
      private static final int AES_KEY_LENGTH = 32; // 256-bit AES key
      private static final byte[] APPLICATION_ID_PERSONALIZATION = "application-id".getBytes();
      // Time between the user credential is verified with GK and the decryption of synthetic password
      // under the auth-bound key. This should always happen one after the other, but give it 15
      // seconds just to be sure.
      private static final int USER_AUTHENTICATION_VALIDITY = 15;
  
      private static byte[] decrypt(SecretKey key, byte[] blob)
              throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
              InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
          if (blob == null) {
              return null;
          }
          byte[] iv = Arrays.copyOfRange(blob, 0, PROFILE_KEY_IV_SIZE);
          byte[] ciphertext = Arrays.copyOfRange(blob, PROFILE_KEY_IV_SIZE, blob.length);
          Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
                  + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
          cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(DEFAULT_TAG_LENGTH_BITS, iv));
          return cipher.doFinal(ciphertext);
      }
  
      private static byte[] encrypt(SecretKey key, byte[] blob)
              throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
              InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
              InvalidParameterSpecException {
          if (blob == null) {
              return null;
          }
          Cipher cipher = Cipher.getInstance(
                  KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
                          + KeyProperties.ENCRYPTION_PADDING_NONE);
          cipher.init(Cipher.ENCRYPT_MODE, key);
          byte[] ciphertext = cipher.doFinal(blob);
          byte[] iv = cipher.getIV();
          if (iv.length != PROFILE_KEY_IV_SIZE) {
              throw new RuntimeException("Invalid iv length: " + iv.length);
          }
          final GCMParameterSpec spec = cipher.getParameters().getParameterSpec(
                  GCMParameterSpec.class);
          if (spec.getTLen() != DEFAULT_TAG_LENGTH_BITS) {
              throw new RuntimeException("Invalid tag length: " + spec.getTLen());
          }
          ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
          outputStream.write(iv);
          outputStream.write(ciphertext);
          return outputStream.toByteArray();
      }
  
      public static byte[] encrypt(byte[] keyBytes, byte[] personalisation, byte[] message) {
          byte[] keyHash = personalisedHash(personalisation, keyBytes);
          SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
                  KeyProperties.KEY_ALGORITHM_AES);
          try {
              return encrypt(key, message);
          } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
                  | IllegalBlockSizeException | BadPaddingException | IOException
                  | InvalidParameterSpecException e) {
              e.printStackTrace();
              return null;
          }
      }
  
      public static byte[] decrypt(byte[] keyBytes, byte[] personalisation, byte[] ciphertext) {
          byte[] keyHash = personalisedHash(personalisation, keyBytes);
          SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
                  KeyProperties.KEY_ALGORITHM_AES);
          try {
              return decrypt(key, ciphertext);
          } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
                  | IllegalBlockSizeException | BadPaddingException
                  | InvalidAlgorithmParameterException e) {
              e.printStackTrace();
              return null;
          }
      }
  
      public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId) {
          try {
              KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
              keyStore.load(null);
  
              SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
              byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob);
              return decrypt(decryptionKey, intermediate);
          } catch (Exception e) {
              e.printStackTrace();
              throw new RuntimeException("Failed to decrypt blob", e);
          }
      }
  
      public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
          try {
              KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
              keyStore.load(null);
  
              SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
              byte[] intermediate = decrypt(decryptionKey, blob);
              return decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
          } catch (CertificateException | IOException | BadPaddingException
                  | IllegalBlockSizeException
                  | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
                  | InvalidKeyException | UnrecoverableKeyException
                  | InvalidAlgorithmParameterException e) {
              e.printStackTrace();
              throw new RuntimeException("Failed to decrypt blob", e);
          }
      }
  
      public static byte[] createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid) {
          try {
              KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
              keyGenerator.init(AES_KEY_LENGTH * 8, new SecureRandom());
              SecretKey secretKey = keyGenerator.generateKey();
              KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
              keyStore.load(null);
              KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
                      .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                      .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                      .setCriticalToDeviceEncryption(true);
              if (sid != 0) {
                  builder.setUserAuthenticationRequired(true)
                          .setBoundToSpecificSecureUserId(sid)
                          .setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_VALIDITY);
              }
  
              keyStore.setEntry(keyAlias,
                      new KeyStore.SecretKeyEntry(secretKey),
                      builder.build());
              byte[] intermediate = encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, data);
              return encrypt(secretKey, intermediate);
          } catch (CertificateException | IOException | BadPaddingException
                  | IllegalBlockSizeException
                  | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
                  | InvalidKeyException
                  | InvalidParameterSpecException e) {
              e.printStackTrace();
              throw new RuntimeException("Failed to encrypt blob", e);
          }
      }
  
      public static void destroyBlobKey(String keyAlias) {
          KeyStore keyStore;
          try {
              keyStore = KeyStore.getInstance("AndroidKeyStore");
              keyStore.load(null);
              keyStore.deleteEntry(keyAlias);
          } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
                  | IOException e) {
              e.printStackTrace();
          }
      }
  
      protected static byte[] personalisedHash(byte[] personalisation, byte[]... message) {
          try {
              final int PADDING_LENGTH = 128;
              MessageDigest digest = MessageDigest.getInstance("SHA-512");
              if (personalisation.length > PADDING_LENGTH) {
                  throw new RuntimeException("Personalisation too long");
              }
              // Personalize the hash
              // Pad it to the block size of the hash function
              personalisation = Arrays.copyOf(personalisation, PADDING_LENGTH);
              digest.update(personalisation);
              for (byte[] data : message) {
                  digest.update(data);
              }
              return digest.digest();
          } catch (NoSuchAlgorithmException e) {
              throw new RuntimeException("NoSuchAlgorithmException for SHA-512", e);
          }
      }
  }
  