• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.cts.verifier.security;
18 
19 import android.content.pm.PackageManager;
20 import android.icu.util.Calendar;
21 import android.os.Bundle;
22 import android.security.ConfirmationAlreadyPresentingException;
23 import android.security.ConfirmationCallback;
24 import android.security.ConfirmationNotAvailableException;
25 import android.security.ConfirmationPrompt;
26 import android.security.keystore.KeyGenParameterSpec;
27 import android.security.keystore.KeyProperties;
28 import android.view.View;
29 import android.view.View.OnClickListener;
30 import android.widget.Button;
31 import android.widget.Toast;
32 
33 import com.android.cts.verifier.PassFailButtons;
34 import com.android.cts.verifier.R;
35 
36 import java.io.IOException;
37 import java.security.InvalidAlgorithmParameterException;
38 import java.security.InvalidKeyException;
39 import java.security.KeyPairGenerator;
40 import java.security.KeyStore;
41 import java.security.KeyStoreException;
42 import java.security.NoSuchAlgorithmException;
43 import java.security.NoSuchProviderException;
44 import java.security.Signature;
45 import java.security.SignatureException;
46 import java.security.UnrecoverableEntryException;
47 import java.security.cert.CertificateException;
48 import java.util.Date;
49 
50 public class ProtectedConfirmationTest extends PassFailButtons.Activity {
51 
52     /**
53      * Alias for our key in the Android Key Store.
54      */
55     private static final String KEY_NAME = "my_confirmation_key";
56     private boolean teeTestSuccess = false;
57     private boolean strongboxTestSuccess = false;
58     private boolean mTeeNegativeTestSuccess = false;
59     private boolean mStrongboxNegativeTestSuccess = false;
60 
61     @Override
onCreate(Bundle savedInstanceState)62     protected void onCreate(Bundle savedInstanceState) {
63         super.onCreate(savedInstanceState);
64         setContentView(R.layout.sec_protected_confirmation_main);
65         setPassFailButtonClickListeners();
66 
67         boolean protectedConfirmationSupported =
68                 ConfirmationPrompt.isSupported(getApplicationContext());
69         if (protectedConfirmationSupported) {
70             setInfoResources(R.string.sec_protected_confirmation_test,
71                     R.string.sec_protected_confirmation_test_info, -1);
72             getPassButton().setEnabled(false);
73         } else {
74             setInfoResources(R.string.sec_protected_confirmation_not_supported_title,
75                     R.string.sec_protected_confirmation_not_supported_info, -1);
76             getPassButton().setEnabled(true);
77             return;
78         }
79         boolean hasStrongbox = this.getPackageManager()
80                 .hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE);
81 
82         findViewById(R.id.sec_protected_confirmation_tee_test_success)
83                 .setVisibility(View.INVISIBLE);
84         findViewById(R.id.sec_protected_confirmation_strongbox_test_success)
85                 .setVisibility(View.INVISIBLE);
86         findViewById(R.id.sec_protected_confirmation_tee_negative_test_success)
87                 .setVisibility(View.INVISIBLE);
88         findViewById(R.id.sec_protected_confirmation_strongbox_negative_test_success)
89                 .setVisibility(View.INVISIBLE);
90         Button startTestButton = (Button) findViewById(R.id.sec_start_test_button);
91         startTestButton.setOnClickListener(new OnClickListener() {
92             @Override
93             public void onClick(View v) {
94                 showToast("TEE positive Test running...");
95                 v.post(new Runnable() {
96                     @Override
97                     public void run() {
98                         runTest(false /* useStrongbox */, true /* positiveScenario */);
99                     }
100                 });
101             }
102 
103         });
104 
105         Button startStrongboxTestButton =
106                 (Button) findViewById(R.id.sec_start_test_strongbox_button);
107         if (hasStrongbox) {
108             startStrongboxTestButton.setOnClickListener(new OnClickListener() {
109                 @Override
110                 public void onClick(View v) {
111                     showToast("Strongbox Positive Test running...");
112                     v.post(new Runnable() {
113                         @Override
114                         public void run() {
115                             runTest(true /* useStrongbox */, true /* positiveScenario */);
116                         }
117                     });
118                 }
119 
120             });
121         } else {
122             startStrongboxTestButton.setVisibility(View.GONE);
123             // since strongbox is unavailable we mark the strongbox test as passed so that the tee
124             // test alone can make the test pass.
125             strongboxTestSuccess = true;
126         }
127 
128         Button startNegativeTestButton =
129                                     (Button) findViewById(R.id.sec_start_tee_negative_test_button);
130         startNegativeTestButton.setOnClickListener(new OnClickListener() {
131             @Override
132             public void onClick(View v) {
133                 showToast("TEE negative Test running...");
134                 v.post(new Runnable() {
135                     @Override
136                     public void run() {
137                         runTest(false /* useStrongbox */, false /* positiveScenario */);
138                     }
139                 });
140             }
141 
142         });
143 
144         Button startStrongboxNegativeTestButton =
145                 (Button) findViewById(R.id.sec_start_test_strongbox_negative_button);
146         if (hasStrongbox) {
147             startStrongboxNegativeTestButton.setOnClickListener(new OnClickListener() {
148                 @Override
149                 public void onClick(View v) {
150                     showToast("Strongbox negative Test running...");
151                     v.post(new Runnable() {
152                         @Override
153                         public void run() {
154                             runTest(true /* useStrongbox */, false /* positiveScenario */);
155                         }
156                     });
157                 }
158 
159             });
160         } else {
161             startStrongboxTestButton.setVisibility(View.GONE);
162             // since strongbox is unavailable we mark the strongbox test as passed so that the tee
163             // test alone can make the test pass.
164             mStrongboxNegativeTestSuccess = true;
165         }
166     }
167 
168     /**
169      * Creates an asymmetric signing key in AndroidKeyStore which can only be used for signing
170      * user confirmed messages.
171      */
createKey(boolean useStrongbox)172     private void createKey(boolean useStrongbox) {
173         Calendar calendar = Calendar.getInstance();
174         Date validityStart = calendar.getTime();
175         calendar.add(Calendar.YEAR, 1);
176         Date validityEnd = calendar.getTime();
177         try {
178             KeyPairGenerator kpg = KeyPairGenerator.getInstance(
179                     KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
180             KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
181                     KEY_NAME,
182                     KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY);
183             builder.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512);
184             builder.setAttestationChallenge("CtsVerifierTest".getBytes());
185             builder.setUserConfirmationRequired(true);
186             builder.setIsStrongBoxBacked(useStrongbox);
187             builder.setKeyValidityStart(validityStart);
188             builder.setKeyValidityEnd(validityEnd);
189             kpg.initialize(builder.build());
190             kpg.generateKeyPair();
191         } catch (NoSuchAlgorithmException | NoSuchProviderException |
192                  InvalidAlgorithmParameterException e) {
193             throw new RuntimeException("Failed to create confirmation key", e);
194         }
195     }
196 
trySign(byte[] dataThatWasConfirmed, boolean positiveScenario)197     private boolean trySign(byte[] dataThatWasConfirmed, boolean positiveScenario) {
198         try {
199             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
200             keyStore.load(null);
201             KeyStore.Entry key = keyStore.getEntry(KEY_NAME, null);
202             Signature s = Signature.getInstance("SHA256withECDSA");
203             s.initSign(((KeyStore.PrivateKeyEntry) key).getPrivateKey());
204             if (!positiveScenario && dataThatWasConfirmed != null
205                     && dataThatWasConfirmed.length > 0) {
206                 // The data received in callback as confirmed data has prompt text and extra data
207                 // included. So even using same prompt text for signing could be considered as
208                 // corrupted data.
209                 dataThatWasConfirmed[0] = (byte) ~dataThatWasConfirmed[0];
210             }
211             s.update(dataThatWasConfirmed);
212             s.sign();
213         } catch (CertificateException | KeyStoreException | IOException | NoSuchAlgorithmException |
214                 UnrecoverableEntryException | InvalidKeyException e) {
215             throw new RuntimeException("Failed to load confirmation key", e);
216         } catch (SignatureException e) {
217             return !positiveScenario;
218         }
219         return positiveScenario;
220     }
221 
runTest(boolean useStrongbox, boolean positiveScenario)222     private void runTest(boolean useStrongbox, boolean positiveScenario) {
223         createKey(useStrongbox);
224         if (trySign(getString(R.string.sec_protected_confirmation_message)
225                 .getBytes(), true /* positiveScenario */)) {
226             showToast("Test failed. Key could sign without confirmation.");
227         } else {
228             showConfirmationPrompt(
229                     getString(R.string.sec_protected_confirmation_message),
230                     useStrongbox, positiveScenario);
231         }
232     }
233 
showConfirmationPrompt(String confirmationMessage, boolean useStrongbox, boolean positiveScenario)234     private void showConfirmationPrompt(String confirmationMessage, boolean useStrongbox,
235                                         boolean positiveScenario) {
236         ConfirmationPrompt.Builder builder = new ConfirmationPrompt.Builder(this);
237         builder.setPromptText(confirmationMessage);
238         builder.setExtraData(new byte[]{0x1, 0x02, 0x03});
239         ConfirmationPrompt prompt = builder.build();
240         try {
241             prompt.presentPrompt(getMainExecutor(),
242                     new ConfirmationCallback() {
243                         @Override
244                         public void onConfirmed(byte[] dataThatWasConfirmed) {
245                             super.onConfirmed(dataThatWasConfirmed);
246                             if (trySign(dataThatWasConfirmed, positiveScenario)) {
247                                 markTestSuccess(useStrongbox, positiveScenario);
248                             } else {
249                                 if (positiveScenario) {
250                                     showToast("Failed to sign confirmed message");
251                                 } else {
252                                     showToast("Failed! Corrupted data should not be signed.");
253                                 }
254                             }
255                         }
256 
257                         @Override
258                         public void onDismissed() {
259                             super.onDismissed();
260                             showToast("User dismissed the dialog.");
261                         }
262 
263                         @Override
264                         public void onCanceled() {
265                             super.onCanceled();
266                             showToast("Confirmation dialog was canceled.");
267                         }
268 
269                         @Override
270                         public void onError(Throwable e) {
271                             super.onError(e);
272                             throw new RuntimeException("Confirmation Callback encountered an error",
273                                                        e);
274                         }
275                     });
276         } catch (ConfirmationAlreadyPresentingException | ConfirmationNotAvailableException e) {
277             throw new RuntimeException("Error trying to present the confirmation prompt", e);
278         }
279     }
280 
showToast(String message)281     private void showToast(String message) {
282         Toast.makeText(this, message, Toast.LENGTH_LONG)
283                 .show();
284     }
285 
markTestSuccess(boolean strongbox, boolean positiveScenario)286     private void markTestSuccess(boolean strongbox, boolean positiveScenario) {
287         if (strongbox) {
288             if (positiveScenario && !strongboxTestSuccess) {
289                 findViewById(R.id.sec_protected_confirmation_strongbox_test_success)
290                         .setVisibility(View.VISIBLE);
291                 strongboxTestSuccess = true;
292             } else if (!mStrongboxNegativeTestSuccess) {
293                 findViewById(R.id.sec_protected_confirmation_strongbox_negative_test_success)
294                         .setVisibility(View.VISIBLE);
295                 mStrongboxNegativeTestSuccess = true;
296             }
297         } else {
298             if (positiveScenario && !teeTestSuccess) {
299                 findViewById(R.id.sec_protected_confirmation_tee_test_success)
300                         .setVisibility(View.VISIBLE);
301                 teeTestSuccess = true;
302             } else if (!mTeeNegativeTestSuccess) {
303                 findViewById(R.id.sec_protected_confirmation_tee_negative_test_success)
304                         .setVisibility(View.VISIBLE);
305                 mTeeNegativeTestSuccess = true;
306             }
307         }
308         if (strongboxTestSuccess && teeTestSuccess && mStrongboxNegativeTestSuccess
309                 && mTeeNegativeTestSuccess) {
310             showToast("Test passed.");
311             getPassButton().setEnabled(true);
312         }
313     }
314 }
315 
316