blob: 88d542123bde577967071d675611672eea161629 [file] [log] [blame]
/*
* Copyright (C) 2015 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 android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyInfo;
import android.security.keystore.KeyProperties;
import android.test.MoreAsserts;
import junit.framework.TestCase;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECGenParameterSpec;
import java.security.Provider.Service;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
public class KeyGeneratorTest extends TestCase {
private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_PROVIDER_NAME;
static final String[] EXPECTED_ALGORITHMS = {
"AES",
"HmacSHA1",
"HmacSHA224",
"HmacSHA256",
"HmacSHA384",
"HmacSHA512",
};
private static final Map<String, Integer> DEFAULT_KEY_SIZES =
new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
static {
DEFAULT_KEY_SIZES.put("AES", 128);
DEFAULT_KEY_SIZES.put("HmacSHA1", 160);
DEFAULT_KEY_SIZES.put("HmacSHA224", 224);
DEFAULT_KEY_SIZES.put("HmacSHA256", 256);
DEFAULT_KEY_SIZES.put("HmacSHA384", 384);
DEFAULT_KEY_SIZES.put("HmacSHA512", 512);
}
private static final int[] AES_SUPPORTED_KEY_SIZES = new int[] {128, 192, 256};
public void testAlgorithmList() {
// Assert that Android Keystore Provider exposes exactly the expected KeyGenerator
// algorithms. We don't care whether the algorithms are exposed via aliases, as long as
// canonical names of algorithms are accepted. If the Provider exposes extraneous
// algorithms, it'll be caught because it'll have to expose at least one Service for such an
// algorithm, and this Service's algorithm will not be in the expected set.
Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
Set<Service> services = provider.getServices();
Set<String> actualAlgsLowerCase = new HashSet<String>();
Set<String> expectedAlgsLowerCase = new HashSet<String>(
Arrays.asList(TestUtils.toLowerCase(EXPECTED_ALGORITHMS)));
for (Service service : services) {
if ("KeyGenerator".equalsIgnoreCase(service.getType())) {
String algLowerCase = service.getAlgorithm().toLowerCase(Locale.US);
actualAlgsLowerCase.add(algLowerCase);
}
}
TestUtils.assertContentsInAnyOrder(actualAlgsLowerCase,
expectedAlgsLowerCase.toArray(new String[0]));
}
public void testGenerateWithoutInitThrowsIllegalStateException() throws Exception {
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
try {
keyGenerator.generateKey();
fail();
} catch (IllegalStateException expected) {}
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testInitWithKeySizeThrowsUnsupportedOperationException() throws Exception {
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
int keySizeBits = DEFAULT_KEY_SIZES.get(algorithm);
try {
keyGenerator.init(keySizeBits);
fail();
} catch (UnsupportedOperationException expected) {}
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testInitWithKeySizeAndSecureRandomThrowsUnsupportedOperationException()
throws Exception {
SecureRandom rng = new SecureRandom();
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
int keySizeBits = DEFAULT_KEY_SIZES.get(algorithm);
try {
keyGenerator.init(keySizeBits, rng);
fail();
} catch (UnsupportedOperationException expected) {}
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testInitWithNullAlgParamsThrowsInvalidAlgorithmParameterException()
throws Exception {
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
try {
keyGenerator.init((AlgorithmParameterSpec) null);
fail();
} catch (InvalidAlgorithmParameterException expected) {}
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testInitWithNullAlgParamsAndSecureRandomThrowsInvalidAlgorithmParameterException()
throws Exception {
SecureRandom rng = new SecureRandom();
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
try {
keyGenerator.init((AlgorithmParameterSpec) null, rng);
fail();
} catch (InvalidAlgorithmParameterException expected) {}
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testInitWithAlgParamsAndNullSecureRandom()
throws Exception {
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
keyGenerator.init(getWorkingSpec().build(), (SecureRandom) null);
// Check that generateKey doesn't fail either, just in case null SecureRandom
// causes trouble there.
keyGenerator.generateKey();
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testInitWithUnsupportedAlgParamsTypeThrowsInvalidAlgorithmParameterException()
throws Exception {
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
try {
keyGenerator.init(new ECGenParameterSpec("secp256r1"));
fail();
} catch (InvalidAlgorithmParameterException expected) {}
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testDefaultKeySize() throws Exception {
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
int expectedSizeBits = DEFAULT_KEY_SIZES.get(algorithm);
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
keyGenerator.init(getWorkingSpec().build());
SecretKey key = keyGenerator.generateKey();
assertEquals(expectedSizeBits, TestUtils.getKeyInfo(key).getKeySize());
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testAesKeySupportedSizes() throws Exception {
KeyGenerator keyGenerator = getKeyGenerator("AES");
KeyGenParameterSpec.Builder goodSpec = getWorkingSpec();
CountingSecureRandom rng = new CountingSecureRandom();
for (int i = -16; i <= 512; i++) {
try {
rng.resetCounters();
KeyGenParameterSpec spec;
if (i >= 0) {
spec = TestUtils.buildUpon(goodSpec.setKeySize(i)).build();
} else {
try {
spec = TestUtils.buildUpon(goodSpec.setKeySize(i)).build();
fail();
} catch (IllegalArgumentException expected) {
continue;
}
}
rng.resetCounters();
if (TestUtils.contains(AES_SUPPORTED_KEY_SIZES, i)) {
keyGenerator.init(spec, rng);
SecretKey key = keyGenerator.generateKey();
assertEquals(i, TestUtils.getKeyInfo(key).getKeySize());
assertEquals((i + 7) / 8, rng.getOutputSizeBytes());
} else {
try {
keyGenerator.init(spec, rng);
fail();
} catch (InvalidAlgorithmParameterException expected) {}
assertEquals(0, rng.getOutputSizeBytes());
}
} catch (Throwable e) {
throw new RuntimeException("Failed to key size " + i, e);
}
}
}
public void testHmacKeySupportedSizes() throws Exception {
CountingSecureRandom rng = new CountingSecureRandom();
for (String algorithm : EXPECTED_ALGORITHMS) {
if (!TestUtils.isHmacAlgorithm(algorithm)) {
continue;
}
for (int i = -16; i <= 1024; i++) {
try {
rng.resetCounters();
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
KeyGenParameterSpec spec;
if (i >= 0) {
spec = getWorkingSpec().setKeySize(i).build();
} else {
try {
spec = getWorkingSpec().setKeySize(i).build();
fail();
} catch (IllegalArgumentException expected) {
continue;
}
}
if ((i > 0) && ((i % 8 ) == 0)) {
keyGenerator.init(spec, rng);
SecretKey key = keyGenerator.generateKey();
assertEquals(i, TestUtils.getKeyInfo(key).getKeySize());
assertEquals((i + 7) / 8, rng.getOutputSizeBytes());
} else {
try {
keyGenerator.init(spec, rng);
fail();
} catch (InvalidAlgorithmParameterException expected) {}
assertEquals(0, rng.getOutputSizeBytes());
}
} catch (Throwable e) {
throw new RuntimeException(
"Failed for " + algorithm + " with key size " + i, e);
}
}
}
}
public void testHmacKeyOnlyOneDigestCanBeAuthorized() throws Exception {
for (String algorithm : EXPECTED_ALGORITHMS) {
if (!TestUtils.isHmacAlgorithm(algorithm)) {
continue;
}
try {
String digest = TestUtils.getHmacAlgorithmDigest(algorithm);
assertNotNull(digest);
KeyGenParameterSpec.Builder goodSpec = new KeyGenParameterSpec.Builder(
"test1", KeyProperties.PURPOSE_SIGN);
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
// Digests authorization not specified in algorithm parameters
assertFalse(goodSpec.build().isDigestsSpecified());
keyGenerator.init(goodSpec.build());
SecretKey key = keyGenerator.generateKey();
TestUtils.assertContentsInAnyOrder(
Arrays.asList(TestUtils.getKeyInfo(key).getDigests()), digest);
// The same digest is specified in algorithm parameters
keyGenerator.init(TestUtils.buildUpon(goodSpec).setDigests(digest).build());
key = keyGenerator.generateKey();
TestUtils.assertContentsInAnyOrder(
Arrays.asList(TestUtils.getKeyInfo(key).getDigests()), digest);
// No digests specified in algorithm parameters
try {
keyGenerator.init(TestUtils.buildUpon(goodSpec).setDigests().build());
fail();
} catch (InvalidAlgorithmParameterException expected) {}
// A different digest specified in algorithm parameters
String anotherDigest = "SHA-256".equalsIgnoreCase(digest) ? "SHA-384" : "SHA-256";
try {
keyGenerator.init(TestUtils.buildUpon(goodSpec)
.setDigests(anotherDigest)
.build());
fail();
} catch (InvalidAlgorithmParameterException expected) {}
try {
keyGenerator.init(TestUtils.buildUpon(goodSpec)
.setDigests(digest, anotherDigest)
.build());
fail();
} catch (InvalidAlgorithmParameterException expected) {}
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testInitWithUnknownBlockModeFails() {
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
try {
keyGenerator.init(getWorkingSpec().setBlockModes("weird").build());
fail();
} catch (InvalidAlgorithmParameterException expected) {}
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testInitWithUnknownEncryptionPaddingFails() {
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
try {
keyGenerator.init(getWorkingSpec().setEncryptionPaddings("weird").build());
fail();
} catch (InvalidAlgorithmParameterException expected) {}
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testInitWithSignaturePaddingFails() {
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
try {
keyGenerator.init(getWorkingSpec()
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.build());
fail();
} catch (InvalidAlgorithmParameterException expected) {}
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testInitWithUnknownDigestFails() {
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
try {
String[] digests;
if (TestUtils.isHmacAlgorithm(algorithm)) {
// The digest from HMAC key algorithm must be specified in the list of
// authorized digests (if the list if provided).
digests = new String[] {algorithm, "weird"};
} else {
digests = new String[] {"weird"};
}
keyGenerator.init(getWorkingSpec().setDigests(digests).build());
fail();
} catch (InvalidAlgorithmParameterException expected) {}
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testInitWithKeyAlgorithmDigestMissingFromAuthorizedDigestFails() {
for (String algorithm : EXPECTED_ALGORITHMS) {
if (!TestUtils.isHmacAlgorithm(algorithm)) {
continue;
}
try {
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
// Authorized for digest(s) none of which is the one implied by key algorithm.
try {
String digest = TestUtils.getHmacAlgorithmDigest(algorithm);
String anotherDigest = KeyProperties.DIGEST_SHA256.equalsIgnoreCase(digest)
? KeyProperties.DIGEST_SHA512 : KeyProperties.DIGEST_SHA256;
keyGenerator.init(getWorkingSpec().setDigests(anotherDigest).build());
fail();
} catch (InvalidAlgorithmParameterException expected) {}
// Authorized for empty set of digests
try {
keyGenerator.init(getWorkingSpec().setDigests().build());
fail();
} catch (InvalidAlgorithmParameterException expected) {}
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testInitRandomizedEncryptionRequiredButViolatedFails() throws Exception {
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
try {
keyGenerator.init(getWorkingSpec(
KeyProperties.PURPOSE_ENCRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
.build());
fail();
} catch (InvalidAlgorithmParameterException expected) {}
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testGenerateHonorsRequestedAuthorizations() throws Exception {
Date keyValidityStart = new Date(System.currentTimeMillis() - TestUtils.DAY_IN_MILLIS);
Date keyValidityForOriginationEnd =
new Date(System.currentTimeMillis() + TestUtils.DAY_IN_MILLIS);
Date keyValidityForConsumptionEnd =
new Date(System.currentTimeMillis() + 3 * TestUtils.DAY_IN_MILLIS);
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
String[] blockModes =
new String[] {KeyProperties.BLOCK_MODE_GCM, KeyProperties.BLOCK_MODE_CBC};
String[] encryptionPaddings =
new String[] {KeyProperties.ENCRYPTION_PADDING_PKCS7,
KeyProperties.ENCRYPTION_PADDING_NONE};
String[] digests;
int purposes;
if (TestUtils.isHmacAlgorithm(algorithm)) {
// HMAC key can only be authorized for one digest, the one implied by the key's
// JCA algorithm name.
digests = new String[] {TestUtils.getHmacAlgorithmDigest(algorithm)};
purposes = KeyProperties.PURPOSE_SIGN;
} else {
digests = new String[] {KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA1};
purposes = KeyProperties.PURPOSE_DECRYPT;
}
KeyGenerator keyGenerator = getKeyGenerator(algorithm);
keyGenerator.init(getWorkingSpec(purposes)
.setBlockModes(blockModes)
.setEncryptionPaddings(encryptionPaddings)
.setDigests(digests)
.setKeyValidityStart(keyValidityStart)
.setKeyValidityForOriginationEnd(keyValidityForOriginationEnd)
.setKeyValidityForConsumptionEnd(keyValidityForConsumptionEnd)
.build());
SecretKey key = keyGenerator.generateKey();
assertEquals(algorithm, key.getAlgorithm());
KeyInfo keyInfo = TestUtils.getKeyInfo(key);
assertEquals(purposes, keyInfo.getPurposes());
TestUtils.assertContentsInAnyOrder(
Arrays.asList(blockModes), keyInfo.getBlockModes());
TestUtils.assertContentsInAnyOrder(
Arrays.asList(encryptionPaddings), keyInfo.getEncryptionPaddings());
TestUtils.assertContentsInAnyOrder(Arrays.asList(digests), keyInfo.getDigests());
MoreAsserts.assertEmpty(Arrays.asList(keyInfo.getSignaturePaddings()));
assertEquals(keyValidityStart, keyInfo.getKeyValidityStart());
assertEquals(keyValidityForOriginationEnd,
keyInfo.getKeyValidityForOriginationEnd());
assertEquals(keyValidityForConsumptionEnd,
keyInfo.getKeyValidityForConsumptionEnd());
assertFalse(keyInfo.isUserAuthenticationRequired());
assertFalse(keyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware());
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
private static KeyGenParameterSpec.Builder getWorkingSpec() {
return getWorkingSpec(0);
}
private static KeyGenParameterSpec.Builder getWorkingSpec(int purposes) {
return new KeyGenParameterSpec.Builder("test1", purposes);
}
private static KeyGenerator getKeyGenerator(String algorithm) throws NoSuchAlgorithmException,
NoSuchProviderException {
return KeyGenerator.getInstance(algorithm, EXPECTED_PROVIDER_NAME);
}
}