Loading services/core/java/com/android/server/security/AttestationVerificationManagerService.java +76 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ import static android.security.attestationverification.AttestationVerificationMa import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE; import static android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.os.Bundle; import android.os.IBinder; Loading @@ -31,12 +33,20 @@ import android.security.attestationverification.AttestationProfile; import android.security.attestationverification.IAttestationVerificationManagerService; import android.security.attestationverification.IVerificationResult; import android.security.attestationverification.VerificationToken; import android.text.TextUtils; import android.util.ExceptionUtils; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.TimeUtils; import com.android.internal.infra.AndroidFuture; import com.android.internal.util.DumpUtils; import com.android.server.SystemService; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayDeque; /** * A {@link SystemService} which provides functionality related to verifying attestations of * (usually) remote computing environments. Loading @@ -46,11 +56,13 @@ import com.android.server.SystemService; public class AttestationVerificationManagerService extends SystemService { private static final String TAG = "AVF"; private static final int DUMP_EVENT_LOG_SIZE = 10; private final AttestationVerificationPeerDeviceVerifier mPeerDeviceVerifier; private final DumpLogger mDumpLogger = new DumpLogger(); public AttestationVerificationManagerService(final Context context) throws Exception { super(context); mPeerDeviceVerifier = new AttestationVerificationPeerDeviceVerifier(context); mPeerDeviceVerifier = new AttestationVerificationPeerDeviceVerifier(context, mDumpLogger); } private final IBinder mService = new IAttestationVerificationManagerService.Stub() { Loading Loading @@ -83,6 +95,28 @@ public class AttestationVerificationManagerService extends SystemService { private void enforceUsePermission() { getContext().enforceCallingOrSelfPermission(USE_ATTESTATION_VERIFICATION_SERVICE, null); } @Override protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) { if (!android.security.Flags.dumpAttestationVerifications()) { super.dump(fd, writer, args); return; } if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, writer)) return; final IndentingPrintWriter fout = new IndentingPrintWriter(writer, " "); fout.print("AttestationVerificationManagerService"); fout.println(); fout.increaseIndent(); fout.println("Event Log:"); fout.increaseIndent(); mDumpLogger.dumpTo(fout); fout.decreaseIndent(); } }; private void verifyAttestationForAllVerifiers( Loading Loading @@ -119,4 +153,45 @@ public class AttestationVerificationManagerService extends SystemService { Slog.d(TAG, "Started"); publishBinderService(Context.ATTESTATION_VERIFICATION_SERVICE, mService); } static class DumpLogger { private final ArrayDeque<DumpData> mData = new ArrayDeque<>(DUMP_EVENT_LOG_SIZE); private int mEventsLogged = 0; void logAttempt(DumpData data) { synchronized (mData) { if (mData.size() == DUMP_EVENT_LOG_SIZE) { mData.removeFirst(); } mEventsLogged++; data.mEventNumber = mEventsLogged; data.mEventTimeMs = System.currentTimeMillis(); mData.add(data); } } void dumpTo(IndentingPrintWriter writer) { synchronized (mData) { for (DumpData data : mData.reversed()) { writer.println( TextUtils.formatSimple("Verification #%d [%s]", data.mEventNumber, TimeUtils.formatForLogging(data.mEventTimeMs))); writer.increaseIndent(); data.dumpTo(writer); writer.decreaseIndent(); } } } } abstract static class DumpData { protected int mEventNumber = -1; protected long mEventTimeMs = -1; abstract void dumpTo(IndentingPrintWriter writer); } } services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java +192 −31 Original line number Diff line number Diff line Loading @@ -30,15 +30,19 @@ import static com.android.server.security.AndroidKeystoreAttestationVerification import static java.nio.charset.StandardCharsets.UTF_8; import android.annotation.NonNull; import android.annotation.SuppressLint; import android.content.Context; import android.os.Build; import android.os.Bundle; import android.security.attestationverification.AttestationVerificationManager; import android.security.attestationverification.AttestationVerificationManager.LocalBindingType; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.server.security.AttestationVerificationManagerService.DumpLogger; import org.json.JSONObject; Loading Loading @@ -71,7 +75,9 @@ import java.util.Set; /** * Verifies Android key attestation according to the * {@link android.security.attestationverification.AttestationVerificationManager#PROFILE_PEER_DEVICE PROFILE_PEER_DEVICE} * {@link * android.security.attestationverification.AttestationVerificationManager#PROFILE_PEER_DEVICE * PROFILE_PEER_DEVICE} * profile. * * <p> Loading Loading @@ -118,9 +124,12 @@ class AttestationVerificationPeerDeviceVerifier { private final LocalDate mTestLocalPatchDate; private final CertificateFactory mCertificateFactory; private final CertPathValidator mCertPathValidator; private final DumpLogger mDumpLogger; AttestationVerificationPeerDeviceVerifier(@NonNull Context context) throws Exception { AttestationVerificationPeerDeviceVerifier(@NonNull Context context, @NonNull DumpLogger dumpLogger) throws Exception { mContext = Objects.requireNonNull(context); mDumpLogger = dumpLogger; mCertificateFactory = CertificateFactory.getInstance("X.509"); mCertPathValidator = CertPathValidator.getInstance("PKIX"); mTrustAnchors = getTrustAnchors(); Loading @@ -132,9 +141,10 @@ class AttestationVerificationPeerDeviceVerifier { // Use ONLY for hermetic unit testing. @VisibleForTesting AttestationVerificationPeerDeviceVerifier(@NonNull Context context, Set<TrustAnchor> trustAnchors, boolean revocationEnabled, DumpLogger dumpLogger, Set<TrustAnchor> trustAnchors, boolean revocationEnabled, LocalDate systemDate, LocalDate localPatchDate) throws Exception { mContext = Objects.requireNonNull(context); mDumpLogger = dumpLogger; mCertificateFactory = CertificateFactory.getInstance("X.509"); mCertPathValidator = CertPathValidator.getInstance("PKIX"); mTrustAnchors = trustAnchors; Loading @@ -154,62 +164,89 @@ class AttestationVerificationPeerDeviceVerifier { * * @param localBindingType Only {@code TYPE_PUBLIC_KEY} and {@code TYPE_CHALLENGE} supported. * @param requirements Only {@code PARAM_PUBLIC_KEY} and {@code PARAM_CHALLENGE} supported. * @param attestation Certificates should be DER encoded with leaf certificate appended first. * @param attestation Certificates should be DER encoded with leaf certificate appended * first. */ int verifyAttestation( @LocalBindingType int localBindingType, @NonNull Bundle requirements, @NonNull byte[] attestation) { MyDumpData dumpData = new MyDumpData(); int result = verifyAttestationInternal(localBindingType, requirements, attestation, dumpData); dumpData.mResult = result; mDumpLogger.logAttempt(dumpData); return result; } private int verifyAttestationInternal( @LocalBindingType int localBindingType, @NonNull Bundle requirements, @NonNull byte[] attestation, @NonNull MyDumpData dumpData) { if (mCertificateFactory == null) { debugVerboseLog("Unable to access CertificateFactory"); return RESULT_FAILURE; } dumpData.mCertificationFactoryAvailable = true; if (mCertPathValidator == null) { debugVerboseLog("Unable to access CertPathValidator"); return RESULT_FAILURE; } dumpData.mCertPathValidatorAvailable = true; // Check if the provided local binding type is supported and if the provided requirements // "match" the binding type. if (!validateAttestationParameters(localBindingType, requirements)) { return RESULT_FAILURE; } dumpData.mAttestationParametersOk = true; // To provide the most information in the dump logs, we track the failure state but keep // verifying the rest of the attestation. For code safety, there are no transitions past // here to set failed = false boolean failed = false; try { // First: parse and validate the certificate chain. final List<X509Certificate> certificateChain = getCertificates(attestation); // (returns void, but throws CertificateException and other similar Exceptions) validateCertificateChain(certificateChain); dumpData.mCertChainOk = true; final var leafCertificate = certificateChain.get(0); final var attestationExtension = fromCertificate(leafCertificate); // Second: verify if the attestation satisfies the "peer device" profile. if (!checkAttestationForPeerDeviceProfile(attestationExtension)) { return RESULT_FAILURE; if (!checkAttestationForPeerDeviceProfile(attestationExtension, dumpData)) { failed = true; } // Third: check if the attestation satisfies local binding requirements. if (!checkLocalBindingRequirements( leafCertificate, attestationExtension, localBindingType, requirements)) { return RESULT_FAILURE; leafCertificate, attestationExtension, localBindingType, requirements, dumpData)) { failed = true; } return RESULT_SUCCESS; } catch (CertificateException | CertPathValidatorException | InvalidAlgorithmParameterException | IOException e) { // Catch all non-RuntimeExpceptions (all of these are thrown by either getCertificates() // or validateCertificateChain() or // AndroidKeystoreAttestationVerificationAttributes.fromCertificate()) debugVerboseLog("Unable to parse/validate Android Attestation certificate(s)", e); return RESULT_FAILURE; failed = true; } catch (RuntimeException e) { // Catch everyting else (RuntimeExpcetions), since we don't want to throw any exceptions // out of this class/method. debugVerboseLog("Unexpected error", e); return RESULT_FAILURE; failed = true; } return failed ? RESULT_FAILURE : RESULT_SUCCESS; } @NonNull Loading Loading @@ -307,8 +344,9 @@ class AttestationVerificationPeerDeviceVerifier { @NonNull X509Certificate leafCertificate, @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes, @LocalBindingType int localBindingType, @NonNull Bundle requirements) { @NonNull Bundle requirements, MyDumpData dumpData) { // First: check non-optional (for the given local binding type) requirements. dumpData.mBindingType = localBindingType; switch (localBindingType) { case TYPE_PUBLIC_KEY: // Verify leaf public key matches provided public key. Loading Loading @@ -336,9 +374,11 @@ class AttestationVerificationPeerDeviceVerifier { throw new IllegalArgumentException("Unsupported local binding type " + localBindingTypeToString(localBindingType)); } dumpData.mBindingOk = true; // Second: check specified optional requirements. if (requirements.containsKey(PARAM_OWNED_BY_SYSTEM)) { dumpData.mSystemOwnershipChecked = true; if (requirements.getBoolean(PARAM_OWNED_BY_SYSTEM)) { // Verify key is owned by the system. final boolean ownedBySystem = checkOwnedBySystem( Loading @@ -347,6 +387,7 @@ class AttestationVerificationPeerDeviceVerifier { debugVerboseLog("Certificate public key is not owned by the AndroidSystem."); return false; } dumpData.mSystemOwned = true; } else { throw new IllegalArgumentException("The value of the requirement key " + PARAM_OWNED_BY_SYSTEM Loading @@ -359,73 +400,98 @@ class AttestationVerificationPeerDeviceVerifier { } private boolean checkAttestationForPeerDeviceProfile( @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes) { @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes, MyDumpData dumpData) { boolean result = true; // Checks for support of Keymaster 4. if (attestationAttributes.getAttestationVersion() < 3) { debugVerboseLog("Attestation version is not at least 3 (Keymaster 4)."); return false; result = false; } else { dumpData.mAttestationVersionAtLeast3 = true; } // Checks for support of Keymaster 4. if (attestationAttributes.getKeymasterVersion() < 4) { debugVerboseLog("Keymaster version is not at least 4."); return false; result = false; } else { dumpData.mKeymasterVersionAtLeast4 = true; } // First two characters are Android OS version. if (attestationAttributes.getKeyOsVersion() < 100000) { debugVerboseLog("Android OS version is not 10+."); return false; result = false; } else { dumpData.mOsVersionAtLeast10 = true; } if (!attestationAttributes.isAttestationHardwareBacked()) { debugVerboseLog("Key is not HW backed."); return false; result = false; } else { dumpData.mKeyHwBacked = true; } if (!attestationAttributes.isKeymasterHardwareBacked()) { debugVerboseLog("Keymaster is not HW backed."); return false; result = false; } else { dumpData.mKeymasterHwBacked = true; } if (attestationAttributes.getVerifiedBootState() != VERIFIED) { debugVerboseLog("Boot state not Verified."); return false; result = false; } else { dumpData.mBootStateIsVerified = true; } try { if (!attestationAttributes.isVerifiedBootLocked()) { debugVerboseLog("Verified boot state is not locked."); return false; result = false; } else { dumpData.mVerifiedBootStateLocked = true; } } catch (IllegalStateException e) { debugVerboseLog("VerifiedBootLocked is not set.", e); return false; result = false; } // Patch level integer YYYYMM is expected to be within 1 year of today. if (!isValidPatchLevel(attestationAttributes.getKeyOsPatchLevel())) { debugVerboseLog("OS patch level is not within valid range."); return false; result = false; } else { dumpData.mOsPatchLevelInRange = true; } // Patch level integer YYYYMMDD is expected to be within 1 year of today. if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) { debugVerboseLog("Boot patch level is not within valid range."); return false; result = false; } else { dumpData.mKeyBootPatchLevelInRange = true; } if (!isValidPatchLevel(attestationAttributes.getKeyVendorPatchLevel())) { debugVerboseLog("Vendor patch level is not within valid range."); return false; result = false; } else { dumpData.mKeyVendorPatchLevelInRange = true; } if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) { debugVerboseLog("Boot patch level is not within valid range."); return false; result = false; } else { dumpData.mKeyBootPatchLevelInRange = true; } return true; return result; } private boolean checkPublicKey( Loading Loading @@ -609,4 +675,99 @@ class AttestationVerificationPeerDeviceVerifier { Slog.v(TAG, str); } } /* Mutable data class for tracking dump data from verifications. */ private static class MyDumpData extends AttestationVerificationManagerService.DumpData { // Top-Level Result int mResult = -1; // Configuration/Setup preconditions boolean mCertificationFactoryAvailable = false; boolean mCertPathValidatorAvailable = false; // AttestationParameters (Valid Input Only) boolean mAttestationParametersOk = false; // Certificate Chain (Structure & Chaining Conditions) boolean mCertChainOk = false; // Binding boolean mBindingOk = false; int mBindingType = -1; // System Ownership boolean mSystemOwnershipChecked = false; boolean mSystemOwned = false; // Android Keystore attestation properties boolean mOsVersionAtLeast10 = false; boolean mKeyHwBacked = false; boolean mAttestationVersionAtLeast3 = false; boolean mKeymasterVersionAtLeast4 = false; boolean mKeymasterHwBacked = false; boolean mBootStateIsVerified = false; boolean mVerifiedBootStateLocked = false; boolean mOsPatchLevelInRange = false; boolean mKeyBootPatchLevelInRange = false; boolean mKeyVendorPatchLevelInRange = false; @SuppressLint("WrongConstant") @Override public void dumpTo(IndentingPrintWriter writer) { writer.println( "Result: " + AttestationVerificationManager.verificationResultCodeToString( mResult)); if (!mCertificationFactoryAvailable) { writer.println("Certificate Factory Unavailable"); return; } if (!mCertPathValidatorAvailable) { writer.println("Cert Path Validator Unavailable"); return; } if (!mAttestationParametersOk) { writer.println("Attestation parameters set incorrectly."); return; } writer.println("Certificate Chain Valid (inc. Trust Anchor): " + booleanToOkFail( mCertChainOk)); if (!mCertChainOk) { return; } // Binding writer.println("Local Binding: " + booleanToOkFail(mBindingOk)); writer.increaseIndent(); writer.println("Binding Type: " + mBindingType); writer.decreaseIndent(); if (mSystemOwnershipChecked) { writer.println("System Ownership: " + booleanToOkFail(mSystemOwned)); } // Keystore Attestation params writer.println("KeyStore Attestation Parameters"); writer.increaseIndent(); writer.println("OS Version >= 10: " + booleanToOkFail(mOsVersionAtLeast10)); writer.println("OS Patch Level in Range: " + booleanToOkFail(mOsPatchLevelInRange)); writer.println( "Attestation Version >= 3: " + booleanToOkFail(mAttestationVersionAtLeast3)); writer.println("Keymaster Version >= 4: " + booleanToOkFail(mKeymasterVersionAtLeast4)); writer.println("Keymaster HW-Backed: " + booleanToOkFail(mKeymasterHwBacked)); writer.println("Key is HW Backed: " + booleanToOkFail(mKeyHwBacked)); writer.println("Boot State is VERIFIED: " + booleanToOkFail(mBootStateIsVerified)); writer.println("Verified Boot is LOCKED: " + booleanToOkFail(mVerifiedBootStateLocked)); writer.println( "Key Boot Level in Range: " + booleanToOkFail(mKeyBootPatchLevelInRange)); writer.println("Key Vendor Patch Level in Range: " + booleanToOkFail( mKeyVendorPatchLevelInRange)); writer.decreaseIndent(); } private String booleanToOkFail(boolean value) { return value ? "OK" : "FAILURE"; } } } tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt +90 −41 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
services/core/java/com/android/server/security/AttestationVerificationManagerService.java +76 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ import static android.security.attestationverification.AttestationVerificationMa import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE; import static android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.os.Bundle; import android.os.IBinder; Loading @@ -31,12 +33,20 @@ import android.security.attestationverification.AttestationProfile; import android.security.attestationverification.IAttestationVerificationManagerService; import android.security.attestationverification.IVerificationResult; import android.security.attestationverification.VerificationToken; import android.text.TextUtils; import android.util.ExceptionUtils; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.TimeUtils; import com.android.internal.infra.AndroidFuture; import com.android.internal.util.DumpUtils; import com.android.server.SystemService; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayDeque; /** * A {@link SystemService} which provides functionality related to verifying attestations of * (usually) remote computing environments. Loading @@ -46,11 +56,13 @@ import com.android.server.SystemService; public class AttestationVerificationManagerService extends SystemService { private static final String TAG = "AVF"; private static final int DUMP_EVENT_LOG_SIZE = 10; private final AttestationVerificationPeerDeviceVerifier mPeerDeviceVerifier; private final DumpLogger mDumpLogger = new DumpLogger(); public AttestationVerificationManagerService(final Context context) throws Exception { super(context); mPeerDeviceVerifier = new AttestationVerificationPeerDeviceVerifier(context); mPeerDeviceVerifier = new AttestationVerificationPeerDeviceVerifier(context, mDumpLogger); } private final IBinder mService = new IAttestationVerificationManagerService.Stub() { Loading Loading @@ -83,6 +95,28 @@ public class AttestationVerificationManagerService extends SystemService { private void enforceUsePermission() { getContext().enforceCallingOrSelfPermission(USE_ATTESTATION_VERIFICATION_SERVICE, null); } @Override protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) { if (!android.security.Flags.dumpAttestationVerifications()) { super.dump(fd, writer, args); return; } if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, writer)) return; final IndentingPrintWriter fout = new IndentingPrintWriter(writer, " "); fout.print("AttestationVerificationManagerService"); fout.println(); fout.increaseIndent(); fout.println("Event Log:"); fout.increaseIndent(); mDumpLogger.dumpTo(fout); fout.decreaseIndent(); } }; private void verifyAttestationForAllVerifiers( Loading Loading @@ -119,4 +153,45 @@ public class AttestationVerificationManagerService extends SystemService { Slog.d(TAG, "Started"); publishBinderService(Context.ATTESTATION_VERIFICATION_SERVICE, mService); } static class DumpLogger { private final ArrayDeque<DumpData> mData = new ArrayDeque<>(DUMP_EVENT_LOG_SIZE); private int mEventsLogged = 0; void logAttempt(DumpData data) { synchronized (mData) { if (mData.size() == DUMP_EVENT_LOG_SIZE) { mData.removeFirst(); } mEventsLogged++; data.mEventNumber = mEventsLogged; data.mEventTimeMs = System.currentTimeMillis(); mData.add(data); } } void dumpTo(IndentingPrintWriter writer) { synchronized (mData) { for (DumpData data : mData.reversed()) { writer.println( TextUtils.formatSimple("Verification #%d [%s]", data.mEventNumber, TimeUtils.formatForLogging(data.mEventTimeMs))); writer.increaseIndent(); data.dumpTo(writer); writer.decreaseIndent(); } } } } abstract static class DumpData { protected int mEventNumber = -1; protected long mEventTimeMs = -1; abstract void dumpTo(IndentingPrintWriter writer); } }
services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java +192 −31 Original line number Diff line number Diff line Loading @@ -30,15 +30,19 @@ import static com.android.server.security.AndroidKeystoreAttestationVerification import static java.nio.charset.StandardCharsets.UTF_8; import android.annotation.NonNull; import android.annotation.SuppressLint; import android.content.Context; import android.os.Build; import android.os.Bundle; import android.security.attestationverification.AttestationVerificationManager; import android.security.attestationverification.AttestationVerificationManager.LocalBindingType; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.server.security.AttestationVerificationManagerService.DumpLogger; import org.json.JSONObject; Loading Loading @@ -71,7 +75,9 @@ import java.util.Set; /** * Verifies Android key attestation according to the * {@link android.security.attestationverification.AttestationVerificationManager#PROFILE_PEER_DEVICE PROFILE_PEER_DEVICE} * {@link * android.security.attestationverification.AttestationVerificationManager#PROFILE_PEER_DEVICE * PROFILE_PEER_DEVICE} * profile. * * <p> Loading Loading @@ -118,9 +124,12 @@ class AttestationVerificationPeerDeviceVerifier { private final LocalDate mTestLocalPatchDate; private final CertificateFactory mCertificateFactory; private final CertPathValidator mCertPathValidator; private final DumpLogger mDumpLogger; AttestationVerificationPeerDeviceVerifier(@NonNull Context context) throws Exception { AttestationVerificationPeerDeviceVerifier(@NonNull Context context, @NonNull DumpLogger dumpLogger) throws Exception { mContext = Objects.requireNonNull(context); mDumpLogger = dumpLogger; mCertificateFactory = CertificateFactory.getInstance("X.509"); mCertPathValidator = CertPathValidator.getInstance("PKIX"); mTrustAnchors = getTrustAnchors(); Loading @@ -132,9 +141,10 @@ class AttestationVerificationPeerDeviceVerifier { // Use ONLY for hermetic unit testing. @VisibleForTesting AttestationVerificationPeerDeviceVerifier(@NonNull Context context, Set<TrustAnchor> trustAnchors, boolean revocationEnabled, DumpLogger dumpLogger, Set<TrustAnchor> trustAnchors, boolean revocationEnabled, LocalDate systemDate, LocalDate localPatchDate) throws Exception { mContext = Objects.requireNonNull(context); mDumpLogger = dumpLogger; mCertificateFactory = CertificateFactory.getInstance("X.509"); mCertPathValidator = CertPathValidator.getInstance("PKIX"); mTrustAnchors = trustAnchors; Loading @@ -154,62 +164,89 @@ class AttestationVerificationPeerDeviceVerifier { * * @param localBindingType Only {@code TYPE_PUBLIC_KEY} and {@code TYPE_CHALLENGE} supported. * @param requirements Only {@code PARAM_PUBLIC_KEY} and {@code PARAM_CHALLENGE} supported. * @param attestation Certificates should be DER encoded with leaf certificate appended first. * @param attestation Certificates should be DER encoded with leaf certificate appended * first. */ int verifyAttestation( @LocalBindingType int localBindingType, @NonNull Bundle requirements, @NonNull byte[] attestation) { MyDumpData dumpData = new MyDumpData(); int result = verifyAttestationInternal(localBindingType, requirements, attestation, dumpData); dumpData.mResult = result; mDumpLogger.logAttempt(dumpData); return result; } private int verifyAttestationInternal( @LocalBindingType int localBindingType, @NonNull Bundle requirements, @NonNull byte[] attestation, @NonNull MyDumpData dumpData) { if (mCertificateFactory == null) { debugVerboseLog("Unable to access CertificateFactory"); return RESULT_FAILURE; } dumpData.mCertificationFactoryAvailable = true; if (mCertPathValidator == null) { debugVerboseLog("Unable to access CertPathValidator"); return RESULT_FAILURE; } dumpData.mCertPathValidatorAvailable = true; // Check if the provided local binding type is supported and if the provided requirements // "match" the binding type. if (!validateAttestationParameters(localBindingType, requirements)) { return RESULT_FAILURE; } dumpData.mAttestationParametersOk = true; // To provide the most information in the dump logs, we track the failure state but keep // verifying the rest of the attestation. For code safety, there are no transitions past // here to set failed = false boolean failed = false; try { // First: parse and validate the certificate chain. final List<X509Certificate> certificateChain = getCertificates(attestation); // (returns void, but throws CertificateException and other similar Exceptions) validateCertificateChain(certificateChain); dumpData.mCertChainOk = true; final var leafCertificate = certificateChain.get(0); final var attestationExtension = fromCertificate(leafCertificate); // Second: verify if the attestation satisfies the "peer device" profile. if (!checkAttestationForPeerDeviceProfile(attestationExtension)) { return RESULT_FAILURE; if (!checkAttestationForPeerDeviceProfile(attestationExtension, dumpData)) { failed = true; } // Third: check if the attestation satisfies local binding requirements. if (!checkLocalBindingRequirements( leafCertificate, attestationExtension, localBindingType, requirements)) { return RESULT_FAILURE; leafCertificate, attestationExtension, localBindingType, requirements, dumpData)) { failed = true; } return RESULT_SUCCESS; } catch (CertificateException | CertPathValidatorException | InvalidAlgorithmParameterException | IOException e) { // Catch all non-RuntimeExpceptions (all of these are thrown by either getCertificates() // or validateCertificateChain() or // AndroidKeystoreAttestationVerificationAttributes.fromCertificate()) debugVerboseLog("Unable to parse/validate Android Attestation certificate(s)", e); return RESULT_FAILURE; failed = true; } catch (RuntimeException e) { // Catch everyting else (RuntimeExpcetions), since we don't want to throw any exceptions // out of this class/method. debugVerboseLog("Unexpected error", e); return RESULT_FAILURE; failed = true; } return failed ? RESULT_FAILURE : RESULT_SUCCESS; } @NonNull Loading Loading @@ -307,8 +344,9 @@ class AttestationVerificationPeerDeviceVerifier { @NonNull X509Certificate leafCertificate, @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes, @LocalBindingType int localBindingType, @NonNull Bundle requirements) { @NonNull Bundle requirements, MyDumpData dumpData) { // First: check non-optional (for the given local binding type) requirements. dumpData.mBindingType = localBindingType; switch (localBindingType) { case TYPE_PUBLIC_KEY: // Verify leaf public key matches provided public key. Loading Loading @@ -336,9 +374,11 @@ class AttestationVerificationPeerDeviceVerifier { throw new IllegalArgumentException("Unsupported local binding type " + localBindingTypeToString(localBindingType)); } dumpData.mBindingOk = true; // Second: check specified optional requirements. if (requirements.containsKey(PARAM_OWNED_BY_SYSTEM)) { dumpData.mSystemOwnershipChecked = true; if (requirements.getBoolean(PARAM_OWNED_BY_SYSTEM)) { // Verify key is owned by the system. final boolean ownedBySystem = checkOwnedBySystem( Loading @@ -347,6 +387,7 @@ class AttestationVerificationPeerDeviceVerifier { debugVerboseLog("Certificate public key is not owned by the AndroidSystem."); return false; } dumpData.mSystemOwned = true; } else { throw new IllegalArgumentException("The value of the requirement key " + PARAM_OWNED_BY_SYSTEM Loading @@ -359,73 +400,98 @@ class AttestationVerificationPeerDeviceVerifier { } private boolean checkAttestationForPeerDeviceProfile( @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes) { @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes, MyDumpData dumpData) { boolean result = true; // Checks for support of Keymaster 4. if (attestationAttributes.getAttestationVersion() < 3) { debugVerboseLog("Attestation version is not at least 3 (Keymaster 4)."); return false; result = false; } else { dumpData.mAttestationVersionAtLeast3 = true; } // Checks for support of Keymaster 4. if (attestationAttributes.getKeymasterVersion() < 4) { debugVerboseLog("Keymaster version is not at least 4."); return false; result = false; } else { dumpData.mKeymasterVersionAtLeast4 = true; } // First two characters are Android OS version. if (attestationAttributes.getKeyOsVersion() < 100000) { debugVerboseLog("Android OS version is not 10+."); return false; result = false; } else { dumpData.mOsVersionAtLeast10 = true; } if (!attestationAttributes.isAttestationHardwareBacked()) { debugVerboseLog("Key is not HW backed."); return false; result = false; } else { dumpData.mKeyHwBacked = true; } if (!attestationAttributes.isKeymasterHardwareBacked()) { debugVerboseLog("Keymaster is not HW backed."); return false; result = false; } else { dumpData.mKeymasterHwBacked = true; } if (attestationAttributes.getVerifiedBootState() != VERIFIED) { debugVerboseLog("Boot state not Verified."); return false; result = false; } else { dumpData.mBootStateIsVerified = true; } try { if (!attestationAttributes.isVerifiedBootLocked()) { debugVerboseLog("Verified boot state is not locked."); return false; result = false; } else { dumpData.mVerifiedBootStateLocked = true; } } catch (IllegalStateException e) { debugVerboseLog("VerifiedBootLocked is not set.", e); return false; result = false; } // Patch level integer YYYYMM is expected to be within 1 year of today. if (!isValidPatchLevel(attestationAttributes.getKeyOsPatchLevel())) { debugVerboseLog("OS patch level is not within valid range."); return false; result = false; } else { dumpData.mOsPatchLevelInRange = true; } // Patch level integer YYYYMMDD is expected to be within 1 year of today. if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) { debugVerboseLog("Boot patch level is not within valid range."); return false; result = false; } else { dumpData.mKeyBootPatchLevelInRange = true; } if (!isValidPatchLevel(attestationAttributes.getKeyVendorPatchLevel())) { debugVerboseLog("Vendor patch level is not within valid range."); return false; result = false; } else { dumpData.mKeyVendorPatchLevelInRange = true; } if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) { debugVerboseLog("Boot patch level is not within valid range."); return false; result = false; } else { dumpData.mKeyBootPatchLevelInRange = true; } return true; return result; } private boolean checkPublicKey( Loading Loading @@ -609,4 +675,99 @@ class AttestationVerificationPeerDeviceVerifier { Slog.v(TAG, str); } } /* Mutable data class for tracking dump data from verifications. */ private static class MyDumpData extends AttestationVerificationManagerService.DumpData { // Top-Level Result int mResult = -1; // Configuration/Setup preconditions boolean mCertificationFactoryAvailable = false; boolean mCertPathValidatorAvailable = false; // AttestationParameters (Valid Input Only) boolean mAttestationParametersOk = false; // Certificate Chain (Structure & Chaining Conditions) boolean mCertChainOk = false; // Binding boolean mBindingOk = false; int mBindingType = -1; // System Ownership boolean mSystemOwnershipChecked = false; boolean mSystemOwned = false; // Android Keystore attestation properties boolean mOsVersionAtLeast10 = false; boolean mKeyHwBacked = false; boolean mAttestationVersionAtLeast3 = false; boolean mKeymasterVersionAtLeast4 = false; boolean mKeymasterHwBacked = false; boolean mBootStateIsVerified = false; boolean mVerifiedBootStateLocked = false; boolean mOsPatchLevelInRange = false; boolean mKeyBootPatchLevelInRange = false; boolean mKeyVendorPatchLevelInRange = false; @SuppressLint("WrongConstant") @Override public void dumpTo(IndentingPrintWriter writer) { writer.println( "Result: " + AttestationVerificationManager.verificationResultCodeToString( mResult)); if (!mCertificationFactoryAvailable) { writer.println("Certificate Factory Unavailable"); return; } if (!mCertPathValidatorAvailable) { writer.println("Cert Path Validator Unavailable"); return; } if (!mAttestationParametersOk) { writer.println("Attestation parameters set incorrectly."); return; } writer.println("Certificate Chain Valid (inc. Trust Anchor): " + booleanToOkFail( mCertChainOk)); if (!mCertChainOk) { return; } // Binding writer.println("Local Binding: " + booleanToOkFail(mBindingOk)); writer.increaseIndent(); writer.println("Binding Type: " + mBindingType); writer.decreaseIndent(); if (mSystemOwnershipChecked) { writer.println("System Ownership: " + booleanToOkFail(mSystemOwned)); } // Keystore Attestation params writer.println("KeyStore Attestation Parameters"); writer.increaseIndent(); writer.println("OS Version >= 10: " + booleanToOkFail(mOsVersionAtLeast10)); writer.println("OS Patch Level in Range: " + booleanToOkFail(mOsPatchLevelInRange)); writer.println( "Attestation Version >= 3: " + booleanToOkFail(mAttestationVersionAtLeast3)); writer.println("Keymaster Version >= 4: " + booleanToOkFail(mKeymasterVersionAtLeast4)); writer.println("Keymaster HW-Backed: " + booleanToOkFail(mKeymasterHwBacked)); writer.println("Key is HW Backed: " + booleanToOkFail(mKeyHwBacked)); writer.println("Boot State is VERIFIED: " + booleanToOkFail(mBootStateIsVerified)); writer.println("Verified Boot is LOCKED: " + booleanToOkFail(mVerifiedBootStateLocked)); writer.println( "Key Boot Level in Range: " + booleanToOkFail(mKeyBootPatchLevelInRange)); writer.println("Key Vendor Patch Level in Range: " + booleanToOkFail( mKeyVendorPatchLevelInRange)); writer.decreaseIndent(); } private String booleanToOkFail(boolean value) { return value ? "OK" : "FAILURE"; } } }
tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt +90 −41 File changed.Preview size limit exceeded, changes collapsed. Show changes