1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.security.attestationverification;
18 
19 import android.Manifest;
20 import android.annotation.CallbackExecutor;
21 import android.annotation.CheckResult;
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.RequiresPermission;
26 import android.annotation.SystemService;
27 import android.content.Context;
28 import android.os.Bundle;
29 import android.os.ParcelDuration;
30 import android.os.RemoteException;
31 import android.util.Log;
32 
33 import com.android.internal.infra.AndroidFuture;
34 
35 import java.lang.annotation.ElementType;
36 import java.lang.annotation.Retention;
37 import java.lang.annotation.RetentionPolicy;
38 import java.lang.annotation.Target;
39 import java.time.Duration;
40 import java.util.concurrent.Executor;
41 import java.util.concurrent.TimeUnit;
42 import java.util.function.BiConsumer;
43 
44 /**
45  * Provides methods for verifying that attestations from remote compute environments meet minimum
46  * security requirements specified by attestation profiles.
47  *
48  * @hide
49  */
50 @SystemService(Context.ATTESTATION_VERIFICATION_SERVICE)
51 public class AttestationVerificationManager {
52 
53     private static final String TAG = "AVF";
54     private static final Duration MAX_TOKEN_AGE = Duration.ofHours(1);
55 
56     private final Context mContext;
57     private final IAttestationVerificationManagerService mService;
58 
59     /**
60      * Verifies that {@code attestation} describes a computing environment that meets the
61      * requirements of {@code profile}, {@code localBindingType}, and {@code requirements}.
62      *
63      * <p>This method verifies that at least one system-registered {@linkplain
64      * AttestationVerificationService attestation verifier} associated with {@code profile} and
65      * {@code localBindingType} has verified that {@code attestation} attests that the remote
66      * environment matching the local binding data (determined by {@code localBindingType}) in
67      * {@code requirements} meets the requirements of the profile.
68      *
69      * <p>For successful verification, the {@code requirements} bundle must contain locally-known
70      * data which must match {@code attestation}. The required data in the bundle is defined by the
71      * {@code localBindingType} (see documentation for the type). Verifiers will fail to verify the
72      * attestation if the bundle contains unsupported data.
73      *
74      * <p>The {@code localBindingType} specifies how {@code attestation} is bound to a local
75      * secure channel endpoint or similar connection with the target remote environment described by
76      * the attestation. The binding is expected to be related to a cryptographic protocol, and each
77      * binding type requires specific arguments to be present in the {@code requirements} bundle. It
78      * is this binding to something known locally that ensures an attestation is not only valid, but
79      * is also associated with a particular connection.
80      *
81      * <p>The {@code callback} is called with a result and {@link VerificationToken} (which may be
82      * null). The result is an integer (see constants in this class with the prefix {@code RESULT_}.
83      * The result is {@link #RESULT_SUCCESS} when at least one verifier has passed its checks. The
84      * token may be used in calls to other parts of the system.
85      *
86      * <p>It's expected that a verifier will be able to decode and understand the passed values,
87      * otherwise fail to verify. {@code attestation} should contain some type data to prevent parse
88      * errors.
89      *
90      * <p>The values put into the {@code requirements} Bundle depend on the {@code
91      * localBindingType} used.
92      *
93      * @param profile          the attestation profile which defines the security requirements which
94      *                         must be met by the environment described by {@code attestation}
95      * @param localBindingType the type of the local binding data; see constants in this class with
96      *                         the prefix {@code TYPE_}
97      * @param requirements     a {@link Bundle} containing locally-known data which must match
98      *                         {@code attestation}
99      * @param attestation      attestation data which describes a remote computing environment
100      * @param executor         {@code callback} will be executed on this executor
101      * @param callback         will be called with the results of the verification
102      * @see AttestationVerificationService
103      */
104     @RequiresPermission(Manifest.permission.USE_ATTESTATION_VERIFICATION_SERVICE)
verifyAttestation( @onNull AttestationProfile profile, @LocalBindingType int localBindingType, @NonNull Bundle requirements, @NonNull byte[] attestation, @NonNull @CallbackExecutor Executor executor, @NonNull BiConsumer<@VerificationResult Integer, VerificationToken> callback)105     public void verifyAttestation(
106             @NonNull AttestationProfile profile,
107             @LocalBindingType int localBindingType,
108             @NonNull Bundle requirements,
109             @NonNull byte[] attestation,
110             @NonNull @CallbackExecutor Executor executor,
111             @NonNull BiConsumer<@VerificationResult Integer, VerificationToken> callback) {
112         try {
113             AndroidFuture<IVerificationResult> resultCallback = new AndroidFuture<>();
114             resultCallback.thenAccept(result -> {
115                 Log.d(TAG, "verifyAttestation result: " + result.resultCode + " / " + result.token);
116                 executor.execute(() -> {
117                     callback.accept(result.resultCode, result.token);
118                 });
119             });
120 
121             mService.verifyAttestation(profile, localBindingType, requirements, attestation,
122                     resultCallback);
123 
124         } catch (RemoteException e) {
125             throw e.rethrowFromSystemServer();
126         }
127     }
128 
129     /**
130      * Verifies that {@code token} is a valid token, returning the result contained in valid
131      * tokens.
132      *
133      * <p>This verifies that the token was issued by the platform and thus the system verified
134      * attestation data against the specified {@code profile}, {@code localBindingType}, and {@code
135      * requirements}. The value returned by this method is the same as the one originally returned
136      * when the token was generated. Callers of this method should not trust the provider of the
137      * token to also specify the profile, local binding type, or requirements, but instead have
138      * their own security requirements about these arguments.
139      *
140      * <p>This method, in contrast to {@code verifyAttestation}, executes synchronously and only
141      * checks that a previous verification succeeded. This allows callers to pass the token to
142      * others, including system APIs, without those components needing to re-verify the attestation
143      * data, an operation which can take several seconds.
144      *
145      * <p>When {@code maximumAge} is not specified (null), this method verifies the token was
146      * generated in the past hour. Otherwise, it verifies the token was generated between now and
147      * {@code maximumAge} ago. The maximum value of {@code maximumAge} is one hour; specifying a
148      * duration greater than one hour will result in an {@link IllegalArgumentException}.
149      *
150      * @param profile          the attestation profile which must be in the token
151      * @param localBindingType the local binding type which must be in the token
152      * @param requirements     the requirements which must be in the token
153      * @param token            the token to be verified
154      * @param maximumAge       the maximum age to accept for the token
155      */
156     @RequiresPermission(Manifest.permission.USE_ATTESTATION_VERIFICATION_SERVICE)
157     @CheckResult
158     @VerificationResult
verifyToken( @onNull AttestationProfile profile, @LocalBindingType int localBindingType, @NonNull Bundle requirements, @NonNull VerificationToken token, @Nullable Duration maximumAge)159     public int verifyToken(
160             @NonNull AttestationProfile profile,
161             @LocalBindingType int localBindingType,
162             @NonNull Bundle requirements,
163             @NonNull VerificationToken token,
164             @Nullable Duration maximumAge) {
165         Duration usedMaximumAge;
166         if (maximumAge == null) {
167             usedMaximumAge = MAX_TOKEN_AGE;
168         } else {
169             if (maximumAge.compareTo(MAX_TOKEN_AGE) > 0) {
170                 throw new IllegalArgumentException(
171                         "maximumAge cannot be greater than " + MAX_TOKEN_AGE + "; was "
172                                 + maximumAge);
173             }
174             usedMaximumAge = maximumAge;
175         }
176 
177         try {
178             AndroidFuture<Integer> resultCallback = new AndroidFuture<>();
179             resultCallback.orTimeout(5, TimeUnit.SECONDS);
180 
181             mService.verifyToken(token, new ParcelDuration(usedMaximumAge), resultCallback);
182             return resultCallback.get(); // block on result callback
183         } catch (RemoteException e) {
184             throw e.rethrowFromSystemServer();
185         } catch (Throwable t) {
186             throw new RuntimeException("Error verifying token.", t);
187         }
188     }
189 
190     /** @hide */
AttestationVerificationManager( @onNull Context context, @NonNull IAttestationVerificationManagerService service)191     public AttestationVerificationManager(
192             @NonNull Context context,
193             @NonNull IAttestationVerificationManagerService service) {
194         this.mContext = context;
195         this.mService = service;
196     }
197 
198     /** @hide */
199     @IntDef(
200             prefix = {"PROFILE_"},
201             value = {
202                     PROFILE_UNKNOWN,
203                     PROFILE_APP_DEFINED,
204                     PROFILE_SELF_TRUSTED,
205                     PROFILE_PEER_DEVICE,
206             })
207     @Retention(RetentionPolicy.SOURCE)
208     public @interface AttestationProfileId {
209     }
210 
211     /**
212      * The profile is unknown because it is a profile unknown to this version of the SDK.
213      */
214     public static final int PROFILE_UNKNOWN = 0;
215 
216     /** The profile is defined by an app. */
217     public static final int PROFILE_APP_DEFINED = 1;
218 
219     /**
220      * A system-defined profile which verifies that the attesting environment can create an
221      * attestation with the same root certificate as the verifying device with a matching
222      * attestation challenge.
223      *
224      * This profile is intended to be used only for testing.
225      */
226     public static final int PROFILE_SELF_TRUSTED = 2;
227 
228     /**
229      * A system-defined profile which verifies that the attesting environment is similar to the
230      * current device in terms of security model and security configuration. This category is fairly
231      * broad and most securely configured Android devices should qualify, along with a variety of
232      * non-Android devices.
233      */
234     public static final int PROFILE_PEER_DEVICE = 3;
235 
236     /** @hide */
237     @IntDef(
238             prefix = {"TYPE_"},
239             value = {
240                     TYPE_UNKNOWN,
241                     TYPE_APP_DEFINED,
242                     TYPE_PUBLIC_KEY,
243                     TYPE_CHALLENGE,
244             })
245     @Retention(RetentionPolicy.SOURCE)
246     public @interface LocalBindingType {
247     }
248 
249     /**
250      * The type of the local binding data is unknown because it is a type unknown to this version of
251      * the SDK.
252      */
253     public static final int TYPE_UNKNOWN = 0;
254 
255     /**
256      * A local binding type for app-defined profiles which use local binding data which does not
257      * match any of the existing system-defined types.
258      */
259     public static final int TYPE_APP_DEFINED = 1;
260 
261     /**
262      * A local binding type where the attestation is bound to a public key negotiated and
263      * authenticated to a public key.
264      *
265      * <p>When using this type, the {@code requirements} bundle contains values for:
266      * <ul>
267      *   <li>{@link #PARAM_PUBLIC_KEY}
268      *   <li>{@link #PARAM_ID}: identifying the remote environment, optional
269      * </ul>
270      */
271     public static final int TYPE_PUBLIC_KEY = 2;
272 
273     /**
274      * A local binding type where the attestation is bound to a challenge.
275      *
276      * <p>When using this type, the {@code requirements} bundle contains values for:
277      * <ul>
278      *   <li>{@link #PARAM_CHALLENGE}: containing the challenge
279      * </ul>
280      */
281     public static final int TYPE_CHALLENGE = 3;
282 
283     /** @hide */
284     @IntDef(
285             prefix = {"RESULT_"},
286             value = {
287                     RESULT_UNKNOWN,
288                     RESULT_SUCCESS,
289                     RESULT_FAILURE,
290             })
291     @Retention(RetentionPolicy.SOURCE)
292     @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
293     public @interface VerificationResult {
294     }
295 
296     /** The result of the verification is unknown because it has a value unknown to this SDK. */
297     public static final int RESULT_UNKNOWN = 0;
298 
299     /** The result of the verification was successful. */
300     public static final int RESULT_SUCCESS = 1;
301 
302     /**
303      * The result of the attestation verification was failure. The attestation could not be
304      * verified.
305      */
306     public static final int RESULT_FAILURE = 2;
307 
308     /**
309      * Requirements bundle parameter key for a public key, a byte array.
310      *
311      * <p>This should contain the encoded key bytes according to the ASN.1 type
312      * {@code SubjectPublicKeyInfo} defined in the X.509 standard, the same as a call to {@link
313      * java.security.spec.X509EncodedKeySpec#getEncoded()} would produce.
314      *
315      * @see Bundle#putByteArray(String, byte[])
316      */
317     public static final String PARAM_PUBLIC_KEY = "localbinding.public_key";
318 
319     /** Requirements bundle parameter key for an ID, String. */
320     public static final String PARAM_ID = "localbinding.id";
321 
322     /** Requirements bundle parameter for a challenge. */
323     public static final String PARAM_CHALLENGE = "localbinding.challenge";
324 
325     /** @hide */
localBindingTypeToString(@ocalBindingType int localBindingType)326     public static String localBindingTypeToString(@LocalBindingType int localBindingType) {
327         final String text;
328         switch (localBindingType) {
329             case TYPE_UNKNOWN:
330                 text = "UNKNOWN";
331                 break;
332 
333             case TYPE_APP_DEFINED:
334                 text = "APP_DEFINED";
335                 break;
336 
337             case TYPE_PUBLIC_KEY:
338                 text = "PUBLIC_KEY";
339                 break;
340 
341             case TYPE_CHALLENGE:
342                 text = "CHALLENGE";
343                 break;
344 
345             default:
346                 return Integer.toString(localBindingType);
347         }
348         return text + "(" + localBindingType + ")";
349     }
350 
351     /** @hide */
verificationResultCodeToString(@erificationResult int resultCode)352     public static String verificationResultCodeToString(@VerificationResult int resultCode) {
353         final String text;
354         switch (resultCode) {
355             case RESULT_UNKNOWN:
356                 text = "UNKNOWN";
357                 break;
358 
359             case RESULT_SUCCESS:
360                 text = "SUCCESS";
361                 break;
362 
363             case RESULT_FAILURE:
364                 text = "FAILURE";
365                 break;
366 
367             default:
368                 return Integer.toString(resultCode);
369         }
370         return text + "(" + resultCode + ")";
371     }
372 }
373