Skip to content

Commit 080b19b

Browse files
TreeHugger RobotAndroid (Google) Code Review
authored andcommitted
Merge "TestDPC: Add key generation and testing facilities" into ub-testdpc-pic
2 parents 016f2a9 + f205792 commit 080b19b

File tree

6 files changed

+328
-7
lines changed

6 files changed

+328
-7
lines changed

app/build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,7 @@ dependencies {
8080
compile 'com.android.support:support-v13:27.0.2'
8181
compile 'com.android.support.constraint:constraint-layout:1.0.2'
8282
compile 'com.google.android.gms:play-services-safetynet:+'
83-
compile(name:'setup-wizard-lib-platform-release', ext:'aar')
83+
compile(name: 'setup-wizard-lib-platform-release', ext: 'aar')
84+
compile 'org.bouncycastle:bcpkix-jdk15on:1.56'
85+
compile 'org.bouncycastle:bcprov-jdk15on:1.56'
8486
}

app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java

Lines changed: 213 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@
1616

1717
package com.afwsamples.testdpc.policy;
1818

19-
import static android.os.UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES;
20-
21-
import static com.afwsamples.testdpc.common.preference.DpcPreferenceHelper.NO_CUSTOM_CONSTRIANT;
22-
2319
import android.accessibilityservice.AccessibilityServiceInfo;
2420
import android.accounts.Account;
2521
import android.accounts.AccountManager;
@@ -53,9 +49,14 @@
5349
import android.os.UserManager;
5450
import android.provider.MediaStore;
5551
import android.provider.Settings;
52+
import android.security.AttestedKeyPair;
5653
import android.security.KeyChain;
5754
import android.security.KeyChainAliasCallback;
55+
import android.security.KeyChainException;
56+
import android.security.keystore.KeyGenParameterSpec;
57+
import android.security.keystore.KeyProperties;
5858
import android.service.notification.NotificationListenerService;
59+
import android.support.annotation.RequiresApi;
5960
import android.support.annotation.StringRes;
6061
import android.support.v14.preference.SwitchPreference;
6162
import android.support.v4.content.FileProvider;
@@ -111,6 +112,7 @@
111112
import com.afwsamples.testdpc.policy.networking.NetworkUsageStatsFragment;
112113
import com.afwsamples.testdpc.policy.resetpassword.ResetPasswordWithTokenFragment;
113114
import com.afwsamples.testdpc.policy.systemupdatepolicy.SystemUpdatePolicyFragment;
115+
import com.afwsamples.testdpc.policy.utils.CertificateUtils;
114116
import com.afwsamples.testdpc.policy.wifimanagement.WifiConfigCreationDialog;
115117
import com.afwsamples.testdpc.policy.wifimanagement.WifiEapTlsCreateDialogFragment;
116118
import com.afwsamples.testdpc.policy.wifimanagement.WifiModificationFragment;
@@ -124,14 +126,20 @@
124126
import com.afwsamples.testdpc.util.MainThreadExecutor;
125127
import com.afwsamples.testdpc.transferownership.PickTransferComponentFragment;
126128

129+
import org.bouncycastle.operator.OperatorCreationException;
130+
127131
import java.io.ByteArrayInputStream;
128132
import java.io.File;
129133
import java.io.FileNotFoundException;
130134
import java.io.IOException;
131135
import java.io.InputStream;
136+
import java.security.InvalidKeyException;
132137
import java.security.KeyStoreException;
133138
import java.security.NoSuchAlgorithmException;
134139
import java.security.PrivateKey;
140+
import java.security.PublicKey;
141+
import java.security.Signature;
142+
import java.security.SignatureException;
135143
import java.security.UnrecoverableKeyException;
136144
import java.security.cert.Certificate;
137145
import java.security.cert.CertificateException;
@@ -151,6 +159,11 @@
151159
import java.util.TimeZone;
152160
import java.util.stream.Collectors;
153161

