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