| /* |
| * 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 android.os.ParcelDuration; |
| import android.os.RemoteException; |
| import android.util.Log; |
| |
| import com.android.internal.infra.AndroidFuture; |
| |
| import java.lang.annotation.ElementType; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.lang.annotation.Target; |
| import java.time.Duration; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.TimeUnit; |
| 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 { |
| |
| private static final String TAG = "AVF"; |
| private static final Duration MAX_TOKEN_AGE = Duration.ofHours(1); |
| |
| private final Context mContext; |
| private final IAttestationVerificationManagerService mService; |
| |
| /** |
| * Verifies that {@code attestation} describes a computing environment that meets the |
| * requirements of {@code profile}, {@code localBindingType}, and {@code requirements}. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <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. |
| * |
| * <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 |
| * errors. |
| * |
| * <p>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) { |
| try { |
| AndroidFuture<IVerificationResult> resultCallback = new AndroidFuture<>(); |
| resultCallback.thenAccept(result -> { |
| Log.d(TAG, "verifyAttestation result: " + result.resultCode + " / " + result.token); |
| executor.execute(() -> { |
| callback.accept(result.resultCode, result.token); |
| }); |
| }); |
| |
| mService.verifyAttestation(profile, localBindingType, requirements, attestation, |
| resultCallback); |
| |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Verifies that {@code token} is a valid token, returning the result contained in valid |
| * tokens. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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 Duration maximumAge) { |
| Duration usedMaximumAge; |
| if (maximumAge == null) { |
| usedMaximumAge = MAX_TOKEN_AGE; |
| } else { |
| if (maximumAge.compareTo(MAX_TOKEN_AGE) > 0) { |
| throw new IllegalArgumentException( |
| "maximumAge cannot be greater than " + MAX_TOKEN_AGE + "; was " |
| + maximumAge); |
| } |
| usedMaximumAge = maximumAge; |
| } |
| |
| try { |
| AndroidFuture<Integer> resultCallback = new AndroidFuture<>(); |
| resultCallback.orTimeout(5, TimeUnit.SECONDS); |
| |
| mService.verifyToken(token, new ParcelDuration(usedMaximumAge), resultCallback); |
| return resultCallback.get(); // block on result callback |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } catch (Throwable t) { |
| throw new RuntimeException("Error verifying token.", t); |
| } |
| } |
| |
| /** @hide */ |
| public AttestationVerificationManager( |
| @NonNull Context context, |
| @NonNull IAttestationVerificationManagerService service) { |
| this.mContext = context; |
| this.mService = service; |
| } |
| |
| /** @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. |
| * |
| * <p>When using this type, the {@code requirements} bundle contains values for: |
| * <ul> |
| * <li>{@link #PARAM_PUBLIC_KEY} |
| * <li>{@link #PARAM_ID}: identifying the remote environment, optional |
| * </ul> |
| */ |
| public static final int TYPE_PUBLIC_KEY = 2; |
| |
| /** |
| * A local binding type where the attestation is bound to a challenge. |
| * |
| * <p>When using this type, the {@code requirements} bundle contains values for: |
| * <ul> |
| * <li>{@link #PARAM_CHALLENGE}: containing the challenge |
| * </ul> |
| */ |
| 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. |
| * |
| * <p>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"; |
| } |