/* * 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}. * *
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) {
try {
AndroidFuture 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 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 When using this type, the {@code requirements} bundle contains values for:
* When using this type, the {@code requirements} bundle contains values for:
* 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";
/** @hide */
public static String localBindingTypeToString(@LocalBindingType int localBindingType) {
final String text;
switch (localBindingType) {
case TYPE_UNKNOWN:
text = "UNKNOWN";
break;
case TYPE_APP_DEFINED:
text = "APP_DEFINED";
break;
case TYPE_PUBLIC_KEY:
text = "PUBLIC_KEY";
break;
case TYPE_CHALLENGE:
text = "CHALLENGE";
break;
default:
return Integer.toString(localBindingType);
}
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 + ")";
}
}
*
*/
public static final int TYPE_PUBLIC_KEY = 2;
/**
* A local binding type where the attestation is bound to a challenge.
*
*
*
*/
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.
*
*