blob: db783ceabcdb4f48d261f7185762fb9cacc0d922 [file] [log] [blame]
/*
* 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";
}