1 /*
2  * Copyright (C) 2017 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.server.devicepolicy;
18 
19 import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG;
20 
21 import android.app.Notification;
22 import android.app.PendingIntent;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.pm.ActivityInfo;
28 import android.content.pm.PackageManager;
29 import android.content.res.Resources;
30 import android.os.Handler;
31 import android.os.RemoteException;
32 import android.os.UserHandle;
33 import android.os.storage.StorageManager;
34 import android.provider.Settings;
35 import android.security.Credentials;
36 import android.security.KeyChain;
37 import android.security.KeyChain.KeyChainConnection;
38 
39 import com.android.internal.R;
40 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
41 import com.android.internal.notification.SystemNotificationChannels;
42 import com.android.server.utils.Slogf;
43 
44 import java.io.ByteArrayInputStream;
45 import java.io.IOException;
46 import java.security.cert.CertificateException;
47 import java.security.cert.CertificateFactory;
48 import java.security.cert.X509Certificate;
49 import java.util.List;
50 
51 public class CertificateMonitor {
52     protected static final int MONITORING_CERT_NOTIFICATION_ID = SystemMessage.NOTE_SSL_CERT_INFO;
53 
54     private final DevicePolicyManagerService mService;
55     private final DevicePolicyManagerService.Injector mInjector;
56     private final Handler mHandler;
57 
CertificateMonitor(final DevicePolicyManagerService service, final DevicePolicyManagerService.Injector injector, final Handler handler)58     public CertificateMonitor(final DevicePolicyManagerService service,
59             final DevicePolicyManagerService.Injector injector, final Handler handler) {
60         mService = service;
61         mInjector = injector;
62         mHandler = handler;
63 
64         // Broadcast filter for changes to the trusted certificate store. Listens on the background
65         // handler to avoid blocking time-critical tasks on the main handler thread.
66         IntentFilter filter = new IntentFilter();
67         filter.addAction(Intent.ACTION_USER_STARTED);
68         filter.addAction(Intent.ACTION_USER_UNLOCKED);
69         filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
70         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
71         mInjector.mContext.registerReceiverAsUser(
72                 mRootCaReceiver, UserHandle.ALL, filter, null, mHandler);
73     }
74 
installCaCert(final UserHandle userHandle, byte[] certBuffer)75     public String installCaCert(final UserHandle userHandle, byte[] certBuffer) {
76         // Convert certificate data from X509 format to PEM.
77         byte[] pemCert;
78         try {
79             X509Certificate cert = parseCert(certBuffer);
80             pemCert = Credentials.convertToPem(cert);
81         } catch (CertificateException | IOException ce) {
82             Slogf.e(LOG_TAG, "Problem converting cert", ce);
83             return null;
84         }
85 
86         try (KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(userHandle)) {
87             return keyChainConnection.getService().installCaCertificate(pemCert);
88         } catch (RemoteException e) {
89             Slogf.e(LOG_TAG, "installCaCertsToKeyChain(): ", e);
90         } catch (InterruptedException e1) {
91             Slogf.w(LOG_TAG, "installCaCertsToKeyChain(): ", e1);
92             Thread.currentThread().interrupt();
93         }
94         return null;
95     }
96 
uninstallCaCerts(final UserHandle userHandle, final String[] aliases)97     public void uninstallCaCerts(final UserHandle userHandle, final String[] aliases) {
98         try (KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(userHandle)) {
99             for (int i = 0 ; i < aliases.length; i++) {
100                 keyChainConnection.getService().deleteCaCertificate(aliases[i]);
101             }
102         } catch (RemoteException e) {
103             Slogf.e(LOG_TAG, "from CaCertUninstaller: ", e);
104         } catch (InterruptedException ie) {
105             Slogf.w(LOG_TAG, "CaCertUninstaller: ", ie);
106             Thread.currentThread().interrupt();
107         }
108     }
109 
getInstalledCaCertificates(UserHandle userHandle)110     private List<String> getInstalledCaCertificates(UserHandle userHandle)
111             throws RemoteException, RuntimeException {
112         try (KeyChainConnection conn = mInjector.keyChainBindAsUser(userHandle)) {
113             return conn.getService().getUserCaAliases().getList();
114         } catch (InterruptedException e) {
115             Thread.currentThread().interrupt();
116             throw new RuntimeException(e);
117         } catch (AssertionError e) {
118             throw new RuntimeException(e);
119         }
120     }
121 
onCertificateApprovalsChanged(int userId)122     public void onCertificateApprovalsChanged(int userId) {
123         mHandler.post(() -> updateInstalledCertificates(UserHandle.of(userId)));
124     }
125 
126     /**
127      * Broadcast receiver for changes to the trusted certificate store.
128      */
129     private final BroadcastReceiver mRootCaReceiver = new BroadcastReceiver() {
130         @Override
131         public void onReceive(Context context, Intent intent) {
132             if (StorageManager.inCryptKeeperBounce()) {
133                 return;
134             }
135             final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
136             updateInstalledCertificates(UserHandle.of(userId));
137         }
138     };
139 
updateInstalledCertificates(final UserHandle userHandle)140     private void updateInstalledCertificates(final UserHandle userHandle) {
141         final int userId = userHandle.getIdentifier();
142         if (!mInjector.getUserManager().isUserUnlocked(userId)) {
143             return;
144         }
145 
146         final List<String> installedCerts;
147         try {
148             installedCerts = getInstalledCaCertificates(userHandle);
149         } catch (RemoteException | RuntimeException e) {
150             Slogf.e(LOG_TAG, e, "Could not retrieve certificates from KeyChain service for user %d",
151                     userId);
152             return;
153         }
154         mService.onInstalledCertificatesChanged(userHandle, installedCerts);
155 
156         final int pendingCertificateCount =
157                 installedCerts.size() - mService.getAcceptedCaCertificates(userHandle).size();
158         if (pendingCertificateCount != 0) {
159             final Notification noti = buildNotification(userHandle, pendingCertificateCount);
160             mInjector.getNotificationManager().notifyAsUser(
161                     LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, noti, userHandle);
162         } else {
163             mInjector.getNotificationManager().cancelAsUser(
164                     LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, userHandle);
165         }
166     }
167 
buildNotification(UserHandle userHandle, int pendingCertificateCount)168     private Notification buildNotification(UserHandle userHandle, int pendingCertificateCount) {
169         final Context userContext;
170         try {
171             userContext = mInjector.createContextAsUser(userHandle);
172         } catch (PackageManager.NameNotFoundException e) {
173             Slogf.e(LOG_TAG, e, "Create context as %s failed", userHandle);
174             return null;
175         }
176 
177         final Resources resources = mInjector.getResources();
178         final int smallIconId;
179         final String contentText;
180 
181         int parentUserId = userHandle.getIdentifier();
182 
183         if (mService.getProfileOwnerAsUser(userHandle.getIdentifier()) != null) {
184             contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
185                     mService.getProfileOwnerName(userHandle.getIdentifier()));
186             smallIconId = R.drawable.stat_sys_certificate_info;
187             parentUserId = mService.getProfileParentId(userHandle.getIdentifier());
188         } else if (mService.getDeviceOwnerUserId() == userHandle.getIdentifier()) {
189             contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
190                     mService.getDeviceOwnerName());
191             smallIconId = R.drawable.stat_sys_certificate_info;
192         } else {
193             contentText = resources.getString(R.string.ssl_ca_cert_noti_by_unknown);
194             smallIconId = android.R.drawable.stat_sys_warning;
195         }
196 
197         // Create an intent to launch an activity showing information about the certificate.
198         Intent dialogIntent = new Intent(Settings.ACTION_MONITORING_CERT_INFO);
199         dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
200         dialogIntent.putExtra(Settings.EXTRA_NUMBER_OF_CERTIFICATES, pendingCertificateCount);
201         dialogIntent.putExtra(Intent.EXTRA_USER_ID, userHandle.getIdentifier());
202 
203         // The intent should only be allowed to resolve to a system app.
204         ActivityInfo targetInfo = dialogIntent.resolveActivityInfo(
205                 mInjector.getPackageManager(), PackageManager.MATCH_SYSTEM_ONLY);
206         if (targetInfo != null) {
207             dialogIntent.setComponent(targetInfo.getComponentName());
208         }
209 
210         // Simple notification clicks are immutable
211         PendingIntent notifyIntent = mInjector.pendingIntentGetActivityAsUser(userContext, 0,
212                 dialogIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE,
213                 null, UserHandle.of(parentUserId));
214 
215         return new Notification.Builder(userContext, SystemNotificationChannels.SECURITY)
216                 .setSmallIcon(smallIconId)
217                 .setContentTitle(resources.getQuantityText(R.plurals.ssl_ca_cert_warning,
218                         pendingCertificateCount))
219                 .setContentText(contentText)
220                 .setContentIntent(notifyIntent)
221                 .setShowWhen(false)
222                 .setColor(R.color.system_notification_accent_color)
223                 .build();
224     }
225 
parseCert(byte[] certBuffer)226     private static X509Certificate parseCert(byte[] certBuffer) throws CertificateException {
227         CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
228         return (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(
229                 certBuffer));
230     }
231 }
232