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