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