Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright 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 android.keystore.cts;
     18 
     19 import static android.security.keymaster.KeymasterDefs.KM_ALGORITHM_3DES;
     20 import static android.security.keymaster.KeymasterDefs.KM_ALGORITHM_AES;
     21 import static android.security.keymaster.KeymasterDefs.KM_KEY_FORMAT_RAW;
     22 import static android.security.keymaster.KeymasterDefs.KM_MODE_CBC;
     23 import static android.security.keymaster.KeymasterDefs.KM_MODE_ECB;
     24 import static android.security.keymaster.KeymasterDefs.KM_PAD_NONE;
     25 import static android.security.keymaster.KeymasterDefs.KM_PAD_PKCS7;
     26 import static android.security.keymaster.KeymasterDefs.KM_PURPOSE_DECRYPT;
     27 import static android.security.keymaster.KeymasterDefs.KM_PURPOSE_ENCRYPT;
     28 import static android.security.keystore.KeyProperties.PURPOSE_WRAP_KEY;
     29 
     30 import android.content.pm.PackageManager;
     31 import android.os.SystemProperties;
     32 import android.security.keystore.KeyGenParameterSpec;
     33 import android.security.keystore.KeyProperties;
     34 import android.security.keystore.SecureKeyImportUnavailableException;
     35 import android.security.keystore.StrongBoxUnavailableException;
     36 import android.security.keystore.WrappedKeyEntry;
     37 import android.test.AndroidTestCase;
     38 
     39 import com.android.org.bouncycastle.asn1.ASN1Encoding;
     40 import com.android.org.bouncycastle.asn1.DEREncodableVector;
     41 import com.android.org.bouncycastle.asn1.DERInteger;
     42 import com.android.org.bouncycastle.asn1.DERNull;
     43 import com.android.org.bouncycastle.asn1.DEROctetString;
     44 import com.android.org.bouncycastle.asn1.DERSequence;
     45 import com.android.org.bouncycastle.asn1.DERSet;
     46 import com.android.org.bouncycastle.asn1.DERTaggedObject;
     47 
     48 import java.security.Key;
     49 import java.security.KeyPair;
     50 import java.security.KeyPairGenerator;
     51 import java.security.KeyStore;
     52 import java.security.KeyStore.Entry;
     53 import java.security.KeyStoreException;
     54 import java.security.PublicKey;
     55 import java.security.SecureRandom;
     56 import java.security.spec.AlgorithmParameterSpec;
     57 import java.security.spec.RSAKeyGenParameterSpec;
     58 import java.util.Arrays;
     59 
     60 import javax.crypto.Cipher;
     61 import javax.crypto.spec.GCMParameterSpec;
     62 import javax.crypto.spec.IvParameterSpec;
     63 import javax.crypto.spec.OAEPParameterSpec;
     64 import javax.crypto.spec.PSource;
     65 import javax.crypto.spec.SecretKeySpec;
     66 
     67 public class ImportWrappedKeyTest extends AndroidTestCase {
     68 
     69     private static final String ALIAS = "my key";
     70     private static final String WRAPPING_KEY_ALIAS = "my_favorite_wrapping_key";
     71 
     72     private static final int WRAPPED_FORMAT_VERSION = 0;
     73     private static final int GCM_TAG_SIZE = 128;
     74 
     75     SecureRandom random = new SecureRandom();
     76 
     77     public void testKeyStore_ImportWrappedKey() throws Exception {
     78         random.setSeed(0);
     79 
     80         byte[] keyMaterial = new byte[32];
     81         random.nextBytes(keyMaterial);
     82         byte[] mask = new byte[32]; // Zero mask
     83 
     84         KeyPair kp;
     85         try {
     86             kp = genKeyPair(WRAPPING_KEY_ALIAS, false);
     87         } catch (SecureKeyImportUnavailableException e) {
     88             return;
     89         }
     90 
     91         try {
     92             importWrappedKey(wrapKey(
     93                     kp.getPublic(),
     94                     keyMaterial,
     95                     mask,
     96                     makeAuthList(keyMaterial.length * 8, KM_ALGORITHM_AES)));
     97         } catch (SecureKeyImportUnavailableException e) {
     98             return;
     99         }
    100 
    101         // Use Key
    102         KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    103         keyStore.load(null, null);
    104 
    105         assertTrue("Failed to load key after wrapped import", keyStore.containsAlias(ALIAS));
    106 
    107         Key key = keyStore.getKey(ALIAS, null);
    108 
    109         Cipher c = Cipher.getInstance("AES/ECB/PKCS7Padding");
    110         c.init(Cipher.ENCRYPT_MODE, key);
    111         byte[] encrypted = c.doFinal("hello, world".getBytes());
    112 
    113         c = Cipher.getInstance("AES/ECB/PKCS7Padding");
    114         c.init(Cipher.DECRYPT_MODE, key);
    115 
    116         assertEquals(new String(c.doFinal(encrypted)), "hello, world");
    117     }
    118 
    119     public void testKeyStore_ImportWrappedKey_3DES() throws Exception {
    120       if (!TestUtils.supports3DES()) {
    121           return;
    122         }
    123 
    124         random.setSeed(0);
    125 
    126         byte[] keyMaterial = new byte[24]; //  192 bits in a 168-bit 3DES key
    127         random.nextBytes(keyMaterial);
    128         byte[] mask = new byte[32]; // Zero mask
    129 
    130         KeyPair kp;
    131         try {
    132             kp = genKeyPair(WRAPPING_KEY_ALIAS, false);
    133         } catch (SecureKeyImportUnavailableException e) {
    134             return;
    135         }
    136 
    137         try {
    138             importWrappedKey(wrapKey(
    139                     kp.getPublic(),
    140                     keyMaterial,
    141                     mask,
    142                     makeAuthList(168, KM_ALGORITHM_3DES)));
    143         } catch (SecureKeyImportUnavailableException e) {
    144             return;
    145         }
    146 
    147         // Use Key
    148         KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    149         keyStore.load(null, null);
    150 
    151         assertTrue("Failed to load key after wrapped import", keyStore.containsAlias(ALIAS));
    152 
    153         Key key = keyStore.getKey(ALIAS, null);
    154 
    155         Cipher c = Cipher.getInstance("DESede/CBC/PKCS7Padding");
    156         c.init(Cipher.ENCRYPT_MODE, key);
    157         IvParameterSpec paramSpec = new IvParameterSpec(c.getIV());
    158         byte[] encrypted = c.doFinal("hello, world".getBytes());
    159 
    160         c = Cipher.getInstance("DESede/CBC/PKCS7Padding");
    161         c.init(Cipher.DECRYPT_MODE, key, paramSpec);
    162 
    163         assertEquals(new String(c.doFinal(encrypted)), "hello, world");
    164     }
    165 
    166     public void testKeyStore_ImportWrappedKey_3DES_StrongBox() throws Exception {
    167       if (!TestUtils.supports3DES()) {
    168           return;
    169       }
    170 
    171       if (TestUtils.hasStrongBox(getContext())) {
    172             random.setSeed(0);
    173 
    174             byte[] keyMaterial = new byte[32];
    175             random.nextBytes(keyMaterial);
    176             byte[] mask = new byte[32]; // Zero mask
    177 
    178             importWrappedKey(wrapKey(
    179                     genKeyPair(WRAPPING_KEY_ALIAS, true).getPublic(),
    180                     keyMaterial,
    181                     mask,
    182                     makeAuthList(168, KM_ALGORITHM_3DES)));
    183         } else {
    184             try {
    185                 genKeyPair(WRAPPING_KEY_ALIAS, true);
    186                 fail();
    187             } catch (StrongBoxUnavailableException | SecureKeyImportUnavailableException e) {
    188             }
    189         }
    190     }
    191 
    192     public void testKeyStore_ImportWrappedKey_AES_StrongBox() throws Exception {
    193         if (TestUtils.hasStrongBox(getContext())) {
    194             random.setSeed(0);
    195 
    196             byte[] keyMaterial = new byte[32];
    197             random.nextBytes(keyMaterial);
    198             byte[] mask = new byte[32]; // Zero mask
    199 
    200             importWrappedKey(wrapKey(
    201                     genKeyPair(WRAPPING_KEY_ALIAS, true).getPublic(),
    202                     keyMaterial,
    203                     mask,
    204                     makeAuthList(keyMaterial.length * 8, KM_ALGORITHM_AES)));
    205         } else {
    206             try {
    207               random.setSeed(0);
    208 
    209               byte[] keyMaterial = new byte[32];
    210               random.nextBytes(keyMaterial);
    211               byte[] mask = new byte[32]; // Zero mask
    212 
    213               importWrappedKey(wrapKey(
    214                   genKeyPair(WRAPPING_KEY_ALIAS, true).getPublic(),
    215                   keyMaterial,
    216                   mask,
    217                   makeAuthList(keyMaterial.length * 8, KM_ALGORITHM_AES)));
    218                 fail();
    219             } catch (StrongBoxUnavailableException | SecureKeyImportUnavailableException e) {
    220             }
    221         }
    222     }
    223 
    224     public void importWrappedKey(byte[] wrappedKey) throws Exception {
    225         KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    226         keyStore.load(null, null);
    227 
    228         AlgorithmParameterSpec spec = new KeyGenParameterSpec.Builder(WRAPPING_KEY_ALIAS,
    229                 KeyProperties.PURPOSE_WRAP_KEY)
    230                 .setDigests(KeyProperties.DIGEST_SHA1)
    231                 .build();
    232         Entry wrappedKeyEntry = new WrappedKeyEntry(wrappedKey, WRAPPING_KEY_ALIAS,
    233                   "RSA/ECB/OAEPPadding", spec);
    234         keyStore.setEntry(ALIAS, wrappedKeyEntry, null);
    235     }
    236 
    237     public byte[] wrapKey(PublicKey publicKey, byte[] keyMaterial, byte[] mask,
    238             DERSequence authorizationList)
    239             throws Exception {
    240         // Build description
    241         DEREncodableVector descriptionItems = new DEREncodableVector();
    242         descriptionItems.add(new DERInteger(KM_KEY_FORMAT_RAW));
    243         descriptionItems.add(authorizationList);
    244         DERSequence wrappedKeyDescription = new DERSequence(descriptionItems);
    245 
    246         // Generate 12 byte initialization vector
    247         byte[] iv = new byte[12];
    248         random.nextBytes(iv);
    249 
    250         // Generate 256 bit AES key. This is the ephemeral key used to encrypt the secure key.
    251         byte[] aesKeyBytes = new byte[32];
    252         random.nextBytes(aesKeyBytes);
    253 
    254         // Encrypt ephemeral keys
    255         Cipher pkCipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
    256         pkCipher.init(Cipher.ENCRYPT_MODE, publicKey);
    257         byte[] encryptedEphemeralKeys = pkCipher.doFinal(aesKeyBytes);
    258 
    259         // Encrypt secure key
    260         Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    261         SecretKeySpec secretKeySpec = new SecretKeySpec(aesKeyBytes, "AES");
    262         GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_SIZE, iv);
    263         cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
    264         byte[] aad = wrappedKeyDescription.getEncoded();
    265 
    266         cipher.updateAAD(aad);
    267         byte[] encryptedSecureKey = cipher.doFinal(keyMaterial);
    268         // Get GCM tag. Java puts the tag at the end of the ciphertext data :(
    269         int len = encryptedSecureKey.length;
    270         int tagSize = (GCM_TAG_SIZE / 8);
    271         byte[] tag = Arrays.copyOfRange(encryptedSecureKey, len - tagSize, len);
    272 
    273         // Remove GCM tag from end of output
    274         encryptedSecureKey = Arrays.copyOfRange(encryptedSecureKey, 0, len - tagSize);
    275 
    276         // Build ASN.1 DER encoded sequence WrappedKeyWrapper
    277         DEREncodableVector items = new DEREncodableVector();
    278         items.add(new DERInteger(WRAPPED_FORMAT_VERSION));
    279         items.add(new DEROctetString(encryptedEphemeralKeys));
    280         items.add(new DEROctetString(iv));
    281         items.add(wrappedKeyDescription);
    282         items.add(new DEROctetString(encryptedSecureKey));
    283         items.add(new DEROctetString(tag));
    284         return new DERSequence(items).getEncoded(ASN1Encoding.DER);
    285     }
    286 
    287     /**
    288      * xor of two byte[] for masking or unmasking transit keys
    289      */
    290     private byte[] xor(byte[] key, byte[] mask) {
    291         byte[] out = new byte[key.length];
    292 
    293         for (int i = 0; i < key.length; i++) {
    294             out[i] = (byte) (key[i] ^ mask[i]);
    295         }
    296         return out;
    297     }
    298 
    299     private DERSequence makeAuthList(int size,
    300             int algorithm_) {
    301         // Make an AuthorizationList to describe the secure key
    302         // https://developer.android.com/training/articles/security-key-attestation.html#verifying
    303         DEREncodableVector allPurposes = new DEREncodableVector();
    304         allPurposes.add(new DERInteger(KM_PURPOSE_ENCRYPT));
    305         allPurposes.add(new DERInteger(KM_PURPOSE_DECRYPT));
    306         DERSet purposeSet = new DERSet(allPurposes);
    307         DERTaggedObject purpose = new DERTaggedObject(true, 1, purposeSet);
    308         DERTaggedObject algorithm = new DERTaggedObject(true, 2, new DERInteger(algorithm_));
    309         DERTaggedObject keySize =
    310                 new DERTaggedObject(true, 3, new DERInteger(size));
    311 
    312         DEREncodableVector allBlockModes = new DEREncodableVector();
    313         allBlockModes.add(new DERInteger(KM_MODE_ECB));
    314         allBlockModes.add(new DERInteger(KM_MODE_CBC));
    315         DERSet blockModeSet = new DERSet(allBlockModes);
    316         DERTaggedObject blockMode = new DERTaggedObject(true, 4, blockModeSet);
    317 
    318         DEREncodableVector allPaddings = new DEREncodableVector();
    319         allPaddings.add(new DERInteger(KM_PAD_PKCS7));
    320         allPaddings.add(new DERInteger(KM_PAD_NONE));
    321         DERSet paddingSet = new DERSet(allPaddings);
    322         DERTaggedObject padding = new DERTaggedObject(true, 6, paddingSet);
    323 
    324         DERTaggedObject noAuthRequired = new DERTaggedObject(true, 503, DERNull.INSTANCE);
    325 
    326         // Build sequence
    327         DEREncodableVector allItems = new DEREncodableVector();
    328         allItems.add(purpose);
    329         allItems.add(algorithm);
    330         allItems.add(keySize);
    331         allItems.add(blockMode);
    332         allItems.add(padding);
    333         allItems.add(noAuthRequired);
    334         return new DERSequence(allItems);
    335     }
    336 
    337     private KeyPair genKeyPair(String alias, boolean isStrongBoxBacked) throws Exception {
    338         KeyPairGenerator kpg =
    339                 KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
    340 
    341         kpg.initialize(
    342                 new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_WRAP_KEY)
    343                         .setDigests(KeyProperties.DIGEST_SHA512, KeyProperties.DIGEST_SHA1)
    344                         .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
    345                         .setBlockModes(KeyProperties.BLOCK_MODE_ECB)
    346                         .setIsStrongBoxBacked(isStrongBoxBacked)
    347                         .build());
    348         return kpg.generateKeyPair();
    349     }
    350 }
    351