diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 05e2e8787d0d5..85df6208c9f39 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3842,6 +3842,7 @@ public abstract class Context { UWB_SERVICE, MEDIA_METRICS_SERVICE, SUPPLEMENTAL_PROCESS_SERVICE, + //@hide: ATTESTATION_VERIFICATION_SERVICE, //@hide: SAFETY_CENTER_SERVICE, }) @Retention(RetentionPolicy.SOURCE) @@ -5738,6 +5739,15 @@ public abstract class Context { */ public static final String INCREMENTAL_SERVICE = "incremental"; + /** + * Use with {@link #getSystemService(String)} to retrieve an + * {@link android.security.attestationverification.AttestationVerificationManager}. + * @see #getSystemService(String) + * @see android.security.attestationverification.AttestationVerificationManager + * @hide + */ + public static final String ATTESTATION_VERIFICATION_SERVICE = "attestation_verification"; + /** * Use with {@link #getSystemService(String)} to retrieve an * {@link android.security.FileIntegrityManager}. diff --git a/core/java/android/security/attestationverification/AttestationProfile.aidl b/core/java/android/security/attestationverification/AttestationProfile.aidl new file mode 100644 index 0000000000000..51696a94cd88c --- /dev/null +++ b/core/java/android/security/attestationverification/AttestationProfile.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.attestationverification; + +/** + * {@hide} + */ +parcelable AttestationProfile; diff --git a/core/java/android/security/attestationverification/AttestationProfile.java b/core/java/android/security/attestationverification/AttestationProfile.java new file mode 100644 index 0000000000000..7a43dac87fc52 --- /dev/null +++ b/core/java/android/security/attestationverification/AttestationProfile.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.attestationverification; + +import static android.security.attestationverification.AttestationVerificationManager.PROFILE_APP_DEFINED; +import static android.security.attestationverification.AttestationVerificationManager.PROFILE_UNKNOWN; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcelable; +import android.security.attestationverification.AttestationVerificationManager.AttestationProfileId; +import android.util.Log; + +import com.android.internal.util.DataClass; + + +/** + * An attestation profile defining the security requirements for verifying the attestation of a + * remote compute environment. + * + *

This class is immutable and thread-safe. When checking this profile against an expected + * profile, it is recommended to construct the expected profile and compare them with {@code + * equals()}. + * + * @hide + * @see AttestationVerificationManager + */ +@DataClass( + genConstructor = false, + genEqualsHashCode = true +) +public final class AttestationProfile implements Parcelable { + + private static final String TAG = "AVF"; + + /** + * The ID of a system-defined attestation profile. + * + * See constants in {@link AttestationVerificationManager} prefixed with {@code PROFILE_}. If + * this has the value of {@link AttestationVerificationManager#PROFILE_APP_DEFINED}, then the + * packageName and profileName are non-null. + */ + @AttestationProfileId + private final int mAttestationProfileId; + + /** + * The package name of a app-defined attestation profile. + * + * This value will be null unless the value of attestationProfileId is {@link + * AttestationVerificationManager#PROFILE_APP_DEFINED}. + */ + @Nullable + private final String mPackageName; + + + /** + * The name of an app-defined attestation profile. + * + * This value will be null unless the value of attestationProfileId is {@link + * AttestationVerificationManager#PROFILE_APP_DEFINED}. + */ + @Nullable + private final String mProfileName; + + private AttestationProfile( + @AttestationProfileId int attestationProfileId, + @Nullable String packageName, + @Nullable String profileName) { + mAttestationProfileId = attestationProfileId; + mPackageName = packageName; + mProfileName = profileName; + } + + /** + * Create a profile with the given id. + * + *

This constructor is for specifying a profile which is defined by the system. These are + * available as constants in the {@link AttestationVerificationManager} class prefixed with + * {@code PROFILE_}. + * + * @param attestationProfileId the ID of the system-defined profile + * @throws IllegalArgumentException when called with + * {@link AttestationVerificationManager#PROFILE_APP_DEFINED} + * (use {@link #AttestationProfile(String, String)}) + */ + public AttestationProfile(@AttestationProfileId int attestationProfileId) { + this(attestationProfileId, null, null); + if (attestationProfileId == PROFILE_APP_DEFINED) { + throw new IllegalArgumentException("App-defined profiles must be specified with the " + + "constructor AttestationProfile#constructor(String, String)"); + } + } + + /** + * Create a profile with the given package name and profile name. + * + *

This constructor is for specifying a profile defined by an app. The packageName must + * match the package name of the app that defines the profile (as specified in the {@code + * package} attribute of the {@code + * } tag in the app's manifest. The profile name matches the {@code name} attribute + * of the {@code } tag. + * + *

Apps must declare profiles in their manifest as an {@code } element. + * However, this constructor does not verify that such a profile exists. If the profile does not + * exist, verifications will fail. + * + * @param packageName the package name of the app defining the profile + * @param profileName the name of the profile + */ + public AttestationProfile(@NonNull String packageName, @NonNull String profileName) { + this(PROFILE_APP_DEFINED, packageName, profileName); + if (packageName == null || profileName == null) { + throw new IllegalArgumentException("Both packageName and profileName must be non-null"); + } + } + + @Override + public String toString() { + if (mAttestationProfileId == PROFILE_APP_DEFINED) { + return "AttestationProfile(package=" + mPackageName + ", name=" + mProfileName + ")"; + } else { + String humanReadableProfileId; + switch (mAttestationProfileId) { + case PROFILE_UNKNOWN: + humanReadableProfileId = "PROFILE_UNKNOWN"; + break; + default: + Log.e(TAG, "ERROR: Missing case in AttestationProfile#toString"); + humanReadableProfileId = "ERROR"; + } + return "AttestationProfile(" + humanReadableProfileId + "/" + mAttestationProfileId + + ")"; + } + } + + + // 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/AttestationProfile.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * The ID of a system-defined attestation profile. + * + * See constants in {@link AttestationVerificationManager} prefixed with {@code PROFILE_}. If + * this has the value of {@link AttestationVerificationManager#PROFILE_APP_DEFINED}, then the + * packageName and profileName are non-null. + */ + @DataClass.Generated.Member + public @AttestationProfileId int getAttestationProfileId() { + return mAttestationProfileId; + } + + /** + * The package name of a app-defined attestation profile. + * + * This value will be null unless the value of attestationProfileId is {@link + * AttestationVerificationManager#PROFILE_APP_DEFINED}. + */ + @DataClass.Generated.Member + public @Nullable String getPackageName() { + return mPackageName; + } + + /** + * The name of an app-defined attestation profile. + * + * This value will be null unless the value of attestationProfileId is {@link + * AttestationVerificationManager#PROFILE_APP_DEFINED}. + */ + @DataClass.Generated.Member + public @Nullable String getProfileName() { + return mProfileName; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(AttestationProfile other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + AttestationProfile that = (AttestationProfile) o; + //noinspection PointlessBooleanExpression + return true + && mAttestationProfileId == that.mAttestationProfileId + && java.util.Objects.equals(mPackageName, that.mPackageName) + && java.util.Objects.equals(mProfileName, that.mProfileName); + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + mAttestationProfileId; + _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName); + _hash = 31 * _hash + java.util.Objects.hashCode(mProfileName); + return _hash; + } + + @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) { ... } + + byte flg = 0; + if (mPackageName != null) flg |= 0x2; + if (mProfileName != null) flg |= 0x4; + dest.writeByte(flg); + dest.writeInt(mAttestationProfileId); + if (mPackageName != null) dest.writeString(mPackageName); + if (mProfileName != null) dest.writeString(mProfileName); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ AttestationProfile(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + int attestationProfileId = in.readInt(); + String packageName = (flg & 0x2) == 0 ? null : in.readString(); + String profileName = (flg & 0x4) == 0 ? null : in.readString(); + + this.mAttestationProfileId = attestationProfileId; + com.android.internal.util.AnnotationValidations.validate( + AttestationProfileId.class, null, mAttestationProfileId); + this.mPackageName = packageName; + this.mProfileName = profileName; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public AttestationProfile[] newArray(int size) { + return new AttestationProfile[size]; + } + + @Override + public AttestationProfile createFromParcel(@NonNull android.os.Parcel in) { + return new AttestationProfile(in); + } + }; + + @DataClass.Generated( + time = 1633629498403L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/security/attestationverification/AttestationProfile.java", + inputSignatures = "private static final java.lang.String TAG\nprivate final @android.security.attestationverification.AttestationVerificationManager.AttestationProfileId int mAttestationProfileId\nprivate final @android.annotation.Nullable java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mProfileName\npublic @java.lang.Override java.lang.String toString()\nclass AttestationProfile extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genEqualsHashCode=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/security/attestationverification/AttestationVerificationManager.java b/core/java/android/security/attestationverification/AttestationVerificationManager.java new file mode 100644 index 0000000000000..8ed5b20b085ee --- /dev/null +++ b/core/java/android/security/attestationverification/AttestationVerificationManager.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.attestationverification; + +import android.Manifest; +import android.annotation.CallbackExecutor; +import android.annotation.CheckResult; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemService; +import android.content.Context; +import android.os.Bundle; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.Executor; +import java.util.function.BiConsumer; + +/** + * Provides methods for verifying that attestations from remote compute environments meet minimum + * security requirements specified by attestation profiles. + * + * @hide + */ +@SystemService(Context.ATTESTATION_VERIFICATION_SERVICE) +public class AttestationVerificationManager { + + /** + * Verifies that {@code attestation} describes a computing environment that meets the + * requirements of {@code profile}, {@code localBindingType}, and {@code requirements}. + * + *

This method verifies that at least one system-registered {@linkplain + * AttestationVerificationService attestation verifier} associated with {@code profile} and + * {@code localBindingType} has verified that {@code attestation} attests that the remote + * environment matching the local binding data (determined by {@code localBindingType}) in + * {@code requirements} meets the requirements of the profile. + * + *

For successful verification, the {@code requirements} bundle must contain locally-known + * data which must match {@code attestation}. The required data in the bundle is defined by the + * {@code localBindingType} (see documentation for the type). Verifiers will fail to verify the + * attestation if the bundle contains unsupported data. + * + *

The {@code localBindingType} specifies how {@code attestation} is bound to a local + * secure channel endpoint or similar connection with the target remote environment described by + * the attestation. The binding is expected to be related to a cryptographic protocol, and each + * binding type requires specific arguments to be present in the {@code requirements} bundle. It + * is this binding to something known locally that ensures an attestation is not only valid, but + * is also associated with a particular connection. + * + *

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. + * + *

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 + * errors. + * + *

The values put into the {@code requirements} Bundle depend on the {@code + * localBindingType} used. + * + * @param profile the attestation profile which defines the security requirements which + * must be met by the environment described by {@code attestation} + * @param localBindingType the type of the local binding data; see constants in this class with + * the prefix {@code TYPE_} + * @param requirements a {@link Bundle} containing locally-known data which must match + * {@code attestation} + * @param attestation attestation data which describes a remote computing environment + * @param executor {@code callback} will be executed on this executor + * @param callback will be called with the results of the verification + * @see AttestationVerificationService + */ + @RequiresPermission(Manifest.permission.USE_ATTESTATION_VERIFICATION_SERVICE) + public void verifyAttestation( + @NonNull AttestationProfile profile, + @LocalBindingType int localBindingType, + @NonNull Bundle requirements, + @NonNull byte[] attestation, + @NonNull @CallbackExecutor Executor executor, + @NonNull BiConsumer<@VerificationResult Integer, VerificationToken> callback) { + executor.execute(() -> callback.accept(RESULT_UNKNOWN, null)); + } + + /** + * Verifies that {@code token} is a valid token, returning the result contained in valid + * tokens. + * + *

This verifies that the token was issued by the platform and thus the system verified + * attestation data against the specified {@code profile}, {@code localBindingType}, and {@code + * requirements}. The value returned by this method is the same as the one originally returned + * when the token was generated. Callers of this method should not trust the provider of the + * token to also specify the profile, local binding type, or requirements, but instead have + * their own security requirements about these arguments. + * + *

This method, in contrast to {@code verifyAttestation}, executes synchronously and only + * checks that a previous verification succeeded. This allows callers to pass the token to + * others, including system APIs, without those components needing to re-verify the attestation + * data, an operation which can take several seconds. + * + *

When {@code maximumAge} is not specified (null), this method verifies the token was + * generated in the past hour. Otherwise, it verifies the token was generated between now and + * {@code maximumAge} ago. The maximum value of {@code maximumAge} is one hour; specifying a + * duration greater than one hour will result in an {@link IllegalArgumentException}. + * + * @param profile the attestation profile which must be in the token + * @param localBindingType the local binding type which must be in the token + * @param requirements the requirements which must be in the token + * @param token the token to be verified + * @param maximumAge the maximum age to accept for the token + */ + @RequiresPermission(Manifest.permission.USE_ATTESTATION_VERIFICATION_SERVICE) + @CheckResult + @VerificationResult + public int verifyToken( + @NonNull AttestationProfile profile, + @LocalBindingType int localBindingType, + @NonNull Bundle requirements, + @NonNull VerificationToken token, + @Nullable java.time.Duration maximumAge) { + return RESULT_UNKNOWN; + } + + /** @hide */ + public AttestationVerificationManager() { + } + + /** @hide */ + @IntDef( + prefix = {"PROFILE_"}, + value = { + PROFILE_UNKNOWN, + PROFILE_APP_DEFINED, + PROFILE_SELF_TRUSTED, + PROFILE_PEER_DEVICE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AttestationProfileId { + } + + /** + * The profile is unknown because it is a profile unknown to this version of the SDK. + */ + public static final int PROFILE_UNKNOWN = 0; + + /** The profile is defined by an app. */ + public static final int PROFILE_APP_DEFINED = 1; + + /** + * A system-defined profile which verifies that the attesting environment can create an + * attestation with the same root certificate as the verifying device with a matching + * attestation challenge. + * + * This profile is intended to be used only for testing. + */ + public static final int PROFILE_SELF_TRUSTED = 2; + + /** + * A system-defined profile which verifies that the attesting environment environment is similar + * to the current device in terms of security model and security configuration. This category is + * fairly broad and most securely configured Android devices should qualify, along with a + * variety of non-Android devices. + */ + public static final int PROFILE_PEER_DEVICE = 3; + + /** @hide */ + @IntDef( + prefix = {"TYPE_"}, + value = { + TYPE_UNKNOWN, + TYPE_APP_DEFINED, + TYPE_PUBLIC_KEY, + TYPE_CHALLENGE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LocalBindingType { + } + + /** + * The type of the local binding data is unknown because it is a type unknown to this version of + * the SDK. + */ + public static final int TYPE_UNKNOWN = 0; + + /** + * A local binding type for app-defined profiles which use local binding data which does not + * match any of the existing system-defined types. + */ + public static final int TYPE_APP_DEFINED = 1; + + /** + * A local binding type where the attestation is bound to a public key negotiated and + * authenticated to a public key. + * + *

When using this type, the {@code requirements} bundle contains values for: + *

+ */ + public static final int TYPE_PUBLIC_KEY = 2; + + /** + * A local binding type where the attestation is bound to a challenge. + * + *

When using this type, the {@code requirements} bundle contains values for: + *

+ */ + public static final int TYPE_CHALLENGE = 3; + + /** @hide */ + @IntDef( + prefix = {"RESULT_"}, + value = { + RESULT_UNKNOWN, + RESULT_SUCCESS, + RESULT_FAILURE, + }) + @Retention(RetentionPolicy.SOURCE) + @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) + public @interface VerificationResult { + } + + /** The result of the verification is unknown because it has a value unknown to this SDK. */ + public static final int RESULT_UNKNOWN = 0; + + /** The result of the verification was successful. */ + public static final int RESULT_SUCCESS = 1; + + /** + * The result of the attestation verification was failure. The attestation could not be + * verified. + */ + public static final int RESULT_FAILURE = 2; + + /** + * Requirements bundle parameter key for a public key, a byte array. + * + *

This should contain the encoded key bytes according to the ASN.1 type + * {@code SubjectPublicKeyInfo} defined in the X.509 standard, the same as a call to {@link + * java.security.spec.X509EncodedKeySpec#getEncoded()} would produce. + * + * @see Bundle#putByteArray(String, byte[]) + */ + public static final String PARAM_PUBLIC_KEY = "localbinding.public_key"; + + /** Requirements bundle parameter key for an ID, String. */ + public static final String PARAM_ID = "localbinding.id"; + + /** Requirements bundle parameter for a challenge. */ + public static final String PARAM_CHALLENGE = "localbinding.challenge"; +} diff --git a/core/java/android/security/attestationverification/AttestationVerificationService.java b/core/java/android/security/attestationverification/AttestationVerificationService.java new file mode 100644 index 0000000000000..26c3051f7f38b --- /dev/null +++ b/core/java/android/security/attestationverification/AttestationVerificationService.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.attestationverification; + +import android.annotation.CheckResult; +import android.annotation.NonNull; +import android.app.Service; +import android.os.Bundle; +import android.security.attestationverification.AttestationVerificationManager.VerificationResult; + +/** + * A verifier which can be implemented by apps to verify an attestation (as described in {@link + * AttestationVerificationManager}). + * + * In the manifest for this service, specify the profile and local binding type this verifier + * supports. Create a new service for each combination of profile & local binding type that your app + * supports. Each service must declare an {@code intent-filter} action of {@link #SERVICE_INTERFACE} + * and permission of {@link android.Manifest.permission#BIND_ATTESTATION_VERIFICATION_SERVICE}. + * + *

Example: + * {@code + *

+ * 
+ *   
+ *     
+ *   
+ *   
+ *   
+ * 
+ * 
+ * } + * + *

For app-defined profiles, an example of the {@code }: + * {@code + *

+ *   
+ *   
+ * 
+ * } + * + * @hide + */ +public abstract class AttestationVerificationService extends Service { + + /** + * An intent action for a service to be bound and act as an attestation verifier. + * + *

The app will be kept alive for a short duration between verification calls after which + * the system will unbind from this service making the app eligible for cleanup. + * + *

The service must also require permission + * {@link android.Manifest.permission#BIND_ATTESTATION_VERIFICATION_SERVICE}. + */ + public static final String SERVICE_INTERFACE = + "android.security.attestationverification.AttestationVerificationService"; + + /** + * Verifies that {@code attestation} attests that the device identified by the local binding + * data in {@code requirements} meets the minimum requirements of this verifier for this + * verifier's profile. + * + *

Called by the system to verify an attestation. + * + *

The data passed into this method comes directly from apps and should be treated as + * potentially dangerous user input. + * + * @param requirements a {@link Bundle} containing locally-known data which must match {@code + * attestation} + * @param attestation the attestation to verify + * @return whether the verification passed + * @see AttestationVerificationManager#verifyAttestation(AttestationProfile, int, Bundle, + * byte[], java.util.concurrent.Executor, java.util.function.BiConsumer) + */ + @CheckResult + @VerificationResult + public abstract int onVerifyPeerDeviceAttestation( + @NonNull Bundle requirements, + @NonNull byte[] attestation); +} diff --git a/core/java/android/security/attestationverification/VerificationToken.aidl b/core/java/android/security/attestationverification/VerificationToken.aidl new file mode 100644 index 0000000000000..666a8b04aac24 --- /dev/null +++ b/core/java/android/security/attestationverification/VerificationToken.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.attestationverification; + +/** + * {@hide} + */ +parcelable VerificationToken; diff --git a/core/java/android/security/attestationverification/VerificationToken.java b/core/java/android/security/attestationverification/VerificationToken.java new file mode 100644 index 0000000000000..ae26823a9c7d0 --- /dev/null +++ b/core/java/android/security/attestationverification/VerificationToken.java @@ -0,0 +1,523 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.attestationverification; + +import android.annotation.NonNull; +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 com.android.internal.util.DataClass; +import com.android.internal.util.Parcelling; +import com.android.internal.util.Parcelling.BuiltIn.ForInstant; + +import java.time.Duration; +import java.util.concurrent.Executor; +import java.util.function.BiConsumer; + +/** + * Token representing the result of an attestation verification, which can be passed to other parts + * of the OS or other apps as proof of the verification. + * + * Tokens are only valid within the same UID (which means within a single app unless the deprecated + * android:sharedUserId manifest value is used). + * + * @hide + * @see Bundle#putParcelable(String, Parcelable) + */ +@DataClass( + genConstructor = false, + genHiddenBuilder = true +) +public final class VerificationToken implements Parcelable { + + /** + * The attestation profile which was used to perform the verification. + * @hide + */ + @NonNull + private final AttestationProfile mAttestationProfile; + + /** + * The local binding type of the local binding data used to perform the verification. + * @hide + */ + @LocalBindingType + private final int mLocalBindingType; + + /** + * The requirements used to perform the verification. + * @hide + */ + @NonNull + 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 not use this + * value directly; call {@link AttestationVerificationManager#verifyToken(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 + private final int mVerificationResult; + + /** + * Time when the token was generated, set by the system. + */ + @NonNull + @DataClass.ParcelWith(ForInstant.class) + private final java.time.Instant mVerificationTime; + + /** + * 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 + */ + @NonNull + private final byte[] 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 + */ + 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( + @NonNull AttestationProfile attestationProfile, + @LocalBindingType int localBindingType, + @NonNull Bundle requirements, + @VerificationResult int verificationResult, + @NonNull java.time.Instant verificationTime, + @NonNull byte[] hmac, + int uid) { + this.mAttestationProfile = attestationProfile; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mAttestationProfile); + this.mLocalBindingType = localBindingType; + com.android.internal.util.AnnotationValidations.validate( + LocalBindingType.class, null, mLocalBindingType); + this.mRequirements = requirements; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mRequirements); + this.mVerificationResult = verificationResult; + com.android.internal.util.AnnotationValidations.validate( + VerificationResult.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. + */ + @DataClass.Generated.Member + public @NonNull AttestationProfile getAttestationProfile() { + return mAttestationProfile; + } + + /** + * The local binding type of the local binding data used to perform the verification. + */ + @DataClass.Generated.Member + public @LocalBindingType int getLocalBindingType() { + return mLocalBindingType; + } + + /** + * The requirements used to perform the verification. + */ + @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 not use this + * value directly; call {@link AttestationVerificationManager#verifyToken(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() { + return mVerificationResult; + } + + /** + * Time when the token was generated, set by the system. + */ + @DataClass.Generated.Member + public @NonNull java.time.Instant getVerificationTime() { + return mVerificationTime; + } + + /** + * 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 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 sParcellingForVerificationTime = + Parcelling.Cache.get( + ForInstant.class); + static { + if (sParcellingForVerificationTime == null) { + sParcellingForVerificationTime = Parcelling.Cache.put( + new ForInstant()); + } + } + + @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) { ... } + + dest.writeTypedObject(mAttestationProfile, flags); + dest.writeInt(mLocalBindingType); + dest.writeBundle(mRequirements); + 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) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + 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( + NonNull.class, null, mAttestationProfile); + this.mLocalBindingType = localBindingType; + com.android.internal.util.AnnotationValidations.validate( + LocalBindingType.class, null, mLocalBindingType); + this.mRequirements = requirements; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mRequirements); + this.mVerificationResult = verificationResult; + com.android.internal.util.AnnotationValidations.validate( + VerificationResult.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 CREATOR + = new Parcelable.Creator() { + @Override + public VerificationToken[] newArray(int size) { + return new VerificationToken[size]; + } + + @Override + public VerificationToken createFromParcel(@NonNull android.os.Parcel in) { + return new VerificationToken(in); + } + }; + + /** + * A builder for {@link VerificationToken} + * @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 @NonNull java.time.Instant mVerificationTime; + private @NonNull byte[] mHmac; + private int mUid; + + private long mBuilderFieldsSet = 0L; + + /** + * Creates a new Builder. + * + * @param attestationProfile + * The attestation profile which was used to perform the verification. + * @param localBindingType + * The local binding type of the local binding data used to perform the verification. + * @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 not use this + * value directly; call {@link AttestationVerificationManager#verifyToken(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. + */ + public Builder( + @NonNull AttestationProfile attestationProfile, + @LocalBindingType int localBindingType, + @NonNull Bundle requirements, + @VerificationResult int verificationResult, + @NonNull java.time.Instant verificationTime, + @NonNull byte[] hmac, + int uid) { + mAttestationProfile = attestationProfile; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mAttestationProfile); + mLocalBindingType = localBindingType; + com.android.internal.util.AnnotationValidations.validate( + LocalBindingType.class, null, mLocalBindingType); + mRequirements = requirements; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mRequirements); + mVerificationResult = verificationResult; + com.android.internal.util.AnnotationValidations.validate( + VerificationResult.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. + */ + @DataClass.Generated.Member + public @NonNull Builder setAttestationProfile(@NonNull AttestationProfile value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mAttestationProfile = value; + return this; + } + + /** + * The local binding type of the local binding data used to perform the verification. + */ + @DataClass.Generated.Member + public @NonNull Builder setLocalBindingType(@LocalBindingType int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mLocalBindingType = value; + return this; + } + + /** + * The requirements used to perform the verification. + */ + @DataClass.Generated.Member + public @NonNull Builder setRequirements(@NonNull Bundle value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mRequirements = value; + return this; + } + + /** + * 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 not use this + * value directly; call {@link AttestationVerificationManager#verifyToken(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) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; + mVerificationResult = value; + return this; + } + + /** + * 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; + mVerificationTime = value; + return this; + } + + /** + * 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; + mHmac = value; + 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 + + VerificationToken o = new VerificationToken( + mAttestationProfile, + mLocalBindingType, + mRequirements, + mVerificationResult, + mVerificationTime, + mHmac, + mUid); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x80) != 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/core/java/android/security/attestationverification/package.html b/core/java/android/security/attestationverification/package.html new file mode 100644 index 0000000000000..783d0a1b54d52 --- /dev/null +++ b/core/java/android/security/attestationverification/package.html @@ -0,0 +1,3 @@ + +{@hide} + diff --git a/core/java/com/android/internal/util/Parcelling.java b/core/java/com/android/internal/util/Parcelling.java index 1ab316d07e42d..3147c34a7129a 100644 --- a/core/java/com/android/internal/util/Parcelling.java +++ b/core/java/com/android/internal/util/Parcelling.java @@ -23,6 +23,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -306,5 +307,33 @@ public interface Parcelling { return string == null ? null : UUID.fromString(string); } } + + /** + * A {@link Parcelling} for {@link Instant}. + * + * The minimum value of an instant uses a millisecond offset of about -3.15e19 which is + * larger than Long.MIN_VALUE, so we can use Long.MIN_VALUE as a sentinel value to indicate + * a null Instant. + */ + class ForInstant implements Parcelling { + + @Override + public void parcel(Instant item, Parcel dest, int parcelFlags) { + dest.writeLong(item == null ? Long.MIN_VALUE : item.getEpochSecond()); + dest.writeInt(item == null ? Integer.MIN_VALUE : item.getNano()); + } + + @Override + public Instant unparcel(Parcel source) { + long epochSecond = source.readLong(); + int afterNano = source.readInt(); + + if (epochSecond == Long.MIN_VALUE) { + return null; + } else { + return Instant.ofEpochSecond(epochSecond, afterNano); + } + } + } } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d926f9880fcff..d7dd3792902c6 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3470,6 +3470,23 @@ + + + + + + + + + diff --git a/tests/Internal/src/com/android/internal/util/ParcellingTests.java b/tests/Internal/src/com/android/internal/util/ParcellingTests.java new file mode 100644 index 0000000000000..65a3436a4c5ec --- /dev/null +++ b/tests/Internal/src/com/android/internal/util/ParcellingTests.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + +import android.os.Parcel; +import android.platform.test.annotations.Presubmit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import androidx.test.filters.SmallTest; + +import com.android.internal.util.Parcelling.BuiltIn.ForInstant; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.time.Instant; + +/** Tests for {@link Parcelling}. */ +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class ParcellingTests { + + private Parcel mParcel = Parcel.obtain(); + + @Test + public void forInstant_normal() { + testForInstant(Instant.ofEpochSecond(500L, 10)); + } + + @Test + public void forInstant_minimum() { + testForInstant(Instant.MIN); + } + + @Test + public void forInstant_maximum() { + testForInstant(Instant.MAX); + } + + @Test + public void forInstant_null() { + testForInstant(null); + } + + private void testForInstant(Instant instant) { + Parcelling parcelling = new ForInstant(); + parcelling.parcel(instant, mParcel, 0); + mParcel.setDataPosition(0); + + Instant created = parcelling.unparcel(mParcel); + + if (instant == null) { + assertNull(created); + } else { + assertEquals(instant, created); + } + } + +}