blob: 58df2bd982dc375b4b831b48173d663426126bf4 [file] [log] [blame]
/*
* 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<X509Certificate> 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<X509Certificate> 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<TrustAnchor> 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<X509Certificate> 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();
}
}