/*
* 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.security.keystore;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
import android.security.keymaster.KeymasterDefs;
import libcore.util.EmptyArray;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collection;
import java.util.Locale;
/**
* Properties of Android Keystore keys.
*/
public abstract class KeyProperties {
private KeyProperties() {}
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true,
value = {
PURPOSE_ENCRYPT,
PURPOSE_DECRYPT,
PURPOSE_SIGN,
PURPOSE_VERIFY,
})
public @interface PurposeEnum {}
/**
* Purpose of key: encryption.
*/
public static final int PURPOSE_ENCRYPT = 1 << 0;
/**
* Purpose of key: decryption.
*/
public static final int PURPOSE_DECRYPT = 1 << 1;
/**
* Purpose of key: signing or generating a Message Authentication Code (MAC).
*/
public static final int PURPOSE_SIGN = 1 << 2;
/**
* Purpose of key: signature or Message Authentication Code (MAC) verification.
*/
public static final int PURPOSE_VERIFY = 1 << 3;
/**
* @hide
*/
public static abstract class Purpose {
private Purpose() {}
public static int toKeymaster(@PurposeEnum int purpose) {
switch (purpose) {
case PURPOSE_ENCRYPT:
return KeymasterDefs.KM_PURPOSE_ENCRYPT;
case PURPOSE_DECRYPT:
return KeymasterDefs.KM_PURPOSE_DECRYPT;
case PURPOSE_SIGN:
return KeymasterDefs.KM_PURPOSE_SIGN;
case PURPOSE_VERIFY:
return KeymasterDefs.KM_PURPOSE_VERIFY;
default:
throw new IllegalArgumentException("Unknown purpose: " + purpose);
}
}
public static @PurposeEnum int fromKeymaster(int purpose) {
switch (purpose) {
case KeymasterDefs.KM_PURPOSE_ENCRYPT:
return PURPOSE_ENCRYPT;
case KeymasterDefs.KM_PURPOSE_DECRYPT:
return PURPOSE_DECRYPT;
case KeymasterDefs.KM_PURPOSE_SIGN:
return PURPOSE_SIGN;
case KeymasterDefs.KM_PURPOSE_VERIFY:
return PURPOSE_VERIFY;
default:
throw new IllegalArgumentException("Unknown purpose: " + purpose);
}
}
@NonNull
public static int[] allToKeymaster(@PurposeEnum int purposes) {
int[] result = getSetFlags(purposes);
for (int i = 0; i < result.length; i++) {
result[i] = toKeymaster(result[i]);
}
return result;
}
public static @PurposeEnum int allFromKeymaster(@NonNull Collection purposes) {
@PurposeEnum int result = 0;
for (int keymasterPurpose : purposes) {
result |= fromKeymaster(keymasterPurpose);
}
return result;
}
}
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@StringDef({
KEY_ALGORITHM_RSA,
KEY_ALGORITHM_EC,
KEY_ALGORITHM_AES,
KEY_ALGORITHM_HMAC_SHA1,
KEY_ALGORITHM_HMAC_SHA224,
KEY_ALGORITHM_HMAC_SHA256,
KEY_ALGORITHM_HMAC_SHA384,
KEY_ALGORITHM_HMAC_SHA512,
})
public @interface KeyAlgorithmEnum {}
/** Rivest Shamir Adleman (RSA) key. */
public static final String KEY_ALGORITHM_RSA = "RSA";
/** Elliptic Curve (EC) Cryptography key. */
public static final String KEY_ALGORITHM_EC = "EC";
/** Advanced Encryption Standard (AES) key. */
public static final String KEY_ALGORITHM_AES = "AES";
/** Keyed-Hash Message Authentication Code (HMAC) key using SHA-1 as the hash. */
public static final String KEY_ALGORITHM_HMAC_SHA1 = "HmacSHA1";
/** Keyed-Hash Message Authentication Code (HMAC) key using SHA-224 as the hash. */
public static final String KEY_ALGORITHM_HMAC_SHA224 = "HmacSHA224";
/** Keyed-Hash Message Authentication Code (HMAC) key using SHA-256 as the hash. */
public static final String KEY_ALGORITHM_HMAC_SHA256 = "HmacSHA256";
/** Keyed-Hash Message Authentication Code (HMAC) key using SHA-384 as the hash. */
public static final String KEY_ALGORITHM_HMAC_SHA384 = "HmacSHA384";
/** Keyed-Hash Message Authentication Code (HMAC) key using SHA-512 as the hash. */
public static final String KEY_ALGORITHM_HMAC_SHA512 = "HmacSHA512";
/**
* @hide
*/
public static abstract class KeyAlgorithm {
private KeyAlgorithm() {}
public static int toKeymasterAsymmetricKeyAlgorithm(
@NonNull @KeyAlgorithmEnum String algorithm) {
if (KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) {
return KeymasterDefs.KM_ALGORITHM_EC;
} else if (KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) {
return KeymasterDefs.KM_ALGORITHM_RSA;
} else {
throw new IllegalArgumentException("Unsupported key algorithm: " + algorithm);
}
}
@NonNull
public static @KeyAlgorithmEnum String fromKeymasterAsymmetricKeyAlgorithm(
int keymasterAlgorithm) {
switch (keymasterAlgorithm) {
case KeymasterDefs.KM_ALGORITHM_EC:
return KEY_ALGORITHM_EC;
case KeymasterDefs.KM_ALGORITHM_RSA:
return KEY_ALGORITHM_RSA;
default:
throw new IllegalArgumentException(
"Unsupported key algorithm: " + keymasterAlgorithm);
}
}
public static int toKeymasterSecretKeyAlgorithm(
@NonNull @KeyAlgorithmEnum String algorithm) {
if (KEY_ALGORITHM_AES.equalsIgnoreCase(algorithm)) {
return KeymasterDefs.KM_ALGORITHM_AES;
} else if (algorithm.toUpperCase(Locale.US).startsWith("HMAC")) {
return KeymasterDefs.KM_ALGORITHM_HMAC;
} else {
throw new IllegalArgumentException(
"Unsupported secret key algorithm: " + algorithm);
}
}
@NonNull
public static @KeyAlgorithmEnum String fromKeymasterSecretKeyAlgorithm(
int keymasterAlgorithm, int keymasterDigest) {
switch (keymasterAlgorithm) {
case KeymasterDefs.KM_ALGORITHM_AES:
return KEY_ALGORITHM_AES;
case KeymasterDefs.KM_ALGORITHM_HMAC:
switch (keymasterDigest) {
case KeymasterDefs.KM_DIGEST_SHA1:
return KEY_ALGORITHM_HMAC_SHA1;
case KeymasterDefs.KM_DIGEST_SHA_2_224:
return KEY_ALGORITHM_HMAC_SHA224;
case KeymasterDefs.KM_DIGEST_SHA_2_256:
return KEY_ALGORITHM_HMAC_SHA256;
case KeymasterDefs.KM_DIGEST_SHA_2_384:
return KEY_ALGORITHM_HMAC_SHA384;
case KeymasterDefs.KM_DIGEST_SHA_2_512:
return KEY_ALGORITHM_HMAC_SHA512;
default:
throw new IllegalArgumentException("Unsupported HMAC digest: "
+ Digest.fromKeymaster(keymasterDigest));
}
default:
throw new IllegalArgumentException(
"Unsupported key algorithm: " + keymasterAlgorithm);
}
}
/**
* @hide
*
* @return keymaster digest or {@code -1} if the algorithm does not involve a digest.
*/
public static int toKeymasterDigest(@NonNull @KeyAlgorithmEnum String algorithm) {
String algorithmUpper = algorithm.toUpperCase(Locale.US);
if (algorithmUpper.startsWith("HMAC")) {
String digestUpper = algorithmUpper.substring("HMAC".length());
switch (digestUpper) {
case "SHA1":
return KeymasterDefs.KM_DIGEST_SHA1;
case "SHA224":
return KeymasterDefs.KM_DIGEST_SHA_2_224;
case "SHA256":
return KeymasterDefs.KM_DIGEST_SHA_2_256;
case "SHA384":
return KeymasterDefs.KM_DIGEST_SHA_2_384;
case "SHA512":
return KeymasterDefs.KM_DIGEST_SHA_2_512;
default:
throw new IllegalArgumentException(
"Unsupported HMAC digest: " + digestUpper);
}
} else {
return -1;
}
}
}
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@StringDef({
BLOCK_MODE_ECB,
BLOCK_MODE_CBC,
BLOCK_MODE_CTR,
BLOCK_MODE_GCM,
})
public @interface BlockModeEnum {}
/** Electronic Codebook (ECB) block mode. */
public static final String BLOCK_MODE_ECB = "ECB";
/** Cipher Block Chaining (CBC) block mode. */
public static final String BLOCK_MODE_CBC = "CBC";
/** Counter (CTR) block mode. */
public static final String BLOCK_MODE_CTR = "CTR";
/** Galois/Counter Mode (GCM) block mode. */
public static final String BLOCK_MODE_GCM = "GCM";
/**
* @hide
*/
public static abstract class BlockMode {
private BlockMode() {}
public static int toKeymaster(@NonNull @BlockModeEnum String blockMode) {
if (BLOCK_MODE_ECB.equalsIgnoreCase(blockMode)) {
return KeymasterDefs.KM_MODE_ECB;
} else if (BLOCK_MODE_CBC.equalsIgnoreCase(blockMode)) {
return KeymasterDefs.KM_MODE_CBC;
} else if (BLOCK_MODE_CTR.equalsIgnoreCase(blockMode)) {
return KeymasterDefs.KM_MODE_CTR;
} else if (BLOCK_MODE_GCM.equalsIgnoreCase(blockMode)) {
return KeymasterDefs.KM_MODE_GCM;
} else {
throw new IllegalArgumentException("Unsupported block mode: " + blockMode);
}
}
@NonNull
public static @BlockModeEnum String fromKeymaster(int blockMode) {
switch (blockMode) {
case KeymasterDefs.KM_MODE_ECB:
return BLOCK_MODE_ECB;
case KeymasterDefs.KM_MODE_CBC:
return BLOCK_MODE_CBC;
case KeymasterDefs.KM_MODE_CTR:
return BLOCK_MODE_CTR;
case KeymasterDefs.KM_MODE_GCM:
return BLOCK_MODE_GCM;
default:
throw new IllegalArgumentException("Unsupported block mode: " + blockMode);
}
}
@NonNull
public static @BlockModeEnum String[] allFromKeymaster(
@NonNull Collection blockModes) {
if ((blockModes == null) || (blockModes.isEmpty())) {
return EmptyArray.STRING;
}
@BlockModeEnum String[] result = new String[blockModes.size()];
int offset = 0;
for (int blockMode : blockModes) {
result[offset] = fromKeymaster(blockMode);
offset++;
}
return result;
}
public static int[] allToKeymaster(@Nullable @BlockModeEnum String[] blockModes) {
if ((blockModes == null) || (blockModes.length == 0)) {
return EmptyArray.INT;
}
int[] result = new int[blockModes.length];
for (int i = 0; i < blockModes.length; i++) {
result[i] = toKeymaster(blockModes[i]);
}
return result;
}
}
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@StringDef({
ENCRYPTION_PADDING_NONE,
ENCRYPTION_PADDING_PKCS7,
ENCRYPTION_PADDING_RSA_PKCS1,
ENCRYPTION_PADDING_RSA_OAEP,
})
public @interface EncryptionPaddingEnum {}
/**
* No encryption padding.
*/
public static final String ENCRYPTION_PADDING_NONE = "NoPadding";
/**
* PKCS#7 encryption padding scheme.
*/
public static final String ENCRYPTION_PADDING_PKCS7 = "PKCS7Padding";
/**
* RSA PKCS#1 v1.5 padding scheme for encryption.
*/
public static final String ENCRYPTION_PADDING_RSA_PKCS1 = "PKCS1Padding";
/**
* RSA Optimal Asymmetric Encryption Padding (OAEP) scheme.
*/
public static final String ENCRYPTION_PADDING_RSA_OAEP = "OAEPPadding";
/**
* @hide
*/
public static abstract class EncryptionPadding {
private EncryptionPadding() {}
public static int toKeymaster(@NonNull @EncryptionPaddingEnum String padding) {
if (ENCRYPTION_PADDING_NONE.equalsIgnoreCase(padding)) {
return KeymasterDefs.KM_PAD_NONE;
} else if (ENCRYPTION_PADDING_PKCS7.equalsIgnoreCase(padding)) {
return KeymasterDefs.KM_PAD_PKCS7;
} else if (ENCRYPTION_PADDING_RSA_PKCS1.equalsIgnoreCase(padding)) {
return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT;
} else if (ENCRYPTION_PADDING_RSA_OAEP.equalsIgnoreCase(padding)) {
return KeymasterDefs.KM_PAD_RSA_OAEP;
} else {
throw new IllegalArgumentException(
"Unsupported encryption padding scheme: " + padding);
}
}
@NonNull
public static @EncryptionPaddingEnum String fromKeymaster(int padding) {
switch (padding) {
case KeymasterDefs.KM_PAD_NONE:
return ENCRYPTION_PADDING_NONE;
case KeymasterDefs.KM_PAD_PKCS7:
return ENCRYPTION_PADDING_PKCS7;
case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT:
return ENCRYPTION_PADDING_RSA_PKCS1;
case KeymasterDefs.KM_PAD_RSA_OAEP:
return ENCRYPTION_PADDING_RSA_OAEP;
default:
throw new IllegalArgumentException(
"Unsupported encryption padding: " + padding);
}
}
@NonNull
public static int[] allToKeymaster(@Nullable @EncryptionPaddingEnum String[] paddings) {
if ((paddings == null) || (paddings.length == 0)) {
return EmptyArray.INT;
}
int[] result = new int[paddings.length];
for (int i = 0; i < paddings.length; i++) {
result[i] = toKeymaster(paddings[i]);
}
return result;
}
}
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@StringDef({
SIGNATURE_PADDING_RSA_PKCS1,
SIGNATURE_PADDING_RSA_PSS,
})
public @interface SignaturePaddingEnum {}
/**
* RSA PKCS#1 v1.5 padding for signatures.
*/
public static final String SIGNATURE_PADDING_RSA_PKCS1 = "PKCS1";
/**
* RSA PKCS#1 v2.1 Probabilistic Signature Scheme (PSS) padding.
*/
public static final String SIGNATURE_PADDING_RSA_PSS = "PSS";
static abstract class SignaturePadding {
private SignaturePadding() {}
static int toKeymaster(@NonNull @SignaturePaddingEnum String padding) {
switch (padding.toUpperCase(Locale.US)) {
case SIGNATURE_PADDING_RSA_PKCS1:
return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN;
case SIGNATURE_PADDING_RSA_PSS:
return KeymasterDefs.KM_PAD_RSA_PSS;
default:
throw new IllegalArgumentException(
"Unsupported signature padding scheme: " + padding);
}
}
@NonNull
static @SignaturePaddingEnum String fromKeymaster(int padding) {
switch (padding) {
case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN:
return SIGNATURE_PADDING_RSA_PKCS1;
case KeymasterDefs.KM_PAD_RSA_PSS:
return SIGNATURE_PADDING_RSA_PSS;
default:
throw new IllegalArgumentException("Unsupported signature padding: " + padding);
}
}
@NonNull
static int[] allToKeymaster(@Nullable @SignaturePaddingEnum String[] paddings) {
if ((paddings == null) || (paddings.length == 0)) {
return EmptyArray.INT;
}
int[] result = new int[paddings.length];
for (int i = 0; i < paddings.length; i++) {
result[i] = toKeymaster(paddings[i]);
}
return result;
}
}
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@StringDef({
DIGEST_NONE,
DIGEST_MD5,
DIGEST_SHA1,
DIGEST_SHA224,
DIGEST_SHA256,
DIGEST_SHA384,
DIGEST_SHA512,
})
public @interface DigestEnum {}
/**
* No digest: sign/authenticate the raw message.
*/
public static final String DIGEST_NONE = "NONE";
/**
* MD5 digest.
*/
public static final String DIGEST_MD5 = "MD5";
/**
* SHA-1 digest.
*/
public static final String DIGEST_SHA1 = "SHA-1";
/**
* SHA-2 224 (aka SHA-224) digest.
*/
public static final String DIGEST_SHA224 = "SHA-224";
/**
* SHA-2 256 (aka SHA-256) digest.
*/
public static final String DIGEST_SHA256 = "SHA-256";
/**
* SHA-2 384 (aka SHA-384) digest.
*/
public static final String DIGEST_SHA384 = "SHA-384";
/**
* SHA-2 512 (aka SHA-512) digest.
*/
public static final String DIGEST_SHA512 = "SHA-512";
/**
* @hide
*/
public static abstract class Digest {
private Digest() {}
public static int toKeymaster(@NonNull @DigestEnum String digest) {
switch (digest.toUpperCase(Locale.US)) {
case DIGEST_SHA1:
return KeymasterDefs.KM_DIGEST_SHA1;
case DIGEST_SHA224:
return KeymasterDefs.KM_DIGEST_SHA_2_224;
case DIGEST_SHA256:
return KeymasterDefs.KM_DIGEST_SHA_2_256;
case DIGEST_SHA384:
return KeymasterDefs.KM_DIGEST_SHA_2_384;
case DIGEST_SHA512:
return KeymasterDefs.KM_DIGEST_SHA_2_512;
case DIGEST_NONE:
return KeymasterDefs.KM_DIGEST_NONE;
case DIGEST_MD5:
return KeymasterDefs.KM_DIGEST_MD5;
default:
throw new IllegalArgumentException("Unsupported digest algorithm: " + digest);
}
}
@NonNull
public static @DigestEnum String fromKeymaster(int digest) {
switch (digest) {
case KeymasterDefs.KM_DIGEST_NONE:
return DIGEST_NONE;
case KeymasterDefs.KM_DIGEST_MD5:
return DIGEST_MD5;
case KeymasterDefs.KM_DIGEST_SHA1:
return DIGEST_SHA1;
case KeymasterDefs.KM_DIGEST_SHA_2_224:
return DIGEST_SHA224;
case KeymasterDefs.KM_DIGEST_SHA_2_256:
return DIGEST_SHA256;
case KeymasterDefs.KM_DIGEST_SHA_2_384:
return DIGEST_SHA384;
case KeymasterDefs.KM_DIGEST_SHA_2_512:
return DIGEST_SHA512;
default:
throw new IllegalArgumentException("Unsupported digest algorithm: " + digest);
}
}
@NonNull
public static @DigestEnum String fromKeymasterToSignatureAlgorithmDigest(int digest) {
switch (digest) {
case KeymasterDefs.KM_DIGEST_NONE:
return "NONE";
case KeymasterDefs.KM_DIGEST_MD5:
return "MD5";
case KeymasterDefs.KM_DIGEST_SHA1:
return "SHA1";
case KeymasterDefs.KM_DIGEST_SHA_2_224:
return "SHA224";
case KeymasterDefs.KM_DIGEST_SHA_2_256:
return "SHA256";
case KeymasterDefs.KM_DIGEST_SHA_2_384:
return "SHA384";
case KeymasterDefs.KM_DIGEST_SHA_2_512:
return "SHA512";
default:
throw new IllegalArgumentException("Unsupported digest algorithm: " + digest);
}
}
@NonNull
public static @DigestEnum String[] allFromKeymaster(@NonNull Collection digests) {
if (digests.isEmpty()) {
return EmptyArray.STRING;
}
String[] result = new String[digests.size()];
int offset = 0;
for (int digest : digests) {
result[offset] = fromKeymaster(digest);
offset++;
}
return result;
}
@NonNull
public static int[] allToKeymaster(@Nullable @DigestEnum String[] digests) {
if ((digests == null) || (digests.length == 0)) {
return EmptyArray.INT;
}
int[] result = new int[digests.length];
int offset = 0;
for (@DigestEnum String digest : digests) {
result[offset] = toKeymaster(digest);
offset++;
}
return result;
}
}
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
ORIGIN_GENERATED,
ORIGIN_IMPORTED,
ORIGIN_UNKNOWN,
})
public @interface OriginEnum {}
/** Key was generated inside AndroidKeyStore. */
public static final int ORIGIN_GENERATED = 1 << 0;
/** Key was imported into AndroidKeyStore. */
public static final int ORIGIN_IMPORTED = 1 << 1;
/**
* Origin of the key is unknown. This can occur only for keys backed by an old TEE-backed
* implementation which does not record origin information.
*/
public static final int ORIGIN_UNKNOWN = 1 << 2;
/**
* @hide
*/
public static abstract class Origin {
private Origin() {}
public static @OriginEnum int fromKeymaster(int origin) {
switch (origin) {
case KeymasterDefs.KM_ORIGIN_GENERATED:
return ORIGIN_GENERATED;
case KeymasterDefs.KM_ORIGIN_IMPORTED:
return ORIGIN_IMPORTED;
case KeymasterDefs.KM_ORIGIN_UNKNOWN:
return ORIGIN_UNKNOWN;
default:
throw new IllegalArgumentException("Unknown origin: " + origin);
}
}
}
private static int[] getSetFlags(int flags) {
if (flags == 0) {
return EmptyArray.INT;
}
int result[] = new int[getSetBitCount(flags)];
int resultOffset = 0;
int flag = 1;
while (flags != 0) {
if ((flags & 1) != 0) {
result[resultOffset] = flag;
resultOffset++;
}
flags >>>= 1;
flag <<= 1;
}
return result;
}
private static int getSetBitCount(int value) {
if (value == 0) {
return 0;
}
int result = 0;
while (value != 0) {
if ((value & 1) != 0) {
result++;
}
value >>>= 1;
}
return result;
}
}