373 lines
15 KiB
Java
373 lines
15 KiB
Java
/*
|
|
* 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 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";
|
|
|
|
/** @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 + ")";
|
|
}
|
|
}
|