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