blob: 2d58d179ca092fc17c48a419cd57b672d4d74ee1 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.cts.verifier.security;
import static android.keystore.cts.CertificateUtils.createCertificate;
import android.app.admin.DevicePolicyManager;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.security.AppUriAuthenticationPolicy;
import android.security.AttestedKeyPair;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.security.KeyChainException;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Log;
import com.android.cts.verifier.ArrayTestListAdapter;
import com.android.cts.verifier.DialogTestListActivity;
import com.android.cts.verifier.R;
import com.android.cts.verifier.TestResult;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.security.auth.x500.X500Principal;
/**
* CTS verifier test for credential management on unmanaged device.
*
* This activity is responsible for starting the credential management app flow. It performs the
* following verifications:
* Can successfully request to become the Credential management app
* The credential management app is correctly set
* The authentication policy is correctly set
* The credential management app can generate a key pair
* The credential management app can install a certificate
* The credential management app can successfully predefine which alias should be used for
* authentication to a remote service
* The chosen alias can be used to get the private key to sign data and the public key to
* validate the signature.
*/
public class CredentialManagementAppActivity extends DialogTestListActivity {
private static final String TAG = "CredentialManagementAppActivity";
private static final int REQUEST_MANAGE_CREDENTIALS_STATUS = 0;
private static final String TEST_APP_PACKAGE_NAME = "com.android.cts.verifier";
private static final Uri TEST_URI = Uri.parse("https://test.com");
private static final String TEST_ALIAS = "testAlias";
private static final AppUriAuthenticationPolicy AUTHENTICATION_POLICY =
new AppUriAuthenticationPolicy.Builder()
.addAppAndUriMapping(TEST_APP_PACKAGE_NAME, TEST_URI, TEST_ALIAS)
.build();
private static final String KEY_ALGORITHM = "RSA";
private static final byte[] DATA = "test".getBytes();
private DevicePolicyManager mDevicePolicyManager;
private DialogTestListItem mRequestManageCredentials;
private DialogTestListItem mCheckIsCredentialManagementApp;
private DialogTestListItem mCheckAuthenticationPolicy;
private DialogTestListItem mGenerateKeyPair;
private DialogTestListItem mCreateAndInstallCertificate;
private DialogTestListItem mRequestCertificateForAuthentication;
private DialogTestListItem mSignDataWithKey;
private DialogTestListItem mVerifySignature;
private DialogTestListItem mRemoveCredentialManagementApp;
private AttestedKeyPair mAttestedKeyPair;
private X509Certificate mCertificate;
private String mChosenAlias;
private byte[] mSignature;
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
private final Handler mHandler = new Handler(Looper.getMainLooper());
private boolean mHasCredentialManagementApp = false;
public CredentialManagementAppActivity() {
super(R.layout.credential_management_app_test,
R.string.credential_management_app_test,
R.string.credential_management_app_info,
R.string.credential_management_app_info);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDevicePolicyManager = getSystemService(DevicePolicyManager.class);
}
@Override
public void finish() {
super.finish();
if (mHasCredentialManagementApp) {
mExecutor.execute(
() -> KeyChain.removeCredentialManagementApp(getApplicationContext()));
}
}
@Override
protected void setupTests(final ArrayTestListAdapter testAdapter) {
mRequestManageCredentials = new DialogTestListItem(this,
R.string.request_manage_credentials,
"request_manage_credentials") {
@Override
public void performTest(DialogTestListActivity activity) {
Intent intent = KeyChain.createManageCredentialsIntent(AUTHENTICATION_POLICY);
startActivityForResult(intent, REQUEST_MANAGE_CREDENTIALS_STATUS);
}
};
testAdapter.add(mRequestManageCredentials);
mCheckIsCredentialManagementApp = new DialogTestListItem(this,
R.string.is_credential_management_app,
"is_credential_management_app") {
@Override
public void performTest(DialogTestListActivity activity) {
checkIsCredentialManagementApp();
}
};
testAdapter.add(mCheckIsCredentialManagementApp);
mCheckAuthenticationPolicy = new DialogTestListItem(this,
R.string.credential_management_app_policy,
"credential_management_app_policy") {
@Override
public void performTest(DialogTestListActivity activity) {
checkAuthenticationPolicy();
}
};
testAdapter.add(mCheckAuthenticationPolicy);
mGenerateKeyPair = new DialogTestListItem(this,
R.string.generate_key_pair,
"generate_key_pair") {
@Override
public void performTest(DialogTestListActivity activity) {
generateKeyPair();
}
};
testAdapter.add(mGenerateKeyPair);
mCreateAndInstallCertificate = new DialogTestListItem(this,
R.string.create_and_install_certificate,
"create_and_install_certificate") {
@Override
public void performTest(DialogTestListActivity activity) {
createAndInstallCertificate();
}
};
testAdapter.add(mCreateAndInstallCertificate);
mRequestCertificateForAuthentication = new DialogTestListItem(this,
R.string.request_certificate_authentication,
"request_certificate_authentication") {
@Override
public void performTest(DialogTestListActivity activity) {
requestCertificateForAuthentication();
}
};
testAdapter.add(mRequestCertificateForAuthentication);
mSignDataWithKey = new DialogTestListItem(this,
R.string.sign_data_with_key,
"sign_data_with_key") {
@Override
public void performTest(DialogTestListActivity activity) {
getPrivateKeyAndSignData();
}
};
testAdapter.add(mSignDataWithKey);
mVerifySignature = new DialogTestListItem(this,
R.string.verify_signature,
"verify_signature") {
@Override
public void performTest(DialogTestListActivity activity) {
getPublicKeyAndVerifySignature();
}
};
testAdapter.add(mVerifySignature);
mRemoveCredentialManagementApp = new DialogTestListItem(this,
R.string.remove_credential_management_app,
"remove_credential_management_app") {
@Override
public void performTest(DialogTestListActivity activity) {
removeCredentialManagementApp();
}
};
testAdapter.add(mRemoveCredentialManagementApp);
}
private void checkIsCredentialManagementApp() {
mExecutor.execute(() -> {
final boolean isCredMngApp =
KeyChain.isCredentialManagementApp(getApplicationContext());
mHandler.post(() -> setResult(mCheckIsCredentialManagementApp, isCredMngApp));
});
}
private void checkAuthenticationPolicy() {
mExecutor.execute(() -> {
final AppUriAuthenticationPolicy authenticationPolicy =
KeyChain.getCredentialManagementAppPolicy(getApplicationContext());
mHandler.post(() -> setResult(mCheckAuthenticationPolicy,
authenticationPolicy.equals(AUTHENTICATION_POLICY)));
});
}
private void generateKeyPair() {
mExecutor.execute(() -> {
KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(
TEST_ALIAS, KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
.setKeySize(2048)
.setDigests(KeyProperties.DIGEST_SHA256)
.setSignaturePaddings(
KeyProperties.SIGNATURE_PADDING_RSA_PSS,
KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.build();
mAttestedKeyPair = mDevicePolicyManager.generateKeyPair(null, KEY_ALGORITHM,
keyGenParameterSpec, 0);
mHandler.post(() -> setResult(mGenerateKeyPair, mAttestedKeyPair != null));
});
}
private void createAndInstallCertificate() {
X500Principal issuer = new X500Principal("CN=SelfSigned, O=Android, C=US");
X500Principal subject = new X500Principal("CN=Subject, O=Android, C=US");
try {
mCertificate = createCertificate(mAttestedKeyPair.getKeyPair(), subject, issuer);
setResult(mCreateAndInstallCertificate,
mDevicePolicyManager.setKeyPairCertificate(null, TEST_ALIAS,
Arrays.asList(new X509Certificate[]{mCertificate}), false));
} catch (Exception e) {
Log.w(TAG, "Failed to create certificate", e);
setResult(mCreateAndInstallCertificate, false);
}
}
private void requestCertificateForAuthentication() {
String[] keyTypes = new String[]{KEY_ALGORITHM};
Principal[] issuers = new Principal[0];
TestKeyChainAliasCallback callback = new TestKeyChainAliasCallback();
KeyChain.choosePrivateKeyAlias(this, callback, keyTypes, issuers, TEST_URI, null);
}
private void getPrivateKeyAndSignData() {
// Get private key with chosen alias
mExecutor.execute(() -> {
try {
final PrivateKey privateKey = KeyChain.getPrivateKey(
getApplicationContext(), mChosenAlias);
mHandler.post(() -> {
// Sign data with the private key
try {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(privateKey);
sign.update(DATA);
mSignature = sign.sign();
} catch (NoSuchAlgorithmException | InvalidKeyException
| SignatureException e) {
Log.w(TAG, "Failed to sign data with key", e);
}
setResult(mSignDataWithKey, mSignature != null);
});
} catch (KeyChainException | InterruptedException e) {
Log.w(TAG, "Failed to get the private key", e);
}
});
}
private void getPublicKeyAndVerifySignature() {
// Get public key from certificate with chosen alias
mExecutor.execute(() -> {
try {
X509Certificate[] certChain =
KeyChain.getCertificateChain(getApplicationContext(), mChosenAlias);
mHandler.post(() -> {
boolean verified = false;
if (certChain != null && certChain.length > 0) {
PublicKey publicKey = certChain[0].getPublicKey();
// Verify the signature with the public key
try {
Signature verify = Signature.getInstance("SHA256withRSA");
verify.initVerify(publicKey);
verify.update(DATA);
verified = verify.verify(mSignature);
} catch (NoSuchAlgorithmException | InvalidKeyException
| SignatureException e) {
Log.w(TAG, "Failed to verify signature", e);
}
}
setResult(mVerifySignature, verified);
});
} catch (KeyChainException | InterruptedException e) {
Log.w(TAG, "Failed to get the public key", e);
}
});
}
private void removeCredentialManagementApp() {
mExecutor.execute(() -> {
final boolean result =
KeyChain.removeCredentialManagementApp(getApplicationContext());
mHandler.post(() -> {
setResult(mRemoveCredentialManagementApp, result);
if (result) {
mHasCredentialManagementApp = false;
}
});
});
}
private void setResult(DialogTestListItem testListItem, boolean passed) {
if (passed) {
setTestResult(testListItem, TestResult.TEST_RESULT_PASSED);
} else {
setTestResult(testListItem, TestResult.TEST_RESULT_FAILED);
}
}
@Override
protected void handleActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_MANAGE_CREDENTIALS_STATUS:
setResult(mRequestManageCredentials, resultCode == RESULT_OK);
if (resultCode == RESULT_OK) {
mHasCredentialManagementApp = true;
}
break;
default:
super.handleActivityResult(requestCode, resultCode, data);
}
}
private class TestKeyChainAliasCallback implements KeyChainAliasCallback {
@Override
public void alias(String alias) {
mChosenAlias = alias;
setResult(mRequestCertificateForAuthentication, mChosenAlias.equals(TEST_ALIAS));
}
}
}