Home | History | Annotate | Download | only in devicepolicy
      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 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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