* commit '9c0f257f0080aba0743f34fb6202215fca295146': Add SecretKeyFactory backed by AndroidKeyStore.
This commit is contained in:
@ -40,6 +40,10 @@ public class AndroidKeyStoreProvider extends Provider {
|
||||
put("KeyGenerator.AES", KeyStoreKeyGeneratorSpi.AES.class.getName());
|
||||
put("KeyGenerator.HmacSHA256", KeyStoreKeyGeneratorSpi.HmacSHA256.class.getName());
|
||||
|
||||
// java.security.SecretKeyFactory
|
||||
put("SecretKeyFactory.AES", KeyStoreSecretKeyFactorySpi.class.getName());
|
||||
put("SecretKeyFactory.HmacSHA256", KeyStoreSecretKeyFactorySpi.class.getName());
|
||||
|
||||
// javax.crypto.Mac
|
||||
putMacImpl("HmacSHA256", KeyStoreHmacSpi.HmacSHA256.class.getName());
|
||||
|
||||
|
@ -146,7 +146,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the set of purposes for which the key can be used to the provided set of purposes.
|
||||
* Gets the set of purposes for which the key can be used.
|
||||
*
|
||||
* @return set of purposes or {@code null} if the key can be used for any purpose.
|
||||
*/
|
||||
|
@ -0,0 +1,52 @@
|
||||
package android.security;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.security.keymaster.KeymasterDefs;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Characteristics of {@code AndroidKeyStore} keys.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public abstract class KeyStoreKeyCharacteristics {
|
||||
private KeyStoreKeyCharacteristics() {}
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({Origin.GENERATED_INSIDE_TEE, Origin.GENERATED_OUTSIDE_OF_TEE, Origin.IMPORTED})
|
||||
public @interface OriginEnum {}
|
||||
|
||||
/**
|
||||
* Origin of the key.
|
||||
*/
|
||||
public static abstract class Origin {
|
||||
private Origin() {}
|
||||
|
||||
/** Key was generated inside a TEE. */
|
||||
public static final int GENERATED_INSIDE_TEE = 1;
|
||||
|
||||
/** Key was generated outside of a TEE. */
|
||||
public static final int GENERATED_OUTSIDE_OF_TEE = 2;
|
||||
|
||||
/** Key was imported. */
|
||||
public static final int IMPORTED = 0;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static @OriginEnum int fromKeymaster(int origin) {
|
||||
switch (origin) {
|
||||
case KeymasterDefs.KM_ORIGIN_HARDWARE:
|
||||
return GENERATED_INSIDE_TEE;
|
||||
case KeymasterDefs.KM_ORIGIN_SOFTWARE:
|
||||
return GENERATED_OUTSIDE_OF_TEE;
|
||||
case KeymasterDefs.KM_ORIGIN_IMPORTED:
|
||||
return IMPORTED;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown origin: " + origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
210
keystore/java/android/security/KeyStoreKeySpec.java
Normal file
210
keystore/java/android/security/KeyStoreKeySpec.java
Normal file
@ -0,0 +1,210 @@
|
||||
package android.security;
|
||||
|
||||
import java.security.spec.KeySpec;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Information about a key from the <a href="{@docRoot}training/articles/keystore.html">Android
|
||||
* KeyStore</a>.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class KeyStoreKeySpec implements KeySpec {
|
||||
private final String mKeystoreAlias;
|
||||
private final int mKeySize;
|
||||
private final @KeyStoreKeyCharacteristics.OriginEnum int mOrigin;
|
||||
private final Date mKeyValidityStart;
|
||||
private final Date mKeyValidityForOriginationEnd;
|
||||
private final Date mKeyValidityForConsumptionEnd;
|
||||
private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
|
||||
private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm;
|
||||
private final @KeyStoreKeyConstraints.PaddingEnum Integer mPadding;
|
||||
private final @KeyStoreKeyConstraints.DigestEnum Integer mDigest;
|
||||
private final @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode;
|
||||
private final Integer mMinSecondsBetweenOperations;
|
||||
private final Integer mMaxUsesPerBoot;
|
||||
private final Set<Integer> mUserAuthenticators;
|
||||
private final Set<Integer> mTeeBackedUserAuthenticators;
|
||||
private final Integer mUserAuthenticationValidityDurationSeconds;
|
||||
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
KeyStoreKeySpec(String keystoreKeyAlias,
|
||||
@KeyStoreKeyCharacteristics.OriginEnum int origin,
|
||||
int keySize, Date keyValidityStart, Date keyValidityForOriginationEnd,
|
||||
Date keyValidityForConsumptionEnd,
|
||||
@KeyStoreKeyConstraints.PurposeEnum int purposes,
|
||||
@KeyStoreKeyConstraints.AlgorithmEnum int algorithm,
|
||||
@KeyStoreKeyConstraints.PaddingEnum Integer padding,
|
||||
@KeyStoreKeyConstraints.DigestEnum Integer digest,
|
||||
@KeyStoreKeyConstraints.BlockModeEnum Integer blockMode,
|
||||
Integer minSecondsBetweenOperations,
|
||||
Integer maxUsesPerBoot,
|
||||
Set<Integer> userAuthenticators,
|
||||
Set<Integer> teeBackedUserAuthenticators,
|
||||
Integer userAuthenticationValidityDurationSeconds) {
|
||||
mKeystoreAlias = keystoreKeyAlias;
|
||||
mOrigin = origin;
|
||||
mKeySize = keySize;
|
||||
mKeyValidityStart = keyValidityStart;
|
||||
mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
|
||||
mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
|
||||
mPurposes = purposes;
|
||||
mAlgorithm = algorithm;
|
||||
mPadding = padding;
|
||||
mDigest = digest;
|
||||
mBlockMode = blockMode;
|
||||
mMinSecondsBetweenOperations = minSecondsBetweenOperations;
|
||||
mMaxUsesPerBoot = maxUsesPerBoot;
|
||||
mUserAuthenticators = (userAuthenticators != null)
|
||||
? new HashSet<Integer>(userAuthenticators)
|
||||
: Collections.<Integer>emptySet();
|
||||
mTeeBackedUserAuthenticators = (teeBackedUserAuthenticators != null)
|
||||
? new HashSet<Integer>(teeBackedUserAuthenticators)
|
||||
: Collections.<Integer>emptySet();
|
||||
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entry alias under which the key is stored in the {@code AndroidKeyStore}.
|
||||
*/
|
||||
public String getKeystoreAlias() {
|
||||
return mKeystoreAlias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the origin of the key.
|
||||
*/
|
||||
public @KeyStoreKeyCharacteristics.OriginEnum int getOrigin() {
|
||||
return mOrigin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the key's size in bits.
|
||||
*/
|
||||
public int getKeySize() {
|
||||
return mKeySize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time instant before which the key is not yet valid.
|
||||
*
|
||||
* @return instant or {@code null} if not restricted.
|
||||
*/
|
||||
public Date getKeyValidityStart() {
|
||||
return mKeyValidityStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time instant after which the key is no long valid for decryption and verification.
|
||||
*
|
||||
* @return instant or {@code null} if not restricted.
|
||||
*/
|
||||
public Date getKeyValidityForConsumptionEnd() {
|
||||
return mKeyValidityForConsumptionEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time instant after which the key is no long valid for encryption and signing.
|
||||
*
|
||||
* @return instant or {@code null} if not restricted.
|
||||
*/
|
||||
public Date getKeyValidityForOriginationEnd() {
|
||||
return mKeyValidityForOriginationEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the set of purposes for which the key can be used.
|
||||
*/
|
||||
public @KeyStoreKeyConstraints.PurposeEnum int getPurposes() {
|
||||
return mPurposes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the algorithm of the key.
|
||||
*/
|
||||
public @KeyStoreKeyConstraints.AlgorithmEnum int getAlgorithm() {
|
||||
return mAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the only block mode with which the key can be used.
|
||||
*
|
||||
* @return block mode or {@code null} if the block mode is not restricted.
|
||||
*/
|
||||
public @KeyStoreKeyConstraints.BlockModeEnum Integer getBlockMode() {
|
||||
return mBlockMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the only padding mode with which the key can be used.
|
||||
*
|
||||
* @return padding mode or {@code null} if the padding mode is not restricted.
|
||||
*/
|
||||
public @KeyStoreKeyConstraints.PaddingEnum Integer getPadding() {
|
||||
return mPadding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the only digest algorithm with which the key can be used.
|
||||
*
|
||||
* @return digest algorithm or {@code null} if the digest algorithm is not restricted.
|
||||
*/
|
||||
public @KeyStoreKeyConstraints.DigestEnum Integer getDigest() {
|
||||
return mDigest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum number of seconds that must expire since the most recent use of the key
|
||||
* before it can be used again.
|
||||
*
|
||||
* @return number of seconds or {@code null} if there is no restriction on how frequently a key
|
||||
* can be used.
|
||||
*/
|
||||
public Integer getMinSecondsBetweenOperations() {
|
||||
return mMinSecondsBetweenOperations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of times the key can be used without rebooting the device.
|
||||
*
|
||||
* @return maximum number of times or {@code null} if there is no restriction.
|
||||
*/
|
||||
public Integer getMaxUsesPerBoot() {
|
||||
return mMaxUsesPerBoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user authenticators which protect access to the key. The key can only be used iff
|
||||
* the user has authenticated to at least one of these user authenticators.
|
||||
*
|
||||
* @return user authenticators or empty set if the key can be used without user authentication.
|
||||
*/
|
||||
public Set<Integer> getUserAuthenticators() {
|
||||
return new HashSet<Integer>(mUserAuthenticators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the TEE-backed user authenticators which protect access to the key. This is a subset of
|
||||
* the user authentications returned by {@link #getUserAuthenticators()}.
|
||||
*/
|
||||
public Set<Integer> getTeeBackedUserAuthenticators() {
|
||||
return new HashSet<Integer>(mTeeBackedUserAuthenticators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the duration of time (seconds) for which the key can be used after the user
|
||||
* successfully authenticates to one of the associated user authenticators.
|
||||
*
|
||||
* @return duration in seconds or {@code null} if not restricted. {@code 0} means authentication
|
||||
* is required for every use of the key.
|
||||
*/
|
||||
public Integer getUserAuthenticationValidityDurationSeconds() {
|
||||
return mUserAuthenticationValidityDurationSeconds;
|
||||
}
|
||||
}
|
140
keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
Normal file
140
keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
Normal file
@ -0,0 +1,140 @@
|
||||
package android.security;
|
||||
|
||||
import android.security.keymaster.KeyCharacteristics;
|
||||
import android.security.keymaster.KeymasterDefs;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactorySpi;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* {@link SecretKeyFactorySpi} backed by Android KeyStore.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
|
||||
|
||||
private final KeyStore mKeyStore = KeyStore.getInstance();
|
||||
|
||||
@Override
|
||||
protected KeySpec engineGetKeySpec(SecretKey key,
|
||||
@SuppressWarnings("rawtypes") Class keySpecClass) throws InvalidKeySpecException {
|
||||
if (keySpecClass == null) {
|
||||
throw new InvalidKeySpecException("keySpecClass == null");
|
||||
}
|
||||
if (!(key instanceof KeyStoreSecretKey)) {
|
||||
throw new InvalidKeySpecException("Only Android KeyStore secret keys supported: " +
|
||||
((key != null) ? key.getClass().getName() : "null"));
|
||||
}
|
||||
if (SecretKeySpec.class.isAssignableFrom(keySpecClass)) {
|
||||
throw new InvalidKeySpecException(
|
||||
"Key material export of Android KeyStore keys is not supported");
|
||||
}
|
||||
if (!KeyStoreKeySpec.class.equals(keySpecClass)) {
|
||||
throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName());
|
||||
}
|
||||
String keyAliasInKeystore = ((KeyStoreSecretKey) key).getAlias();
|
||||
String entryAlias;
|
||||
if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) {
|
||||
entryAlias = keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length());
|
||||
} else {
|
||||
throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore);
|
||||
}
|
||||
|
||||
KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
|
||||
int errorCode =
|
||||
mKeyStore.getKeyCharacteristics(keyAliasInKeystore, null, null, keyCharacteristics);
|
||||
if (errorCode != KeyStore.NO_ERROR) {
|
||||
throw new InvalidKeySpecException("Failed to obtain information about key."
|
||||
+ " Keystore error: " + errorCode);
|
||||
}
|
||||
|
||||
@KeyStoreKeyCharacteristics.OriginEnum Integer origin;
|
||||
int keySize;
|
||||
@KeyStoreKeyConstraints.PurposeEnum int purposes;
|
||||
@KeyStoreKeyConstraints.AlgorithmEnum int algorithm;
|
||||
@KeyStoreKeyConstraints.PaddingEnum Integer padding;
|
||||
@KeyStoreKeyConstraints.DigestEnum Integer digest;
|
||||
@KeyStoreKeyConstraints.BlockModeEnum Integer blockMode;
|
||||
try {
|
||||
origin = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_ORIGIN);
|
||||
if (origin == null) {
|
||||
throw new InvalidKeySpecException("Key origin not available");
|
||||
}
|
||||
origin = KeyStoreKeyCharacteristics.Origin.fromKeymaster(origin);
|
||||
Integer keySizeInteger =
|
||||
KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_KEY_SIZE);
|
||||
if (keySizeInteger == null) {
|
||||
throw new InvalidKeySpecException("Key size not available");
|
||||
}
|
||||
keySize = keySizeInteger;
|
||||
purposes = KeyStoreKeyConstraints.Purpose.allFromKeymaster(
|
||||
KeymasterUtils.getInts(keyCharacteristics, KeymasterDefs.KM_TAG_PURPOSE));
|
||||
Integer alg = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_ALGORITHM);
|
||||
if (alg == null) {
|
||||
throw new InvalidKeySpecException("Key algorithm not available");
|
||||
}
|
||||
algorithm = KeyStoreKeyConstraints.Algorithm.fromKeymaster(alg);
|
||||
padding = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_PADDING);
|
||||
if (padding != null) {
|
||||
padding = KeyStoreKeyConstraints.Padding.fromKeymaster(padding);
|
||||
}
|
||||
digest = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_DIGEST);
|
||||
if (digest != null) {
|
||||
digest = KeyStoreKeyConstraints.Digest.fromKeymaster(digest);
|
||||
}
|
||||
blockMode = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_BLOCK_MODE);
|
||||
if (blockMode != null) {
|
||||
blockMode = KeyStoreKeyConstraints.BlockMode.fromKeymaster(blockMode);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidKeySpecException("Unsupported key characteristic", e);
|
||||
}
|
||||
|
||||
// TODO: Read user authentication IDs once the Keymaster API has stabilized
|
||||
Set<Integer> userAuthenticators = Collections.emptySet();
|
||||
Set<Integer> teeBackedUserAuthenticators = Collections.emptySet();
|
||||
// Set<Integer> userAuthenticators = new HashSet<Integer>(
|
||||
// getInts(keyCharacteristics, KeymasterDefs.KM_TAG_USER_AUTH_ID));
|
||||
// Set<Integer> teeBackedUserAuthenticators = new HashSet<Integer>(
|
||||
// keyCharacteristics.hwEnforced.getInts(KeymasterDefs.KM_TAG_USER_AUTH_ID));
|
||||
|
||||
return new KeyStoreKeySpec(entryAlias,
|
||||
origin,
|
||||
keySize,
|
||||
KeymasterUtils.getDate(keyCharacteristics, KeymasterDefs.KM_TAG_ACTIVE_DATETIME),
|
||||
KeymasterUtils.getDate(keyCharacteristics,
|
||||
KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME),
|
||||
KeymasterUtils.getDate(keyCharacteristics,
|
||||
KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME),
|
||||
purposes,
|
||||
algorithm,
|
||||
padding,
|
||||
digest,
|
||||
blockMode,
|
||||
KeymasterUtils.getInt(keyCharacteristics,
|
||||
KeymasterDefs.KM_TAG_MIN_SECONDS_BETWEEN_OPS),
|
||||
KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_MAX_USES_PER_BOOT),
|
||||
userAuthenticators,
|
||||
teeBackedUserAuthenticators,
|
||||
KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_AUTH_TIMEOUT));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException {
|
||||
throw new UnsupportedOperationException(
|
||||
"Key import into Android KeyStore is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException {
|
||||
throw new UnsupportedOperationException(
|
||||
"Key import into Android KeyStore is not supported");
|
||||
}
|
||||
}
|
@ -1,7 +1,12 @@
|
||||
package android.security;
|
||||
|
||||
import android.security.keymaster.KeyCharacteristics;
|
||||
import android.security.keymaster.KeymasterDefs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@ -20,4 +25,37 @@ public abstract class KeymasterUtils {
|
||||
KeymasterDefs.getErrorMessage(keymasterErrorCode));
|
||||
}
|
||||
}
|
||||
|
||||
public static Integer getInt(KeyCharacteristics keyCharacteristics, int tag) {
|
||||
if (keyCharacteristics.hwEnforced.containsTag(tag)) {
|
||||
return keyCharacteristics.hwEnforced.getInt(tag, -1);
|
||||
} else if (keyCharacteristics.swEnforced.containsTag(tag)) {
|
||||
return keyCharacteristics.swEnforced.getInt(tag, -1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Integer> getInts(KeyCharacteristics keyCharacteristics, int tag) {
|
||||
List<Integer> result = new ArrayList<Integer>();
|
||||
result.addAll(keyCharacteristics.hwEnforced.getInts(tag));
|
||||
result.addAll(keyCharacteristics.swEnforced.getInts(tag));
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Date getDate(KeyCharacteristics keyCharacteristics, int tag) {
|
||||
Date result = keyCharacteristics.hwEnforced.getDate(tag, null);
|
||||
if (result == null) {
|
||||
result = keyCharacteristics.swEnforced.getDate(tag, null);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean getBoolean(KeyCharacteristics keyCharacteristics, int tag) {
|
||||
if (keyCharacteristics.hwEnforced.containsTag(tag)) {
|
||||
return keyCharacteristics.hwEnforced.getBoolean(tag, false);
|
||||
} else {
|
||||
return keyCharacteristics.swEnforced.getBoolean(tag, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user