162+
import javax.security.auth.x500.X500Principal;
163+
164+
import static android.os.UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES;
165+
import static com.afwsamples.testdpc.common.preference.DpcPreferenceHelper.NO_CUSTOM_CONSTRIANT;
166+
154167
/**
155168
* Provides several device management functions.
156169
*
@@ -265,6 +278,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
265278
= "enable_system_apps_by_package_name";
266279
private static final String ENABLE_SYSTEM_APPS_KEY = "enable_system_apps";
267280
private static final String INSTALL_EXISTING_PACKAGE_KEY = "install_existing_packages";
281+
private static final String GENERATE_KEY_CERTIFICATE_KEY = "generate_key_and_certificate";
268282
private static final String GET_CA_CERTIFICATES_KEY = "get_ca_certificates";
269283
private static final String GET_DISABLE_ACCOUNT_MANAGEMENT_KEY
270284
= "get_disable_account_management";
@@ -328,6 +342,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
328342
private static final String SUSPEND_APPS_KEY = "suspend_apps";
329343
private static final String SYSTEM_UPDATE_POLICY_KEY = "system_update_policy";
330344
private static final String SYSTEM_UPDATE_PENDING_KEY = "system_update_pending";
345+
private static final String TEST_KEY_USABILITY_KEY = "test_key_usability";
331346

332347
private static final String UNHIDE_APPS_KEY = "unhide_apps";
333348
private static final String UNSUSPEND_APPS_KEY = "unsuspend_apps";
@@ -552,7 +567,9 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
552567
findPreference(GENERIC_DELEGATION_KEY).setOnPreferenceClickListener(this);
553568
findPreference(APP_RESTRICTIONS_MANAGING_PACKAGE_KEY).setOnPreferenceClickListener(this);
554569
findPreference(INSTALL_KEY_CERTIFICATE_KEY).setOnPreferenceClickListener(this);
570+
findPreference(GENERATE_KEY_CERTIFICATE_KEY).setOnPreferenceClickListener(this);
555571
findPreference(REMOVE_KEY_CERTIFICATE_KEY).setOnPreferenceClickListener(this);
572+
findPreference(TEST_KEY_USABILITY_KEY).setOnPreferenceClickListener(this);
556573
findPreference(INSTALL_CA_CERTIFICATE_KEY).setOnPreferenceClickListener(this);
557574
findPreference(GET_CA_CERTIFICATES_KEY).setOnPreferenceClickListener(this);
558575
findPreference(REMOVE_ALL_CERTIFICATES_KEY).setOnPreferenceClickListener(this);
@@ -866,6 +883,12 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
866883
case REMOVE_KEY_CERTIFICATE_KEY:
867884
choosePrivateKeyForRemoval();
868885
return true;
886+
case GENERATE_KEY_CERTIFICATE_KEY:
887+
showPromptForGeneratedKeyAlias("generated-rsa-testdpc-1");
888+
return true;
889+
case TEST_KEY_USABILITY_KEY:
890+
testKeyCanBeUsedForSigning();
891+
return true;
869892
case INSTALL_CA_CERTIFICATE_KEY:
870893
Util.showFileViewerForImportingCertificate(this,
871894
INSTALL_CA_CERTIFICATE_REQUEST_CODE);
@@ -1006,6 +1029,22 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
10061029
return false;
10071030
}
10081031

1032+
@RequiresApi(api = Build.VERSION_CODES.M)
1033+
private void testKeyCanBeUsedForSigning() {
1034+
KeyChain.choosePrivateKeyAlias(getActivity(), new KeyChainAliasCallback() {
1035+
@Override
1036+
public void alias(String alias) {
1037+
if (alias == null) {
1038+
// No value was chosen.
1039+
showToast("No key chosen.");
1040+
return;
1041+
}
1042+
1043+
new SignAndVerifyTask().execute(alias);
1044+
}
1045+
}, null, null, null, null);
1046+
}
1047+
10091048
@TargetApi(Build.VERSION_CODES.O)
10101049
private void showPendingSystemUpdate() {
10111050
final SystemUpdateInfo updateInfo =
@@ -1228,6 +1267,131 @@ private void setStatusBarDisabled(boolean disable) {
12281267
}
12291268
}
12301269

1270+
@TargetApi(28)
1271+
private boolean installKeyPair(final PrivateKey key, final Certificate cert, final String alias,
1272+
boolean isUserSelectable) {
1273+
if (BuildCompat.isAtLeastP()) {
1274+
return mDevicePolicyManager.installKeyPair(
1275+
mAdminComponentName, key, new Certificate[]{cert}, alias, false,
1276+
isUserSelectable);
1277+
} else {
1278+
if (!isUserSelectable) {
1279+
throw new IllegalArgumentException(
1280+
"Cannot set key as non-user-selectable prior to P");
1281+
}
1282+
return mDevicePolicyManager.installKeyPair(mAdminComponentName, key, cert, alias);
1283+
}
1284+
}
1285+
1286+
class GenerateKeyAndCertificateTask extends AsyncTask<Void, Integer, Boolean> {
1287+
final String mAlias;
1288+
final boolean mIsUserSelectable;
1289+
1290+
GenerateKeyAndCertificateTask(String mAlias, boolean mIsUserSelectable) {
1291+
this.mAlias = mAlias;
1292+
this.mIsUserSelectable = mIsUserSelectable;
1293+
}
1294+
1295+
@TargetApi(28)
1296+
@Override
1297+
protected Boolean doInBackground(Void... voids) {
1298+
try {
1299+
KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder(mAlias,
1300+
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
1301+
.setKeySize(2048)
1302+
.setDigests(KeyProperties.DIGEST_SHA256)
1303+
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS,
1304+
KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
1305+
.build();
1306+
AttestedKeyPair keyPair = mDevicePolicyManager.generateKeyPair(
1307+
mAdminComponentName, "RSA", keySpec, 0);
1308+
1309+
if (keyPair == null) {
1310+
return false;
1311+
}
1312+
1313+
X500Principal subject = new X500Principal("CN=TestDPC, O=Android, C=US");
1314+
// Self-signed certificate: Same subject and issuer.
1315+
X509Certificate selfSigned = CertificateUtils.createCertificate(
1316+
keyPair.getKeyPair(), subject, subject);
1317+
1318+
List<Certificate> certs = new ArrayList<Certificate>();
1319+
certs.add(selfSigned);
1320+
1321+
return mDevicePolicyManager.setKeyPairCertificate(
1322+
mAdminComponentName, mAlias, certs, mIsUserSelectable);
1323+
} catch (CertificateException e) {
1324+
Log.e(TAG, "Failed to create certificate", e);
1325+
} catch (OperatorCreationException e) {
1326+
Log.e(TAG, "Failed to create certificate", e);
1327+
} catch (IOException e) {
1328+
Log.e(TAG, "Failed to encode certificate", e);
1329+
}
1330+
1331+
return false;
1332+
}
1333+
1334+
@Override
1335+
protected void onPostExecute(Boolean result) {
1336+
if (result.booleanValue()) {
1337+
showToast(R.string.key_generation_successful, mAlias);
1338+
} else {
1339+
showToast(R.string.key_generation_failed, mAlias);
1340+
}
1341+
}
1342+
}
1343+
1344+
private void generateKeyPair(final String alias, boolean isUserSelectable) {
1345+
new GenerateKeyAndCertificateTask(alias, isUserSelectable).execute();
1346+
}
1347+
1348+
class SignAndVerifyTask extends AsyncTask<String, Integer, String> {
1349+
@Override
1350+
protected String doInBackground(String... aliases) {
1351+
String alias = aliases[0];
1352+
try {
1353+
final String algorithmIdentifier = "SHA256withRSA";
1354+
PrivateKey privateKey = KeyChain.getPrivateKey(getContext(), alias);
1355+
1356+
byte[] data = new String("hello").getBytes();
1357+
Signature signer = Signature.getInstance(algorithmIdentifier);
1358+
signer.initSign(privateKey);
1359+
signer.update(data);
1360+
byte[] signature = signer.sign();
1361+
1362+
X509Certificate cert = KeyChain.getCertificateChain(getContext(), alias)[0];
1363+
PublicKey publicKey = cert.getPublicKey();
1364+
Signature verifier = Signature.getInstance(algorithmIdentifier);
1365+
verifier.initVerify(publicKey);
1366+
verifier.update(data);
1367+
if (verifier.verify(signature)) {
1368+
return cert.getSubjectX500Principal().getName();
1369+
}
1370+
} catch (KeyChainException e) {
1371+
Log.e(TAG, "Error getting key", e);
1372+
} catch (InterruptedException e) {
1373+
Log.e(TAG, "Interrupted while getting the key", e);
1374+
} catch (NoSuchAlgorithmException e) {
1375+
e.printStackTrace();
1376+
} catch (SignatureException e) {
1377+
Log.e(TAG, "Failed signing with key", e);
1378+
} catch (InvalidKeyException e) {
1379+
Log.e(TAG, "Provided alias resolves to an invalid key", e);
1380+
}
1381+
return null;
1382+
}
1383+
1384+
@Override
1385+
protected void onPostExecute(String result) {
1386+
if (result != null) {
1387+
showToast(R.string.key_usage_successful, result);
1388+
} else {
1389+
showToast(R.string.key_usage_failed);
1390+
}
1391+
}
1392+
};
1393+
1394+
12311395
/**
12321396
* Dispatches an intent to capture image or video.
12331397
*/
@@ -2200,15 +2364,20 @@ private void showPromptForKeyCertificateAlias(final PrivateKey key,
22002364
input.selectAll();
22012365
}
22022366

