content: /*
   * Copyright (C) 2017 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server.devicepolicy;
  
  import android.app.Notification;
  import android.app.NotificationManager;
  import android.app.PendingIntent;
  import android.content.BroadcastReceiver;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.content.pm.ActivityInfo;
  import android.content.pm.PackageManager;
  import android.content.res.Resources;
  import android.graphics.Color;
  import android.os.Build;
  import android.os.Handler;
  import android.os.RemoteException;
  import android.os.UserHandle;
  import android.os.UserManager;
  import android.os.storage.StorageManager;
  import android.provider.Settings;
  import android.security.Credentials;
  import android.security.KeyChain;
  import android.security.KeyChain.KeyChainConnection;
  import android.util.Log;
  
  import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
  import com.android.internal.notification.SystemNotificationChannels;
  import com.android.internal.R;
  
  import java.io.ByteArrayInputStream;
  import java.io.IOException;
  import java.security.cert.CertificateException;
  import java.security.cert.CertificateFactory;
  import java.security.cert.X509Certificate;
  import java.util.List;
  import java.util.Set;
  
  public class CertificateMonitor {
      protected static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
      protected static final int MONITORING_CERT_NOTIFICATION_ID = SystemMessage.NOTE_SSL_CERT_INFO;
  
      private final DevicePolicyManagerService mService;
      private final DevicePolicyManagerService.Injector mInjector;
      private final Handler mHandler;
  
      public CertificateMonitor(final DevicePolicyManagerService service,
              final DevicePolicyManagerService.Injector injector, final Handler handler) {
          mService = service;
          mInjector = injector;
          mHandler = handler;
  
          // Broadcast filter for changes to the trusted certificate store. Listens on the background
          // handler to avoid blocking time-critical tasks on the main handler thread.
          IntentFilter filter = new IntentFilter();
          filter.addAction(Intent.ACTION_USER_STARTED);
          filter.addAction(Intent.ACTION_USER_UNLOCKED);
          filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
          filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
          mInjector.mContext.registerReceiverAsUser(
                  mRootCaReceiver, UserHandle.ALL, filter, null, mHandler);
      }
  
      public String installCaCert(final UserHandle userHandle, byte[] certBuffer) {
          // Convert certificate data from X509 format to PEM.
          byte[] pemCert;
          try {
              X509Certificate cert = parseCert(certBuffer);
              pemCert = Credentials.convertToPem(cert);
          } catch (CertificateException | IOException ce) {
              Log.e(LOG_TAG, "Problem converting cert", ce);
              return null;
          }
  
          try (KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(userHandle)) {
              return keyChainConnection.getService().installCaCertificate(pemCert);
          } catch (RemoteException e) {
              Log.e(LOG_TAG, "installCaCertsToKeyChain(): ", e);
          } catch (InterruptedException e1) {
              Log.w(LOG_TAG, "installCaCertsToKeyChain(): ", e1);
              Thread.currentThread().interrupt();
          }
          return null;
      }
  
      public void uninstallCaCerts(final UserHandle userHandle, final String[] aliases) {
          try (KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(userHandle)) {
              for (int i = 0 ; i < aliases.length; i++) {
                  keyChainConnection.getService().deleteCaCertificate(aliases[i]);
              }
          } catch (RemoteException e) {
              Log.e(LOG_TAG, "from CaCertUninstaller: ", e);
          } catch (InterruptedException ie) {
              Log.w(LOG_TAG, "CaCertUninstaller: ", ie);
              Thread.currentThread().interrupt();
          }
      }
  
      public List<String> getInstalledCaCertificates(UserHandle userHandle)
              throws RemoteException, RuntimeException {
          try (KeyChainConnection conn = mInjector.keyChainBindAsUser(userHandle)) {
              return conn.getService().getUserCaAliases().getList();
          } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
              return null;
          } catch (AssertionError e) {
              throw new RuntimeException(e);
          }
      }
  
      public void onCertificateApprovalsChanged(int userId) {
          mHandler.post(() -> updateInstalledCertificates(UserHandle.of(userId)));
      }
  
      /**
       * Broadcast receiver for changes to the trusted certificate store.
       */
      private final BroadcastReceiver mRootCaReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              if (StorageManager.inCryptKeeperBounce()) {
                  return;
              }
              final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
              updateInstalledCertificates(UserHandle.of(userId));
          }
      };
  
      private void updateInstalledCertificates(final UserHandle userHandle) {
          if (!mInjector.getUserManager().isUserUnlocked(userHandle.getIdentifier())) {
              return;
          }
  
          final List<String> installedCerts;
          try {
              installedCerts = getInstalledCaCertificates(userHandle);
          } catch (RemoteException | RuntimeException e) {
              Log.e(LOG_TAG, "Could not retrieve certificates from KeyChain service", e);
              return;
          }
          mService.onInstalledCertificatesChanged(userHandle, installedCerts);
  
          final int pendingCertificateCount =
                  installedCerts.size() - mService.getAcceptedCaCertificates(userHandle).size();
          if (pendingCertificateCount != 0) {
              final Notification noti = buildNotification(userHandle, pendingCertificateCount);
              mInjector.getNotificationManager().notifyAsUser(
                      LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, noti, userHandle);
          } else {
              mInjector.getNotificationManager().cancelAsUser(
                      LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, userHandle);
          }
      }
  
      private Notification buildNotification(UserHandle userHandle, int pendingCertificateCount) {
          final Context userContext;
          try {
              userContext = mInjector.createContextAsUser(userHandle);
          } catch (PackageManager.NameNotFoundException e) {
              Log.e(LOG_TAG, "Create context as " + userHandle + " failed", e);
              return null;
          }
  
          final Resources resources = mInjector.getResources();
          final int smallIconId;
          final String contentText;
  
          int parentUserId = userHandle.getIdentifier();
  
          if (mService.getProfileOwner(userHandle.getIdentifier()) != null) {
              contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
                      mService.getProfileOwnerName(userHandle.getIdentifier()));
              smallIconId = R.drawable.stat_sys_certificate_info;
              parentUserId = mService.getProfileParentId(userHandle.getIdentifier());
          } else if (mService.getDeviceOwnerUserId() == userHandle.getIdentifier()) {
              final String ownerName = mService.getDeviceOwnerName();
              contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
                      mService.getDeviceOwnerName());
              smallIconId = R.drawable.stat_sys_certificate_info;
          } else {
              contentText = resources.getString(R.string.ssl_ca_cert_noti_by_unknown);
              smallIconId = android.R.drawable.stat_sys_warning;
          }
  
          // Create an intent to launch an activity showing information about the certificate.
          Intent dialogIntent = new Intent(Settings.ACTION_MONITORING_CERT_INFO);
          dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
          dialogIntent.putExtra(Settings.EXTRA_NUMBER_OF_CERTIFICATES, pendingCertificateCount);
          dialogIntent.putExtra(Intent.EXTRA_USER_ID, userHandle.getIdentifier());
  
          // The intent should only be allowed to resolve to a system app.
          ActivityInfo targetInfo = dialogIntent.resolveActivityInfo(
                  mInjector.getPackageManager(), PackageManager.MATCH_SYSTEM_ONLY);
          if (targetInfo != null) {
              dialogIntent.setComponent(targetInfo.getComponentName());
          }
  
          PendingIntent notifyIntent = mInjector.pendingIntentGetActivityAsUser(userContext, 0,
                  dialogIntent, PendingIntent.FLAG_UPDATE_CURRENT, null,
                  UserHandle.of(parentUserId));
  
          return new Notification.Builder(userContext, SystemNotificationChannels.SECURITY)
                  .setSmallIcon(smallIconId)
                  .setContentTitle(resources.getQuantityText(R.plurals.ssl_ca_cert_warning,
                          pendingCertificateCount))
                  .setContentText(contentText)
                  .setContentIntent(notifyIntent)
                  .setShowWhen(false)
                  .setColor(R.color.system_notification_accent_color)
                  .build();
      }
  
      private static X509Certificate parseCert(byte[] certBuffer) throws CertificateException {
          CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
          return (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(
                  certBuffer));
      }
  }
  