blob: e96743899e9734222570188ba229746b0f2bf6c4 [file] [log] [blame]
/*
* Copyright 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 android.keystore.cts;
import static android.security.keymaster.KeymasterDefs.KM_ALGORITHM_3DES;
import static android.security.keymaster.KeymasterDefs.KM_ALGORITHM_AES;
import static android.security.keymaster.KeymasterDefs.KM_KEY_FORMAT_RAW;
import static android.security.keymaster.KeymasterDefs.KM_MODE_CBC;
import static android.security.keymaster.KeymasterDefs.KM_MODE_ECB;
import static android.security.keymaster.KeymasterDefs.KM_PAD_NONE;
import static android.security.keymaster.KeymasterDefs.KM_PAD_PKCS7;
import static android.security.keymaster.KeymasterDefs.KM_PURPOSE_DECRYPT;
import static android.security.keymaster.KeymasterDefs.KM_PURPOSE_ENCRYPT;
import static android.security.keystore.KeyProperties.PURPOSE_WRAP_KEY;
import android.content.pm.PackageManager;
import android.os.SystemProperties;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.SecureKeyImportUnavailableException;
import android.security.keystore.StrongBoxUnavailableException;
import android.security.keystore.WrappedKeyEntry;
import android.test.AndroidTestCase;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.DEREncodableVector;
import org.bouncycastle.asn1.DERInteger;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.DERTaggedObject;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStore.Entry;
import java.security.KeyStoreException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import javax.crypto.spec.SecretKeySpec;
import java.lang.Process;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.lang.InterruptedException;
public class ImportWrappedKeyTest extends AndroidTestCase {
private static final String TAG = "ImportWrappedKeyTest";
private static final String ALIAS = "my key";
private static final String WRAPPING_KEY_ALIAS = "my_favorite_wrapping_key";
private static final int WRAPPED_FORMAT_VERSION = 0;
private static final int GCM_TAG_SIZE = 128;
SecureRandom random = new SecureRandom();
public void testKeyStore_ImportWrappedKey() throws Exception {
random.setSeed(0);
byte[] keyMaterial = new byte[32];
random.nextBytes(keyMaterial);
byte[] mask = new byte[32]; // Zero mask
KeyPair kp;
try {
kp = genKeyPair(WRAPPING_KEY_ALIAS, false);
} catch (SecureKeyImportUnavailableException e) {
return;
}
try {
importWrappedKey(wrapKey(
kp.getPublic(),
keyMaterial,
mask,
makeAuthList(keyMaterial.length * 8, KM_ALGORITHM_AES)));
} catch (SecureKeyImportUnavailableException e) {
return;
}
// Use Key
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null, null);
assertTrue("Failed to load key after wrapped import", keyStore.containsAlias(ALIAS));
Key key = keyStore.getKey(ALIAS, null);
Cipher c = Cipher.getInstance("AES/ECB/PKCS7Padding");
c.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = c.doFinal("hello, world".getBytes());
c = Cipher.getInstance("AES/ECB/PKCS7Padding");
c.init(Cipher.DECRYPT_MODE, key);
assertEquals(new String(c.doFinal(encrypted)), "hello, world");
}
public void testKeyStore_ImportWrappedKeyWrappingKeyMissing() throws Exception {
final String EXPECTED_FAILURE = "Failed to import wrapped key. Keystore error code: 7";
String failureMessage = null;
try {
byte [] fakeWrappedKey = new byte[1];
importWrappedKey(fakeWrappedKey, WRAPPING_KEY_ALIAS + "_Missing");
} catch (KeyStoreException e) {
failureMessage = e.getMessage();
}
if (failureMessage == null) {
fail("Did not hit a failure but expected one");
}
assertEquals(failureMessage, EXPECTED_FAILURE);
}
public void testKeyStore_ImportWrappedKey_3DES() throws Exception {
if (!TestUtils.supports3DES()) {
return;
}
KeyGenerator kg = KeyGenerator.getInstance("DESEDE");
kg.init(168);
byte[] keyMaterial = kg.generateKey().getEncoded();
random.nextBytes(keyMaterial);
byte[] mask = new byte[24]; // Zero mask
KeyPair kp;
try {
kp = genKeyPair(WRAPPING_KEY_ALIAS, false);
} catch (SecureKeyImportUnavailableException e) {
return;
}
try {
importWrappedKey(wrapKey(
kp.getPublic(),
keyMaterial,
mask,
makeAuthList(168, KM_ALGORITHM_3DES)));
} catch (SecureKeyImportUnavailableException e) {
return;
}
// Use Key
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null, null);
assertTrue("Failed to load key after wrapped import", keyStore.containsAlias(ALIAS));
Key key = keyStore.getKey(ALIAS, null);
Cipher c = Cipher.getInstance("DESede/CBC/PKCS7Padding");
c.init(Cipher.ENCRYPT_MODE, key);
IvParameterSpec paramSpec = new IvParameterSpec(c.getIV());
byte[] encrypted = c.doFinal("hello, world".getBytes());
c = Cipher.getInstance("DESede/CBC/PKCS7Padding");
c.init(Cipher.DECRYPT_MODE, key, paramSpec);
assertEquals(new String(c.doFinal(encrypted)), "hello, world");
}
public void testKeyStore_ImportWrappedKey_3DES_StrongBox() throws Exception {
if (!TestUtils.supports3DES()) {
return;
}
if (TestUtils.hasStrongBox(getContext())) {
KeyGenerator kg = KeyGenerator.getInstance("DESEDE");
kg.init(168);
byte[] keyMaterial = kg.generateKey().getEncoded();
random.nextBytes(keyMaterial);
byte[] mask = new byte[24]; // Zero mask
importWrappedKey(wrapKey(
genKeyPair(WRAPPING_KEY_ALIAS, true).getPublic(),
keyMaterial,
mask,
makeAuthList(168, KM_ALGORITHM_3DES)));
// Use Key
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null, null);
assertTrue("Failed to load key after wrapped import", keyStore.containsAlias(ALIAS));
Key key = keyStore.getKey(ALIAS, null);
Cipher c = Cipher.getInstance("DESede/CBC/PKCS7Padding");
c.init(Cipher.ENCRYPT_MODE, key);
IvParameterSpec paramSpec = new IvParameterSpec(c.getIV());
byte[] encrypted = c.doFinal("hello, world".getBytes());
c = Cipher.getInstance("DESede/CBC/PKCS7Padding");
c.init(Cipher.DECRYPT_MODE, key, paramSpec);
assertEquals(new String(c.doFinal(encrypted)), "hello, world");
} else {
try {
genKeyPair(WRAPPING_KEY_ALIAS, true);
fail();
} catch (StrongBoxUnavailableException | SecureKeyImportUnavailableException e) {
}
}
}
public void testKeyStore_ImportWrappedKey_AES_StrongBox() throws Exception {
if (TestUtils.hasStrongBox(getContext())) {
random.setSeed(0);
byte[] keyMaterial = new byte[32];
random.nextBytes(keyMaterial);
byte[] mask = new byte[32]; // Zero mask
importWrappedKey(wrapKey(
genKeyPair(WRAPPING_KEY_ALIAS, true).getPublic(),
keyMaterial,
mask,
makeAuthList(keyMaterial.length * 8, KM_ALGORITHM_AES)));
// Use Key
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null, null);
assertTrue("Failed to load key after wrapped import", keyStore.containsAlias(ALIAS));
Key key = keyStore.getKey(ALIAS, null);
Cipher c = Cipher.getInstance("AES/CBC/PKCS7Padding");
c.init(Cipher.ENCRYPT_MODE, key);
IvParameterSpec paramSpec = new IvParameterSpec(c.getIV());
byte[] encrypted = c.doFinal("hello, world".getBytes());
c = Cipher.getInstance("AES/CBC/PKCS7Padding");
c.init(Cipher.DECRYPT_MODE, key, paramSpec);
assertEquals(new String(c.doFinal(encrypted)), "hello, world");
} else {
try {
random.setSeed(0);
byte[] keyMaterial = new byte[32];
random.nextBytes(keyMaterial);
byte[] mask = new byte[32]; // Zero mask
importWrappedKey(wrapKey(
genKeyPair(WRAPPING_KEY_ALIAS, true).getPublic(),
keyMaterial,
mask,
makeAuthList(keyMaterial.length * 8, KM_ALGORITHM_AES)));
fail();
} catch (StrongBoxUnavailableException | SecureKeyImportUnavailableException e) {
}
}
}
public void importWrappedKey(byte[] wrappedKey, String wrappingKeyAlias) throws Exception {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null, null);
AlgorithmParameterSpec spec = new KeyGenParameterSpec.Builder(wrappingKeyAlias,
KeyProperties.PURPOSE_WRAP_KEY)
.setDigests(KeyProperties.DIGEST_SHA256)
.build();
Entry wrappedKeyEntry = new WrappedKeyEntry(wrappedKey, wrappingKeyAlias,
"RSA/ECB/OAEPPadding", spec);
keyStore.setEntry(ALIAS, wrappedKeyEntry, null);
}
public void importWrappedKey(byte[] wrappedKey) throws Exception {
importWrappedKey(wrappedKey, WRAPPING_KEY_ALIAS);
}
public byte[] wrapKey(PublicKey publicKey, byte[] keyMaterial, byte[] mask,
DERSequence authorizationList)
throws Exception {
// Build description
DEREncodableVector descriptionItems = new DEREncodableVector();
descriptionItems.add(new DERInteger(KM_KEY_FORMAT_RAW));
descriptionItems.add(authorizationList);
DERSequence wrappedKeyDescription = new DERSequence(descriptionItems);
// Generate 12 byte initialization vector
byte[] iv = new byte[12];
random.nextBytes(iv);
// Generate 256 bit AES key. This is the ephemeral key used to encrypt the secure key.
byte[] aesKeyBytes = new byte[32];
random.nextBytes(aesKeyBytes);
// Encrypt ephemeral keys
OAEPParameterSpec spec = new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);
Cipher pkCipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
pkCipher.init(Cipher.ENCRYPT_MODE, publicKey, spec);
byte[] encryptedEphemeralKeys = pkCipher.doFinal(aesKeyBytes);
// Encrypt secure key
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec secretKeySpec = new SecretKeySpec(aesKeyBytes, "AES");
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_SIZE, iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
byte[] aad = wrappedKeyDescription.getEncoded();
cipher.updateAAD(aad);
byte[] encryptedSecureKey = cipher.doFinal(keyMaterial);
// Get GCM tag. Java puts the tag at the end of the ciphertext data :(
int len = encryptedSecureKey.length;
int tagSize = (GCM_TAG_SIZE / 8);
byte[] tag = Arrays.copyOfRange(encryptedSecureKey, len - tagSize, len);
// Remove GCM tag from end of output
encryptedSecureKey = Arrays.copyOfRange(encryptedSecureKey, 0, len - tagSize);
// Build ASN.1 DER encoded sequence WrappedKeyWrapper
DEREncodableVector items = new DEREncodableVector();
items.add(new DERInteger(WRAPPED_FORMAT_VERSION));
items.add(new DEROctetString(encryptedEphemeralKeys));
items.add(new DEROctetString(iv));
items.add(wrappedKeyDescription);
items.add(new DEROctetString(encryptedSecureKey));
items.add(new DEROctetString(tag));
return new DERSequence(items).getEncoded(ASN1Encoding.DER);
}
/**
* xor of two byte[] for masking or unmasking transit keys
*/
private byte[] xor(byte[] key, byte[] mask) {
byte[] out = new byte[key.length];
for (int i = 0; i < key.length; i++) {
out[i] = (byte) (key[i] ^ mask[i]);
}
return out;
}
private DERSequence makeAuthList(int size,
int algorithm_) {
// Make an AuthorizationList to describe the secure key
// https://developer.android.com/training/articles/security-key-attestation.html#verifying
DEREncodableVector allPurposes = new DEREncodableVector();
allPurposes.add(new DERInteger(KM_PURPOSE_ENCRYPT));
allPurposes.add(new DERInteger(KM_PURPOSE_DECRYPT));
DERSet purposeSet = new DERSet(allPurposes);
DERTaggedObject purpose = new DERTaggedObject(true, 1, purposeSet);
DERTaggedObject algorithm = new DERTaggedObject(true, 2, new DERInteger(algorithm_));
DERTaggedObject keySize =
new DERTaggedObject(true, 3, new DERInteger(size));
DEREncodableVector allBlockModes = new DEREncodableVector();
allBlockModes.add(new DERInteger(KM_MODE_ECB));
allBlockModes.add(new DERInteger(KM_MODE_CBC));
DERSet blockModeSet = new DERSet(allBlockModes);
DERTaggedObject blockMode = new DERTaggedObject(true, 4, blockModeSet);
DEREncodableVector allPaddings = new DEREncodableVector();
allPaddings.add(new DERInteger(KM_PAD_PKCS7));
allPaddings.add(new DERInteger(KM_PAD_NONE));
DERSet paddingSet = new DERSet(allPaddings);
DERTaggedObject padding = new DERTaggedObject(true, 6, paddingSet);
DERTaggedObject noAuthRequired = new DERTaggedObject(true, 503, DERNull.INSTANCE);
// Build sequence
DEREncodableVector allItems = new DEREncodableVector();
allItems.add(purpose);
allItems.add(algorithm);
allItems.add(keySize);
allItems.add(blockMode);
allItems.add(padding);
allItems.add(noAuthRequired);
return new DERSequence(allItems);
}
private KeyPair genKeyPair(String alias, boolean isStrongBoxBacked) throws Exception {
KeyPairGenerator kpg =
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
kpg.initialize(
new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_WRAP_KEY)
.setDigests(KeyProperties.DIGEST_SHA256)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
.setIsStrongBoxBacked(isStrongBoxBacked)
.build());
return kpg.generateKeyPair();
}
}