2367+
final CheckBox userSelectableCheckbox = passwordInputView.findViewById(
2368+
R.id.alias_user_selectable);
2369+
userSelectableCheckbox.setEnabled(BuildCompat.isAtLeastP());
2370+
userSelectableCheckbox.setChecked(!BuildCompat.isAtLeastP());
2371+
22032372
new AlertDialog.Builder(getActivity())
22042373
.setTitle(getString(R.string.certificate_alias_prompt_title))
22052374
.setView(passwordInputView)
22062375
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
22072376
@Override
22082377
public void onClick(DialogInterface dialog, int which) {
22092378
String alias = input.getText().toString();
2210-
if (mDevicePolicyManager.installKeyPair(mAdminComponentName, key,
2211-
certificate, alias) == true) {
2379+
boolean isUserSelectable = userSelectableCheckbox.isChecked();
2380+
if (installKeyPair(key, certificate, alias, isUserSelectable) == true) {
22122381
showToast(R.string.certificate_added, alias);
22132382
} else {
22142383
showToast(R.string.certificate_add_failed, alias);
@@ -2225,6 +2394,44 @@ public void onClick(DialogInterface dialog, int which) {
22252394
.show();
22262395
}
22272396

2397+
/**
2398+
* Shows a prompt to ask for the certificate alias. A key will be generated for this alias.
2399+
*
2400+
* @param alias A name that represents the certificate in the profile.
2401+
*/
2402+
private void showPromptForGeneratedKeyAlias(String alias) {
2403+
if (getActivity() == null || getActivity().isFinishing()) {
2404+
return;
2405+
}
2406+
2407+
View aliasNamingView = getActivity().getLayoutInflater().inflate(
2408+
R.layout.certificate_alias_prompt, null);
2409+
final EditText input = (EditText) aliasNamingView.findViewById(R.id.alias_input);
2410+
if (!TextUtils.isEmpty(alias)) {
2411+
input.setText(alias);
2412+
input.selectAll();
2413+
}
2414+
2415+
final CheckBox userSelectableCheckbox = aliasNamingView.findViewById(
2416+
R.id.alias_user_selectable);
2417+
userSelectableCheckbox.setChecked(!BuildCompat.isAtLeastP());
2418+
2419+
new AlertDialog.Builder(getActivity())
2420+
.setTitle(getString(R.string.certificate_alias_prompt_title))
2421+
.setView(aliasNamingView)
2422+
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
2423+
@Override
2424+
public void onClick(DialogInterface dialog, int which) {
2425+
String alias = input.getText().toString();
2426+
boolean isUserSelectable = userSelectableCheckbox.isChecked();
2427+
generateKeyPair(alias, isUserSelectable);
2428+
}
2429+
})
2430+
.setNegativeButton(android.R.string.cancel, null)
2431+
.show();
2432+
}
2433+
2434+
22282435
/**
22292436
* Selects a private/public key pair to uninstall, using the system dialog to choose
22302437
* an alias.

0 commit comments

Comments
 (0)