Merge "Keystore 2.0: Remove attestKey from KeyChain." am: eb45aabc03 am: c5792c3904
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1559810 MUST ONLY BE SUBMITTED BY AUTOMERGER Change-Id: Ie6a096ec2f220c60b27b1e26f81817b00ae6af35
This commit is contained in:
@@ -37,8 +37,6 @@ interface IKeyChainService {
|
|||||||
void setUserSelectable(String alias, boolean isUserSelectable);
|
void setUserSelectable(String alias, boolean isUserSelectable);
|
||||||
|
|
||||||
int generateKeyPair(in String algorithm, in ParcelableKeyGenParameterSpec spec);
|
int generateKeyPair(in String algorithm, in ParcelableKeyGenParameterSpec spec);
|
||||||
int attestKey(in String alias, in byte[] challenge, in int[] idAttestationFlags,
|
|
||||||
out KeymasterCertificateChain chain);
|
|
||||||
boolean setKeyPairCertificate(String alias, in byte[] userCert, in byte[] certChain);
|
boolean setKeyPairCertificate(String alias, in byte[] userCert, in byte[] certChain);
|
||||||
|
|
||||||
// APIs used by CertInstaller and DevicePolicyManager
|
// APIs used by CertInstaller and DevicePolicyManager
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ import android.os.UserManager;
|
|||||||
import android.security.keystore.AndroidKeyStoreProvider;
|
import android.security.keystore.AndroidKeyStoreProvider;
|
||||||
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||||
import android.security.keystore.KeyProperties;
|
import android.security.keystore.KeyProperties;
|
||||||
|
import android.system.keystore2.Domain;
|
||||||
|
import android.system.keystore2.KeyDescriptor;
|
||||||
|
|
||||||
import com.android.org.conscrypt.TrustedCertificateStore;
|
import com.android.org.conscrypt.TrustedCertificateStore;
|
||||||
|
|
||||||
@@ -622,6 +624,33 @@ public final class KeyChain {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This prefix is used to disambiguate grant aliase strings from normal key alias strings.
|
||||||
|
* Technically, a key alias string can use the same prefix. However, a collision does not
|
||||||
|
* lead to privilege escalation, because grants are access controlled in the Keystore daemon.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final String GRANT_ALIAS_PREFIX = "ks2_keychain_grant_id:";
|
||||||
|
|
||||||
|
private static KeyDescriptor getGrantDescriptor(String keyid) {
|
||||||
|
KeyDescriptor result = new KeyDescriptor();
|
||||||
|
result.domain = Domain.GRANT;
|
||||||
|
result.blob = null;
|
||||||
|
result.alias = null;
|
||||||
|
try {
|
||||||
|
result.nspace = Long.parseUnsignedLong(
|
||||||
|
keyid.substring(GRANT_ALIAS_PREFIX.length()), 16 /* radix */);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public static String getGrantString(KeyDescriptor key) {
|
||||||
|
return String.format(GRANT_ALIAS_PREFIX + "%016X", key.nspace);
|
||||||
|
}
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
@Nullable @WorkerThread
|
@Nullable @WorkerThread
|
||||||
public static KeyPair getKeyPair(@NonNull Context context, @NonNull String alias)
|
public static KeyPair getKeyPair(@NonNull Context context, @NonNull String alias)
|
||||||
@@ -645,11 +674,23 @@ public final class KeyChain {
|
|||||||
|
|
||||||
if (keyId == null) {
|
if (keyId == null) {
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AndroidKeyStoreProvider.isKeystore2Enabled()) {
|
||||||
|
try {
|
||||||
|
return android.security.keystore2.AndroidKeyStoreProvider
|
||||||
|
.loadAndroidKeyStoreKeyPairFromKeystore(
|
||||||
|
KeyStore2.getInstance(),
|
||||||
|
getGrantDescriptor(keyId));
|
||||||
|
} catch (UnrecoverableKeyException | KeyPermanentlyInvalidatedException e) {
|
||||||
|
throw new KeyChainException(e);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
|
return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
|
||||||
KeyStore.getInstance(), keyId, KeyStore.UID_SELF);
|
KeyStore.getInstance(), keyId, KeyStore.UID_SELF);
|
||||||
} catch (RuntimeException | UnrecoverableKeyException | KeyPermanentlyInvalidatedException e) {
|
} catch (RuntimeException | UnrecoverableKeyException
|
||||||
|
| KeyPermanentlyInvalidatedException e) {
|
||||||
throw new KeyChainException(e);
|
throw new KeyChainException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -767,11 +808,8 @@ public final class KeyChain {
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
public static boolean isBoundKeyAlgorithm(
|
public static boolean isBoundKeyAlgorithm(
|
||||||
@NonNull @KeyProperties.KeyAlgorithmEnum String algorithm) {
|
@NonNull @KeyProperties.KeyAlgorithmEnum String algorithm) {
|
||||||
if (!isKeyAlgorithmSupported(algorithm)) {
|
// All supported algorithms are hardware backed. Individual keys may not be.
|
||||||
return false;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
return KeyStore.getInstance().isHardwareBacked(algorithm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
|
|||||||
@@ -273,10 +273,10 @@ public class AndroidKeyStoreProvider extends Provider {
|
|||||||
/** @hide **/
|
/** @hide **/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore(
|
public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore(
|
||||||
@NonNull KeyStore2 keyStore, @NonNull String privateKeyAlias, int namespace)
|
@NonNull KeyStore2 keyStore, @NonNull KeyDescriptor descriptor)
|
||||||
throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
|
throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
|
||||||
AndroidKeyStoreKey key =
|
AndroidKeyStoreKey key =
|
||||||
loadAndroidKeyStoreKeyFromKeystore(keyStore, privateKeyAlias, namespace);
|
loadAndroidKeyStoreKeyFromKeystore(keyStore, descriptor);
|
||||||
if (key instanceof AndroidKeyStorePublicKey) {
|
if (key instanceof AndroidKeyStorePublicKey) {
|
||||||
AndroidKeyStorePublicKey publicKey = (AndroidKeyStorePublicKey) key;
|
AndroidKeyStorePublicKey publicKey = (AndroidKeyStorePublicKey) key;
|
||||||
return new KeyPair(publicKey, publicKey.getPrivateKey());
|
return new KeyPair(publicKey, publicKey.getPrivateKey());
|
||||||
@@ -336,7 +336,7 @@ public class AndroidKeyStoreProvider extends Provider {
|
|||||||
@NonNull
|
@NonNull
|
||||||
public static AndroidKeyStoreKey loadAndroidKeyStoreKeyFromKeystore(
|
public static AndroidKeyStoreKey loadAndroidKeyStoreKeyFromKeystore(
|
||||||
@NonNull KeyStore2 keyStore, @NonNull String alias, int namespace)
|
@NonNull KeyStore2 keyStore, @NonNull String alias, int namespace)
|
||||||
throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
|
throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
|
||||||
|
|
||||||
KeyDescriptor descriptor = new KeyDescriptor();
|
KeyDescriptor descriptor = new KeyDescriptor();
|
||||||
if (namespace == KeyProperties.NAMESPACE_APPLICATION) {
|
if (namespace == KeyProperties.NAMESPACE_APPLICATION) {
|
||||||
@@ -348,6 +348,18 @@ public class AndroidKeyStoreProvider extends Provider {
|
|||||||
}
|
}
|
||||||
descriptor.alias = alias;
|
descriptor.alias = alias;
|
||||||
descriptor.blob = null;
|
descriptor.blob = null;
|
||||||
|
|
||||||
|
final AndroidKeyStoreKey key = loadAndroidKeyStoreKeyFromKeystore(keyStore, descriptor);
|
||||||
|
if (key instanceof AndroidKeyStorePublicKey) {
|
||||||
|
return ((AndroidKeyStorePublicKey) key).getPrivateKey();
|
||||||
|
} else {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AndroidKeyStoreKey loadAndroidKeyStoreKeyFromKeystore(
|
||||||
|
@NonNull KeyStore2 keyStore, @NonNull KeyDescriptor descriptor)
|
||||||
|
throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
|
||||||
KeyEntryResponse response = null;
|
KeyEntryResponse response = null;
|
||||||
try {
|
try {
|
||||||
response = keyStore.getKeyEntry(descriptor);
|
response = keyStore.getKeyEntry(descriptor);
|
||||||
@@ -397,7 +409,7 @@ public class AndroidKeyStoreProvider extends Provider {
|
|||||||
keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) {
|
keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) {
|
||||||
return makeAndroidKeyStorePublicKeyFromKeyEntryResponse(descriptor, response.metadata,
|
return makeAndroidKeyStorePublicKeyFromKeyEntryResponse(descriptor, response.metadata,
|
||||||
new KeyStoreSecurityLevel(response.iSecurityLevel),
|
new KeyStoreSecurityLevel(response.iSecurityLevel),
|
||||||
keymasterAlgorithm).getPrivateKey();
|
keymasterAlgorithm);
|
||||||
} else {
|
} else {
|
||||||
throw new UnrecoverableKeyException("Key algorithm unknown");
|
throw new UnrecoverableKeyException("Key algorithm unknown");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -310,6 +310,7 @@ import org.xmlpull.v1.XmlPullParser;
|
|||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
import org.xmlpull.v1.XmlSerializer;
|
import org.xmlpull.v1.XmlSerializer;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
@@ -319,6 +320,9 @@ import java.io.IOException;
|
|||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -6502,7 +6506,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
|
|||||||
enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
|
enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
|
||||||
DELEGATION_CERT_INSTALL);
|
DELEGATION_CERT_INSTALL);
|
||||||
}
|
}
|
||||||
final KeyGenParameterSpec keySpec = parcelableKeySpec.getSpec();
|
KeyGenParameterSpec keySpec = parcelableKeySpec.getSpec();
|
||||||
final String alias = keySpec.getKeystoreAlias();
|
final String alias = keySpec.getKeystoreAlias();
|
||||||
if (TextUtils.isEmpty(alias)) {
|
if (TextUtils.isEmpty(alias)) {
|
||||||
throw new IllegalArgumentException("Empty alias provided.");
|
throw new IllegalArgumentException("Empty alias provided.");
|
||||||
@@ -6514,9 +6518,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deviceIdAttestationRequired && (keySpec.getAttestationChallenge() == null)) {
|
if (deviceIdAttestationRequired) {
|
||||||
throw new IllegalArgumentException(
|
if (keySpec.getAttestationChallenge() == null) {
|
||||||
"Requested Device ID attestation but challenge is empty.");
|
throw new IllegalArgumentException(
|
||||||
|
"Requested Device ID attestation but challenge is empty.");
|
||||||
|
}
|
||||||
|
KeyGenParameterSpec.Builder specBuilder = new KeyGenParameterSpec.Builder(keySpec);
|
||||||
|
specBuilder.setAttestationIds(attestationUtilsFlags);
|
||||||
|
specBuilder.setDevicePropertiesAttestationIncluded(true);
|
||||||
|
keySpec = specBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
|
final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
|
||||||
@@ -6526,15 +6536,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
|
|||||||
KeyChain.bindAsUser(mContext, userHandle)) {
|
KeyChain.bindAsUser(mContext, userHandle)) {
|
||||||
IKeyChainService keyChain = keyChainConnection.getService();
|
IKeyChainService keyChain = keyChainConnection.getService();
|
||||||
|
|
||||||
// Copy the provided keySpec, excluding the attestation challenge, which will be
|
|
||||||
// used later for requesting key attestation record.
|
|
||||||
final KeyGenParameterSpec noAttestationSpec =
|
|
||||||
new KeyGenParameterSpec.Builder(keySpec)
|
|
||||||
.setAttestationChallenge(null)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
final int generationResult = keyChain.generateKeyPair(algorithm,
|
final int generationResult = keyChain.generateKeyPair(algorithm,
|
||||||
new ParcelableKeyGenParameterSpec(noAttestationSpec));
|
new ParcelableKeyGenParameterSpec(keySpec));
|
||||||
if (generationResult != KeyChain.KEY_GEN_SUCCESS) {
|
if (generationResult != KeyChain.KEY_GEN_SUCCESS) {
|
||||||
Log.e(LOG_TAG, String.format(
|
Log.e(LOG_TAG, String.format(
|
||||||
"KeyChain failed to generate a keypair, error %d.", generationResult));
|
"KeyChain failed to generate a keypair, error %d.", generationResult));
|
||||||
@@ -6543,6 +6546,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
|
|||||||
throw new ServiceSpecificException(
|
throw new ServiceSpecificException(
|
||||||
DevicePolicyManager.KEY_GEN_STRONGBOX_UNAVAILABLE,
|
DevicePolicyManager.KEY_GEN_STRONGBOX_UNAVAILABLE,
|
||||||
String.format("KeyChain error: %d", generationResult));
|
String.format("KeyChain error: %d", generationResult));
|
||||||
|
case KeyChain.KEY_ATTESTATION_CANNOT_ATTEST_IDS:
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"Device does not support Device ID attestation.");
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -6555,22 +6561,26 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
|
|||||||
// that UID.
|
// that UID.
|
||||||
keyChain.setGrant(callingUid, alias, true);
|
keyChain.setGrant(callingUid, alias, true);
|
||||||
|
|
||||||
final byte[] attestationChallenge = keySpec.getAttestationChallenge();
|
try {
|
||||||
if (attestationChallenge != null) {
|
final List<byte[]> encodedCerts = new ArrayList();
|
||||||
final int attestationResult = keyChain.attestKey(
|
final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||||
alias, attestationChallenge, attestationUtilsFlags, attestationChain);
|
final byte[] certChainBytes = keyChain.getCaCertificates(alias);
|
||||||
if (attestationResult != KeyChain.KEY_ATTESTATION_SUCCESS) {
|
encodedCerts.add(keyChain.getCertificate(alias));
|
||||||
Log.e(LOG_TAG, String.format(
|
if (certChainBytes != null) {
|
||||||
"Attestation for %s failed (rc=%d), deleting key.",
|
final Collection<X509Certificate> certs =
|
||||||
alias, attestationResult));
|
(Collection<X509Certificate>) certFactory.generateCertificates(
|
||||||
keyChain.removeKeyPair(alias);
|
new ByteArrayInputStream(certChainBytes));
|
||||||
if (attestationResult == KeyChain.KEY_ATTESTATION_CANNOT_ATTEST_IDS) {
|
for (X509Certificate cert : certs) {
|
||||||
throw new UnsupportedOperationException(
|
encodedCerts.add(cert.getEncoded());
|
||||||
"Device does not support Device ID attestation.");
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attestationChain.shallowCopyFrom(new KeymasterCertificateChain(encodedCerts));
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
Log.e(LOG_TAG, "While retrieving certificate chain.", e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean isDelegate = (who == null);
|
final boolean isDelegate = (who == null);
|
||||||
DevicePolicyEventLogger
|
DevicePolicyEventLogger
|
||||||
.createEvent(DevicePolicyEnums.GENERATE_KEY_PAIR)
|
.createEvent(DevicePolicyEnums.GENERATE_KEY_PAIR)
|
||||||
|
|||||||
Reference in New Issue
Block a user