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