1 /*
2  * Copyright (C) 2022 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 com.android.server.companion.securechannel;
18 
19 import static android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE;
20 import static android.security.attestationverification.AttestationVerificationManager.PROFILE_PEER_DEVICE;
21 import static android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE;
22 
23 import android.annotation.NonNull;
24 import android.content.Context;
25 import android.os.Bundle;
26 import android.security.attestationverification.AttestationProfile;
27 import android.security.attestationverification.AttestationVerificationManager;
28 import android.security.attestationverification.VerificationToken;
29 
30 import java.util.concurrent.CountDownLatch;
31 import java.util.concurrent.TimeUnit;
32 import java.util.concurrent.atomic.AtomicInteger;
33 import java.util.function.BiConsumer;
34 
35 /**
36  * Helper class to perform attestation verification synchronously.
37  */
38 public class AttestationVerifier {
39     private static final long ATTESTATION_VERIFICATION_TIMEOUT_SECONDS = 10; // 10 seconds
40     private static final String PARAM_OWNED_BY_SYSTEM = "android.key_owned_by_system";
41 
42     private final Context mContext;
43 
AttestationVerifier(Context context)44     AttestationVerifier(Context context) {
45         this.mContext = context;
46     }
47 
48     /**
49      * Synchronously verify remote attestation as a suitable peer device on current thread.
50      *
51      * The peer device must be owned by the Android system and be protected with appropriate
52      * public key that this device can verify as attestation challenge.
53      *
54      * @param remoteAttestation the full certificate chain containing attestation extension.
55      * @param attestationChallenge attestation challenge for authentication.
56      * @return true if attestation is successfully verified; false otherwise.
57      */
58     @NonNull
verifyAttestation( @onNull byte[] remoteAttestation, @NonNull byte[] attestationChallenge )59     public int verifyAttestation(
60             @NonNull byte[] remoteAttestation,
61             @NonNull byte[] attestationChallenge
62     ) throws SecureChannelException {
63         Bundle requirements = new Bundle();
64         requirements.putByteArray(PARAM_CHALLENGE, attestationChallenge);
65         requirements.putBoolean(PARAM_OWNED_BY_SYSTEM, true); // Custom parameter for CDM
66 
67         // Synchronously execute attestation verification.
68         AtomicInteger verificationResult = new AtomicInteger(0);
69         CountDownLatch verificationFinished = new CountDownLatch(1);
70         BiConsumer<Integer, VerificationToken> onVerificationResult = (result, token) -> {
71             verificationResult.set(result);
72             verificationFinished.countDown();
73         };
74 
75         mContext.getSystemService(AttestationVerificationManager.class).verifyAttestation(
76                 new AttestationProfile(PROFILE_PEER_DEVICE),
77                 /* localBindingType */ TYPE_CHALLENGE,
78                 requirements,
79                 remoteAttestation,
80                 Runnable::run,
81                 onVerificationResult
82         );
83 
84         boolean finished;
85         try {
86             finished = verificationFinished.await(
87                     ATTESTATION_VERIFICATION_TIMEOUT_SECONDS,
88                     TimeUnit.SECONDS
89             );
90         } catch (InterruptedException e) {
91             throw new SecureChannelException("Attestation verification was interrupted", e);
92         }
93 
94         if (!finished) {
95             throw new SecureChannelException("Attestation verification timed out.");
96         }
97 
98         return verificationResult.get();
99     }
100 }
101