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