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

Commit a1730640 authored by Eran Messeri's avatar Eran Messeri
Browse files

DevicePolicyManager: Support attestation for generated keys.

If the KeyGenParameterSpec passed into
DevicePolicyManager.generateKeyPair contains an attestation challenge,
request an attestation record for the newly-generated key with the
challenge provided.

This particular implementation was chosen, rather than letting the
attestation record be generated at the same time as key generation, to
avoid having the attestation chain stored in Keystore and associated
with the generated alias.

The rationale is that this is a key that is potentially accessible by
multiple applications and the attestation chain may end up being sent
as a TLS client certificate chain, for example.

As the attestation challenge should be unique per device, to avoid
the potential of sending / sharing unique device information, by
explicitly requesting an attestation record after key generation, the
attestation record is only returned to the generateKeyPair client and
not persistend in Keystore.

Bug: 63388672
Test: New CTS test to be run with: 'cts-tradefed run commandAndExit cts-dev -a armeabi-v7a -m CtsDevicePolicyManagerTestCases -t com.android.cts.devicepolicy.DeviceOwnerTest#testKeyManagement -l DEBUG'
Change-Id: I95a9aef179173b571b533301ac438c675e8fe702
parent 7d6688f3
Loading
Loading
Loading
Loading
+18 −3
Original line number Diff line number Diff line
@@ -64,6 +64,9 @@ import android.security.AttestedKeyPair;
import android.security.Credentials;
import android.security.KeyChain;
import android.security.KeyChainException;
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keystore.AttestationUtils;
import android.security.keystore.KeyAttestationException;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.ParcelableKeyGenParameterSpec;
import android.service.restrictions.RestrictionsReceiver;
@@ -4005,15 +4008,27 @@ public class DevicePolicyManager {
        try {
            final ParcelableKeyGenParameterSpec parcelableSpec =
                    new ParcelableKeyGenParameterSpec(keySpec);
            KeymasterCertificateChain attestationChain = new KeymasterCertificateChain();
            final boolean success = mService.generateKeyPair(
                    admin, mContext.getPackageName(), algorithm, parcelableSpec);
                    admin, mContext.getPackageName(), algorithm, parcelableSpec, attestationChain);
            if (!success) {
                Log.e(TAG, "Error generating key via DevicePolicyManagerService.");
                return null;
            }

            final KeyPair keyPair = KeyChain.getKeyPair(mContext, keySpec.getKeystoreAlias());
            return new AttestedKeyPair(keyPair, null);
            final String alias = keySpec.getKeystoreAlias();
            final KeyPair keyPair = KeyChain.getKeyPair(mContext, alias);
            Certificate[] outputChain = null;
            try {
                if (AttestationUtils.isChainValid(attestationChain)) {
                    outputChain = AttestationUtils.parseCertificateChain(attestationChain);
                }
            } catch (KeyAttestationException e) {
                Log.e(TAG, "Error parsing attestation chain for alias " + alias, e);
                mService.removeKeyPair(admin, mContext.getPackageName(), alias);
                return null;
            }
            return new AttestedKeyPair(keyPair, outputChain);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        } catch (KeyChainException e) {
+4 −1
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.UserHandle;
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keystore.ParcelableKeyGenParameterSpec;

import java.util.List;
@@ -166,7 +167,9 @@ interface IDevicePolicyManager {
            in byte[] certBuffer, in byte[] certChainBuffer, String alias, boolean requestAccess,
            boolean isUserSelectable);
    boolean removeKeyPair(in ComponentName who, in String callerPackage, String alias);
    boolean generateKeyPair(in ComponentName who, in String callerPackage, in String algorithm, in ParcelableKeyGenParameterSpec keySpec);
    boolean generateKeyPair(in ComponentName who, in String callerPackage, in String algorithm,
            in ParcelableKeyGenParameterSpec keySpec,
            out KeymasterCertificateChain attestationChain);
    void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback);

    void setDelegatedScopes(in ComponentName who, in String delegatePackage, in List<String> scopes);
+2 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
package android.security;

