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