Merge "Keystore 2.0: Remove attestKey from KeyChain." into sc-dev

This commit is contained in:
Pavel Grafov
2021-02-24 15:04:11 +00:00
committed by Android (Google) Code Review
4 changed files with 103 additions and 34 deletions

View File

@@ -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

View File

@@ -44,6 +44,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 android.util.Log; import android.util.Log;
import com.android.org.conscrypt.TrustedCertificateStore; import com.android.org.conscrypt.TrustedCertificateStore;
@@ -682,6 +684,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)
@@ -705,11 +734,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);
} }
} }
@@ -827,11 +868,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 */

View File

@@ -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");
} }

View File

@@ -333,6 +333,7 @@ import com.google.android.collect.Sets;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
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;
@@ -340,6 +341,9 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
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;
@@ -5622,7 +5626,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// Get attestation flags, if any. // Get attestation flags, if any.
final int[] attestationUtilsFlags = translateIdAttestationFlags(idAttestationFlags); final int[] attestationUtilsFlags = translateIdAttestationFlags(idAttestationFlags);
final boolean deviceIdAttestationRequired = attestationUtilsFlags != null; final boolean deviceIdAttestationRequired = attestationUtilsFlags != null;
final KeyGenParameterSpec keySpec = parcelableKeySpec.getSpec(); KeyGenParameterSpec keySpec = parcelableKeySpec.getSpec();
final String alias = keySpec.getKeystoreAlias(); final String alias = keySpec.getKeystoreAlias();
Preconditions.checkStringNotEmpty(alias, "Empty alias provided"); Preconditions.checkStringNotEmpty(alias, "Empty alias provided");
@@ -5643,6 +5647,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
|| (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
} }
if (TextUtils.isEmpty(alias)) {
throw new IllegalArgumentException("Empty alias provided.");
}
// As the caller will be granted access to the key, ensure no UID was specified, as // As the caller will be granted access to the key, ensure no UID was specified, as
// it will not have the desired effect. // it will not have the desired effect.
if (keySpec.getUid() != KeyStore.UID_SELF) { if (keySpec.getUid() != KeyStore.UID_SELF) {
@@ -5651,19 +5658,26 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return false; return false;
} }
if (deviceIdAttestationRequired) {
if (keySpec.getAttestationChallenge() == null) {
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 long id = mInjector.binderClearCallingIdentity(); final long id = mInjector.binderClearCallingIdentity();
try { try {
try (KeyChainConnection keyChainConnection = try (KeyChainConnection keyChainConnection =
KeyChain.bindAsUser(mContext, caller.getUserHandle())) { 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));
@@ -5672,6 +5686,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:
logGenerateKeyPairFailure(caller, isCredentialManagementApp); logGenerateKeyPairFailure(caller, isCredentialManagementApp);
return false; return false;
@@ -5685,23 +5702,27 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// that UID. // that UID.
keyChain.setGrant(caller.getUid(), alias, true); keyChain.setGrant(caller.getUid(), 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.");
} }
logGenerateKeyPairFailure(caller, isCredentialManagementApp);
return false;
} }
attestationChain.shallowCopyFrom(new KeymasterCertificateChain(encodedCerts));
} catch (CertificateException e) {
logGenerateKeyPairFailure(caller, isCredentialManagementApp);
Log.e(LOG_TAG, "While retrieving certificate chain.", e);
return false;
} }
DevicePolicyEventLogger DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.GENERATE_KEY_PAIR) .createEvent(DevicePolicyEnums.GENERATE_KEY_PAIR)
.setAdmin(caller.getPackageName()) .setAdmin(caller.getPackageName())