Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0f138dc0 authored by Janis Danisevskis's avatar Janis Danisevskis
Browse files

Keystore 2.0: Remove attestKey from KeyChain.

KeyChain supports device id attestation through KeyGenParameterSpec now.
No need to call attest key individually. Also calling attest key
individually is no longer supported by Keystore 2.0 and KeyMint.

Also isBoundKeyAlgorithm returns true.

Test: atest FrameworksServicesTests:DevicePolicyManagerTest
Bug: 171305387
Change-Id: I759fe245b48fe435153fded2c74c9ae99634c146
parent 6d23bc6d
Loading
Loading
Loading
Loading
+0 −2
Original line number Diff line number Diff line
@@ -37,8 +37,6 @@ interface IKeyChainService {
    void setUserSelectable(String alias, boolean isUserSelectable);

    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);

    // APIs used by CertInstaller and DevicePolicyManager
+44 −6
Original line number Diff line number Diff line
@@ -44,6 +44,8 @@ import android.os.UserManager;
import android.security.keystore.AndroidKeyStoreProvider;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.system.keystore2.Domain;
import android.system.keystore2.KeyDescriptor;
import android.util.Log;

import com.android.org.conscrypt.TrustedCertificateStore;
@@ -682,6 +684,33 @@ public final class KeyChain {
        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 */
    @Nullable @WorkerThread
    public static KeyPair getKeyPair(@NonNull Context context, @NonNull String alias)
@@ -705,11 +734,23 @@ public final class KeyChain {

        if (keyId == 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 {
            try {
                return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
                        KeyStore.getInstance(), keyId, KeyStore.UID_SELF);
            } catch (RuntimeException | UnrecoverableKeyException | KeyPermanentlyInvalidatedException e) {
            } catch (RuntimeException | UnrecoverableKeyException
                    | KeyPermanentlyInvalidatedException e) {
                throw new KeyChainException(e);
            }
        }
@@ -827,11 +868,8 @@ public final class KeyChain {
    @Deprecated
    public static boolean isBoundKeyAlgorithm(
            @NonNull @KeyProperties.KeyAlgorithmEnum String algorithm) {
        if (!isKeyAlgorithmSupported(algorithm)) {
            return false;
        }

        return KeyStore.getInstance().isHardwareBacked(algorithm);
        // All supported algorithms are hardware backed. Individual keys may not be.
        return true;
    }

    /** @hide */
+16 −4
Original line number Diff line number Diff line
@@ -273,10 +273,10 @@ public class AndroidKeyStoreProvider extends Provider {
    /** @hide **/
    @NonNull
    public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore(
            @NonNull KeyStore2 keyStore, @NonNull String privateKeyAlias, int namespace)
            @NonNull KeyStore2 keyStore, @NonNull KeyDescriptor descriptor)
            throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
        AndroidKeyStoreKey key =
                loadAndroidKeyStoreKeyFromKeystore(keyStore, privateKeyAlias, namespace);
                loadAndroidKeyStoreKeyFromKeystore(keyStore, descriptor);
        if (key instanceof AndroidKeyStorePublicKey) {
            AndroidKeyStorePublicKey publicKey = (AndroidKeyStorePublicKey) key;
            return new KeyPair(publicKey, publicKey.getPrivateKey());
@@ -348,6 +348,18 @@ public class AndroidKeyStoreProvider extends Provider {
        }
        descriptor.alias = alias;
        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;
        try {
            response = keyStore.getKeyEntry(descriptor);
@@ -397,7 +409,7 @@ public class AndroidKeyStoreProvider extends Provider {
                keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) {
            return makeAndroidKeyStorePublicKeyFromKeyEntryResponse(descriptor, response.metadata,
                    new KeyStoreSecurityLevel(response.iSecurityLevel),
                    keymasterAlgorithm).getPrivateKey();
                    keymasterAlgorithm);
        } else {
            throw new UnrecoverableKeyException("Key algorithm unknown");
        }
+43 −22
Original line number Diff line number Diff line
@@ -333,6 +333,7 @@ import com.google.android.collect.Sets;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -340,6 +341,9 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
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.time.LocalDate;
import java.util.ArrayList;
@@ -5622,7 +5626,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        // Get attestation flags, if any.
        final int[] attestationUtilsFlags = translateIdAttestationFlags(idAttestationFlags);
        final boolean deviceIdAttestationRequired = attestationUtilsFlags != null;
        final KeyGenParameterSpec keySpec = parcelableKeySpec.getSpec();
        KeyGenParameterSpec keySpec = parcelableKeySpec.getSpec();
        final String alias = keySpec.getKeystoreAlias();
        Preconditions.checkStringNotEmpty(alias, "Empty alias provided");
@@ -5643,6 +5647,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                    || (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
        // it will not have the desired effect.
        if (keySpec.getUid() != KeyStore.UID_SELF) {
@@ -5651,19 +5658,26 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
            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();
        try {
            try (KeyChainConnection keyChainConnection =
                    KeyChain.bindAsUser(mContext, caller.getUserHandle())) {
                    KeyChain.bindAsUser(mContext, userHandle)) {
                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,
                    new ParcelableKeyGenParameterSpec(noAttestationSpec));
                        new ParcelableKeyGenParameterSpec(keySpec));
                if (generationResult != KeyChain.KEY_GEN_SUCCESS) {
                    Log.e(LOG_TAG, String.format(
                            "KeyChain failed to generate a keypair, error %d.", generationResult));
@@ -5672,6 +5686,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                            throw new ServiceSpecificException(
                                    DevicePolicyManager.KEY_GEN_STRONGBOX_UNAVAILABLE,
                                    String.format("KeyChain error: %d", generationResult));
                        case KeyChain.KEY_ATTESTATION_CANNOT_ATTEST_IDS:
                            throw new UnsupportedOperationException(
                                "Device does not support Device ID attestation.");
                        default:
                            logGenerateKeyPairFailure(caller, isCredentialManagementApp);
                            return false;
@@ -5685,23 +5702,27 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                // that UID.
                keyChain.setGrant(caller.getUid(), alias, true);
                final byte[] attestationChallenge = keySpec.getAttestationChallenge();
                if (attestationChallenge != null) {
                    final int attestationResult = keyChain.attestKey(
                            alias, attestationChallenge, attestationUtilsFlags, attestationChain);
                    if (attestationResult != KeyChain.KEY_ATTESTATION_SUCCESS) {
                        Log.e(LOG_TAG, String.format(
                                "Attestation for %s failed (rc=%d), deleting key.",
                                alias, attestationResult));
                        keyChain.removeKeyPair(alias);
                        if (attestationResult == KeyChain.KEY_ATTESTATION_CANNOT_ATTEST_IDS) {
                            throw new UnsupportedOperationException(
                                    "Device does not support Device ID attestation.");
                try {
                    final List<byte[]> encodedCerts = new ArrayList();
                    final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
                    final byte[] certChainBytes = keyChain.getCaCertificates(alias);
                    encodedCerts.add(keyChain.getCertificate(alias));
                    if (certChainBytes != null) {
                        final Collection<X509Certificate> certs =
                                (Collection<X509Certificate>) certFactory.generateCertificates(
                                    new ByteArrayInputStream(certChainBytes));
                        for (X509Certificate cert : certs) {
                            encodedCerts.add(cert.getEncoded());
                        }
                    }
                    attestationChain.shallowCopyFrom(new KeymasterCertificateChain(encodedCerts));
                } catch (CertificateException e) {
                    logGenerateKeyPairFailure(caller, isCredentialManagementApp);
                    Log.e(LOG_TAG, "While retrieving certificate chain.", e);
                    return false;
                }
                }
                DevicePolicyEventLogger
                        .createEvent(DevicePolicyEnums.GENERATE_KEY_PAIR)
                        .setAdmin(caller.getPackageName())