Export KeyFactory backed by Android Keystore.
The KeyFactory can be used to obtain information (KeyInfo) about Android Keystore private keys. Bug: 18088752 Change-Id: Ied1a69928f391537de6765cef7dc7d7241cf62bb
This commit is contained in:
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.security.Credentials;
|
||||
import android.security.KeyStore;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactorySpi;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
|
||||
/**
|
||||
* {@link KeyFactorySpi} backed by Android KeyStore.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class AndroidKeyStoreKeyFactorySpi extends KeyFactorySpi {
|
||||
|
||||
private final KeyStore mKeyStore = KeyStore.getInstance();
|
||||
|
||||
@Override
|
||||
protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpecClass)
|
||||
throws InvalidKeySpecException {
|
||||
if (keySpecClass == null) {
|
||||
throw new InvalidKeySpecException("keySpecClass == null");
|
||||
}
|
||||
if (!(key instanceof AndroidKeyStorePrivateKey)) {
|
||||
throw new InvalidKeySpecException("Only Android KeyStore private keys supported: " +
|
||||
((key != null) ? key.getClass().getName() : "null"));
|
||||
}
|
||||
if (PKCS8EncodedKeySpec.class.isAssignableFrom(keySpecClass)) {
|
||||
throw new InvalidKeySpecException(
|
||||
"Key material export of Android KeyStore keys is not supported");
|
||||
}
|
||||
if (!KeyInfo.class.equals(keySpecClass)) {
|
||||
throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName());
|
||||
}
|
||||
String keyAliasInKeystore = ((AndroidKeyStoreKey) key).getAlias();
|
||||
String entryAlias;
|
||||
if (keyAliasInKeystore.startsWith(Credentials.USER_PRIVATE_KEY)) {
|
||||
entryAlias = keyAliasInKeystore.substring(Credentials.USER_PRIVATE_KEY.length());
|
||||
} else {
|
||||
throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
T result = (T) AndroidKeyStoreSecretKeyFactorySpi.getKeyInfo(
|
||||
mKeyStore, entryAlias, keyAliasInKeystore);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PrivateKey engineGeneratePrivate(KeySpec spec) throws InvalidKeySpecException {
|
||||
throw new UnsupportedOperationException(
|
||||
"To generate a key pair in Android KeyStore, use KeyPairGenerator initialized with"
|
||||
+ " " + KeyGenParameterSpec.class.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PublicKey engineGeneratePublic(KeySpec spec) throws InvalidKeySpecException {
|
||||
throw new UnsupportedOperationException(
|
||||
"To generate a key pair in Android KeyStore, use KeyPairGenerator initialized with"
|
||||
+ " " + KeyGenParameterSpec.class.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Key engineTranslateKey(Key arg0) throws InvalidKeyException {
|
||||
throw new UnsupportedOperationException(
|
||||
"To import a key into Android KeyStore, use KeyStore.setEntry with "
|
||||
+ KeyProtection.class.getName());
|
||||
}
|
||||
}
|
@ -52,6 +52,10 @@ public class AndroidKeyStoreProvider extends Provider {
|
||||
put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
|
||||
put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
|
||||
|
||||
// java.security.KeyFactory
|
||||
putKeyFactoryImpl("EC");
|
||||
putKeyFactoryImpl("RSA");
|
||||
|
||||
// javax.crypto.KeyGenerator
|
||||
put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES");
|
||||
put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA1");
|
||||
@ -100,6 +104,10 @@ public class AndroidKeyStoreProvider extends Provider {
|
||||
put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreSecretKeyFactorySpi");
|
||||
}
|
||||
|
||||
private void putKeyFactoryImpl(String algorithm) {
|
||||
put("KeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreKeyFactorySpi");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto
|
||||
* primitive.
|
||||
|
@ -21,9 +21,8 @@ import android.security.KeyStore;
|
||||
import android.security.keymaster.KeyCharacteristics;
|
||||
import android.security.keymaster.KeymasterDefs;
|
||||
|
||||
import libcore.util.EmptyArray;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.ProviderException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.util.ArrayList;
|
||||
@ -35,7 +34,7 @@ import javax.crypto.SecretKeyFactorySpi;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* {@link SecretKeyFactorySpi} backed by Android KeyStore.
|
||||
* {@link SecretKeyFactorySpi} backed by Android Keystore.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@ -60,7 +59,7 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
|
||||
if (!KeyInfo.class.equals(keySpecClass)) {
|
||||
throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName());
|
||||
}
|
||||
String keyAliasInKeystore = ((AndroidKeyStoreSecretKey) key).getAlias();
|
||||
String keyAliasInKeystore = ((AndroidKeyStoreKey) key).getAlias();
|
||||
String entryAlias;
|
||||
if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) {
|
||||
entryAlias = keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length());
|
||||
@ -68,11 +67,15 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
|
||||
throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore);
|
||||
}
|
||||
|
||||
return getKeyInfo(mKeyStore, entryAlias, keyAliasInKeystore);
|
||||
}
|
||||
|
||||
static KeyInfo getKeyInfo(KeyStore keyStore, String entryAlias, String keyAliasInKeystore) {
|
||||
KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
|
||||
int errorCode =
|
||||
mKeyStore.getKeyCharacteristics(keyAliasInKeystore, null, null, keyCharacteristics);
|
||||
keyStore.getKeyCharacteristics(keyAliasInKeystore, null, null, keyCharacteristics);
|
||||
if (errorCode != KeyStore.NO_ERROR) {
|
||||
throw new InvalidKeySpecException("Failed to obtain information about key."
|
||||
throw new ProviderException("Failed to obtain information about key."
|
||||
+ " Keystore error: " + errorCode);
|
||||
}
|
||||
|
||||
@ -81,6 +84,7 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
|
||||
int keySize;
|
||||
@KeyProperties.PurposeEnum int purposes;
|
||||
String[] encryptionPaddings;
|
||||
String[] signaturePaddings;
|
||||
@KeyProperties.DigestEnum String[] digests;
|
||||
@KeyProperties.BlockModeEnum String[] blockModes;
|
||||
int keymasterSwEnforcedUserAuthenticators;
|
||||
@ -95,29 +99,40 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
|
||||
origin = KeyProperties.Origin.fromKeymaster(
|
||||
keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1));
|
||||
} else {
|
||||
throw new InvalidKeySpecException("Key origin not available");
|
||||
throw new ProviderException("Key origin not available");
|
||||
}
|
||||
Integer keySizeInteger = keyCharacteristics.getInteger(KeymasterDefs.KM_TAG_KEY_SIZE);
|
||||
if (keySizeInteger == null) {
|
||||
throw new InvalidKeySpecException("Key size not available");
|
||||
throw new ProviderException("Key size not available");
|
||||
}
|
||||
keySize = keySizeInteger;
|
||||
purposes = KeyProperties.Purpose.allFromKeymaster(
|
||||
keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PURPOSE));
|
||||
|
||||
List<String> encryptionPaddingsList = new ArrayList<String>();
|
||||
List<String> signaturePaddingsList = new ArrayList<String>();
|
||||
// Keymaster stores both types of paddings in the same array -- we split it into two.
|
||||
for (int keymasterPadding : keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PADDING)) {
|
||||
@KeyProperties.EncryptionPaddingEnum String jcaPadding;
|
||||
try {
|
||||
jcaPadding = KeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding);
|
||||
@KeyProperties.EncryptionPaddingEnum String jcaPadding =
|
||||
KeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding);
|
||||
encryptionPaddingsList.add(jcaPadding);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidKeySpecException(
|
||||
"Unsupported encryption padding: " + keymasterPadding);
|
||||
try {
|
||||
@KeyProperties.SignaturePaddingEnum String padding =
|
||||
KeyProperties.SignaturePadding.fromKeymaster(keymasterPadding);
|
||||
signaturePaddingsList.add(padding);
|
||||
} catch (IllegalArgumentException e2) {
|
||||
throw new ProviderException(
|
||||
"Unsupported encryption padding: " + keymasterPadding);
|
||||
}
|
||||
}
|
||||
encryptionPaddingsList.add(jcaPadding);
|
||||
|
||||
}
|
||||
encryptionPaddings =
|
||||
encryptionPaddingsList.toArray(new String[encryptionPaddingsList.size()]);
|
||||
signaturePaddings =
|
||||
signaturePaddingsList.toArray(new String[signaturePaddingsList.size()]);
|
||||
|
||||
digests = KeyProperties.Digest.allFromKeymaster(
|
||||
keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST));
|
||||
@ -128,7 +143,7 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
|
||||
keymasterHwEnforcedUserAuthenticators =
|
||||
keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidKeySpecException("Unsupported key characteristic", e);
|
||||
throw new ProviderException("Unsupported key characteristic", e);
|
||||
}
|
||||
|
||||
Date keyValidityStart = keyCharacteristics.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME);
|
||||
@ -164,7 +179,7 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
|
||||
keyValidityForConsumptionEnd,
|
||||
purposes,
|
||||
encryptionPaddings,
|
||||
EmptyArray.STRING, // no signature paddings -- this is symmetric crypto
|
||||
signaturePaddings,
|
||||
digests,
|
||||
blockModes,
|
||||
userAuthenticationRequired,
|
||||
@ -175,12 +190,14 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
|
||||
@Override
|
||||
protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException {
|
||||
throw new UnsupportedOperationException(
|
||||
"Key import into Android KeyStore is not supported");
|
||||
"To generate secret key in Android KeyStore, use KeyGenerator initialized with "
|
||||
+ KeyGenParameterSpec.class.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException {
|
||||
throw new UnsupportedOperationException(
|
||||
"Key import into Android KeyStore is not supported");
|
||||
"To import a secret key into Android KeyStore, use KeyStore.setEntry with "
|
||||
+ KeyProtection.class.getName());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user