All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.security.AttestationVerificationSelfTrustedVerifierForTesting Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * Copyright (C) 2022 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 com.android.server.security;

import static android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE;
import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE;
import static android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS;
import static android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE;

import android.annotation.NonNull;
import android.os.Build;
import android.os.Bundle;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Log;
import android.util.Slog;

import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
import com.android.internal.org.bouncycastle.asn1.ASN1ObjectIdentifier;
import com.android.internal.org.bouncycastle.asn1.ASN1OctetString;
import com.android.internal.org.bouncycastle.asn1.ASN1Sequence;
import com.android.internal.org.bouncycastle.asn1.x509.Certificate;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;

/**
 * Verifies {@code PROFILE_SELF_TRUSTED} attestations.
 *
 * Verifies that the attesting environment can create an attestation with the same root certificate
 * as the verifying device with a matching attestation challenge. Skips CRL revocations checking
 * so this verifier can work in a hermetic test environment.
 *
 * This verifier profile is intended to be used only for testing.
 */
class AttestationVerificationSelfTrustedVerifierForTesting {
    private static final String TAG = "AVF";
    private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);

    // The OID for the extension Android Keymint puts into device-generated certificates.
    private static final String ANDROID_KEYMINT_KEY_DESCRIPTION_EXTENSION_OID =
            "1.3.6.1.4.1.11129.2.1.17";

    // ASN.1 sequence index values for the Android Keymint extension.
    private static final int ATTESTATION_CHALLENGE_INDEX = 4;

    private static final String ANDROID_KEYSTORE = "AndroidKeyStore";
    private static final String GOLDEN_ALIAS =
            AttestationVerificationSelfTrustedVerifierForTesting.class.getCanonicalName()
                    + ".Golden";

    private static volatile AttestationVerificationSelfTrustedVerifierForTesting
            sAttestationVerificationSelfTrustedVerifier = null;

    private final CertificateFactory mCertificateFactory;
    private final CertPathValidator mCertPathValidator;
    private final KeyStore mAndroidKeyStore;
    private X509Certificate mGoldenRootCert;

    static AttestationVerificationSelfTrustedVerifierForTesting getInstance()
            throws Exception {
        if (sAttestationVerificationSelfTrustedVerifier == null) {
            synchronized (AttestationVerificationSelfTrustedVerifierForTesting.class) {
                if (sAttestationVerificationSelfTrustedVerifier == null) {
                    sAttestationVerificationSelfTrustedVerifier =
                            new AttestationVerificationSelfTrustedVerifierForTesting();
                }
            }
        }
        return sAttestationVerificationSelfTrustedVerifier;
    }

    private static void debugVerboseLog(String str, Throwable t) {
        if (DEBUG) {
            Slog.v(TAG, str, t);
        }
    }

    private static void debugVerboseLog(String str) {
        if (DEBUG) {
            Slog.v(TAG, str);
        }
    }

    private AttestationVerificationSelfTrustedVerifierForTesting() throws Exception {
        mCertificateFactory = CertificateFactory.getInstance("X.509");
        mCertPathValidator = CertPathValidator.getInstance("PKIX");
        mAndroidKeyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
        mAndroidKeyStore.load(null);
        if (!mAndroidKeyStore.containsAlias(GOLDEN_ALIAS)) {
            KeyPairGenerator kpg =
                    KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, ANDROID_KEYSTORE);
            KeyGenParameterSpec parameterSpec = new KeyGenParameterSpec.Builder(
                    GOLDEN_ALIAS, KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
                    .setAttestationChallenge(GOLDEN_ALIAS.getBytes())
                    .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512).build();
            kpg.initialize(parameterSpec);
            kpg.generateKeyPair();
        }

        X509Certificate[] goldenCerts = (X509Certificate[])
                ((KeyStore.PrivateKeyEntry) mAndroidKeyStore.getEntry(GOLDEN_ALIAS, null))
                        .getCertificateChain();
        mGoldenRootCert = goldenCerts[goldenCerts.length - 1];
    }

    int verifyAttestation(
            int localBindingType, @NonNull Bundle requirements,  @NonNull byte[] attestation) {
        List certificates = new ArrayList<>();
        ByteArrayInputStream bis = new ByteArrayInputStream(attestation);
        try {
            while (bis.available() > 0) {
                certificates.add((X509Certificate) mCertificateFactory.generateCertificate(bis));
            }
        } catch (CertificateException e) {
            debugVerboseLog("Unable to parse certificates from attestation", e);
            return RESULT_FAILURE;
        }

        if (localBindingType == TYPE_CHALLENGE
                && validateRequirements(requirements)
                && checkLeafChallenge(requirements, certificates)
                && verifyCertificateChain(certificates)) {
            return RESULT_SUCCESS;
        }

        return RESULT_FAILURE;
    }

    private boolean verifyCertificateChain(List certificates) {
        if (certificates.size() < 2) {
            debugVerboseLog("Certificate chain less than 2 in size.");
            return false;
        }

        try {
            CertPath certificatePath = mCertificateFactory.generateCertPath(certificates);
            PKIXParameters validationParams = new PKIXParameters(getTrustAnchors());
            // Skipping revocation checking because we want this to work in a hermetic test
            // environment.
            validationParams.setRevocationEnabled(false);
            mCertPathValidator.validate(certificatePath, validationParams);
        } catch (Throwable t) {
            debugVerboseLog("Invalid certificate chain", t);
            return false;
        }

        return true;
    }

    private Set getTrustAnchors() {
        return Collections.singleton(new TrustAnchor(mGoldenRootCert, null));
    }

    private boolean validateRequirements(Bundle requirements) {
        if (requirements.size() != 1) {
            debugVerboseLog("Requirements does not contain exactly 1 key.");
            return false;
        }
        if (!requirements.containsKey(PARAM_CHALLENGE)) {
            debugVerboseLog("Requirements does not contain key: " + PARAM_CHALLENGE);
            return false;
        }
        return true;
    }

    private boolean checkLeafChallenge(Bundle requirements, List certificates) {
        // Verify challenge
        byte[] challenge;
        try {
            challenge = getChallengeFromCert(certificates.get(0));
        } catch (Throwable t) {
            debugVerboseLog("Unable to parse challenge from certificate.", t);
            return false;
        }

        if (Arrays.equals(requirements.getByteArray(PARAM_CHALLENGE), challenge)) {
            return true;
        } else {
            debugVerboseLog("Self-Trusted validation failed; challenge mismatch.");
            return false;
        }
    }

    private byte[] getChallengeFromCert(@NonNull X509Certificate x509Certificate)
            throws CertificateEncodingException, IOException {
        Certificate certificate = Certificate.getInstance(
                new ASN1InputStream(x509Certificate.getEncoded()).readObject());
        ASN1Sequence keyAttributes = (ASN1Sequence) certificate.getTBSCertificate().getExtensions()
                .getExtensionParsedValue(
                        new ASN1ObjectIdentifier(ANDROID_KEYMINT_KEY_DESCRIPTION_EXTENSION_OID));
        return ((ASN1OctetString) keyAttributes.getObjectAt(ATTESTATION_CHALLENGE_INDEX))
                .getOctets();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy