am 274a4ee3: Merge "Symmetric key import for AndroidKeyStore."
* commit '274a4ee3446e76a34a9cfe987e98f7bf4e53f78d': Symmetric key import for AndroidKeyStore.
This commit is contained in:
@@ -136,6 +136,8 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi {
|
|||||||
throw new IllegalStateException("could not generate key in keystore");
|
throw new IllegalStateException("could not generate key in keystore");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias);
|
||||||
|
|
||||||
final PrivateKey privKey;
|
final PrivateKey privKey;
|
||||||
final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
|
final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ package android.security;
|
|||||||
import com.android.org.conscrypt.OpenSSLEngine;
|
import com.android.org.conscrypt.OpenSSLEngine;
|
||||||
import com.android.org.conscrypt.OpenSSLKeyHolder;
|
import com.android.org.conscrypt.OpenSSLKeyHolder;
|
||||||
|
|
||||||
|
import android.security.keymaster.KeyCharacteristics;
|
||||||
|
import android.security.keymaster.KeymasterArguments;
|
||||||
|
import android.security.keymaster.KeymasterDefs;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@@ -31,6 +34,7 @@ import java.security.KeyStore.Entry;
|
|||||||
import java.security.KeyStore.PrivateKeyEntry;
|
import java.security.KeyStore.PrivateKeyEntry;
|
||||||
import java.security.KeyStore.ProtectionParameter;
|
import java.security.KeyStore.ProtectionParameter;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStore.SecretKeyEntry;
|
||||||
import java.security.KeyStoreException;
|
import java.security.KeyStoreException;
|
||||||
import java.security.KeyStoreSpi;
|
import java.security.KeyStoreSpi;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
@@ -50,6 +54,8 @@ import java.util.HashSet;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A java.security.KeyStore interface for the Android KeyStore. An instance of
|
* A java.security.KeyStore interface for the Android KeyStore. An instance of
|
||||||
* it can be created via the {@link java.security.KeyStore#getInstance(String)
|
* it can be created via the {@link java.security.KeyStore#getInstance(String)
|
||||||
@@ -77,18 +83,72 @@ public class AndroidKeyStore extends KeyStoreSpi {
|
|||||||
@Override
|
@Override
|
||||||
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
|
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
|
||||||
UnrecoverableKeyException {
|
UnrecoverableKeyException {
|
||||||
if (!isKeyEntry(alias)) {
|
if (isPrivateKeyEntry(alias)) {
|
||||||
return null;
|
final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
|
||||||
|
try {
|
||||||
|
return engine.getPrivateKeyById(Credentials.USER_PRIVATE_KEY + alias);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
UnrecoverableKeyException t = new UnrecoverableKeyException("Can't get key");
|
||||||
|
t.initCause(e);
|
||||||
|
throw t;
|
||||||
|
}
|
||||||
|
} else if (isSecretKeyEntry(alias)) {
|
||||||
|
KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
|
||||||
|
String keyAliasInKeystore = Credentials.USER_SECRET_KEY + alias;
|
||||||
|
int errorCode = mKeyStore.getKeyCharacteristics(
|
||||||
|
keyAliasInKeystore, null, null, keyCharacteristics);
|
||||||
|
if ((errorCode != KeymasterDefs.KM_ERROR_OK)
|
||||||
|
&& (errorCode != android.security.KeyStore.NO_ERROR)) {
|
||||||
|
throw new UnrecoverableKeyException("Failed to load information about key."
|
||||||
|
+ " Error code: " + errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
int keymasterAlgorithm =
|
||||||
|
keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1);
|
||||||
|
if (keymasterAlgorithm == -1) {
|
||||||
|
keymasterAlgorithm =
|
||||||
|
keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1);
|
||||||
|
}
|
||||||
|
if (keymasterAlgorithm == -1) {
|
||||||
|
throw new UnrecoverableKeyException("Key algorithm unknown");
|
||||||
|
}
|
||||||
|
@KeyStoreKeyConstraints.AlgorithmEnum int keyAlgorithm;
|
||||||
|
try {
|
||||||
|
keyAlgorithm = KeyStoreKeyConstraints.Algorithm.fromKeymaster(keymasterAlgorithm);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw (UnrecoverableKeyException)
|
||||||
|
new UnrecoverableKeyException("Unsupported key algorithm").initCause(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
int keymasterDigest =
|
||||||
|
keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_DIGEST, -1);
|
||||||
|
if (keymasterDigest == -1) {
|
||||||
|
keymasterDigest =
|
||||||
|
keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_DIGEST, -1);
|
||||||
|
}
|
||||||
|
@KeyStoreKeyConstraints.DigestEnum Integer digest = null;
|
||||||
|
if (keymasterDigest != -1) {
|
||||||
|
try {
|
||||||
|
digest = KeyStoreKeyConstraints.Digest.fromKeymaster(keymasterDigest);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw (UnrecoverableKeyException)
|
||||||
|
new UnrecoverableKeyException("Unsupported digest").initCause(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String keyAlgorithmString;
|
||||||
|
try {
|
||||||
|
keyAlgorithmString = KeyStoreKeyConstraints.Algorithm.toJCASecretKeyAlgorithm(
|
||||||
|
keyAlgorithm, digest);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw (UnrecoverableKeyException)
|
||||||
|
new UnrecoverableKeyException("Unsupported secret key type").initCause(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmString);
|
||||||
}
|
}
|
||||||
|
|
||||||
final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
|
return null;
|
||||||
try {
|
|
||||||
return engine.getPrivateKeyById(Credentials.USER_PRIVATE_KEY + alias);
|
|
||||||
} catch (InvalidKeyException e) {
|
|
||||||
UnrecoverableKeyException t = new UnrecoverableKeyException("Can't get key");
|
|
||||||
t.initCause(e);
|
|
||||||
throw t;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -186,6 +246,11 @@ public class AndroidKeyStore extends KeyStoreSpi {
|
|||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d = getModificationDate(Credentials.USER_SECRET_KEY + alias);
|
||||||
|
if (d != null) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
d = getModificationDate(Credentials.USER_CERTIFICATE + alias);
|
d = getModificationDate(Credentials.USER_CERTIFICATE + alias);
|
||||||
if (d != null) {
|
if (d != null) {
|
||||||
return d;
|
return d;
|
||||||
@@ -203,8 +268,10 @@ public class AndroidKeyStore extends KeyStoreSpi {
|
|||||||
|
|
||||||
if (key instanceof PrivateKey) {
|
if (key instanceof PrivateKey) {
|
||||||
setPrivateKeyEntry(alias, (PrivateKey) key, chain, null);
|
setPrivateKeyEntry(alias, (PrivateKey) key, chain, null);
|
||||||
|
} else if (key instanceof SecretKey) {
|
||||||
|
setSecretKeyEntry(alias, (SecretKey) key, null);
|
||||||
} else {
|
} else {
|
||||||
throw new KeyStoreException("Only PrivateKeys are supported");
|
throw new KeyStoreException("Only PrivateKey and SecretKey are supported");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,6 +386,7 @@ public class AndroidKeyStore extends KeyStoreSpi {
|
|||||||
Credentials.deleteAllTypesForAlias(mKeyStore, alias);
|
Credentials.deleteAllTypesForAlias(mKeyStore, alias);
|
||||||
} else {
|
} else {
|
||||||
Credentials.deleteCertificateTypesForAlias(mKeyStore, alias);
|
Credentials.deleteCertificateTypesForAlias(mKeyStore, alias);
|
||||||
|
Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
final int flags = (params == null) ? 0 : params.getFlags();
|
final int flags = (params == null) ? 0 : params.getFlags();
|
||||||
@@ -340,6 +408,160 @@ public class AndroidKeyStore extends KeyStoreSpi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setSecretKeyEntry(String entryAlias, SecretKey key, KeyStoreParameter params)
|
||||||
|
throws KeyStoreException {
|
||||||
|
if (key instanceof KeyStoreSecretKey) {
|
||||||
|
// KeyStore-backed secret key. It cannot be duplicated into another entry and cannot
|
||||||
|
// overwrite its own entry.
|
||||||
|
String keyAliasInKeystore = ((KeyStoreSecretKey) key).getAlias();
|
||||||
|
if (keyAliasInKeystore == null) {
|
||||||
|
throw new KeyStoreException("KeyStore-backed secret key does not have an alias");
|
||||||
|
}
|
||||||
|
if (!keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) {
|
||||||
|
throw new KeyStoreException("KeyStore-backed secret key has invalid alias: "
|
||||||
|
+ keyAliasInKeystore);
|
||||||
|
}
|
||||||
|
String keyEntryAlias =
|
||||||
|
keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length());
|
||||||
|
if (!entryAlias.equals(keyEntryAlias)) {
|
||||||
|
throw new KeyStoreException("Can only replace KeyStore-backed keys with same"
|
||||||
|
+ " alias: " + entryAlias + " != " + keyEntryAlias);
|
||||||
|
}
|
||||||
|
// This is the entry where this key is already stored. No need to do anything.
|
||||||
|
if (params != null) {
|
||||||
|
throw new KeyStoreException("Modifying KeyStore-backed key using protection"
|
||||||
|
+ " parameters not supported");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params == null) {
|
||||||
|
throw new KeyStoreException(
|
||||||
|
"Protection parameters must be specified when importing a symmetric key");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a KeyStore-backed secret key -- import its key material into keystore.
|
||||||
|
String keyExportFormat = key.getFormat();
|
||||||
|
if (keyExportFormat == null) {
|
||||||
|
throw new KeyStoreException(
|
||||||
|
"Only secret keys that export their key material are supported");
|
||||||
|
} else if (!"RAW".equals(keyExportFormat)) {
|
||||||
|
throw new KeyStoreException(
|
||||||
|
"Unsupported secret key material export format: " + keyExportFormat);
|
||||||
|
}
|
||||||
|
byte[] keyMaterial = key.getEncoded();
|
||||||
|
if (keyMaterial == null) {
|
||||||
|
throw new KeyStoreException("Key did not export its key material despite supporting"
|
||||||
|
+ " RAW format export");
|
||||||
|
}
|
||||||
|
|
||||||
|
String keyAlgorithmString = key.getAlgorithm();
|
||||||
|
@KeyStoreKeyConstraints.AlgorithmEnum int keyAlgorithm;
|
||||||
|
@KeyStoreKeyConstraints.AlgorithmEnum Integer digest;
|
||||||
|
try {
|
||||||
|
keyAlgorithm =
|
||||||
|
KeyStoreKeyConstraints.Algorithm.fromJCASecretKeyAlgorithm(keyAlgorithmString);
|
||||||
|
digest = KeyStoreKeyConstraints.Digest.fromJCASecretKeyAlgorithm(keyAlgorithmString);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((params.getAlgorithm() != null) && (params.getAlgorithm() != keyAlgorithm)) {
|
||||||
|
throw new KeyStoreException("Key algorithm mismatch. Key: " + keyAlgorithmString
|
||||||
|
+ ", parameter spec: "
|
||||||
|
+ KeyStoreKeyConstraints.Algorithm.toString(params.getAlgorithm()));
|
||||||
|
}
|
||||||
|
|
||||||
|
KeymasterArguments args = new KeymasterArguments();
|
||||||
|
args.addInt(KeymasterDefs.KM_TAG_ALGORITHM,
|
||||||
|
KeyStoreKeyConstraints.Algorithm.toKeymaster(keyAlgorithm));
|
||||||
|
|
||||||
|
if (digest != null) {
|
||||||
|
// Digest available from JCA key algorithm
|
||||||
|
if (params.getDigest() != null) {
|
||||||
|
// Digest also specified in parameters -- check that these two match
|
||||||
|
if (digest != params.getDigest()) {
|
||||||
|
throw new KeyStoreException("Key digest mismatch. Key: " + keyAlgorithmString
|
||||||
|
+ ", parameter spec: "
|
||||||
|
+ KeyStoreKeyConstraints.Digest.toString(params.getDigest()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Digest not available from JCA key algorithm
|
||||||
|
digest = params.getDigest();
|
||||||
|
}
|
||||||
|
if (digest != null) {
|
||||||
|
args.addInt(KeymasterDefs.KM_TAG_DIGEST,
|
||||||
|
KeyStoreKeyConstraints.Digest.toKeymaster(digest));
|
||||||
|
}
|
||||||
|
|
||||||
|
@KeyStoreKeyConstraints.PurposeEnum int purposes = (params.getPurposes() != null)
|
||||||
|
? params.getPurposes()
|
||||||
|
: (KeyStoreKeyConstraints.Purpose.ENCRYPT
|
||||||
|
| KeyStoreKeyConstraints.Purpose.DECRYPT
|
||||||
|
| KeyStoreKeyConstraints.Purpose.SIGN
|
||||||
|
| KeyStoreKeyConstraints.Purpose.VERIFY);
|
||||||
|
for (int keymasterPurpose :
|
||||||
|
KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) {
|
||||||
|
args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
|
||||||
|
}
|
||||||
|
if (params.getBlockMode() != null) {
|
||||||
|
args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE,
|
||||||
|
KeyStoreKeyConstraints.BlockMode.toKeymaster(params.getBlockMode()));
|
||||||
|
}
|
||||||
|
if (params.getPadding() != null) {
|
||||||
|
args.addInt(KeymasterDefs.KM_TAG_PADDING,
|
||||||
|
KeyStoreKeyConstraints.Padding.toKeymaster(params.getPadding()));
|
||||||
|
}
|
||||||
|
if (params.getMaxUsesPerBoot() != null) {
|
||||||
|
args.addInt(KeymasterDefs.KM_TAG_MAX_USES_PER_BOOT, params.getMaxUsesPerBoot());
|
||||||
|
}
|
||||||
|
if (params.getMinSecondsBetweenOperations() != null) {
|
||||||
|
args.addInt(KeymasterDefs.KM_TAG_MIN_SECONDS_BETWEEN_OPS,
|
||||||
|
params.getMinSecondsBetweenOperations());
|
||||||
|
}
|
||||||
|
if (params.getUserAuthenticators().isEmpty()) {
|
||||||
|
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
|
||||||
|
} else {
|
||||||
|
// TODO: Pass-in user authenticator IDs once the Keymaster API has stabilized
|
||||||
|
// for (int userAuthenticatorId : params.getUserAuthenticators()) {
|
||||||
|
// args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_ID, userAuthenticatorId);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
if (params.getUserAuthenticationValidityDurationSeconds() != null) {
|
||||||
|
args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
|
||||||
|
params.getUserAuthenticationValidityDurationSeconds());
|
||||||
|
}
|
||||||
|
if (params.getKeyValidityStart() != null) {
|
||||||
|
args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, params.getKeyValidityStart());
|
||||||
|
}
|
||||||
|
if (params.getKeyValidityForOriginationEnd() != null) {
|
||||||
|
args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
|
||||||
|
params.getKeyValidityForOriginationEnd());
|
||||||
|
}
|
||||||
|
if (params.getKeyValidityForConsumptionEnd() != null) {
|
||||||
|
args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
|
||||||
|
params.getKeyValidityForConsumptionEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove this once keymaster does not require us to specify the size of imported key.
|
||||||
|
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keyMaterial.length * 8);
|
||||||
|
|
||||||
|
Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias);
|
||||||
|
String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias;
|
||||||
|
int errorCode = mKeyStore.importKey(
|
||||||
|
keyAliasInKeystore,
|
||||||
|
args,
|
||||||
|
KeymasterDefs.KM_KEY_FORMAT_RAW,
|
||||||
|
keyMaterial,
|
||||||
|
params.getFlags(),
|
||||||
|
new KeyCharacteristics());
|
||||||
|
if (errorCode != android.security.KeyStore.NO_ERROR) {
|
||||||
|
throw new KeyStoreException("Failed to import secret key. Keystore error code: "
|
||||||
|
+ errorCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain)
|
public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain)
|
||||||
throws KeyStoreException {
|
throws KeyStoreException {
|
||||||
@@ -413,6 +635,7 @@ public class AndroidKeyStore extends KeyStoreSpi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias)
|
return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias)
|
||||||
|
|| mKeyStore.contains(Credentials.USER_SECRET_KEY + alias)
|
||||||
|| mKeyStore.contains(Credentials.USER_CERTIFICATE + alias)
|
|| mKeyStore.contains(Credentials.USER_CERTIFICATE + alias)
|
||||||
|| mKeyStore.contains(Credentials.CA_CERTIFICATE + alias);
|
|| mKeyStore.contains(Credentials.CA_CERTIFICATE + alias);
|
||||||
}
|
}
|
||||||
@@ -428,6 +651,10 @@ public class AndroidKeyStore extends KeyStoreSpi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isKeyEntry(String alias) {
|
private boolean isKeyEntry(String alias) {
|
||||||
|
return isPrivateKeyEntry(alias) || isSecretKeyEntry(alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPrivateKeyEntry(String alias) {
|
||||||
if (alias == null) {
|
if (alias == null) {
|
||||||
throw new NullPointerException("alias == null");
|
throw new NullPointerException("alias == null");
|
||||||
}
|
}
|
||||||
@@ -435,6 +662,14 @@ public class AndroidKeyStore extends KeyStoreSpi {
|
|||||||
return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias);
|
return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isSecretKeyEntry(String alias) {
|
||||||
|
if (alias == null) {
|
||||||
|
throw new NullPointerException("alias == null");
|
||||||
|
}
|
||||||
|
|
||||||
|
return mKeyStore.contains(Credentials.USER_SECRET_KEY + alias);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isCertificateEntry(String alias) {
|
private boolean isCertificateEntry(String alias) {
|
||||||
if (alias == null) {
|
if (alias == null) {
|
||||||
throw new NullPointerException("alias == null");
|
throw new NullPointerException("alias == null");
|
||||||
@@ -554,11 +789,14 @@ public class AndroidKeyStore extends KeyStoreSpi {
|
|||||||
PrivateKeyEntry prE = (PrivateKeyEntry) entry;
|
PrivateKeyEntry prE = (PrivateKeyEntry) entry;
|
||||||
setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(),
|
setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(),
|
||||||
(KeyStoreParameter) param);
|
(KeyStoreParameter) param);
|
||||||
return;
|
} else if (entry instanceof SecretKeyEntry) {
|
||||||
|
SecretKeyEntry secE = (SecretKeyEntry) entry;
|
||||||
|
setSecretKeyEntry(alias, secE.getSecretKey(), (KeyStoreParameter) param);
|
||||||
|
} else {
|
||||||
|
throw new KeyStoreException(
|
||||||
|
"Entry must be a PrivateKeyEntry, SecretKeyEntry or TrustedCertificateEntry"
|
||||||
|
+ "; was " + entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new KeyStoreException(
|
|
||||||
"Entry must be a PrivateKeyEntry or TrustedCertificateEntry; was " + entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ public class Credentials {
|
|||||||
/** Key prefix for user private keys. */
|
/** Key prefix for user private keys. */
|
||||||
public static final String USER_PRIVATE_KEY = "USRPKEY_";
|
public static final String USER_PRIVATE_KEY = "USRPKEY_";
|
||||||
|
|
||||||
|
/** Key prefix for user secret keys. */
|
||||||
|
public static final String USER_SECRET_KEY = "USRSKEY_";
|
||||||
|
|
||||||
/** Key prefix for VPN. */
|
/** Key prefix for VPN. */
|
||||||
public static final String VPN = "VPN_";
|
public static final String VPN = "VPN_";
|
||||||
|
|
||||||
@@ -218,7 +221,8 @@ public class Credentials {
|
|||||||
* Make sure every type is deleted. There can be all three types, so
|
* Make sure every type is deleted. There can be all three types, so
|
||||||
* don't use a conditional here.
|
* don't use a conditional here.
|
||||||
*/
|
*/
|
||||||
return keystore.delKey(Credentials.USER_PRIVATE_KEY + alias)
|
return keystore.delete(Credentials.USER_PRIVATE_KEY + alias)
|
||||||
|
| keystore.delete(Credentials.USER_SECRET_KEY + alias)
|
||||||
| deleteCertificateTypesForAlias(keystore, alias);
|
| deleteCertificateTypesForAlias(keystore, alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,4 +239,20 @@ public class Credentials {
|
|||||||
return keystore.delete(Credentials.USER_CERTIFICATE + alias)
|
return keystore.delete(Credentials.USER_CERTIFICATE + alias)
|
||||||
| keystore.delete(Credentials.CA_CERTIFICATE + alias);
|
| keystore.delete(Credentials.CA_CERTIFICATE + alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete private key for a particular {@code alias}.
|
||||||
|
* Returns {@code true} if an entry was was deleted.
|
||||||
|
*/
|
||||||
|
static boolean deletePrivateKeyTypeForAlias(KeyStore keystore, String alias) {
|
||||||
|
return keystore.delete(Credentials.USER_PRIVATE_KEY + alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete secret key for a particular {@code alias}.
|
||||||
|
* Returns {@code true} if an entry was was deleted.
|
||||||
|
*/
|
||||||
|
static boolean deleteSecretKeyTypeForAlias(KeyStore keystore, String alias) {
|
||||||
|
return keystore.delete(Credentials.USER_SECRET_KEY + alias);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
429
keystore/java/android/security/KeyStoreKeyConstraints.java
Normal file
429
keystore/java/android/security/KeyStoreKeyConstraints.java
Normal file
@@ -0,0 +1,429 @@
|
|||||||
|
package android.security;
|
||||||
|
|
||||||
|
import android.annotation.IntDef;
|
||||||
|
import android.security.keymaster.KeymasterDefs;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constraints for {@code AndroidKeyStore} keys.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public abstract class KeyStoreKeyConstraints {
|
||||||
|
private KeyStoreKeyConstraints() {}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef(flag=true, value={Purpose.ENCRYPT, Purpose.DECRYPT, Purpose.SIGN, Purpose.VERIFY})
|
||||||
|
public @interface PurposeEnum {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purpose of key.
|
||||||
|
*/
|
||||||
|
public static abstract class Purpose {
|
||||||
|
private Purpose() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purpose: encryption.
|
||||||
|
*/
|
||||||
|
public static final int ENCRYPT = 1 << 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purpose: decryption.
|
||||||
|
*/
|
||||||
|
public static final int DECRYPT = 1 << 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purpose: signing.
|
||||||
|
*/
|
||||||
|
public static final int SIGN = 1 << 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purpose: signature verification.
|
||||||
|
*/
|
||||||
|
public static final int VERIFY = 1 << 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of flags defined above. Needs to be kept in sync with the flags above.
|
||||||
|
*/
|
||||||
|
private static final int VALUE_COUNT = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static int toKeymaster(@PurposeEnum int purpose) {
|
||||||
|
switch (purpose) {
|
||||||
|
case ENCRYPT:
|
||||||
|
return KeymasterDefs.KM_PURPOSE_ENCRYPT;
|
||||||
|
case DECRYPT:
|
||||||
|
return KeymasterDefs.KM_PURPOSE_DECRYPT;
|
||||||
|
case SIGN:
|
||||||
|
return KeymasterDefs.KM_PURPOSE_SIGN;
|
||||||
|
case VERIFY:
|
||||||
|
return KeymasterDefs.KM_PURPOSE_VERIFY;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown purpose: " + purpose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static @PurposeEnum int fromKeymaster(int purpose) {
|
||||||
|
switch (purpose) {
|
||||||
|
case KeymasterDefs.KM_PURPOSE_ENCRYPT:
|
||||||
|
return ENCRYPT;
|
||||||
|
case KeymasterDefs.KM_PURPOSE_DECRYPT:
|
||||||
|
return DECRYPT;
|
||||||
|
case KeymasterDefs.KM_PURPOSE_SIGN:
|
||||||
|
return SIGN;
|
||||||
|
case KeymasterDefs.KM_PURPOSE_VERIFY:
|
||||||
|
return VERIFY;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown purpose: " + purpose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static int[] allToKeymaster(int purposes) {
|
||||||
|
int[] result = new int[VALUE_COUNT];
|
||||||
|
int resultCount = 0;
|
||||||
|
int purpose = 1;
|
||||||
|
for (int i = 0; i < 32; i++) {
|
||||||
|
if ((purposes & 1) != 0) {
|
||||||
|
result[resultCount] = toKeymaster(purpose);
|
||||||
|
resultCount++;
|
||||||
|
}
|
||||||
|
purposes >>>= 1;
|
||||||
|
purpose <<= 1;
|
||||||
|
if (purposes == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Arrays.copyOf(result, resultCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static @PurposeEnum int allFromKeymaster(Collection<Integer> purposes) {
|
||||||
|
@PurposeEnum int result = 0;
|
||||||
|
for (int keymasterPurpose : purposes) {
|
||||||
|
result |= fromKeymaster(keymasterPurpose);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({Algorithm.AES, Algorithm.HMAC})
|
||||||
|
public @interface AlgorithmEnum {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key algorithm.
|
||||||
|
*/
|
||||||
|
public static abstract class Algorithm {
|
||||||
|
private Algorithm() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key algorithm: AES.
|
||||||
|
*/
|
||||||
|
public static final int AES = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key algorithm: HMAC.
|
||||||
|
*/
|
||||||
|
public static final int HMAC = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static int toKeymaster(@AlgorithmEnum int algorithm) {
|
||||||
|
switch (algorithm) {
|
||||||
|
case AES:
|
||||||
|
return KeymasterDefs.KM_ALGORITHM_AES;
|
||||||
|
case HMAC:
|
||||||
|
return KeymasterDefs.KM_ALGORITHM_HMAC;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static @AlgorithmEnum int fromKeymaster(int algorithm) {
|
||||||
|
switch (algorithm) {
|
||||||
|
case KeymasterDefs.KM_ALGORITHM_AES:
|
||||||
|
return AES;
|
||||||
|
case KeymasterDefs.KM_ALGORITHM_HMAC:
|
||||||
|
return HMAC;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static String toString(@AlgorithmEnum int algorithm) {
|
||||||
|
switch (algorithm) {
|
||||||
|
case AES:
|
||||||
|
return "AES";
|
||||||
|
case HMAC:
|
||||||
|
return "HMAC";
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static @AlgorithmEnum int fromJCASecretKeyAlgorithm(String algorithm) {
|
||||||
|
if (algorithm == null) {
|
||||||
|
throw new NullPointerException("algorithm == null");
|
||||||
|
} else if ("AES".equalsIgnoreCase(algorithm)) {
|
||||||
|
return AES;
|
||||||
|
} else if (algorithm.toLowerCase(Locale.US).startsWith("hmac")) {
|
||||||
|
return HMAC;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unsupported secret key algorithm: " + algorithm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static String toJCASecretKeyAlgorithm(@AlgorithmEnum int algorithm,
|
||||||
|
@DigestEnum Integer digest) {
|
||||||
|
switch (algorithm) {
|
||||||
|
case AES:
|
||||||
|
return "AES";
|
||||||
|
case HMAC:
|
||||||
|
if (digest == null) {
|
||||||
|
throw new IllegalArgumentException("HMAC digest not specified");
|
||||||
|
}
|
||||||
|
switch (digest) {
|
||||||
|
case Digest.SHA256:
|
||||||
|
return "HmacSHA256";
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unsupported HMAC digest: " + digest);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unsupported key algorithm: " + algorithm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static String toJCAKeyPairAlgorithm(@AlgorithmEnum int algorithm) {
|
||||||
|
switch (algorithm) {
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unsupported key alorithm: " + algorithm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({Padding.NONE, Padding.ZERO, Padding.PKCS7})
|
||||||
|
public @interface PaddingEnum {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Padding for signing and encryption.
|
||||||
|
*/
|
||||||
|
public static abstract class Padding {
|
||||||
|
private Padding() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No padding.
|
||||||
|
*/
|
||||||
|
public static final int NONE = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pad with zeros.
|
||||||
|
*/
|
||||||
|
public static final int ZERO = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PKCS#7 padding.
|
||||||
|
*/
|
||||||
|
public static final int PKCS7 = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static int toKeymaster(int padding) {
|
||||||
|
switch (padding) {
|
||||||
|
case NONE:
|
||||||
|
return KeymasterDefs.KM_PAD_NONE;
|
||||||
|
case ZERO:
|
||||||
|
return KeymasterDefs.KM_PAD_ZERO;
|
||||||
|
case PKCS7:
|
||||||
|
return KeymasterDefs.KM_PAD_PKCS7;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown padding: " + padding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static @PaddingEnum int fromKeymaster(int padding) {
|
||||||
|
switch (padding) {
|
||||||
|
case KeymasterDefs.KM_PAD_NONE:
|
||||||
|
return NONE;
|
||||||
|
case KeymasterDefs.KM_PAD_ZERO:
|
||||||
|
return ZERO;
|
||||||
|
case KeymasterDefs.KM_PAD_PKCS7:
|
||||||
|
return PKCS7;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown padding: " + padding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({Digest.NONE, Digest.SHA256})
|
||||||
|
public @interface DigestEnum {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Digests that can be used with a key when signing or generating Message Authentication
|
||||||
|
* Codes (MACs).
|
||||||
|
*/
|
||||||
|
public static abstract class Digest {
|
||||||
|
private Digest() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No digest: sign/authenticate the raw message.
|
||||||
|
*/
|
||||||
|
public static final int NONE = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SHA-256 digest.
|
||||||
|
*/
|
||||||
|
public static final int SHA256 = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static String toString(@DigestEnum int digest) {
|
||||||
|
switch (digest) {
|
||||||
|
case NONE:
|
||||||
|
return "NONE";
|
||||||
|
case SHA256:
|
||||||
|
return "SHA256";
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown digest: " + digest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static int toKeymaster(@DigestEnum int digest) {
|
||||||
|
switch (digest) {
|
||||||
|
case NONE:
|
||||||
|
return KeymasterDefs.KM_DIGEST_NONE;
|
||||||
|
case SHA256:
|
||||||
|
return KeymasterDefs.KM_DIGEST_SHA_2_256;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown digest: " + digest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static @DigestEnum int fromKeymaster(int digest) {
|
||||||
|
switch (digest) {
|
||||||
|
case KeymasterDefs.KM_DIGEST_NONE:
|
||||||
|
return NONE;
|
||||||
|
case KeymasterDefs.KM_DIGEST_SHA_2_256:
|
||||||
|
return SHA256;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown digest: " + digest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static @DigestEnum Integer fromJCASecretKeyAlgorithm(String algorithm) {
|
||||||
|
String algorithmLower = algorithm.toLowerCase(Locale.US);
|
||||||
|
if (algorithmLower.startsWith("hmac")) {
|
||||||
|
if ("hmacsha256".equals(algorithmLower)) {
|
||||||
|
return SHA256;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unsupported digest: "
|
||||||
|
+ algorithmLower.substring("hmac".length()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static String toJCASignatureAlgorithmDigest(@DigestEnum int digest) {
|
||||||
|
switch (digest) {
|
||||||
|
case NONE:
|
||||||
|
return "NONE";
|
||||||
|
case SHA256:
|
||||||
|
return "SHA256";
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown digest: " + digest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({BlockMode.ECB})
|
||||||
|
public @interface BlockModeEnum {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block modes that can be used when encrypting/decrypting using a key.
|
||||||
|
*/
|
||||||
|
public static abstract class BlockMode {
|
||||||
|
private BlockMode() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Electronic Codebook (ECB) block mode.
|
||||||
|
*/
|
||||||
|
public static final int ECB = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static int toKeymaster(@BlockModeEnum int mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case ECB:
|
||||||
|
return KeymasterDefs.KM_MODE_ECB;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown block mode: " + mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static @BlockModeEnum int fromKeymaster(int mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case KeymasterDefs.KM_MODE_ECB:
|
||||||
|
return ECB;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown block mode: " + mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,10 @@ import android.content.Context;
|
|||||||
|
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.KeyStore.ProtectionParameter;
|
import java.security.KeyStore.ProtectionParameter;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This provides the optional parameters that can be specified for
|
* This provides the optional parameters that can be specified for
|
||||||
@@ -43,9 +47,51 @@ import java.security.KeyStore.ProtectionParameter;
|
|||||||
*/
|
*/
|
||||||
public final class KeyStoreParameter implements ProtectionParameter {
|
public final class KeyStoreParameter implements ProtectionParameter {
|
||||||
private int mFlags;
|
private int mFlags;
|
||||||
|
private final Date mKeyValidityStart;
|
||||||
|
private final Date mKeyValidityForOriginationEnd;
|
||||||
|
private final Date mKeyValidityForConsumptionEnd;
|
||||||
|
private final @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes;
|
||||||
|
private final @KeyStoreKeyConstraints.AlgorithmEnum Integer 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 Integer mUserAuthenticationValidityDurationSeconds;
|
||||||
|
|
||||||
|
private KeyStoreParameter(int flags, Date keyValidityStart,
|
||||||
|
Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd,
|
||||||
|
@KeyStoreKeyConstraints.PurposeEnum Integer purposes,
|
||||||
|
@KeyStoreKeyConstraints.AlgorithmEnum Integer algorithm,
|
||||||
|
@KeyStoreKeyConstraints.PaddingEnum Integer padding,
|
||||||
|
@KeyStoreKeyConstraints.DigestEnum Integer digest,
|
||||||
|
@KeyStoreKeyConstraints.BlockModeEnum Integer blockMode,
|
||||||
|
Integer minSecondsBetweenOperations,
|
||||||
|
Integer maxUsesPerBoot,
|
||||||
|
Set<Integer> userAuthenticators,
|
||||||
|
Integer userAuthenticationValidityDurationSeconds) {
|
||||||
|
if ((userAuthenticationValidityDurationSeconds != null)
|
||||||
|
&& (userAuthenticationValidityDurationSeconds < 0)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"userAuthenticationValidityDurationSeconds must not be negative");
|
||||||
|
}
|
||||||
|
|
||||||
private KeyStoreParameter(int flags) {
|
|
||||||
mFlags = flags;
|
mFlags = flags;
|
||||||
|
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();
|
||||||
|
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,6 +109,141 @@ public final class KeyStoreParameter implements ProtectionParameter {
|
|||||||
return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0;
|
return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the time instant before which the key is not yet valid.
|
||||||
|
*
|
||||||
|
* @return instant or {@code null} if not restricted.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Date getKeyValidityForOriginationEnd() {
|
||||||
|
return mKeyValidityForOriginationEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the set of purposes for which the key can be used to the provided set of purposes.
|
||||||
|
*
|
||||||
|
* @return set of purposes or {@code null} if the key can be used for any purpose.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public @KeyStoreKeyConstraints.PurposeEnum Integer getPurposes() {
|
||||||
|
return mPurposes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the algorithm to which the key is restricted.
|
||||||
|
*
|
||||||
|
* @return algorithm or {@code null} if it's not restricted.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public @KeyStoreKeyConstraints.AlgorithmEnum Integer getAlgorithm() {
|
||||||
|
return mAlgorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the padding scheme to which the key is restricted.
|
||||||
|
*
|
||||||
|
* @return padding scheme or {@code null} if the padding scheme is not restricted.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public @KeyStoreKeyConstraints.PaddingEnum Integer getPadding() {
|
||||||
|
return mPadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the digest to which the key is restricted when generating Message Authentication Codes
|
||||||
|
* (MACs).
|
||||||
|
*
|
||||||
|
* @return digest or {@code null} if the digest is not restricted.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public @KeyStoreKeyConstraints.DigestEnum Integer getDigest() {
|
||||||
|
return mDigest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the block mode to which the key is restricted when used for encryption or decryption.
|
||||||
|
*
|
||||||
|
* @return block more or {@code null} if block mode is not restricted.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public @KeyStoreKeyConstraints.BlockModeEnum Integer getBlockMode() {
|
||||||
|
return mBlockMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Integer getMaxUsesPerBoot() {
|
||||||
|
return mMaxUsesPerBoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the user authenticators which protect access to this 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.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Set<Integer> getUserAuthenticators() {
|
||||||
|
return new HashSet<Integer>(mUserAuthenticators);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the duration of time (seconds) for which this 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.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Integer getUserAuthenticationValidityDurationSeconds() {
|
||||||
|
return mUserAuthenticationValidityDurationSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builder class for {@link KeyStoreParameter} objects.
|
* Builder class for {@link KeyStoreParameter} objects.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -82,6 +263,18 @@ public final class KeyStoreParameter implements ProtectionParameter {
|
|||||||
*/
|
*/
|
||||||
public final static class Builder {
|
public final static class Builder {
|
||||||
private int mFlags;
|
private int mFlags;
|
||||||
|
private Date mKeyValidityStart;
|
||||||
|
private Date mKeyValidityForOriginationEnd;
|
||||||
|
private Date mKeyValidityForConsumptionEnd;
|
||||||
|
private @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes;
|
||||||
|
private @KeyStoreKeyConstraints.AlgorithmEnum Integer mAlgorithm;
|
||||||
|
private @KeyStoreKeyConstraints.PaddingEnum Integer mPadding;
|
||||||
|
private @KeyStoreKeyConstraints.DigestEnum Integer mDigest;
|
||||||
|
private @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode;
|
||||||
|
private Integer mMinSecondsBetweenOperations;
|
||||||
|
private Integer mMaxUsesPerBoot;
|
||||||
|
private Set<Integer> mUserAuthenticators;
|
||||||
|
private Integer mUserAuthenticationValidityDurationSeconds;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of the {@code Builder} with the given
|
* Creates a new instance of the {@code Builder} with the given
|
||||||
@@ -113,13 +306,207 @@ public final class KeyStoreParameter implements ProtectionParameter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the instance of the {@code KeyPairGeneratorSpec}.
|
* Sets the time instant before which the key is not yet valid.
|
||||||
|
*
|
||||||
|
* <b>By default, the key is valid at any instant.
|
||||||
|
*
|
||||||
|
* @see #setKeyValidityEnd(Date)
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Builder setKeyValidityStart(Date startDate) {
|
||||||
|
mKeyValidityStart = startDate;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the time instant after which the key is no longer valid.
|
||||||
|
*
|
||||||
|
* <b>By default, the key is valid at any instant.
|
||||||
|
*
|
||||||
|
* @see #setKeyValidityStart(Date)
|
||||||
|
* @see #setKeyValidityForConsumptionEnd(Date)
|
||||||
|
* @see #setKeyValidityForOriginationEnd(Date)
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Builder setKeyValidityEnd(Date endDate) {
|
||||||
|
setKeyValidityForOriginationEnd(endDate);
|
||||||
|
setKeyValidityForConsumptionEnd(endDate);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the time instant after which the key is no longer valid for encryption and signing.
|
||||||
|
*
|
||||||
|
* <b>By default, the key is valid at any instant.
|
||||||
|
*
|
||||||
|
* @see #setKeyValidityForConsumptionEnd(Date)
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Builder setKeyValidityForOriginationEnd(Date endDate) {
|
||||||
|
mKeyValidityForOriginationEnd = endDate;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the time instant after which the key is no longer valid for decryption and
|
||||||
|
* verification.
|
||||||
|
*
|
||||||
|
* <b>By default, the key is valid at any instant.
|
||||||
|
*
|
||||||
|
* @see #setKeyValidityForOriginationEnd(Date)
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Builder setKeyValidityForConsumptionEnd(Date endDate) {
|
||||||
|
mKeyValidityForConsumptionEnd = endDate;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restricts the purposes for which the key can be used to the provided set of purposes.
|
||||||
|
*
|
||||||
|
* <p>By default, the key can be used for encryption, decryption, signing, and verification.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Builder setPurposes(@KeyStoreKeyConstraints.PurposeEnum int purposes) {
|
||||||
|
mPurposes = purposes;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the algorithm of the key.
|
||||||
|
*
|
||||||
|
* <p>The algorithm of symmetric keys can be deduced from the key itself. Thus, explicitly
|
||||||
|
* specifying the algorithm of symmetric keys using this method is not necessary.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Builder setAlgorithm(@KeyStoreKeyConstraints.AlgorithmEnum int algorithm) {
|
||||||
|
mAlgorithm = algorithm;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restricts the key to being used only with the provided padding scheme. Attempts to use
|
||||||
|
* the key with any other padding will be rejected.
|
||||||
|
*
|
||||||
|
* <p>This restriction must be specified for keys which are used for encryption/decryption.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Builder setPadding(@KeyStoreKeyConstraints.PaddingEnum int padding) {
|
||||||
|
mPadding = padding;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restricts the key to being used only with the provided digest when generating Message
|
||||||
|
* Authentication Codes (MACs). Attempts to use the key with any other digest will be
|
||||||
|
* rejected.
|
||||||
|
*
|
||||||
|
* <p>For MAC keys, the default is to restrict to the digest specified in the key algorithm
|
||||||
|
* name.
|
||||||
|
*
|
||||||
|
* @see java.security.Key#getAlgorithm()
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Builder setDigest(@KeyStoreKeyConstraints.DigestEnum int digest) {
|
||||||
|
mDigest = digest;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restricts the key to being used only with the provided block mode when encrypting or
|
||||||
|
* decrypting. Attempts to use the key with any other block modes will be rejected.
|
||||||
|
*
|
||||||
|
* <p>This restriction must be specified for keys which are used for encryption/decryption.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Builder setBlockMode(@KeyStoreKeyConstraints.BlockModeEnum int blockMode) {
|
||||||
|
mBlockMode = blockMode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the minimum number of seconds that must expire since the most recent use of the key
|
||||||
|
* before it can be used again.
|
||||||
|
*
|
||||||
|
* <p>By default, there is no restriction on how frequently a key can be used.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Builder setMinSecondsBetweenOperations(int seconds) {
|
||||||
|
mMinSecondsBetweenOperations = seconds;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the maximum number of times a key can be used without rebooting the device.
|
||||||
|
*
|
||||||
|
* <p>By default, the key can be used for an unlimited number of times.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Builder setMaxUsesPerBoot(int count) {
|
||||||
|
mMaxUsesPerBoot = count;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the user authenticators which protect access to this key. The key can only be used
|
||||||
|
* iff the user has authenticated to at least one of these user authenticators.
|
||||||
|
*
|
||||||
|
* <p>By default, the key can be used without user authentication.
|
||||||
|
*
|
||||||
|
* @param userAuthenticators user authenticators or empty list if this key can be accessed
|
||||||
|
* without user authentication.
|
||||||
|
*
|
||||||
|
* @see #setUserAuthenticationValidityDurationSeconds(int)
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Builder setUserAuthenticators(Set<Integer> userAuthenticators) {
|
||||||
|
mUserAuthenticators =
|
||||||
|
(userAuthenticators != null) ? new HashSet<Integer>(userAuthenticators) : null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the duration of time (seconds) for which this key can be used after the user
|
||||||
|
* successfully authenticates to one of the associated user authenticators.
|
||||||
|
*
|
||||||
|
* <p>By default, the user needs to authenticate for every use of the key.
|
||||||
|
*
|
||||||
|
* @param seconds duration in seconds or {@code 0} if the user needs to authenticate for
|
||||||
|
* every use of the key.
|
||||||
|
*
|
||||||
|
* @see #setUserAuthenticators(Set)
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Builder setUserAuthenticationValidityDurationSeconds(int seconds) {
|
||||||
|
mUserAuthenticationValidityDurationSeconds = seconds;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the instance of the {@code KeyStoreParameter}.
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if a required field is missing
|
* @throws IllegalArgumentException if a required field is missing
|
||||||
* @return built instance of {@code KeyPairGeneratorSpec}
|
* @return built instance of {@code KeyStoreParameter}
|
||||||
*/
|
*/
|
||||||
public KeyStoreParameter build() {
|
public KeyStoreParameter build() {
|
||||||
return new KeyStoreParameter(mFlags);
|
return new KeyStoreParameter(mFlags, mKeyValidityStart,
|
||||||
|
mKeyValidityForOriginationEnd, mKeyValidityForConsumptionEnd, mPurposes,
|
||||||
|
mAlgorithm, mPadding, mDigest, mBlockMode, mMinSecondsBetweenOperations,
|
||||||
|
mMaxUsesPerBoot, mUserAuthenticators,
|
||||||
|
mUserAuthenticationValidityDurationSeconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
39
keystore/java/android/security/KeyStoreSecretKey.java
Normal file
39
keystore/java/android/security/KeyStoreSecretKey.java
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package android.security;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link SecretKey} backed by keystore.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public class KeyStoreSecretKey implements SecretKey {
|
||||||
|
private final String mAlias;
|
||||||
|
private final String mAlgorithm;
|
||||||
|
|
||||||
|
public KeyStoreSecretKey(String alias, String algorithm) {
|
||||||
|
mAlias = alias;
|
||||||
|
mAlgorithm = algorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getAlias() {
|
||||||
|
return mAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAlgorithm() {
|
||||||
|
return mAlgorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFormat() {
|
||||||
|
// This key does not export its key material
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getEncoded() {
|
||||||
|
// This key does not export its key material
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2127,7 +2127,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
|
|||||||
assertEquals("The keystore size should match expected", 2, mKeyStore.size());
|
assertEquals("The keystore size should match expected", 2, mKeyStore.size());
|
||||||
assertAliases(new String[] { TEST_ALIAS_2, TEST_ALIAS_3 });
|
assertAliases(new String[] { TEST_ALIAS_2, TEST_ALIAS_3 });
|
||||||
|
|
||||||
assertTrue(mAndroidKeyStore.delKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3));
|
assertTrue(mAndroidKeyStore.delete(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3));
|
||||||
|
|
||||||
assertEquals("The keystore size should match expected", 1, mKeyStore.size());
|
assertEquals("The keystore size should match expected", 1, mKeyStore.size());
|
||||||
assertAliases(new String[] { TEST_ALIAS_2 });
|
assertAliases(new String[] { TEST_ALIAS_2 });
|
||||||
|
|||||||
Reference in New Issue
Block a user