blob: d654e3ae4d405c8aa4ca6cf52006fe980ac94d09 [file] [log] [blame]
/*
* Copyright 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.KeyProperties;
import android.security.keystore.KeyProtection;
import junit.framework.TestCase;
import java.security.InvalidKeyException;
import java.security.Provider;
import java.security.Provider.Service;
import java.security.Security;
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.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class MacTest extends TestCase {
private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_CRYPTO_OP_PROVIDER_NAME;
private static final String[] EXPECTED_ALGORITHMS = {
"HmacSHA1",
"HmacSHA224",
"HmacSHA256",
"HmacSHA384",
"HmacSHA512",
};
private static final byte[] KAT_KEY = HexEncoding.decode(
"227b212bebd775493929ef626729a587d3f81b8e18a3ed482d403910e184479b448cfa79b62bd90595efdd"
+ "15f87bd7b2d2dac480c61e969ba90a7b8ceadd3284");
private static final byte[] SHORT_MSG_KAT_MESSAGE = HexEncoding.decode("a16037e3c901c9a1ab");
private static final Map<String, byte[]> SHORT_MSG_KAT_MACS =
new TreeMap<String, byte[]>(String.CASE_INSENSITIVE_ORDER);
static {
// From RI
SHORT_MSG_KAT_MACS.put("HmacSHA1", HexEncoding.decode(
"47d5677267f0efe1f7416bf504b210765674ef50"));
SHORT_MSG_KAT_MACS.put("HmacSHA224", HexEncoding.decode(
"03a8bbcd05e7166bff5b0b2368709a0c61c0b9d94f1b4d65c0e04948"));
SHORT_MSG_KAT_MACS.put("HmacSHA256", HexEncoding.decode(
"17feed3de0b2d53b69b228c2d9d26e9d57b314c50d36662596777f49445df729"));
SHORT_MSG_KAT_MACS.put("HmacSHA384", HexEncoding.decode(
"26e034c6696d28722ffc446ff0994f835e616cc704517d283a29648aee1eca5569c792ada8a176cdc7"
+ "813a87437e4ea0"));
SHORT_MSG_KAT_MACS.put("HmacSHA512", HexEncoding.decode(
"15cff189d754d6612bd18d157c8e59ac2ecd9a4b97b2ef343b7778130f7741795d5d2dc2b7addb9a36"
+ "7677ad57833b42bfa0733f49b57afd6bc32cddc0dcebec"));
}
private static final byte[] LONG_MSG_KAT_SEED = SHORT_MSG_KAT_MESSAGE;
private static final int LONG_MSG_KAT_SIZE_BYTES = 3 * 1024 * 1024 + 149;
private static final Map<String, byte[]> LONG_MSG_KAT_MACS =
new TreeMap<String, byte[]>(String.CASE_INSENSITIVE_ORDER);
static {
// From RI
LONG_MSG_KAT_MACS.put("HmacSHA1", HexEncoding.decode(
"2a89d12da79f541512db9c35c0a1e76750e01d48"));
LONG_MSG_KAT_MACS.put("HmacSHA224", HexEncoding.decode(
"5fef55c822f9b931c1b4ad7142e0a74ceaddf03f0a6533155cc06871"));
LONG_MSG_KAT_MACS.put("HmacSHA256", HexEncoding.decode(
"0bc25f22b8993d003a95a88c6cfa1c5a7b067a8aae1064ef897712418569bfe9"));
LONG_MSG_KAT_MACS.put("HmacSHA384", HexEncoding.decode(
"595a616295123966126102c06d69f8bb06c11090490186243420c2c4692877d75752b220c1b0447320"
+ "959e28345523fc"));
LONG_MSG_KAT_MACS.put("HmacSHA512", HexEncoding.decode(
"aa97d594d799164d56e6652578f7884d1198bb2663641ad7903e3c0bda4c136e9f94ca0d16c3504302"
+ "2944224e538e88a5410adb38eaa5169b3125738990e6d0"));
}
private static final long DAY_IN_MILLIS = 1000 * 60 * 60 * 24;
public void testAlgorithmList() {
// Assert that Android Keystore Provider exposes exactly the expected MAC algorithms. We
// don't care whether the algorithms are exposed via aliases, as long as the 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 ("Mac".equalsIgnoreCase(service.getType())) {
String algLowerCase = service.getAlgorithm().toLowerCase(Locale.US);
actualAlgsLowerCase.add(algLowerCase);
}
}
TestUtils.assertContentsInAnyOrder(actualAlgsLowerCase,
expectedAlgsLowerCase.toArray(new String[0]));
}
public void testAndroidKeyStoreKeysHandledByAndroidKeyStoreProvider() throws Exception {
SecretKey key = importDefaultKatKey();
Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
assertNotNull(provider);
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
// Generate a MAC
Mac mac = Mac.getInstance(algorithm);
mac.init(key);
assertSame(provider, mac.getProvider());
} catch (Exception e) {
throw new RuntimeException(algorithm + " failed", e);
}
}
}
public void testGeneratedSignatureVerifies() throws Exception {
SecretKey key = importDefaultKatKey();
Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
assertNotNull(provider);
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
// Generate a MAC
Mac mac = Mac.getInstance(algorithm);
mac.init(key);
byte[] message = "This is a test".getBytes("UTF-8");
byte[] macBytes = mac.doFinal(message);
assertMacVerifiesOneShot(algorithm, key, message, macBytes);
} catch (Exception e) {
throw new RuntimeException(algorithm + " failed", e);
}
}
}
public void testSmallMsgKat() throws Exception {
SecretKey key = importDefaultKatKey();
byte[] message = SHORT_MSG_KAT_MESSAGE;
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
byte[] goodMacBytes = SHORT_MSG_KAT_MACS.get(algorithm);
assertNotNull(goodMacBytes);
assertMacVerifiesOneShot(algorithm, key, message, goodMacBytes);
assertMacVerifiesFedOneByteAtATime(algorithm, key, message, goodMacBytes);
assertMacVerifiesFedUsingFixedSizeChunks(algorithm, key, message, goodMacBytes, 3);
byte[] messageWithBitFlip = message.clone();
messageWithBitFlip[messageWithBitFlip.length / 2] ^= 1;
assertMacDoesNotVerifyOneShot(algorithm, key, messageWithBitFlip, goodMacBytes);
byte[] goodMacWithBitFlip = goodMacBytes.clone();
goodMacWithBitFlip[goodMacWithBitFlip.length / 2] ^= 1;
assertMacDoesNotVerifyOneShot(algorithm, key, message, goodMacWithBitFlip);
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testLargeMsgKat() throws Exception {
SecretKey key = importDefaultKatKey();
byte[] message = TestUtils.generateLargeKatMsg(LONG_MSG_KAT_SEED, LONG_MSG_KAT_SIZE_BYTES);
for (String algorithm : EXPECTED_ALGORITHMS) {
try {
byte[] goodMacBytes = LONG_MSG_KAT_MACS.get(algorithm);
assertNotNull(goodMacBytes);
assertMacVerifiesOneShot(algorithm, key, message, goodMacBytes);
assertMacVerifiesFedUsingFixedSizeChunks(
algorithm, key, message, goodMacBytes, 20389);
assertMacVerifiesFedUsingFixedSizeChunks(
algorithm, key, message, goodMacBytes, 393571);
} catch (Throwable e) {
throw new RuntimeException("Failed for " + algorithm, e);
}
}
}
public void testInitFailsWhenNotAuthorizedToSign() throws Exception {
KeyProtection.Builder good = new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN);
int badPurposes = KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
| KeyProperties.PURPOSE_VERIFY;
String algorithm = "HmacSHA512";
assertInitSucceeds(algorithm, algorithm, good.build());
assertInitThrowsInvalidKeyException(algorithm, algorithm,
TestUtils.buildUpon(good, badPurposes).build());
}
public void testInitFailsWhenDigestNotAuthorized() throws Exception {
KeyProtection spec = new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN).build();
assertInitSucceeds("HmacSHA384", "HmacSHA384", spec);
assertInitThrowsInvalidKeyException("HmacSHA256", "HmacSHA384", spec);
}
public void testInitFailsWhenKeyNotYetValid() throws Exception {
KeyProtection.Builder good = new KeyProtection.Builder(
KeyProperties.PURPOSE_SIGN)
.setKeyValidityStart(new Date(System.currentTimeMillis() - DAY_IN_MILLIS));
String algorithm = "HmacSHA224";
assertInitSucceeds(algorithm, algorithm, good.build());
Date badStartDate = new Date(System.currentTimeMillis() + DAY_IN_MILLIS);
assertInitThrowsInvalidKeyException(algorithm, algorithm,
TestUtils.buildUpon(good).setKeyValidityStart(badStartDate).build());
}
public void testInitFailsWhenKeyNoLongerValid() throws Exception {
KeyProtection.Builder good = new KeyProtection.Builder(
KeyProperties.PURPOSE_SIGN)
.setKeyValidityForOriginationEnd(
new Date(System.currentTimeMillis() + DAY_IN_MILLIS));
String algorithm = "HmacSHA1";
assertInitSucceeds(algorithm, algorithm, good.build());
Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
assertInitThrowsInvalidKeyException(algorithm, algorithm,
TestUtils.buildUpon(good).setKeyValidityForOriginationEnd(badEndDate).build());
}
private void assertMacVerifiesOneShot(
String algorithm,
SecretKey key,
byte[] message,
byte[] mac) throws Exception {
Mac m = Mac.getInstance(algorithm);
m.init(key);
byte[] mac2 = m.doFinal(message);
if (!Arrays.equals(mac, mac2)) {
fail("MAC did not verify. algorithm: " + algorithm
+ ", provider: " + m.getProvider().getName()
+ ", MAC (" + mac.length + " bytes): " + HexEncoding.encode(mac)
+ ", actual MAC (" + mac2.length + " bytes): " + HexEncoding.encode(mac2));
}
}
private void assertMacDoesNotVerifyOneShot(
String algorithm,
SecretKey key,
byte[] message,
byte[] mac) throws Exception {
Mac m = Mac.getInstance(algorithm);
m.init(key);
byte[] mac2 = m.doFinal(message);
if (Arrays.equals(mac, mac2)) {
fail("MAC verifies unexpectedly. algorithm: " + algorithm
+ ", provider: " + m.getProvider().getName()
+ ", MAC (" + mac.length + " bytes): " + HexEncoding.encode(mac));
}
}
private void assertMacVerifiesFedOneByteAtATime(
String algorithm,
SecretKey key,
byte[] message,
byte[] mac) throws Exception {
Mac m = Mac.getInstance(algorithm);
m.init(key);
for (int i = 0; i < message.length; i++) {
m.update(message[i]);
}
byte[] mac2 = m.doFinal();
if (!Arrays.equals(mac, mac2)) {
fail("MAC did not verify. algorithm: " + algorithm
+ ", provider: " + m.getProvider().getName()
+ ", MAC (" + mac.length + " bytes): " + HexEncoding.encode(mac)
+ ", actual MAC (" + mac2.length + " bytes): " + HexEncoding.encode(mac2));
}
}
private void assertMacVerifiesFedUsingFixedSizeChunks(
String algorithm,
SecretKey key,
byte[] message,
byte[] mac,
int chunkSizeBytes) throws Exception {
Mac m = Mac.getInstance(algorithm);
m.init(key);
int messageRemaining = message.length;
int messageOffset = 0;
while (messageRemaining > 0) {
int actualChunkSizeBytes = Math.min(chunkSizeBytes, messageRemaining);
m.update(message, messageOffset, actualChunkSizeBytes);
messageOffset += actualChunkSizeBytes;
messageRemaining -= actualChunkSizeBytes;
}
byte[] mac2 = m.doFinal();
if (!Arrays.equals(mac, mac2)) {
fail("MAC did not verify. algorithm: " + algorithm
+ ", provider: " + m.getProvider().getName()
+ ", MAC (" + mac.length + " bytes): " + HexEncoding.encode(mac)
+ ", actual MAC (" + mac2.length + " bytes): " + HexEncoding.encode(mac2));
}
}
private void assertInitSucceeds(
String macAlgorithm, String keyAlgorithm, KeyProtection keyProtection)
throws Exception {
SecretKey key = importDefaultKatKey(keyAlgorithm, keyProtection);
Mac mac = Mac.getInstance(macAlgorithm);
mac.init(key);
}
private void assertInitThrowsInvalidKeyException(
String macAlgorithm, String keyAlgorithm, KeyProtection keyProtection)
throws Exception {
assertInitThrowsInvalidKeyException(null, macAlgorithm, keyAlgorithm, keyProtection);
}
private void assertInitThrowsInvalidKeyException(
String message, String macAlgorithm, String keyAlgorithm,
KeyProtection keyProtection) throws Exception {
SecretKey key = importDefaultKatKey(keyAlgorithm, keyProtection);
Mac mac = Mac.getInstance(macAlgorithm);
try {
mac.init(key);
fail(message);
} catch (InvalidKeyException expected) {}
}
private SecretKey importDefaultKatKey() throws Exception {
return importDefaultKatKey("HmacSHA1",
new KeyProtection.Builder(
KeyProperties.PURPOSE_SIGN)
.setDigests(KeyProperties.DIGEST_NONE,
KeyProperties.DIGEST_SHA1, // TODO: Remove these digests
KeyProperties.DIGEST_SHA224,
KeyProperties.DIGEST_SHA256,
KeyProperties.DIGEST_SHA384,
KeyProperties.DIGEST_SHA512)
.build());
}
private SecretKey importDefaultKatKey(
String keyAlgorithm, KeyProtection keyProtection) throws Exception {
return TestUtils.importIntoAndroidKeyStore(
"test1",
new SecretKeySpec(KAT_KEY, keyAlgorithm),
keyProtection);
}
}