Home | History | Annotate | Download | only in locksettings
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.locksettings;
     18 
     19 import android.security.keystore.KeyProperties;
     20 import android.security.keystore.KeyProtection;
     21 
     22 import java.io.ByteArrayOutputStream;
     23 import java.io.IOException;
     24 import java.security.InvalidAlgorithmParameterException;
     25 import java.security.InvalidKeyException;
     26 import java.security.KeyStore;
     27 import java.security.KeyStoreException;
     28 import java.security.MessageDigest;
     29 import java.security.NoSuchAlgorithmException;
     30 import java.security.SecureRandom;
     31 import java.security.UnrecoverableKeyException;
     32 import java.security.cert.CertificateException;
     33 import java.util.Arrays;
     34 
     35 import javax.crypto.BadPaddingException;
     36 import javax.crypto.Cipher;
     37 import javax.crypto.IllegalBlockSizeException;
     38 import javax.crypto.KeyGenerator;
     39 import javax.crypto.NoSuchPaddingException;
     40 import javax.crypto.SecretKey;
     41 import javax.crypto.spec.GCMParameterSpec;
     42 import javax.crypto.spec.SecretKeySpec;
     43 
     44 public class SyntheticPasswordCrypto {
     45     private static final int PROFILE_KEY_IV_SIZE = 12;
     46     private static final int AES_KEY_LENGTH = 32; // 256-bit AES key
     47     private static final byte[] APPLICATION_ID_PERSONALIZATION = "application-id".getBytes();
     48     // Time between the user credential is verified with GK and the decryption of synthetic password
     49     // under the auth-bound key. This should always happen one after the other, but give it 15
     50     // seconds just to be sure.
     51     private static final int USER_AUTHENTICATION_VALIDITY = 15;
     52 
     53     private static byte[] decrypt(SecretKey key, byte[] blob)
     54             throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
     55             InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
     56         if (blob == null) {
     57             return null;
     58         }
     59         byte[] iv = Arrays.copyOfRange(blob, 0, PROFILE_KEY_IV_SIZE);
     60         byte[] ciphertext = Arrays.copyOfRange(blob, PROFILE_KEY_IV_SIZE, blob.length);
     61         Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
     62                 + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
     63         cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
     64         return cipher.doFinal(ciphertext);
     65     }
     66 
     67     private static byte[] encrypt(SecretKey key, byte[] blob)
     68             throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
     69             InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
     70         if (blob == null) {
     71             return null;
     72         }
     73         Cipher cipher = Cipher.getInstance(
     74                 KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
     75                         + KeyProperties.ENCRYPTION_PADDING_NONE);
     76         cipher.init(Cipher.ENCRYPT_MODE, key);
     77         byte[] ciphertext = cipher.doFinal(blob);
     78         byte[] iv = cipher.getIV();
     79         if (iv.length != PROFILE_KEY_IV_SIZE) {
     80             throw new RuntimeException("Invalid iv length: " + iv.length);
     81         }
     82         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
     83         outputStream.write(iv);
     84         outputStream.write(ciphertext);
     85         return outputStream.toByteArray();
     86     }
     87 
     88     public static byte[] encrypt(byte[] keyBytes, byte[] personalisation, byte[] message) {
     89         byte[] keyHash = personalisedHash(personalisation, keyBytes);
     90         SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
     91                 KeyProperties.KEY_ALGORITHM_AES);
     92         try {
     93             return encrypt(key, message);
     94         } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
     95                 | IllegalBlockSizeException | BadPaddingException | IOException e) {
     96             e.printStackTrace();
     97             return null;
     98         }
     99     }
    100 
    101     public static byte[] decrypt(byte[] keyBytes, byte[] personalisation, byte[] ciphertext) {
    102         byte[] keyHash = personalisedHash(personalisation, keyBytes);
    103         SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
    104                 KeyProperties.KEY_ALGORITHM_AES);
    105         try {
    106             return decrypt(key, ciphertext);
    107         } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
    108                 | IllegalBlockSizeException | BadPaddingException
    109                 | InvalidAlgorithmParameterException e) {
    110             e.printStackTrace();
    111             return null;
    112         }
    113     }
    114 
    115     public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId) {
    116         try {
    117             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    118             keyStore.load(null);
    119 
    120             SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
    121             byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob);
    122             return decrypt(decryptionKey, intermediate);
    123         } catch (Exception e) {
    124             e.printStackTrace();
    125             throw new RuntimeException("Failed to decrypt blob", e);
    126         }
    127     }
    128 
    129     public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
    130         try {
    131             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    132             keyStore.load(null);
    133 
    134             SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
    135             byte[] intermediate = decrypt(decryptionKey, blob);
    136             return decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
    137         } catch (CertificateException | IOException | BadPaddingException
    138                 | IllegalBlockSizeException
    139                 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
    140                 | InvalidKeyException | UnrecoverableKeyException
    141                 | InvalidAlgorithmParameterException e) {
    142             e.printStackTrace();
    143             throw new RuntimeException("Failed to decrypt blob", e);
    144         }
    145     }
    146 
    147     public static byte[] createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid) {
    148         try {
    149             KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
    150             keyGenerator.init(new SecureRandom());
    151             SecretKey secretKey = keyGenerator.generateKey();
    152             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    153             keyStore.load(null);
    154             KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
    155                     .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
    156                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
    157                     .setCriticalToDeviceEncryption(true);
    158             if (sid != 0) {
    159                 builder.setUserAuthenticationRequired(true)
    160                         .setBoundToSpecificSecureUserId(sid)
    161                         .setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_VALIDITY);
    162             }
    163 
    164             keyStore.setEntry(keyAlias,
    165                     new KeyStore.SecretKeyEntry(secretKey),
    166                     builder.build());
    167             byte[] intermediate = encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, data);
    168             return encrypt(secretKey, intermediate);
    169         } catch (CertificateException | IOException | BadPaddingException
    170                 | IllegalBlockSizeException
    171                 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
    172                 | InvalidKeyException e) {
    173             e.printStackTrace();
    174             throw new RuntimeException("Failed to encrypt blob", e);
    175         }
    176     }
    177 
    178     public static void destroyBlobKey(String keyAlias) {
    179         KeyStore keyStore;
    180         try {
    181             keyStore = KeyStore.getInstance("AndroidKeyStore");
    182             keyStore.load(null);
    183             keyStore.deleteEntry(keyAlias);
    184         } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
    185                 | IOException e) {
    186             e.printStackTrace();
    187         }
    188     }
    189 
    190     protected static byte[] personalisedHash(byte[] personalisation, byte[]... message) {
    191         try {
    192             final int PADDING_LENGTH = 128;
    193             MessageDigest digest = MessageDigest.getInstance("SHA-512");
    194             if (personalisation.length > PADDING_LENGTH) {
    195                 throw new RuntimeException("Personalisation too long");
    196             }
    197             // Personalize the hash
    198             // Pad it to the block size of the hash function
    199             personalisation = Arrays.copyOf(personalisation, PADDING_LENGTH);
    200             digest.update(personalisation);
    201             for (byte[] data : message) {
    202                 digest.update(data);
    203             }
    204             return digest.digest();
    205         } catch (NoSuchAlgorithmException e) {
    206             throw new RuntimeException("NoSuchAlgorithmException for SHA-512", e);
    207         }
    208     }
    209 }
    210