blob: b1ba453551197b13cdd80c94151b614bb09c8852 [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.content.Context;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyInfo;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.test.MoreAsserts;
import junit.framework.Assert;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.UnrecoverableEntryException;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.EllipticCurve;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.SecretKeySpec;
abstract class TestUtils extends Assert {
static final String EXPECTED_CRYPTO_OP_PROVIDER_NAME = "AndroidKeyStoreBCWorkaround";
static final String EXPECTED_PROVIDER_NAME = "AndroidKeyStore";
static final long DAY_IN_MILLIS = 1000 * 60 * 60 * 24;
private TestUtils() {}
/**
* Asserts the the key algorithm and algorithm-specific parameters of the two keys in the
* provided pair match.
*/
static void assertKeyPairSelfConsistent(KeyPair keyPair) {
assertKeyPairSelfConsistent(keyPair.getPublic(), keyPair.getPrivate());
}
/**
* Asserts the the key algorithm and public algorithm-specific parameters of the two provided
* keys match.
*/
static void assertKeyPairSelfConsistent(PublicKey publicKey, PrivateKey privateKey) {
assertNotNull(publicKey);
assertNotNull(privateKey);
assertEquals(publicKey.getAlgorithm(), privateKey.getAlgorithm());
String keyAlgorithm = publicKey.getAlgorithm();
if ("EC".equalsIgnoreCase(keyAlgorithm)) {
assertTrue("EC public key must be instanceof ECKey: "
+ publicKey.getClass().getName(),
publicKey instanceof ECKey);
assertTrue("EC private key must be instanceof ECKey: "
+ privateKey.getClass().getName(),
privateKey instanceof ECKey);
assertECParameterSpecEqualsIgnoreSeedIfNotPresent(
"Private key must have the same EC parameters as public key",
((ECKey) publicKey).getParams(), ((ECKey) privateKey).getParams());
} else if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
assertTrue("RSA public key must be instance of RSAKey: "
+ publicKey.getClass().getName(),
publicKey instanceof RSAKey);
assertTrue("RSA private key must be instance of RSAKey: "
+ privateKey.getClass().getName(),
privateKey instanceof RSAKey);
assertEquals("Private and public key must have the same RSA modulus",
((RSAKey) publicKey).getModulus(), ((RSAKey) privateKey).getModulus());
} else {
fail("Unsuported key algorithm: " + keyAlgorithm);
}
}
static int getKeySizeBits(Key key) {
if (key instanceof ECKey) {
return ((ECKey) key).getParams().getCurve().getField().getFieldSize();
} else if (key instanceof RSAKey) {
return ((RSAKey) key).getModulus().bitLength();
} else {
throw new IllegalArgumentException("Unsupported key type: " + key.getClass());
}
}
static void assertKeySize(int expectedSizeBits, KeyPair keyPair) {
assertEquals(expectedSizeBits, getKeySizeBits(keyPair.getPrivate()));
assertEquals(expectedSizeBits, getKeySizeBits(keyPair.getPublic()));
}
/**
* Asserts that the provided key pair is an Android Keystore key pair stored under the provided
* alias.
*/
static void assertKeyStoreKeyPair(KeyStore keyStore, String alias, KeyPair keyPair) {
assertKeyMaterialExportable(keyPair.getPublic());
assertKeyMaterialNotExportable(keyPair.getPrivate());
assertTransparentKey(keyPair.getPublic());
assertOpaqueKey(keyPair.getPrivate());
KeyStore.Entry entry;
Certificate cert;
try {
entry = keyStore.getEntry(alias, null);
cert = keyStore.getCertificate(alias);
} catch (KeyStoreException | UnrecoverableEntryException | NoSuchAlgorithmException e) {
throw new RuntimeException("Failed to load entry: " + alias, e);
}
assertNotNull(entry);
assertTrue(entry instanceof KeyStore.PrivateKeyEntry);
KeyStore.PrivateKeyEntry privEntry = (KeyStore.PrivateKeyEntry) entry;
assertEquals(cert, privEntry.getCertificate());
assertTrue("Certificate must be an X.509 certificate: " + cert.getClass(),
cert instanceof X509Certificate);
final X509Certificate x509Cert = (X509Certificate) cert;
PrivateKey keystorePrivateKey = privEntry.getPrivateKey();
PublicKey keystorePublicKey = cert.getPublicKey();
assertEquals(keyPair.getPrivate(), keystorePrivateKey);
assertEquals(keyPair.getPublic(), keystorePublicKey);
assertEquals(
"Public key used to sign certificate should have the same algorithm as in KeyPair",
keystorePublicKey.getAlgorithm(), x509Cert.getPublicKey().getAlgorithm());
Certificate[] chain = privEntry.getCertificateChain();
if (chain.length == 0) {
fail("Empty certificate chain");
return;
}
assertEquals(cert, chain[0]);
}
private static void assertKeyMaterialExportable(Key key) {
if (key instanceof PublicKey) {
assertEquals("X.509", key.getFormat());
} else if (key instanceof PrivateKey) {
assertEquals("PKCS#8", key.getFormat());
} else if (key instanceof SecretKey) {
assertEquals("RAW", key.getFormat());
} else {
fail("Unsupported key type: " + key.getClass().getName());
}
byte[] encodedForm = key.getEncoded();
assertNotNull(encodedForm);
if (encodedForm.length == 0) {
fail("Empty encoded form");
}
}
private static void assertKeyMaterialNotExportable(Key key) {
assertEquals(null, key.getFormat());
assertEquals(null, key.getEncoded());
}
private static void assertOpaqueKey(Key key) {
assertFalse(key.getClass().getName() + " is a transparent key", isTransparentKey(key));
}
private static void assertTransparentKey(Key key) {
assertTrue(key.getClass().getName() + " is not a transparent key", isTransparentKey(key));
}
private static boolean isTransparentKey(Key key) {
if (key instanceof PrivateKey) {
return (key instanceof ECPrivateKey) || (key instanceof RSAPrivateKey);
} else if (key instanceof PublicKey) {
return (key instanceof ECPublicKey) || (key instanceof RSAPublicKey);
} else if (key instanceof SecretKey) {
return (key instanceof SecretKeySpec);
} else {
throw new IllegalArgumentException("Unsupported key type: " + key.getClass().getName());
}
}
static void assertECParameterSpecEqualsIgnoreSeedIfNotPresent(
ECParameterSpec expected, ECParameterSpec actual) {
assertECParameterSpecEqualsIgnoreSeedIfNotPresent(null, expected, actual);
}
static void assertECParameterSpecEqualsIgnoreSeedIfNotPresent(String message,
ECParameterSpec expected, ECParameterSpec actual) {
EllipticCurve expectedCurve = expected.getCurve();
EllipticCurve actualCurve = actual.getCurve();
String msgPrefix = (message != null) ? message + ": " : "";
assertEquals(msgPrefix + "curve field", expectedCurve.getField(), actualCurve.getField());
assertEquals(msgPrefix + "curve A", expectedCurve.getA(), actualCurve.getA());
assertEquals(msgPrefix + "curve B", expectedCurve.getB(), actualCurve.getB());
assertEquals(msgPrefix + "order", expected.getOrder(), actual.getOrder());
assertEquals(msgPrefix + "generator",
expected.getGenerator(), actual.getGenerator());
assertEquals(msgPrefix + "cofactor", expected.getCofactor(), actual.getCofactor());
// If present, the seed must be the same
byte[] expectedSeed = expectedCurve.getSeed();
byte[] actualSeed = expectedCurve.getSeed();
if ((expectedSeed != null) && (actualSeed != null)) {
MoreAsserts.assertEquals(expectedSeed, actualSeed);
}
}
static KeyInfo getKeyInfo(Key key) throws InvalidKeySpecException, NoSuchAlgorithmException,
NoSuchProviderException {
if ((key instanceof PrivateKey) || (key instanceof PublicKey)) {
return KeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore")
.getKeySpec(key, KeyInfo.class);
} else if (key instanceof SecretKey) {
return (KeyInfo) SecretKeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore")
.getKeySpec((SecretKey) key, KeyInfo.class);
} else {
throw new IllegalArgumentException("Unexpected key type: " + key.getClass());
}
}
static <T> void assertContentsInAnyOrder(Iterable<T> actual, T... expected) {
assertContentsInAnyOrder(null, actual, expected);
}
static <T> void assertContentsInAnyOrder(String message, Iterable<T> actual, T... expected) {
Map<T, Integer> actualFreq = getFrequencyTable(actual);
Map<T, Integer> expectedFreq = getFrequencyTable(expected);
if (actualFreq.equals(expectedFreq)) {
return;
}
Map<T, Integer> extraneousFreq = new HashMap<T, Integer>();
for (Map.Entry<T, Integer> actualEntry : actualFreq.entrySet()) {
int actualCount = actualEntry.getValue();
Integer expectedCount = expectedFreq.get(actualEntry.getKey());
int diff = actualCount - ((expectedCount != null) ? expectedCount : 0);
if (diff > 0) {
extraneousFreq.put(actualEntry.getKey(), diff);
}
}
Map<T, Integer> missingFreq = new HashMap<T, Integer>();
for (Map.Entry<T, Integer> expectedEntry : expectedFreq.entrySet()) {
int expectedCount = expectedEntry.getValue();
Integer actualCount = actualFreq.get(expectedEntry.getKey());
int diff = expectedCount - ((actualCount != null) ? actualCount : 0);
if (diff > 0) {
missingFreq.put(expectedEntry.getKey(), diff);
}
}
List<T> extraneous = frequencyTableToValues(extraneousFreq);
List<T> missing = frequencyTableToValues(missingFreq);
StringBuilder result = new StringBuilder();
String delimiter = "";
if (message != null) {
result.append(message).append(".");
delimiter = " ";
}
if (!missing.isEmpty()) {
result.append(delimiter).append("missing: " + missing);
delimiter = ", ";
}
if (!extraneous.isEmpty()) {
result.append(delimiter).append("extraneous: " + extraneous);
}
fail(result.toString());
}
private static <T> Map<T, Integer> getFrequencyTable(Iterable<T> values) {
Map<T, Integer> result = new HashMap<T, Integer>();
for (T value : values) {
Integer count = result.get(value);
if (count == null) {
count = 1;
} else {
count++;
}
result.put(value, count);
}
return result;
}
private static <T> Map<T, Integer> getFrequencyTable(T... values) {
Map<T, Integer> result = new HashMap<T, Integer>();
for (T value : values) {
Integer count = result.get(value);
if (count == null) {
count = 1;
} else {
count++;
}
result.put(value, count);
}
return result;
}
@SuppressWarnings("rawtypes")
private static <T> List<T> frequencyTableToValues(Map<T, Integer> table) {
if (table.isEmpty()) {
return Collections.emptyList();
}
List<T> result = new ArrayList<T>();
boolean comparableValues = true;
for (Map.Entry<T, Integer> entry : table.entrySet()) {
T value = entry.getKey();
if (!(value instanceof Comparable)) {
comparableValues = false;
}
int frequency = entry.getValue();
for (int i = 0; i < frequency; i++) {
result.add(value);
}
}
if (comparableValues) {
sortAssumingComparable(result);
}
return result;
}
@SuppressWarnings({"rawtypes", "unchecked"})
private static void sortAssumingComparable(List<?> values) {
Collections.sort((List<Comparable>)values);
}
static String[] toLowerCase(String... values) {
if (values == null) {
return null;
}
String[] result = new String[values.length];
for (int i = 0; i < values.length; i++) {
String value = values[i];
result[i] = (value != null) ? value.toLowerCase() : null;
}
return result;
}
static PrivateKey getRawResPrivateKey(Context context, int resId) throws Exception {
byte[] pkcs8EncodedForm;
try (InputStream in = context.getResources().openRawResource(resId)) {
pkcs8EncodedForm = drain(in);
}
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(pkcs8EncodedForm);
try {
return KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
} catch (InvalidKeySpecException e) {
try {
return KeyFactory.getInstance("RSA").generatePrivate(privateKeySpec);
} catch (InvalidKeySpecException e2) {
throw new InvalidKeySpecException("The key is neither EC nor RSA", e);
}
}
}
static X509Certificate getRawResX509Certificate(Context context, int resId) throws Exception {
try (InputStream in = context.getResources().openRawResource(resId)) {
return (X509Certificate) CertificateFactory.getInstance("X.509")
.generateCertificate(in);
}
}
static KeyPair importIntoAndroidKeyStore(
String alias,
PrivateKey privateKey,
Certificate certificate,
KeyProtection keyProtection) throws Exception {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
keyStore.setEntry(alias,
new KeyStore.PrivateKeyEntry(privateKey, new Certificate[] {certificate}),
keyProtection);
return new KeyPair(
keyStore.getCertificate(alias).getPublicKey(),
(PrivateKey) keyStore.getKey(alias, null));
}
static ImportedKey importIntoAndroidKeyStore(
String alias,
SecretKey key,
KeyProtection keyProtection) throws Exception {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
keyStore.setEntry(alias,
new KeyStore.SecretKeyEntry(key),
keyProtection);
return new ImportedKey(alias, key, (SecretKey) keyStore.getKey(alias, null));
}
static ImportedKey importIntoAndroidKeyStore(
String alias, Context context, int privateResId, int certResId, KeyProtection params)
throws Exception {
Certificate originalCert = TestUtils.getRawResX509Certificate(context, certResId);
PublicKey originalPublicKey = originalCert.getPublicKey();
PrivateKey originalPrivateKey = TestUtils.getRawResPrivateKey(context, privateResId);
// Check that the domain parameters match between the private key and the public key. This
// is to catch accidental errors where a test provides the wrong resource ID as one of the
// parameters.
if (!originalPublicKey.getAlgorithm().equalsIgnoreCase(originalPrivateKey.getAlgorithm())) {
throw new IllegalArgumentException("Key algorithm mismatch."
+ " Public: " + originalPublicKey.getAlgorithm()
+ ", private: " + originalPrivateKey.getAlgorithm());
}
assertKeyPairSelfConsistent(originalPublicKey, originalPrivateKey);
KeyPair keystoreBacked = TestUtils.importIntoAndroidKeyStore(
alias, originalPrivateKey, originalCert,
params);
assertKeyPairSelfConsistent(keystoreBacked);
assertKeyPairSelfConsistent(keystoreBacked.getPublic(), originalPrivateKey);
return new ImportedKey(
alias,
new KeyPair(originalCert.getPublicKey(), originalPrivateKey),
keystoreBacked);
}
static byte[] drain(InputStream in) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[16 * 1024];
int chunkSize;
while ((chunkSize = in.read(buffer)) != -1) {
result.write(buffer, 0, chunkSize);
}
return result.toByteArray();
}
static KeyProtection.Builder buildUpon(KeyProtection params) {
return buildUponInternal(params, null);
}
static KeyProtection.Builder buildUpon(KeyProtection params, int newPurposes) {
return buildUponInternal(params, newPurposes);
}
static KeyProtection.Builder buildUpon(
KeyProtection.Builder builder) {
return buildUponInternal(builder.build(), null);
}
static KeyProtection.Builder buildUpon(
KeyProtection.Builder builder, int newPurposes) {
return buildUponInternal(builder.build(), newPurposes);
}
private static KeyProtection.Builder buildUponInternal(
KeyProtection spec, Integer newPurposes) {
int purposes = (newPurposes == null) ? spec.getPurposes() : newPurposes;
KeyProtection.Builder result = new KeyProtection.Builder(purposes);
result.setBlockModes(spec.getBlockModes());
if (spec.isDigestsSpecified()) {
result.setDigests(spec.getDigests());
}
result.setEncryptionPaddings(spec.getEncryptionPaddings());
result.setSignaturePaddings(spec.getSignaturePaddings());
result.setKeyValidityStart(spec.getKeyValidityStart());
result.setKeyValidityForOriginationEnd(spec.getKeyValidityForOriginationEnd());
result.setKeyValidityForConsumptionEnd(spec.getKeyValidityForConsumptionEnd());
result.setRandomizedEncryptionRequired(spec.isRandomizedEncryptionRequired());
result.setUserAuthenticationRequired(spec.isUserAuthenticationRequired());
result.setUserAuthenticationValidityDurationSeconds(
spec.getUserAuthenticationValidityDurationSeconds());
return result;
}
static KeyGenParameterSpec.Builder buildUpon(KeyGenParameterSpec spec) {
return buildUponInternal(spec, null);
}
static KeyGenParameterSpec.Builder buildUpon(KeyGenParameterSpec spec, int newPurposes) {
return buildUponInternal(spec, newPurposes);
}
static KeyGenParameterSpec.Builder buildUpon(
KeyGenParameterSpec.Builder builder) {
return buildUponInternal(builder.build(), null);
}
static KeyGenParameterSpec.Builder buildUpon(
KeyGenParameterSpec.Builder builder, int newPurposes) {
return buildUponInternal(builder.build(), newPurposes);
}
private static KeyGenParameterSpec.Builder buildUponInternal(
KeyGenParameterSpec spec, Integer newPurposes) {
int purposes = (newPurposes == null) ? spec.getPurposes() : newPurposes;
KeyGenParameterSpec.Builder result =
new KeyGenParameterSpec.Builder(spec.getKeystoreAlias(), purposes);
if (spec.getKeySize() >= 0) {
result.setKeySize(spec.getKeySize());
}
if (spec.getAlgorithmParameterSpec() != null) {
result.setAlgorithmParameterSpec(spec.getAlgorithmParameterSpec());
}
result.setCertificateNotBefore(spec.getCertificateNotBefore());
result.setCertificateNotAfter(spec.getCertificateNotAfter());
result.setCertificateSerialNumber(spec.getCertificateSerialNumber());
result.setCertificateSubject(spec.getCertificateSubject());
result.setBlockModes(spec.getBlockModes());
if (spec.isDigestsSpecified()) {
result.setDigests(spec.getDigests());
}
result.setEncryptionPaddings(spec.getEncryptionPaddings());
result.setSignaturePaddings(spec.getSignaturePaddings());
result.setKeyValidityStart(spec.getKeyValidityStart());
result.setKeyValidityForOriginationEnd(spec.getKeyValidityForOriginationEnd());
result.setKeyValidityForConsumptionEnd(spec.getKeyValidityForConsumptionEnd());
result.setRandomizedEncryptionRequired(spec.isRandomizedEncryptionRequired());
result.setUserAuthenticationRequired(spec.isUserAuthenticationRequired());
result.setUserAuthenticationValidityDurationSeconds(
spec.getUserAuthenticationValidityDurationSeconds());
return result;
}
static KeyPair getKeyPairForKeyAlgorithm(String keyAlgorithm, Iterable<KeyPair> keyPairs) {
for (KeyPair keyPair : keyPairs) {
if (keyAlgorithm.equalsIgnoreCase(keyPair.getPublic().getAlgorithm())) {
return keyPair;
}
}
throw new IllegalArgumentException("No KeyPair for key algorithm " + keyAlgorithm);
}
static Key getKeyForKeyAlgorithm(String keyAlgorithm, Iterable<? extends Key> keys) {
for (Key key : keys) {
if (keyAlgorithm.equalsIgnoreCase(key.getAlgorithm())) {
return key;
}
}
throw new IllegalArgumentException("No Key for key algorithm " + keyAlgorithm);
}
static byte[] generateLargeKatMsg(byte[] seed, int msgSizeBytes) throws Exception {
byte[] result = new byte[msgSizeBytes];
MessageDigest digest = MessageDigest.getInstance("SHA-512");
int resultOffset = 0;
int resultRemaining = msgSizeBytes;
while (resultRemaining > 0) {
seed = digest.digest(seed);
int chunkSize = Math.min(seed.length, resultRemaining);
System.arraycopy(seed, 0, result, resultOffset, chunkSize);
resultOffset += chunkSize;
resultRemaining -= chunkSize;
}
return result;
}
static byte[] leftPadWithZeroBytes(byte[] array, int length) {
if (array.length >= length) {
return array;
}
byte[] result = new byte[length];
System.arraycopy(array, 0, result, result.length - array.length, array.length);
return result;
}
static boolean contains(int[] array, int value) {
for (int element : array) {
if (element == value) {
return true;
}
}
return false;
}
static boolean isHmacAlgorithm(String algorithm) {
return algorithm.toUpperCase(Locale.US).startsWith("HMAC");
}
static String getHmacAlgorithmDigest(String algorithm) {
String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
if (!algorithmUpperCase.startsWith("HMAC")) {
return null;
}
String result = algorithmUpperCase.substring("HMAC".length());
if (result.startsWith("SHA")) {
result = "SHA-" + result.substring("SHA".length());
}
return result;
}
static String getCipherKeyAlgorithm(String transformation) {
String transformationUpperCase = transformation.toUpperCase(Locale.US);
if (transformationUpperCase.startsWith("AES/")) {
return KeyProperties.KEY_ALGORITHM_AES;
} else if (transformationUpperCase.startsWith("RSA/")) {
return KeyProperties.KEY_ALGORITHM_RSA;
} else {
throw new IllegalArgumentException("Unsupported transformation: " + transformation);
}
}
static boolean isCipherSymmetric(String transformation) {
String transformationUpperCase = transformation.toUpperCase(Locale.US);
if (transformationUpperCase.startsWith("AES/")) {
return true;
} else if (transformationUpperCase.startsWith("RSA/")) {
return false;
} else {
throw new IllegalArgumentException("Unsupported transformation: " + transformation);
}
}
static String getCipherDigest(String transformation) {
String transformationUpperCase = transformation.toUpperCase(Locale.US);
if (transformationUpperCase.contains("/OAEP")) {
if (transformationUpperCase.endsWith("/OAEPPADDING")) {
return KeyProperties.DIGEST_SHA1;
} else if (transformationUpperCase.endsWith(
"/OAEPWITHSHA-1ANDMGF1PADDING")) {
return KeyProperties.DIGEST_SHA1;
} else if (transformationUpperCase.endsWith(
"/OAEPWITHSHA-224ANDMGF1PADDING")) {
return KeyProperties.DIGEST_SHA224;
} else if (transformationUpperCase.endsWith(
"/OAEPWITHSHA-256ANDMGF1PADDING")) {
return KeyProperties.DIGEST_SHA256;
} else if (transformationUpperCase.endsWith(
"/OAEPWITHSHA-384ANDMGF1PADDING")) {
return KeyProperties.DIGEST_SHA384;
} else if (transformationUpperCase.endsWith(
"/OAEPWITHSHA-512ANDMGF1PADDING")) {
return KeyProperties.DIGEST_SHA512;
} else {
throw new RuntimeException("Unsupported OAEP padding scheme: "
+ transformation);
}
} else {
return null;
}
}
static String getCipherEncryptionPadding(String transformation) {
String transformationUpperCase = transformation.toUpperCase(Locale.US);
if (transformationUpperCase.endsWith("/NOPADDING")) {
return KeyProperties.ENCRYPTION_PADDING_NONE;
} else if (transformationUpperCase.endsWith("/PKCS7PADDING")) {
return KeyProperties.ENCRYPTION_PADDING_PKCS7;
} else if (transformationUpperCase.endsWith("/PKCS1PADDING")) {
return KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1;
} else if (transformationUpperCase.split("/")[2].startsWith("OAEP")) {
return KeyProperties.ENCRYPTION_PADDING_RSA_OAEP;
} else {
throw new IllegalArgumentException("Unsupported transformation: " + transformation);
}
}
static String getCipherBlockMode(String transformation) {
return transformation.split("/")[1].toUpperCase(Locale.US);
}
static String getSignatureAlgorithmDigest(String algorithm) {
String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
int withIndex = algorithmUpperCase.indexOf("WITH");
if (withIndex == -1) {
throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
}
String digest = algorithmUpperCase.substring(0, withIndex);
if (digest.startsWith("SHA")) {
digest = "SHA-" + digest.substring("SHA".length());
}
return digest;
}
static String getSignatureAlgorithmPadding(String algorithm) {
String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
if (algorithmUpperCase.endsWith("WITHECDSA")) {
return null;
} else if (algorithmUpperCase.endsWith("WITHRSA")) {
return KeyProperties.SIGNATURE_PADDING_RSA_PKCS1;
} else if (algorithmUpperCase.endsWith("WITHRSA/PSS")) {
return KeyProperties.SIGNATURE_PADDING_RSA_PSS;
} else {
throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
}
}
static String getSignatureAlgorithmKeyAlgorithm(String algorithm) {
String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
if (algorithmUpperCase.endsWith("WITHECDSA")) {
return KeyProperties.KEY_ALGORITHM_EC;
} else if ((algorithmUpperCase.endsWith("WITHRSA"))
|| (algorithmUpperCase.endsWith("WITHRSA/PSS"))) {
return KeyProperties.KEY_ALGORITHM_RSA;
} else {
throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
}
}
static boolean isKeyLongEnoughForSignatureAlgorithm(String algorithm, Key key) {
String keyAlgorithm = key.getAlgorithm();
if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
// No length restrictions for ECDSA
return true;
} else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
// No length restrictions for RSA
String digest = getSignatureAlgorithmDigest(algorithm);
int digestOutputSizeBits = getDigestOutputSizeBits(digest);
if (digestOutputSizeBits == -1) {
// No digesting -- assume the key is long enough for the message
return true;
}
String paddingScheme = getSignatureAlgorithmPadding(algorithm);
int paddingOverheadBytes;
if (KeyProperties.SIGNATURE_PADDING_RSA_PKCS1.equalsIgnoreCase(paddingScheme)) {
paddingOverheadBytes = 30;
} else if (KeyProperties.SIGNATURE_PADDING_RSA_PSS.equalsIgnoreCase(paddingScheme)) {
paddingOverheadBytes = 22;
} else {
throw new IllegalArgumentException(
"Unsupported signature padding scheme: " + paddingScheme);
}
int minKeySizeBytes = paddingOverheadBytes + (digestOutputSizeBits + 7) / 8 + 1;
int keySizeBytes = ((RSAKey) key).getModulus().bitLength() / 8;
return keySizeBytes >= minKeySizeBytes;
} else {
throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
}
}
static int getMaxSupportedPlaintextInputSizeBytes(String transformation, Key key) {
String keyAlgorithm = getCipherKeyAlgorithm(transformation);
if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)) {
return Integer.MAX_VALUE;
} else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
String encryptionPadding = getCipherEncryptionPadding(transformation);
int modulusSizeBytes = (getKeySizeBits(key) + 7) / 8;
if (KeyProperties.ENCRYPTION_PADDING_NONE.equalsIgnoreCase(encryptionPadding)) {
return modulusSizeBytes - 1;
} else if (KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1.equalsIgnoreCase(
encryptionPadding)) {
return modulusSizeBytes - 11;
} else if (KeyProperties.ENCRYPTION_PADDING_RSA_OAEP.equalsIgnoreCase(
encryptionPadding)) {
String digest = getCipherDigest(transformation);
int digestOutputSizeBytes = (getDigestOutputSizeBits(digest) + 7) / 8;
return modulusSizeBytes - 2 * digestOutputSizeBytes - 2;
} else {
throw new IllegalArgumentException(
"Unsupported encryption padding scheme: " + encryptionPadding);
}
} else {
throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
}
}
static int getDigestOutputSizeBits(String digest) {
if (KeyProperties.DIGEST_NONE.equals(digest)) {
return -1;
} else if (KeyProperties.DIGEST_MD5.equals(digest)) {
return 128;
} else if (KeyProperties.DIGEST_SHA1.equals(digest)) {
return 160;
} else if (KeyProperties.DIGEST_SHA224.equals(digest)) {
return 224;
} else if (KeyProperties.DIGEST_SHA256.equals(digest)) {
return 256;
} else if (KeyProperties.DIGEST_SHA384.equals(digest)) {
return 384;
} else if (KeyProperties.DIGEST_SHA512.equals(digest)) {
return 512;
} else {
throw new IllegalArgumentException("Unsupported digest: " + digest);
}
}
static byte[] concat(byte[] arr1, byte[] arr2) {
return concat(arr1, 0, (arr1 != null) ? arr1.length : 0,
arr2, 0, (arr2 != null) ? arr2.length : 0);
}
static byte[] concat(byte[] arr1, int offset1, int len1,
byte[] arr2, int offset2, int len2) {
if (len1 == 0) {
return subarray(arr2, offset2, len2);
} else if (len2 == 0) {
return subarray(arr1, offset1, len1);
}
byte[] result = new byte[len1 + len2];
if (len1 > 0) {
System.arraycopy(arr1, offset1, result, 0, len1);
}
if (len2 > 0) {
System.arraycopy(arr2, offset2, result, len1, len2);
}
return result;
}
static byte[] subarray(byte[] arr, int offset, int len) {
if (len == 0) {
return EmptyArray.BYTE;
}
if ((offset == 0) && (arr.length == len)) {
return arr;
}
byte[] result = new byte[len];
System.arraycopy(arr, offset, result, 0, len);
return result;
}
static KeyProtection getMinimalWorkingImportParametersForSigningingWith(
String signatureAlgorithm) {
String keyAlgorithm = getSignatureAlgorithmKeyAlgorithm(signatureAlgorithm);
String digest = getSignatureAlgorithmDigest(signatureAlgorithm);
if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
.setDigests(digest)
.build();
} else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
String padding = getSignatureAlgorithmPadding(signatureAlgorithm);
return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
.setDigests(digest)
.setSignaturePaddings(padding)
.build();
} else {
throw new IllegalArgumentException(
"Unsupported signature algorithm: " + signatureAlgorithm);
}
}
static KeyProtection getMinimalWorkingImportParametersForCipheringWith(
String transformation, int purposes, boolean ivProvidedWhenEncrypting) {
String keyAlgorithm = TestUtils.getCipherKeyAlgorithm(transformation);
if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)) {
String encryptionPadding = TestUtils.getCipherEncryptionPadding(transformation);
String blockMode = TestUtils.getCipherBlockMode(transformation);
boolean randomizedEncryptionRequired = true;
if (KeyProperties.BLOCK_MODE_ECB.equalsIgnoreCase(blockMode)) {
randomizedEncryptionRequired = false;
} else if ((ivProvidedWhenEncrypting)
&& ((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)) {
randomizedEncryptionRequired = false;
}
return new KeyProtection.Builder(
purposes)
.setBlockModes(blockMode)
.setEncryptionPaddings(encryptionPadding)
.setRandomizedEncryptionRequired(randomizedEncryptionRequired)
.build();
} else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
String digest = TestUtils.getCipherDigest(transformation);
String encryptionPadding = TestUtils.getCipherEncryptionPadding(transformation);
boolean randomizedEncryptionRequired =
!KeyProperties.ENCRYPTION_PADDING_NONE.equalsIgnoreCase(encryptionPadding);
return new KeyProtection.Builder(
purposes)
.setDigests((digest != null) ? new String[] {digest} : EmptyArray.STRING)
.setEncryptionPaddings(encryptionPadding)
.setRandomizedEncryptionRequired(randomizedEncryptionRequired)
.build();
} else {
throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
}
}
static byte[] getBigIntegerMagnitudeBytes(BigInteger value) {
return removeLeadingZeroByteIfPresent(value.toByteArray());
}
private static byte[] removeLeadingZeroByteIfPresent(byte[] value) {
if ((value.length < 1) || (value[0] != 0)) {
return value;
}
return TestUtils.subarray(value, 1, value.length - 1);
}
}