import android.content.pm.StringParceledListSlice;
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keystore.ParcelableKeyGenParameterSpec;

/**
@@ -33,6 +34,7 @@ interface IKeyChainService {
    void setUserSelectable(String alias, boolean isUserSelectable);

    boolean generateKeyPair(in String algorithm, in ParcelableKeyGenParameterSpec spec);
    boolean attestKey(in String alias, in byte[] challenge, out KeymasterCertificateChain chain);

    // APIs used by CertInstaller and DevicePolicyManager
    String installCaCertificate(in byte[] caCertificate);
+38 −15
Original line number Diff line number Diff line
@@ -72,6 +72,33 @@ public abstract class AttestationUtils {
     */
    public static final int ID_TYPE_MEID = 3;

    /**
     * Creates an array of X509Certificates from the provided KeymasterCertificateChain.
     *
     * @hide Only called by the DevicePolicyManager.
     */
    @NonNull public static X509Certificate[] parseCertificateChain(
            final KeymasterCertificateChain kmChain) throws
            KeyAttestationException {
        // Extract certificate chain.
        final Collection<byte[]> rawChain = kmChain.getCertificates();
        if (rawChain.size() < 2) {
            throw new KeyAttestationException("Attestation certificate chain contained "
                    + rawChain.size() + " entries. At least two are required.");
        }
        final ByteArrayOutputStream concatenatedRawChain = new ByteArrayOutputStream();
        try {
            for (final byte[] cert : rawChain) {
                concatenatedRawChain.write(cert);
            }
            return CertificateFactory.getInstance("X.509").generateCertificates(
                    new ByteArrayInputStream(concatenatedRawChain.toByteArray()))
                            .toArray(new X509Certificate[0]);
        } catch (Exception e) {
            throw new KeyAttestationException("Unable to construct certificate chain", e);
        }
    }

    /**
     * Performs attestation of the device's identifiers. This method returns a certificate chain
     * whose first element contains the requested device identifiers in an extension. The device's
@@ -173,22 +200,18 @@ public abstract class AttestationUtils {
                    KeyStore.getKeyStoreException(errorCode));
        }

        // Extract certificate chain.
        final Collection<byte[]> rawChain = outChain.getCertificates();
        if (rawChain.size() < 2) {
            throw new DeviceIdAttestationException("Attestation certificate chain contained "
                    + rawChain.size() + " entries. At least two are required.");
        }
        final ByteArrayOutputStream concatenatedRawChain = new ByteArrayOutputStream();
        try {
            for (final byte[] cert : rawChain) {
                concatenatedRawChain.write(cert);
            return parseCertificateChain(outChain);
        } catch (KeyAttestationException e) {
            throw new DeviceIdAttestationException(e.getMessage(), e);
        }
            return CertificateFactory.getInstance("X.509").generateCertificates(
                    new ByteArrayInputStream(concatenatedRawChain.toByteArray()))
                            .toArray(new X509Certificate[0]);
        } catch (Exception e) {
            throw new DeviceIdAttestationException("Unable to construct certificate chain", e);
    }

    /**
     * Returns true if the attestation chain provided is a valid key attestation chain.
     * @hide
     */
    public static boolean isChainValid(KeymasterCertificateChain chain) {
        return chain != null && chain.getCertificates().size() >= 2;
    }
}
+46 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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;

/**
 * Thrown when {@link AttestationUtils} is unable to attest the given key or handle
 * the resulting attestation record.
 *
 * @hide
 */
public class KeyAttestationException extends Exception {
    /**
     * Constructs a new {@code KeyAttestationException} with the current stack trace and the
     * specified detail message.
     *
     * @param detailMessage the detail message for this exception.
     */
    public KeyAttestationException(String detailMessage) {
        super(detailMessage);
    }

    /**
     * Constructs a new {@code KeyAttestationException} with the current stack trace, the
     * specified detail message and the specified cause.
     *
     * @param message the detail message for this exception.
     * @param cause the cause of this exception, may be {@code null}.
     */
    public KeyAttestationException(String message, Throwable cause) {
        super(message, cause);
    }
}
Loading