• 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 
59     @Override
onCreate(Bundle savedInstanceState)60     protected void onCreate(Bundle savedInstanceState) {
61         super.onCreate(savedInstanceState);
62         setContentView(R.layout.sec_protected_confirmation_main);
63         setPassFailButtonClickListeners();
64 
65         boolean protectedConfirmationSupported =
66                 ConfirmationPrompt.isSupported(getApplicationContext());
67         if (protectedConfirmationSupported) {
68             setInfoResources(R.string.sec_protected_confirmation_test,
69                     R.string.sec_protected_confirmation_test_info, -1);
70             getPassButton().setEnabled(false);
71         } else {
72             setInfoResources(R.string.sec_protected_confirmation_not_supported_title,
73                     R.string.sec_protected_confirmation_not_supported_info, -1);
74             getPassButton().setEnabled(true);
75             return;
76         }
77         boolean hasStrongbox = this.getPackageManager()
78                 .hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE);
79 
80         findViewById(R.id.sec_protected_confirmation_tee_test_success)
81                 .setVisibility(View.INVISIBLE);
82         findViewById(R.id.sec_protected_confirmation_strongbox_test_success)
83                 .setVisibility(View.INVISIBLE);
84         Button startTestButton = (Button) findViewById(R.id.sec_start_test_button);
85         startTestButton.setOnClickListener(new OnClickListener() {
86             @Override
87             public void onClick(View v) {
88                 showToast("Test running...");
89                 v.post(new Runnable() {
90                     @Override
91                     public void run() {
92                         runTest(false /* useStrongbox */);
93                     }
94                 });
95             }
96 
97         });
98 
99         Button startStrongboxTestButton =
100                 (Button) findViewById(R.id.sec_start_test_strongbox_button);
101         if (hasStrongbox) {
102             startStrongboxTestButton.setOnClickListener(new OnClickListener() {
103                 @Override
104                 public void onClick(View v) {
105                     showToast("Test running...");
106                     v.post(new Runnable() {
107                         @Override
108                         public void run() {
109                             runTest(true /* useStrongbox */);
110                         }
111                     });
112                 }
113 
114             });
115         } else {
116             startStrongboxTestButton.setVisibility(View.GONE);
117             // since strongbox is unavailable we mark the strongbox test as passed so that the tee
118             // test alone can make the test pass.
119             strongboxTestSuccess = true;
120         }
121 
122     }
123 
124     /**
125      * Creates an asymmetric signing key in AndroidKeyStore which can only be used for signing
126      * user confirmed messages.
127      */
createKey(boolean useStrongbox)128     private void createKey(boolean useStrongbox) {
129         Calendar calendar = Calendar.getInstance();
130         Date validityStart = calendar.getTime();
131         calendar.add(Calendar.YEAR, 1);
132         Date validityEnd = calendar.getTime();
133         try {
134             KeyPairGenerator kpg = KeyPairGenerator.getInstance(
135                     KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
136             KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
137                     KEY_NAME,
138                     KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY);
139             builder.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512);
140             builder.setAttestationChallenge("CtsVerifierTest".getBytes());
141             builder.setUserConfirmationRequired(true);
142             builder.setIsStrongBoxBacked(useStrongbox);
143             builder.setKeyValidityStart(validityStart);
144             builder.setKeyValidityEnd(validityEnd);
145             kpg.initialize(builder.build());
146             kpg.generateKeyPair();
147         } catch (NoSuchAlgorithmException | NoSuchProviderException |
148                  InvalidAlgorithmParameterException e) {
149             throw new RuntimeException("Failed to create confirmation key", e);
150         }
151     }
152 
trySign(byte[] dataThatWasConfirmed)153     private boolean trySign(byte[] dataThatWasConfirmed) {
154         try {
155             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
156             keyStore.load(null);
157             KeyStore.Entry key = keyStore.getEntry(KEY_NAME, null);
158             Signature s = Signature.getInstance("SHA256withECDSA");
159             s.initSign(((KeyStore.PrivateKeyEntry) key).getPrivateKey());
160             s.update(dataThatWasConfirmed);
161             s.sign();
162         } catch (CertificateException | KeyStoreException | IOException | NoSuchAlgorithmException |
163                 UnrecoverableEntryException | InvalidKeyException e) {
164             throw new RuntimeException("Failed to load confirmation key", e);
165         } catch (SignatureException e) {
166             return false;
167         }
168         return true;
169     }
170 
runTest(boolean useStrongbox)171     private void runTest(boolean useStrongbox) {
172         createKey(useStrongbox);
173         if (trySign(getString(R.string.sec_protected_confirmation_message)
174                 .getBytes())) {
175             showToast("Test failed. Key could sign without confirmation.");
176         } else {
177             showConfirmationPrompt(
178                     getString(R.string.sec_protected_confirmation_message),
179                     useStrongbox);
180         }
181     }
182 
showConfirmationPrompt(String confirmationMessage, boolean useStrongbox)183     private void showConfirmationPrompt(String confirmationMessage, boolean useStrongbox) {
184         ConfirmationPrompt.Builder builder = new ConfirmationPrompt.Builder(this);
185         builder.setPromptText(confirmationMessage);
186         builder.setExtraData(new byte[]{0x1, 0x02, 0x03});
187         ConfirmationPrompt prompt = builder.build();
188         try {
189             prompt.presentPrompt(getMainExecutor(),
190                     new ConfirmationCallback() {
191                         @Override
192                         public void onConfirmed(byte[] dataThatWasConfirmed) {
193                             super.onConfirmed(dataThatWasConfirmed);
194                             if (trySign(dataThatWasConfirmed)) {
195                                 markTestSuccess(useStrongbox);
196                             } else {
197                                 showToast("Failed to sign confirmed message");
198                             }
199                         }
200 
201                         @Override
202                         public void onDismissed() {
203                             super.onDismissed();
204                             showToast("User dismissed the dialog.");
205                         }
206 
207                         @Override
208                         public void onCanceled() {
209                             super.onCanceled();
210                             showToast("Confirmation dialog was canceled.");
211                         }
212 
213                         @Override
214                         public void onError(Throwable e) {
215                             super.onError(e);
216                             throw new RuntimeException("Confirmation Callback encountered an error",
217                                                        e);
218                         }
219                     });
220         } catch (ConfirmationAlreadyPresentingException | ConfirmationNotAvailableException e) {
221             throw new RuntimeException("Error trying to present the confirmation prompt", e);
222         }
223     }
224 
showToast(String message)225     private void showToast(String message) {
226         Toast.makeText(this, message, Toast.LENGTH_LONG)
227                 .show();
228     }
229 
markTestSuccess(boolean strongbox)230     private void markTestSuccess(boolean strongbox) {
231         if (strongbox) {
232             if (!strongboxTestSuccess) {
233                 findViewById(R.id.sec_protected_confirmation_strongbox_test_success)
234                         .setVisibility(View.VISIBLE);
235             }
236             strongboxTestSuccess = true;
237         } else {
238             if (!teeTestSuccess) {
239                 findViewById(R.id.sec_protected_confirmation_tee_test_success)
240                         .setVisibility(View.VISIBLE);
241             }
242             teeTestSuccess = true;
243         }
244         if (strongboxTestSuccess && teeTestSuccess) {
245             showToast("Test passed.");
246             getPassButton().setEnabled(true);
247         }
248     }
249 }
250 
251