diff options
author | 2024-09-19 18:08:35 +0000 | |
---|---|---|
committer | 2024-09-27 22:12:28 +0000 | |
commit | e855f08496f5d3efc96df3ce51d31f8fc7344274 (patch) | |
tree | b46e175e06c8e48c3d8bf5600d86833a05cd0727 | |
parent | 21e18dc45c669cf92b90966d36a1ce8ff1c30bd3 (diff) |
[AVF] Expose patch level check failure code
Bug: 359629878
Test: Unit test
Flag: EXEMPT bugfix
Ignore-AOSP-First: This hasn't been tested e2e by CDM and XR yet.
Releasing this to AOSP directly may not be appropriate.
Change-Id: I660689e61a235017f2e969ceb38672ef32b62d10
10 files changed, 294 insertions, 385 deletions
diff --git a/core/java/android/security/attestationverification/AttestationVerificationManager.java b/core/java/android/security/attestationverification/AttestationVerificationManager.java index acf33822b3c7..ca4d417ad8fe 100644 --- a/core/java/android/security/attestationverification/AttestationVerificationManager.java +++ b/core/java/android/security/attestationverification/AttestationVerificationManager.java @@ -79,9 +79,7 @@ public class AttestationVerificationManager { * is also associated with a particular connection. * * <p>The {@code callback} is called with a result and {@link VerificationToken} (which may be - * null). The result is an integer (see constants in this class with the prefix {@code RESULT_}. - * The result is {@link #RESULT_SUCCESS} when at least one verifier has passed its checks. The - * token may be used in calls to other parts of the system. + * null). The result is an integer (see constants in {@link VerificationResultFlags}). * * <p>It's expected that a verifier will be able to decode and understand the passed values, * otherwise fail to verify. {@code attestation} should contain some type data to prevent parse @@ -108,7 +106,7 @@ public class AttestationVerificationManager { @NonNull Bundle requirements, @NonNull byte[] attestation, @NonNull @CallbackExecutor Executor executor, - @NonNull BiConsumer<@VerificationResult Integer, VerificationToken> callback) { + @NonNull BiConsumer<@VerificationResultFlags Integer, VerificationToken> callback) { try { AndroidFuture<IVerificationResult> resultCallback = new AndroidFuture<>(); resultCallback.thenAccept(result -> { @@ -155,7 +153,7 @@ public class AttestationVerificationManager { */ @RequiresPermission(Manifest.permission.USE_ATTESTATION_VERIFICATION_SERVICE) @CheckResult - @VerificationResult + @VerificationResultFlags public int verifyToken( @NonNull AttestationProfile profile, @LocalBindingType int localBindingType, @@ -280,30 +278,66 @@ public class AttestationVerificationManager { */ public static final int TYPE_CHALLENGE = 3; - /** @hide */ - @IntDef( - prefix = {"RESULT_"}, - value = { - RESULT_UNKNOWN, - RESULT_SUCCESS, - RESULT_FAILURE, - }) - @Retention(RetentionPolicy.SOURCE) + /** + * Verification result returned from {@link #verifyAttestation}. + * + * A value of {@code 0} indicates success. Otherwise, a bit flag is set from first failing stage + * below: + * <ol> + * <li> The received attestation's integrity (e.g. the certificate signatures) is validated. + * If this fails, {@link #FLAG_FAILURE_CERTS} will be returned with all other bits unset. + * <li> The local binding requirements are checked. If this fails, + * {@link #FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS} is returned with all other bits unset. + * <li> The profile requirements are checked. If this fails, a bit flag will be returned with + * some of the these bits set to indicate the type of profile requirement failure: + * {@link #FLAG_FAILURE_UNSUPPORTED_PROFILE}, {@link #FLAG_FAILURE_KEYSTORE_REQUIREMENTS}, + * {@link #FLAG_FAILURE_BOOT_STATE}, and {@link #FLAG_FAILURE_PATCH_LEVEL_DIFF}. + * </ol> + * + * Note: The reason of the failure must be not be provided to the remote device. + * + * @hide + */ + @IntDef(flag = true, prefix = {"FLAG_FAILURE_"}, + value = { + FLAG_FAILURE_UNKNOWN, + FLAG_FAILURE_UNSUPPORTED_PROFILE, + FLAG_FAILURE_CERTS, + FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS, + FLAG_FAILURE_KEYSTORE_REQUIREMENTS, + FLAG_FAILURE_BOOT_STATE, + FLAG_FAILURE_PATCH_LEVEL_DIFF, + }) @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) - public @interface VerificationResult { - } + @Retention(RetentionPolicy.SOURCE) + public @interface VerificationResultFlags{} - /** The result of the verification is unknown because it has a value unknown to this SDK. */ - public static final int RESULT_UNKNOWN = 0; + /** Flag: If there are unknown failures e.g. runtime exception. 0 = no, 1 = yes. */ + public static final int FLAG_FAILURE_UNKNOWN = 1; - /** The result of the verification was successful. */ - public static final int RESULT_SUCCESS = 1; + /** Flag: If the AVF profile is supported. 0 = supported, 1 = not supported */ + public static final int FLAG_FAILURE_UNSUPPORTED_PROFILE = 1 << 1; /** - * The result of the attestation verification was failure. The attestation could not be - * verified. + * Flag: Result bit for certs verification e.g. loading, generating, parsing certs. + * 0 = success, 1 = failure */ - public static final int RESULT_FAILURE = 2; + public static final int FLAG_FAILURE_CERTS = 1 << 2; + + /** Flag: Result bit for local binding requirements verification. 0 = success, 1 = failure. */ + public static final int FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS = 1 << 3; + + /** + * Flag: Result bit for KeyStore requirements verification. + * 0 = success, 1 = failure. + */ + public static final int FLAG_FAILURE_KEYSTORE_REQUIREMENTS = 1 << 4; + + /** Flag: Result bit for boot state verification. 0 = success, 1 = failure */ + public static final int FLAG_FAILURE_BOOT_STATE = 1 << 5; + + /** Flag: Result bit for patch level diff checks. 0 = success, 1 = failure. */ + public static final int FLAG_FAILURE_PATCH_LEVEL_DIFF = 1 << 6; /** * Requirements bundle parameter key for a public key, a byte array. @@ -351,26 +385,4 @@ public class AttestationVerificationManager { } return text + "(" + localBindingType + ")"; } - - /** @hide */ - public static String verificationResultCodeToString(@VerificationResult int resultCode) { - final String text; - switch (resultCode) { - case RESULT_UNKNOWN: - text = "UNKNOWN"; - break; - - case RESULT_SUCCESS: - text = "SUCCESS"; - break; - - case RESULT_FAILURE: - text = "FAILURE"; - break; - - default: - return Integer.toString(resultCode); - } - return text + "(" + resultCode + ")"; - } } diff --git a/core/java/android/security/attestationverification/AttestationVerificationService.java b/core/java/android/security/attestationverification/AttestationVerificationService.java index 26c3051f7f38..01668d729282 100644 --- a/core/java/android/security/attestationverification/AttestationVerificationService.java +++ b/core/java/android/security/attestationverification/AttestationVerificationService.java @@ -20,7 +20,7 @@ import android.annotation.CheckResult; import android.annotation.NonNull; import android.app.Service; import android.os.Bundle; -import android.security.attestationverification.AttestationVerificationManager.VerificationResult; +import android.security.attestationverification.AttestationVerificationManager.VerificationResultFlags; /** * A verifier which can be implemented by apps to verify an attestation (as described in {@link @@ -93,7 +93,7 @@ public abstract class AttestationVerificationService extends Service { * byte[], java.util.concurrent.Executor, java.util.function.BiConsumer) */ @CheckResult - @VerificationResult + @VerificationResultFlags public abstract int onVerifyPeerDeviceAttestation( @NonNull Bundle requirements, @NonNull byte[] attestation); diff --git a/core/java/android/security/attestationverification/VerificationToken.java b/core/java/android/security/attestationverification/VerificationToken.java index ae26823a9c7d..015010c52755 100644 --- a/core/java/android/security/attestationverification/VerificationToken.java +++ b/core/java/android/security/attestationverification/VerificationToken.java @@ -21,7 +21,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.Parcelable; import android.security.attestationverification.AttestationVerificationManager.LocalBindingType; -import android.security.attestationverification.AttestationVerificationManager.VerificationResult; +import android.security.attestationverification.AttestationVerificationManager.VerificationResultFlags; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; @@ -41,10 +41,6 @@ import java.util.function.BiConsumer; * @hide * @see Bundle#putParcelable(String, Parcelable) */ -@DataClass( - genConstructor = false, - genHiddenBuilder = true -) public final class VerificationToken implements Parcelable { /** @@ -69,17 +65,18 @@ public final class VerificationToken implements Parcelable { private final Bundle mRequirements; /** - * The result of the {@link AttestationVerificationManager#verifyAttestation(int, int, Bundle, - * byte[], Executor, BiConsumer)} call. This value is kept hidden to prevent token holders from - * accidentally reading this value without calling {@code verifyToken}. Do <b>not</b> use this - * value directly; call {@link AttestationVerificationManager#verifyToken(VerificationToken, - * Duration)} to verify a valid token and it will return this value. + * The result of the {@link AttestationVerificationManager#verifyAttestation(AttestationProfile, + * int, Bundle, byte[], Executor, BiConsumer)} call. This value is kept hidden to prevent token + * holders from accidentally reading this value without calling {@code verifyToken}. Do + * <b>not</b> use this value directly; call {@link AttestationVerificationManager#verifyToken( + * AttestationProfile, int, Bundle, VerificationToken, Duration)} to verify a valid token and it + * will return this value. * * If the token is valid, this value is returned directly by {#verifyToken}. * * @hide */ - @VerificationResult + @VerificationResultFlags private final int mVerificationResult; /** @@ -111,28 +108,13 @@ public final class VerificationToken implements Parcelable { private int mUid; - // Code below generated by codegen v1.0.23. - // - // DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // - // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/security/attestationverification/VerificationToken.java - // - // To exclude the generated code from IntelliJ auto-formatting enable (one-time): - // Settings > Editor > Code Style > Formatter Control - //@formatter:off - - - @DataClass.Generated.Member - /* package-private */ VerificationToken( + VerificationToken( @NonNull AttestationProfile attestationProfile, @LocalBindingType int localBindingType, @NonNull Bundle requirements, - @VerificationResult int verificationResult, + @VerificationResultFlags int verificationResult, @NonNull java.time.Instant verificationTime, - @NonNull byte[] hmac, - int uid) { + @NonNull byte[] hmac) { this.mAttestationProfile = attestationProfile; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mAttestationProfile); @@ -144,62 +126,61 @@ public final class VerificationToken implements Parcelable { NonNull.class, null, mRequirements); this.mVerificationResult = verificationResult; com.android.internal.util.AnnotationValidations.validate( - VerificationResult.class, null, mVerificationResult); + VerificationResultFlags.class, null, mVerificationResult); this.mVerificationTime = verificationTime; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mVerificationTime); this.mHmac = hmac; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mHmac); - this.mUid = uid; - - // onConstructed(); // You can define this method to get a callback } /** * The attestation profile which was used to perform the verification. + * + * @hide */ - @DataClass.Generated.Member public @NonNull AttestationProfile getAttestationProfile() { return mAttestationProfile; } /** * The local binding type of the local binding data used to perform the verification. + * + * @hide */ - @DataClass.Generated.Member public @LocalBindingType int getLocalBindingType() { return mLocalBindingType; } /** * The requirements used to perform the verification. + * + * @hide */ - @DataClass.Generated.Member public @NonNull Bundle getRequirements() { return mRequirements; } /** - * The result of the {@link AttestationVerificationManager#verifyAttestation(int, int, Bundle, - * byte[], Executor, BiConsumer)} call. This value is kept hidden to prevent token holders from - * accidentally reading this value without calling {@code verifyToken}. Do <b>not</b> use this - * value directly; call {@link AttestationVerificationManager#verifyToken(VerificationToken, - * Duration)} to verify a valid token and it will return this value. + * The result of the {@link AttestationVerificationManager#verifyAttestation(AttestationProfile, + * int, Bundle, byte[], Executor, BiConsumer)} call. This value is kept hidden to prevent token + * holders from accidentally reading this value without calling {@code verifyToken}. Do + * <b>not</b> use this value directly; call {@link AttestationVerificationManager#verifyToken( + * AttestationProfile, int, Bundle, VerificationToken, Duration)} to verify a valid token and it + * will return this value. * * If the token is valid, this value is returned directly by {#verifyToken}. * * @hide */ - @DataClass.Generated.Member - public @VerificationResult int getVerificationResult() { + public @VerificationResultFlags int getVerificationResult() { return mVerificationResult; } /** * Time when the token was generated, set by the system. */ - @DataClass.Generated.Member public @NonNull java.time.Instant getVerificationTime() { return mVerificationTime; } @@ -212,25 +193,10 @@ public final class VerificationToken implements Parcelable { * * @hide */ - @DataClass.Generated.Member public @NonNull byte[] getHmac() { return mHmac; } - /** - * The UID of the process which called {@code verifyAttestation} to create the token, as - * returned by {@link Binder#getCallingUid()}. Calls to {@code verifyToken} will fail if the UID - * of calling process does not match this value. This ensures that tokens cannot be shared - * between UIDs. - * - * @hide - */ - @DataClass.Generated.Member - public int getUid() { - return mUid; - } - - @DataClass.Generated.Member static Parcelling<java.time.Instant> sParcellingForVerificationTime = Parcelling.Cache.get( ForInstant.class); @@ -242,7 +208,6 @@ public final class VerificationToken implements Parcelable { } @Override - @DataClass.Generated.Member public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } @@ -253,27 +218,24 @@ public final class VerificationToken implements Parcelable { dest.writeInt(mVerificationResult); sParcellingForVerificationTime.parcel(mVerificationTime, dest, flags); dest.writeByteArray(mHmac); - dest.writeInt(mUid); } @Override - @DataClass.Generated.Member public int describeContents() { return 0; } /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) - @DataClass.Generated.Member - /* package-private */ VerificationToken(@NonNull android.os.Parcel in) { + VerificationToken(@NonNull android.os.Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } - AttestationProfile attestationProfile = (AttestationProfile) in.readTypedObject(AttestationProfile.CREATOR); + AttestationProfile attestationProfile = (AttestationProfile) in.readTypedObject( + AttestationProfile.CREATOR); int localBindingType = in.readInt(); Bundle requirements = in.readBundle(); int verificationResult = in.readInt(); java.time.Instant verificationTime = sParcellingForVerificationTime.unparcel(in); byte[] hmac = in.createByteArray(); - int uid = in.readInt(); this.mAttestationProfile = attestationProfile; com.android.internal.util.AnnotationValidations.validate( @@ -286,19 +248,15 @@ public final class VerificationToken implements Parcelable { NonNull.class, null, mRequirements); this.mVerificationResult = verificationResult; com.android.internal.util.AnnotationValidations.validate( - VerificationResult.class, null, mVerificationResult); + VerificationResultFlags.class, null, mVerificationResult); this.mVerificationTime = verificationTime; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mVerificationTime); this.mHmac = hmac; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mHmac); - this.mUid = uid; - - // onConstructed(); // You can define this method to get a callback } - @DataClass.Generated.Member public static final @NonNull Parcelable.Creator<VerificationToken> CREATOR = new Parcelable.Creator<VerificationToken>() { @Override @@ -317,16 +275,14 @@ public final class VerificationToken implements Parcelable { * @hide */ @SuppressWarnings("WeakerAccess") - @DataClass.Generated.Member public static final class Builder { private @NonNull AttestationProfile mAttestationProfile; private @LocalBindingType int mLocalBindingType; private @NonNull Bundle mRequirements; - private @VerificationResult int mVerificationResult; + private @VerificationResultFlags int mVerificationResult; private @NonNull java.time.Instant mVerificationTime; private @NonNull byte[] mHmac; - private int mUid; private long mBuilderFieldsSet = 0L; @@ -340,34 +296,29 @@ public final class VerificationToken implements Parcelable { * @param requirements * The requirements used to perform the verification. * @param verificationResult - * The result of the {@link AttestationVerificationManager#verifyAttestation(int, int, Bundle, - * byte[], Executor, BiConsumer)} call. This value is kept hidden to prevent token holders from - * accidentally reading this value without calling {@code verifyToken}. Do <b>not</b> use this - * value directly; call {@link AttestationVerificationManager#verifyToken(VerificationToken, - * Duration)} to verify a valid token and it will return this value. + * The result of the {@link AttestationVerificationManager#verifyAttestation( + * AttestationProfile, int, Bundle, byte[], Executor, BiConsumer)} call. This value is + * kept hidden to prevent token holders from accidentally reading this value without + * calling {@code verifyToken}. Do <b>not</b> use this value directly; call {@link + * AttestationVerificationManager#verifyToken(AttestationProfile, int, Bundle, + * VerificationToken, Duration)} to verify a valid token and it will return this value. * * If the token is valid, this value is returned directly by {#verifyToken}. * @param verificationTime * Time when the token was generated, set by the system. * @param hmac - * A Hash-based message authentication code used to verify the contents and authenticity of the - * rest of the token. The hash is created using a secret key known only to the system server. - * When verifying the token, the system re-hashes the token and verifies the generated HMAC is - * the same. - * @param uid - * The UID of the process which called {@code verifyAttestation} to create the token, as - * returned by {@link Binder#getCallingUid()}. Calls to {@code verifyToken} will fail if the UID - * of calling process does not match this value. This ensures that tokens cannot be shared - * between UIDs. + * A Hash-based message authentication code used to verify the contents and authenticity + * of the rest of the token. The hash is created using a secret key known only to the + * system server. When verifying the token, the system re-hashes the token and verifies + * the generated HMAC is the same. */ public Builder( @NonNull AttestationProfile attestationProfile, @LocalBindingType int localBindingType, @NonNull Bundle requirements, - @VerificationResult int verificationResult, + @VerificationResultFlags int verificationResult, @NonNull java.time.Instant verificationTime, - @NonNull byte[] hmac, - int uid) { + @NonNull byte[] hmac) { mAttestationProfile = attestationProfile; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mAttestationProfile); @@ -379,20 +330,20 @@ public final class VerificationToken implements Parcelable { NonNull.class, null, mRequirements); mVerificationResult = verificationResult; com.android.internal.util.AnnotationValidations.validate( - VerificationResult.class, null, mVerificationResult); + VerificationResultFlags.class, null, mVerificationResult); mVerificationTime = verificationTime; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mVerificationTime); mHmac = hmac; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mHmac); - mUid = uid; } /** * The attestation profile which was used to perform the verification. + * + * @hide */ - @DataClass.Generated.Member public @NonNull Builder setAttestationProfile(@NonNull AttestationProfile value) { checkNotUsed(); mBuilderFieldsSet |= 0x1; @@ -402,8 +353,9 @@ public final class VerificationToken implements Parcelable { /** * The local binding type of the local binding data used to perform the verification. + * + * @hide */ - @DataClass.Generated.Member public @NonNull Builder setLocalBindingType(@LocalBindingType int value) { checkNotUsed(); mBuilderFieldsSet |= 0x2; @@ -413,8 +365,9 @@ public final class VerificationToken implements Parcelable { /** * The requirements used to perform the verification. + * + * @hide */ - @DataClass.Generated.Member public @NonNull Builder setRequirements(@NonNull Bundle value) { checkNotUsed(); mBuilderFieldsSet |= 0x4; @@ -423,18 +376,18 @@ public final class VerificationToken implements Parcelable { } /** - * The result of the {@link AttestationVerificationManager#verifyAttestation(int, int, Bundle, - * byte[], Executor, BiConsumer)} call. This value is kept hidden to prevent token holders from - * accidentally reading this value without calling {@code verifyToken}. Do <b>not</b> use this - * value directly; call {@link AttestationVerificationManager#verifyToken(VerificationToken, - * Duration)} to verify a valid token and it will return this value. + * The result of the {@link AttestationVerificationManager#verifyAttestation( + * AttestationProfile, int, Bundle, byte[], Executor, BiConsumer)} call. This value is kept + * hidden to prevent token holders from accidentally reading this value without calling + * {@code verifyToken}. Do <b>not</b> use this value directly; call {@link + * AttestationVerificationManager#verifyToken(AttestationProfile, int, Bundle, + * VerificationToken, Duration)} to verify a valid token and it will return this value. * * If the token is valid, this value is returned directly by {#verifyToken}. * * @hide */ - @DataClass.Generated.Member - public @NonNull Builder setVerificationResult(@VerificationResult int value) { + public @NonNull Builder setVerificationResult(@VerificationResultFlags int value) { checkNotUsed(); mBuilderFieldsSet |= 0x8; mVerificationResult = value; @@ -444,7 +397,6 @@ public final class VerificationToken implements Parcelable { /** * Time when the token was generated, set by the system. */ - @DataClass.Generated.Member public @NonNull Builder setVerificationTime(@NonNull java.time.Instant value) { checkNotUsed(); mBuilderFieldsSet |= 0x10; @@ -453,14 +405,13 @@ public final class VerificationToken implements Parcelable { } /** - * A Hash-based message authentication code used to verify the contents and authenticity of the - * rest of the token. The hash is created using a secret key known only to the system server. - * When verifying the token, the system re-hashes the token and verifies the generated HMAC is - * the same. + * A Hash-based message authentication code used to verify the contents and authenticity of + * the rest of the token. The hash is created using a secret key known only to the system + * server. When verifying the token, the system re-hashes the token and verifies the + * generated HMAC is the same. * * @hide */ - @DataClass.Generated.Member public @NonNull Builder setHmac(@NonNull byte... value) { checkNotUsed(); mBuilderFieldsSet |= 0x20; @@ -468,26 +419,10 @@ public final class VerificationToken implements Parcelable { return this; } - /** - * The UID of the process which called {@code verifyAttestation} to create the token, as - * returned by {@link Binder#getCallingUid()}. Calls to {@code verifyToken} will fail if the UID - * of calling process does not match this value. This ensures that tokens cannot be shared - * between UIDs. - * - * @hide - */ - @DataClass.Generated.Member - public @NonNull Builder setUid(int value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x40; - mUid = value; - return this; - } - /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull VerificationToken build() { checkNotUsed(); - mBuilderFieldsSet |= 0x80; // Mark builder used + mBuilderFieldsSet |= 0x40; // Mark builder used VerificationToken o = new VerificationToken( mAttestationProfile, @@ -495,29 +430,15 @@ public final class VerificationToken implements Parcelable { mRequirements, mVerificationResult, mVerificationTime, - mHmac, - mUid); + mHmac); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x80) != 0) { + if ((mBuilderFieldsSet & 0x40) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } } } - - @DataClass.Generated( - time = 1633629747234L, - codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/security/attestationverification/VerificationToken.java", - inputSignatures = "private final @android.annotation.NonNull android.security.attestationverification.AttestationProfile mAttestationProfile\nprivate final @android.security.attestationverification.AttestationVerificationManager.LocalBindingType int mLocalBindingType\nprivate final @android.annotation.NonNull android.os.Bundle mRequirements\nprivate final @android.security.attestationverification.AttestationVerificationManager.VerificationResult int mVerificationResult\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) java.time.Instant mVerificationTime\nprivate final @android.annotation.NonNull byte[] mHmac\nprivate int mUid\nclass VerificationToken extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genHiddenBuilder=true)") - @Deprecated - private void __metadata() {} - - - //@formatter:on - // End of generated code - } diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java index 71a182225013..dbeca82ade89 100644 --- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java +++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java @@ -16,8 +16,6 @@ package com.android.server.companion.securechannel; -import static android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS; - import android.annotation.NonNull; import android.content.Context; import android.os.Build; @@ -498,7 +496,7 @@ public class SecureChannel { private void exchangeAttestation() throws IOException, GeneralSecurityException, BadHandleException, CryptoException { - if (mVerificationResult == RESULT_SUCCESS) { + if (mVerificationResult == 0) { Slog.d(TAG, "Remote attestation was already verified."); return; } @@ -530,11 +528,11 @@ public class SecureChannel { sendMessage(MessageType.AVF_RESULT, verificationResult); byte[] remoteVerificationResult = readMessage(MessageType.AVF_RESULT); - if (ByteBuffer.wrap(remoteVerificationResult).getInt() != RESULT_SUCCESS) { + if (ByteBuffer.wrap(remoteVerificationResult).getInt() != 0) { throw new SecureChannelException("Remote device failed to verify local attestation."); } - if (mVerificationResult != RESULT_SUCCESS) { + if (mVerificationResult != 0) { throw new SecureChannelException("Failed to verify remote attestation."); } @@ -549,7 +547,7 @@ public class SecureChannel { return false; } // Is authenticated - return mPskVerified || mVerificationResult == RESULT_SUCCESS; + return mPskVerified || mVerificationResult == 0; } // First byte indicates message type; 0 = CLIENT INIT, 1 = SERVER INIT diff --git a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java index 55f85ea27c82..22a359bced86 100644 --- a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java +++ b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java @@ -17,10 +17,10 @@ package com.android.server.security; import static android.Manifest.permission.USE_ATTESTATION_VERIFICATION_SERVICE; +import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS; +import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_UNSUPPORTED_PROFILE; import static android.security.attestationverification.AttestationVerificationManager.PROFILE_PEER_DEVICE; import static android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED; -import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE; -import static android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN; import android.annotation.NonNull; import android.annotation.Nullable; @@ -88,8 +88,8 @@ public class AttestationVerificationManagerService extends SystemService { public void verifyToken(VerificationToken token, ParcelDuration parcelDuration, AndroidFuture resultCallback) throws RemoteException { enforceUsePermission(); - // TODO(b/201696614): Implement - resultCallback.complete(RESULT_UNKNOWN); + + throw new UnsupportedOperationException(); } private void enforceUsePermission() { @@ -123,9 +123,9 @@ public class AttestationVerificationManagerService extends SystemService { AttestationProfile profile, int localBindingType, Bundle requirements, byte[] attestation, AndroidFuture<IVerificationResult> resultCallback) { IVerificationResult result = new IVerificationResult(); - // TODO(b/201696614): Implement result.token = null; - switch (profile.getAttestationProfileId()) { + int profileId = profile.getAttestationProfileId(); + switch (profileId) { case PROFILE_SELF_TRUSTED: Slog.d(TAG, "Verifying Self Trusted profile."); try { @@ -133,7 +133,7 @@ public class AttestationVerificationManagerService extends SystemService { AttestationVerificationSelfTrustedVerifierForTesting.getInstance() .verifyAttestation(localBindingType, requirements, attestation); } catch (Throwable t) { - result.resultCode = RESULT_FAILURE; + result.resultCode = FLAG_FAILURE_CERTS; } break; case PROFILE_PEER_DEVICE: @@ -142,8 +142,8 @@ public class AttestationVerificationManagerService extends SystemService { localBindingType, requirements, attestation); break; default: - Slog.d(TAG, "No profile found, defaulting."); - result.resultCode = RESULT_UNKNOWN; + Slog.e(TAG, "Profile [" + profileId + "] is not supported."); + result.resultCode = FLAG_FAILURE_UNSUPPORTED_PROFILE; } resultCallback.complete(result); } diff --git a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java index 41e3d00f3924..dc1f93664f79 100644 --- a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java +++ b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java @@ -16,11 +16,15 @@ package com.android.server.security; +import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_BOOT_STATE; +import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_KEYSTORE_REQUIREMENTS; +import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS; +import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS; +import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_PATCH_LEVEL_DIFF; +import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_UNKNOWN; import static android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE; import static android.security.attestationverification.AttestationVerificationManager.PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS; import static android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY; -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 static android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY; import static android.security.attestationverification.AttestationVerificationManager.localBindingTypeToString; @@ -35,10 +39,8 @@ 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; @@ -100,7 +102,6 @@ import java.util.Set; */ class AttestationVerificationPeerDeviceVerifier { private static final String TAG = "AVF"; - private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE); private static final int MAX_PATCH_AGE_MONTHS = 12; /** @@ -188,32 +189,24 @@ class AttestationVerificationPeerDeviceVerifier { @NonNull byte[] attestation, @NonNull MyDumpData dumpData) { if (mCertificateFactory == null) { - debugVerboseLog("Unable to access CertificateFactory"); - return RESULT_FAILURE; + Slog.e(TAG, "Unable to access CertificateFactory"); + return FLAG_FAILURE_CERTS; } dumpData.mCertificationFactoryAvailable = true; if (mCertPathValidator == null) { - debugVerboseLog("Unable to access CertPathValidator"); - return RESULT_FAILURE; + Slog.e(TAG, "Unable to access CertPathValidator"); + return FLAG_FAILURE_CERTS; } 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; + // here to set result = 0. + int result = 0; try { - // First: parse and validate the certificate chain. + // 1. parse and validate the certificate chain. final List<X509Certificate> certificateChain = getCertificates(attestation); // (returns void, but throws CertificateException and other similar Exceptions) validateCertificateChain(certificateChain); @@ -222,33 +215,38 @@ class AttestationVerificationPeerDeviceVerifier { final var leafCertificate = certificateChain.get(0); final var attestationExtension = fromCertificate(leafCertificate); - // Second: verify if the attestation satisfies the "peer device" profile. - if (!checkAttestationForPeerDeviceProfile(requirements, attestationExtension, - dumpData)) { - failed = true; + // 2. Check if the provided local binding type is supported and if the provided + // requirements "match" the binding type. + if (!validateAttestationParameters(localBindingType, requirements)) { + return FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS; } + dumpData.mAttestationParametersOk = true; - // Third: check if the attestation satisfies local binding requirements. + // 3. check if the attestation satisfies local binding requirements. if (!checkLocalBindingRequirements( leafCertificate, attestationExtension, localBindingType, requirements, dumpData)) { - failed = true; + result |= FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS; } + + // 4. verify if the attestation satisfies the "peer device" profile. + result |= checkAttestationForPeerDeviceProfile(requirements, attestationExtension, + dumpData); } catch (CertificateException | CertPathValidatorException | InvalidAlgorithmParameterException | IOException e) { - // Catch all non-RuntimeExpceptions (all of these are thrown by either getCertificates() + // Catch all non-RuntimeExceptions (all of these are thrown by either getCertificates() // or validateCertificateChain() or // AndroidKeystoreAttestationVerificationAttributes.fromCertificate()) - debugVerboseLog("Unable to parse/validate Android Attestation certificate(s)", e); - failed = true; + Slog.e(TAG, "Unable to parse/validate Android Attestation certificate(s)", e); + result = FLAG_FAILURE_CERTS; } 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); - failed = true; + // Catch everything else (RuntimeExceptions), since we don't want to throw any + // exceptions out of this class/method. + Slog.e(TAG, "Unexpected error", e); + result = FLAG_FAILURE_UNKNOWN; } - return failed ? RESULT_FAILURE : RESULT_SUCCESS; + return result; } @NonNull @@ -270,22 +268,22 @@ class AttestationVerificationPeerDeviceVerifier { private boolean validateAttestationParameters( @LocalBindingType int localBindingType, @NonNull Bundle requirements) { if (localBindingType != TYPE_PUBLIC_KEY && localBindingType != TYPE_CHALLENGE) { - debugVerboseLog("Binding type is not supported: " + localBindingType); + Slog.e(TAG, "Binding type is not supported: " + localBindingType); return false; } if (requirements.size() < 1) { - debugVerboseLog("At least 1 requirement is required."); + Slog.e(TAG, "At least 1 requirement is required."); return false; } if (localBindingType == TYPE_PUBLIC_KEY && !requirements.containsKey(PARAM_PUBLIC_KEY)) { - debugVerboseLog("Requirements does not contain key: " + PARAM_PUBLIC_KEY); + Slog.e(TAG, "Requirements does not contain key: " + PARAM_PUBLIC_KEY); return false; } if (localBindingType == TYPE_CHALLENGE && !requirements.containsKey(PARAM_CHALLENGE)) { - debugVerboseLog("Requirements does not contain key: " + PARAM_CHALLENGE); + Slog.e(TAG, "Requirements does not contain key: " + PARAM_CHALLENGE); return false; } @@ -296,7 +294,7 @@ class AttestationVerificationPeerDeviceVerifier { throws CertificateException, CertPathValidatorException, InvalidAlgorithmParameterException { if (certificates.size() < 2) { - debugVerboseLog("Certificate chain less than 2 in size."); + Slog.e(TAG, "Certificate chain less than 2 in size."); throw new CertificateException("Certificate chain less than 2 in size."); } @@ -355,7 +353,7 @@ class AttestationVerificationPeerDeviceVerifier { final boolean publicKeyMatches = checkPublicKey( leafCertificate, requirements.getByteArray(PARAM_PUBLIC_KEY)); if (!publicKeyMatches) { - debugVerboseLog( + Slog.e(TAG, "Provided public key does not match leaf certificate public key."); return false; } @@ -366,7 +364,7 @@ class AttestationVerificationPeerDeviceVerifier { final boolean attestationChallengeMatches = checkAttestationChallenge( attestationAttributes, requirements.getByteArray(PARAM_CHALLENGE)); if (!attestationChallengeMatches) { - debugVerboseLog( + Slog.e(TAG, "Provided challenge does not match leaf certificate challenge."); return false; } @@ -386,7 +384,7 @@ class AttestationVerificationPeerDeviceVerifier { final boolean ownedBySystem = checkOwnedBySystem( leafCertificate, attestationAttributes); if (!ownedBySystem) { - debugVerboseLog("Certificate public key is not owned by the AndroidSystem."); + Slog.e(TAG, "Certificate public key is not owned by the AndroidSystem."); return false; } dumpData.mSystemOwned = true; @@ -401,67 +399,67 @@ class AttestationVerificationPeerDeviceVerifier { return true; } - private boolean checkAttestationForPeerDeviceProfile( + private int checkAttestationForPeerDeviceProfile( @NonNull Bundle requirements, @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes, MyDumpData dumpData) { - boolean result = true; + int result = 0; // Checks for support of Keymaster 4. if (attestationAttributes.getAttestationVersion() < 3) { - debugVerboseLog("Attestation version is not at least 3 (Keymaster 4)."); - result = false; + Slog.e(TAG, "Attestation version is not at least 3 (Keymaster 4)."); + result |= FLAG_FAILURE_KEYSTORE_REQUIREMENTS; } else { dumpData.mAttestationVersionAtLeast3 = true; } // Checks for support of Keymaster 4. if (attestationAttributes.getKeymasterVersion() < 4) { - debugVerboseLog("Keymaster version is not at least 4."); - result = false; + Slog.e(TAG, "Keymaster version is not at least 4."); + result |= FLAG_FAILURE_KEYSTORE_REQUIREMENTS; } else { dumpData.mKeymasterVersionAtLeast4 = true; } // First two characters are Android OS version. if (attestationAttributes.getKeyOsVersion() < 100000) { - debugVerboseLog("Android OS version is not 10+."); - result = false; + Slog.e(TAG, "Android OS version is not 10+."); + result |= FLAG_FAILURE_KEYSTORE_REQUIREMENTS; } else { dumpData.mOsVersionAtLeast10 = true; } if (!attestationAttributes.isAttestationHardwareBacked()) { - debugVerboseLog("Key is not HW backed."); - result = false; + Slog.e(TAG, "Key is not HW backed."); + result |= FLAG_FAILURE_KEYSTORE_REQUIREMENTS; } else { dumpData.mKeyHwBacked = true; } if (!attestationAttributes.isKeymasterHardwareBacked()) { - debugVerboseLog("Keymaster is not HW backed."); - result = false; + Slog.e(TAG, "Keymaster is not HW backed."); + result |= FLAG_FAILURE_KEYSTORE_REQUIREMENTS; } else { dumpData.mKeymasterHwBacked = true; } if (attestationAttributes.getVerifiedBootState() != VERIFIED) { - debugVerboseLog("Boot state not Verified."); - result = false; + Slog.e(TAG, "Boot state not Verified."); + result |= FLAG_FAILURE_BOOT_STATE; } else { dumpData.mBootStateIsVerified = true; } try { if (!attestationAttributes.isVerifiedBootLocked()) { - debugVerboseLog("Verified boot state is not locked."); - result = false; + Slog.e(TAG, "Verified boot state is not locked."); + result |= FLAG_FAILURE_BOOT_STATE; } else { dumpData.mVerifiedBootStateLocked = true; } } catch (IllegalStateException e) { - debugVerboseLog("VerifiedBootLocked is not set.", e); - result = false; + Slog.e(TAG, "VerifiedBootLocked is not set.", e); + result = FLAG_FAILURE_BOOT_STATE; } int maxPatchLevelDiffMonths = requirements.getInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS, @@ -470,8 +468,8 @@ class AttestationVerificationPeerDeviceVerifier { // Patch level integer YYYYMM is expected to be within maxPatchLevelDiffMonths of today. if (!isValidPatchLevel(attestationAttributes.getKeyOsPatchLevel(), maxPatchLevelDiffMonths)) { - debugVerboseLog("OS patch level is not within valid range."); - result = false; + Slog.e(TAG, "OS patch level is not within valid range."); + result |= FLAG_FAILURE_PATCH_LEVEL_DIFF; } else { dumpData.mOsPatchLevelInRange = true; } @@ -479,24 +477,24 @@ class AttestationVerificationPeerDeviceVerifier { // Patch level integer YYYYMMDD is expected to be within maxPatchLevelDiffMonths of today. if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel(), maxPatchLevelDiffMonths)) { - debugVerboseLog("Boot patch level is not within valid range."); - result = false; + Slog.e(TAG, "Boot patch level is not within valid range."); + result |= FLAG_FAILURE_PATCH_LEVEL_DIFF; } else { dumpData.mKeyBootPatchLevelInRange = true; } if (!isValidPatchLevel(attestationAttributes.getKeyVendorPatchLevel(), maxPatchLevelDiffMonths)) { - debugVerboseLog("Vendor patch level is not within valid range."); - result = false; + Slog.e(TAG, "Vendor patch level is not within valid range."); + result |= FLAG_FAILURE_PATCH_LEVEL_DIFF; } else { dumpData.mKeyVendorPatchLevelInRange = true; } if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel(), maxPatchLevelDiffMonths)) { - debugVerboseLog("Boot patch level is not within valid range."); - result = false; + Slog.e(TAG, "Boot patch level is not within valid range."); + result |= FLAG_FAILURE_PATCH_LEVEL_DIFF; } else { dumpData.mKeyBootPatchLevelInRange = true; } @@ -522,7 +520,7 @@ class AttestationVerificationPeerDeviceVerifier { final Set<String> ownerPackages = attestationAttributes.getApplicationPackageNameVersion().keySet(); if (!ANDROID_SYSTEM_PACKAGE_NAME_SET.equals(ownerPackages)) { - debugVerboseLog("Owner is not system, packages=" + ownerPackages); + Slog.e(TAG, "Owner is not system, packages=" + ownerPackages); return false; } @@ -548,7 +546,7 @@ class AttestationVerificationPeerDeviceVerifier { localPatchDate = LocalDate.parse(Build.VERSION.SECURITY_PATCH); } } catch (Throwable t) { - debugVerboseLog("Build.VERSION.SECURITY_PATCH: " + Slog.e(TAG, "Build.VERSION.SECURITY_PATCH: " + Build.VERSION.SECURITY_PATCH + " is not in format YYYY-MM-DD"); return false; } @@ -563,7 +561,7 @@ class AttestationVerificationPeerDeviceVerifier { // Convert remote patch dates to LocalDate. String remoteDeviceDateStr = String.valueOf(patchLevel); if (remoteDeviceDateStr.length() != 6 && remoteDeviceDateStr.length() != 8) { - debugVerboseLog("Patch level is not in format YYYYMM or YYYYMMDD"); + Slog.e(TAG, "Patch level is not in format YYYYMM or YYYYMMDD"); return false; } @@ -666,18 +664,6 @@ class AttestationVerificationPeerDeviceVerifier { } } - 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); - } - } - /* Mutable data class for tracking dump data from verifications. */ private static class MyDumpData extends AttestationVerificationManagerService.DumpData { @@ -717,9 +703,7 @@ class AttestationVerificationPeerDeviceVerifier { @SuppressLint("WrongConstant") @Override public void dumpTo(IndentingPrintWriter writer) { - writer.println( - "Result: " + AttestationVerificationManager.verificationResultCodeToString( - mResult)); + writer.println("Result: " + mResult); if (!mCertificationFactoryAvailable) { writer.println("Certificate Factory Unavailable"); return; diff --git a/services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java b/services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java index 58df2bd982dc..5039f6f42ac3 100644 --- a/services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java +++ b/services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java @@ -16,17 +16,15 @@ package com.android.server.security; +import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS; +import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS; 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; @@ -64,7 +62,6 @@ import java.util.Set; */ 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 = @@ -99,18 +96,6 @@ class 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"); @@ -142,23 +127,42 @@ class AttestationVerificationSelfTrustedVerifierForTesting { certificates.add((X509Certificate) mCertificateFactory.generateCertificate(bis)); } } catch (CertificateException e) { - debugVerboseLog("Unable to parse certificates from attestation", e); - return RESULT_FAILURE; + Slog.e("Unable to parse certificates from attestation", e.getLocalizedMessage()); + return FLAG_FAILURE_CERTS; } - if (localBindingType == TYPE_CHALLENGE - && validateRequirements(requirements) - && checkLeafChallenge(requirements, certificates) - && verifyCertificateChain(certificates)) { - return RESULT_SUCCESS; + int result = 0; + + if (localBindingType != TYPE_CHALLENGE + || !validateRequirements(requirements)) { + Slog.e(TAG, "Local binding requirements verification failure." + localBindingType); + return FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS; + } + + // Verify challenge + byte[] challenge; + try { + challenge = getChallengeFromCert(certificates.get(0)); + } catch (Throwable t) { + Slog.e("Unable to parse challenge from certificate.", t.getLocalizedMessage()); + result |= FLAG_FAILURE_CERTS; + return result; + } + if (!Arrays.equals(requirements.getByteArray(PARAM_CHALLENGE), challenge)) { + Slog.e(TAG, "Self-Trusted validation failed; challenge mismatch."); + result |= FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS; } - return RESULT_FAILURE; + if (!verifyCertificateChain(certificates)) { + result |= FLAG_FAILURE_CERTS; + } + + return result; } private boolean verifyCertificateChain(List<X509Certificate> certificates) { if (certificates.size() < 2) { - debugVerboseLog("Certificate chain less than 2 in size."); + Slog.e(TAG, "Certificate chain less than 2 in size."); return false; } @@ -170,7 +174,7 @@ class AttestationVerificationSelfTrustedVerifierForTesting { validationParams.setRevocationEnabled(false); mCertPathValidator.validate(certificatePath, validationParams); } catch (Throwable t) { - debugVerboseLog("Invalid certificate chain", t); + Slog.e(TAG, "Invalid certificate chain", t); return false; } @@ -183,34 +187,16 @@ class AttestationVerificationSelfTrustedVerifierForTesting { private boolean validateRequirements(Bundle requirements) { if (requirements.size() != 1) { - debugVerboseLog("Requirements does not contain exactly 1 key."); + Slog.e(TAG, "Requirements does not contain exactly 1 key."); return false; } if (!requirements.containsKey(PARAM_CHALLENGE)) { - debugVerboseLog("Requirements does not contain key: " + PARAM_CHALLENGE); + Slog.e(TAG, "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( diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt index ad95fbc36867..88ebf3edc7ed 100644 --- a/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt +++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt @@ -2,10 +2,11 @@ package android.security.attestationverification import android.app.Activity import android.os.Bundle +import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS +import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE import android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY import android.security.attestationverification.AttestationVerificationManager.PROFILE_PEER_DEVICE -import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY import android.security.attestationverification.AttestationVerificationManager.TYPE_UNKNOWN @@ -54,7 +55,7 @@ class PeerDeviceSystemAttestationVerificationTest { future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } @Test @@ -66,7 +67,7 @@ class PeerDeviceSystemAttestationVerificationTest { future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } @Test @@ -80,7 +81,7 @@ class PeerDeviceSystemAttestationVerificationTest { future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) val future2 = CompletableFuture<Int>() val challengeRequirements = Bundle() @@ -90,7 +91,7 @@ class PeerDeviceSystemAttestationVerificationTest { future2.complete(result) } - assertThat(future2.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future2.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } @Test @@ -104,7 +105,7 @@ class PeerDeviceSystemAttestationVerificationTest { future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } @Test @@ -118,7 +119,7 @@ class PeerDeviceSystemAttestationVerificationTest { future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_CERTS) } @Test @@ -131,7 +132,7 @@ class PeerDeviceSystemAttestationVerificationTest { invalidAttestationByteArray, activity.mainExecutor) { result, _ -> future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_CERTS) } private fun <T> CompletableFuture<T>.getSoon(): T { diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt index 8f06b4a2ea0a..e77364de8747 100644 --- a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt +++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt @@ -2,6 +2,9 @@ package android.security.attestationverification import android.os.Bundle import android.app.Activity +import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS +import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS +import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_UNSUPPORTED_PROFILE import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -14,9 +17,6 @@ import com.google.common.truth.Truth.assertThat import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE import android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED import android.security.attestationverification.AttestationVerificationManager.PROFILE_UNKNOWN -import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE -import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS -import android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE import android.security.keystore.KeyGenParameterSpec @@ -58,19 +58,19 @@ class SystemAttestationVerificationTest { future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_UNKNOWN) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_UNSUPPORTED_PROFILE) } @Test fun verifyAttestation_returnsFailureWithEmptyAttestation() { val future = CompletableFuture<Int>() - val profile = AttestationProfile(PROFILE_SELF_TRUSTED) - avm.verifyAttestation(profile, TYPE_CHALLENGE, Bundle(), ByteArray(0), - activity.mainExecutor) { result, _ -> + val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr") + avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType, + selfTrusted.requirements, ByteArray(0), activity.mainExecutor) { result, _ -> future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_CERTS) } @Test @@ -81,7 +81,7 @@ class SystemAttestationVerificationTest { Bundle(), selfTrusted.attestation, activity.mainExecutor) { result, _ -> future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } @Test @@ -92,7 +92,7 @@ class SystemAttestationVerificationTest { selfTrusted.requirements, selfTrusted.attestation, activity.mainExecutor) { result, _ -> future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } @Test @@ -106,7 +106,7 @@ class SystemAttestationVerificationTest { wrongKeyRequirements, selfTrusted.attestation, activity.mainExecutor) { result, _ -> future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } @Test @@ -119,7 +119,7 @@ class SystemAttestationVerificationTest { wrongChallengeRequirements, selfTrusted.attestation, activity.mainExecutor) { result, _ -> future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } // TODO(b/216144791): Add more failure tests for PROFILE_SELF_TRUSTED. @@ -131,20 +131,7 @@ class SystemAttestationVerificationTest { selfTrusted.requirements, selfTrusted.attestation, activity.mainExecutor) { result, _ -> future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_SUCCESS) - } - - @Test - fun verifyToken_returnsUnknown() { - val future = CompletableFuture<Int>() - val profile = AttestationProfile(PROFILE_SELF_TRUSTED) - avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), - activity.mainExecutor) { _, token -> - val result = avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), token, null) - future.complete(result) - } - - assertThat(future.getSoon()).isEqualTo(RESULT_UNKNOWN) + assertThat(future.getSoon()).isEqualTo(0) } @Test diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt index 4712d6b51bd5..4d1a1a55af74 100644 --- a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt +++ b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt @@ -3,11 +3,12 @@ package com.android.server.security import android.app.Activity import android.content.Context import android.os.Bundle +import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS +import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS +import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_PATCH_LEVEL_DIFF import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE import android.security.attestationverification.AttestationVerificationManager.PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS import android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY -import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE -import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY import android.util.IndentingPrintWriter @@ -72,7 +73,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_CHALLENGE, challengeRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_SUCCESS) + assertThat(result).isEqualTo(0) } @Test @@ -88,7 +89,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_CHALLENGE, challengeRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_SUCCESS) + assertThat(result).isEqualTo(0) } @Test @@ -108,7 +109,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_PUBLIC_KEY, pkRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_SUCCESS) + assertThat(result).isEqualTo(0) } @Test @@ -126,7 +127,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_SUCCESS) + assertThat(result).isEqualTo(0) } @Test @@ -143,7 +144,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_CHALLENGE, challengeRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_FAILURE) + assertThat(result).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } @Test @@ -159,7 +160,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_CHALLENGE, challengeRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_FAILURE) + assertThat(result).isEqualTo(FLAG_FAILURE_PATCH_LEVEL_DIFF) } @Test @@ -176,7 +177,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_CHALLENGE, challengeRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_SUCCESS) + assertThat(result).isEqualTo(0) } @Test @@ -191,10 +192,28 @@ class AttestationVerificationPeerDeviceVerifierTest { val result = verifier.verifyAttestation( TYPE_CHALLENGE, challengeRequirements, - // The patch date of this file is early 2022 TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_FAILURE) + assertThat(result).isEqualTo(FLAG_FAILURE_PATCH_LEVEL_DIFF) + } + + @Test + fun verifyAttestation_returnsFailureOwnedBySystemAndPatchDataNotWithinMaxPatchDiff() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, dumpLogger, trustAnchors, false, LocalDate.of(2024, 10, 1), + LocalDate.of(2024, 9, 1) + ) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) + challengeRequirements.putBoolean("android.key_owned_by_system", true) + challengeRequirements.putInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS, 24) + + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) + // Both "owned by system" and "patch level diff" checks should fail. + assertThat(result).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS or FLAG_FAILURE_PATCH_LEVEL_DIFF) } @Test @@ -210,7 +229,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_CHALLENGE, challengeRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_FAILURE) + assertThat(result).isEqualTo(FLAG_FAILURE_CERTS) } @Test @@ -232,7 +251,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_CHALLENGE, challengeRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_FAILURE) + assertThat(result).isEqualTo(FLAG_FAILURE_CERTS) } fun verifyAttestation_returnsFailureChallenge() { @@ -247,7 +266,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_CHALLENGE, challengeRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_FAILURE) + assertThat(result).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } private fun String.fromPEMFileToCerts(): Collection<Certificate> { @@ -281,6 +300,7 @@ class AttestationVerificationPeerDeviceVerifierTest { companion object { private const val TAG = "AVFTest" private const val TEST_ROOT_CERT_FILENAME = "test_root_certs.pem" + // Local patch date is 20220105 private const val TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME = "test_attestation_with_root_certs.pem" private const val TEST_ATTESTATION_CERT_FILENAME = "test_attestation_wrong_root_certs.pem" |