1 /* 2 * Copyright (C) 2015 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 package com.android.cts.certinstaller; 17 18 import android.app.admin.DevicePolicyManager; 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.util.Base64; 23 import android.util.Base64InputStream; 24 import android.util.Log; 25 26 import java.io.ByteArrayInputStream; 27 import java.security.cert.CertificateException; 28 import java.security.cert.CertificateFactory; 29 import java.security.spec.PKCS8EncodedKeySpec; 30 import java.security.KeyFactory; 31 import java.security.PrivateKey; 32 import java.security.cert.Certificate; 33 import java.util.List; 34 35 /** 36 * Delegated certificate installer app that responds to specific intents and executes various DPM 37 * certificate manipulation APIs. The following APIs are exercised: 38 * {@link DevicePolicyManager#installCaCert}, 39 * {@link DevicePolicyManager#uninstallCaCert}, 40 * {@link DevicePolicyManager#hasCaCertInstalled}, 41 * {@link DevicePolicyManager#getInstalledCaCerts}, 42 * {@link DevicePolicyManager#installKeyPair}. 43 */ 44 public class CertInstallerReceiver extends BroadcastReceiver { 45 46 private static final String TAG = "DelegatedCertInstaller"; 47 // exercises {@link DevicePolicyManager#installCaCert} and 48 // {@link DevicePolicyManager#hasCaCertInstalled}, 49 private static final String ACTION_INSTALL_CERT = "com.android.cts.certinstaller.install_cert"; 50 // exercises {@link DevicePolicyManager#uninstallCaCert} and 51 // {@link DevicePolicyManager#hasCaCertInstalled}, 52 private static final String ACTION_REMOVE_CERT = "com.android.cts.certinstaller.remove_cert"; 53 // exercises {@link DevicePolicyManager#getInstalledCaCerts}, 54 private static final String ACTION_VERIFY_CERT = "com.android.cts.certinstaller.verify_cert"; 55 // exercises {@link DevicePolicyManager#installKeyPair}, 56 private static final String ACTION_INSTALL_KEYPAIR = 57 "com.android.cts.certinstaller.install_keypair"; 58 59 private static final String ACTION_CERT_OPERATION_DONE = "com.android.cts.certinstaller.done"; 60 61 private static final String EXTRA_CERT_DATA = "extra_cert_data"; 62 private static final String EXTRA_KEY_DATA = "extra_key_data"; 63 private static final String EXTRA_KEY_ALIAS = "extra_key_alias"; 64 private static final String EXTRA_RESULT_VALUE = "extra_result_value"; 65 private static final String EXTRA_RESULT_EXCEPTION = "extra_result_exception"; 66 67 @Override onReceive(Context context, Intent intent)68 public void onReceive(Context context, Intent intent) { 69 if (intent == null) { 70 return; 71 } 72 73 String action = intent.getAction(); 74 DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( 75 Context.DEVICE_POLICY_SERVICE); 76 77 byte[] certBuffer; 78 79 if (ACTION_INSTALL_CERT.equals(action)) { 80 try { 81 certBuffer = intent.getByteArrayExtra(EXTRA_CERT_DATA); 82 // Verify cert is not currently installed. 83 if (dpm.hasCaCertInstalled(null, certBuffer)) { 84 throw new RuntimeException("Cert already on device?"); 85 } 86 if (!dpm.installCaCert(null, certBuffer)) { 87 throw new RuntimeException("installCaCert returned false."); 88 } 89 if (!dpm.hasCaCertInstalled(null, certBuffer)) { 90 throw new RuntimeException("Cannot find cert after installation."); 91 } 92 sendResult(context, true, null); 93 } catch (Exception e) { 94 Log.e(TAG, "Exception raised duing ACTION_INSTALL_CERT", e); 95 sendResult(context, false, e); 96 } 97 } else if (ACTION_REMOVE_CERT.equals(action)) { 98 try { 99 certBuffer = intent.getByteArrayExtra(EXTRA_CERT_DATA); 100 if (!dpm.hasCaCertInstalled(null, certBuffer)) { 101 throw new RuntimeException("Trying to uninstall a non-existent cert."); 102 } 103 dpm.uninstallCaCert(null, certBuffer); 104 sendResult(context, !dpm.hasCaCertInstalled(null, certBuffer), null); 105 } catch (Exception e) { 106 Log.e(TAG, "Exception raised duing ACTION_REMOVE_CERT", e); 107 sendResult(context, false, e); 108 } 109 } else if (ACTION_VERIFY_CERT.equals(action)) { 110 try { 111 certBuffer = intent.getByteArrayExtra(EXTRA_CERT_DATA); 112 sendResult(context, containsCertificate(dpm.getInstalledCaCerts(null), certBuffer), 113 null); 114 } catch (Exception e) { 115 Log.e(TAG, "Exception raised duing ACTION_VERIFY_CERT", e); 116 sendResult(context, false, e); 117 } 118 } else if (ACTION_INSTALL_KEYPAIR.equals(action)) { 119 String alias = intent.getStringExtra(EXTRA_KEY_ALIAS); 120 String key = intent.getStringExtra(EXTRA_KEY_DATA); 121 String cert = intent.getStringExtra(EXTRA_CERT_DATA); 122 try { 123 PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec( 124 Base64.decode(key, Base64.DEFAULT)); 125 KeyFactory kf = KeyFactory.getInstance("RSA"); 126 PrivateKey privatekey = kf.generatePrivate(keySpec); 127 128 Certificate certificate = CertificateFactory.getInstance("X.509") 129 .generateCertificate( 130 new Base64InputStream(new ByteArrayInputStream(cert.getBytes()), 131 Base64.DEFAULT)); 132 // Unfortunately there is no programmatically way to check if the given private key 133 // is indeed in the key store as a unprivileged app. So we just rely on 134 // installKeyPair() returning true as the success criteria of this test. Separate 135 // CTS keychain tests will make sure the API's behaviour is correct. 136 // Note: installKeyPair() will silently return false if there is no lockscreen 137 // password, however the test setup should have set one already. 138 sendResult(context, dpm.installKeyPair(null, privatekey, certificate, alias), 139 null); 140 } catch (Exception e) { 141 Log.e(TAG, "Exception raised duing ACTION_INSTALL_KEYPAIR", e); 142 sendResult(context, false, e); 143 } 144 } 145 } 146 147 sendResult(Context context, boolean succeed, Exception e)148 private void sendResult(Context context, boolean succeed, Exception e) { 149 Intent intent = new Intent(); 150 intent.setAction(ACTION_CERT_OPERATION_DONE); 151 intent.putExtra(EXTRA_RESULT_VALUE, succeed); 152 if (e != null) { 153 intent.putExtra(EXTRA_RESULT_EXCEPTION, e); 154 } 155 context.sendBroadcast(intent); 156 } 157 containsCertificate(List<byte[]> certificates, byte[] toMatch)158 private static boolean containsCertificate(List<byte[]> certificates, byte[] toMatch) 159 throws CertificateException { 160 Certificate certificateToMatch = readCertificate(toMatch); 161 for (byte[] certBuffer : certificates) { 162 Certificate cert = readCertificate(certBuffer); 163 if (certificateToMatch.equals(cert)) { 164 return true; 165 } 166 } 167 return false; 168 } 169 readCertificate(byte[] certBuffer)170 private static Certificate readCertificate(byte[] certBuffer) throws CertificateException { 171 final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 172 return certFactory.generateCertificate(new ByteArrayInputStream(certBuffer)); 173 } 174 175 } 176