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
17package com.android.server.devicepolicy;
18
19import android.app.Notification;
20import android.app.NotificationManager;
21import android.app.PendingIntent;
22import android.content.BroadcastReceiver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.pm.ActivityInfo;
27import android.content.pm.PackageManager;
28import android.content.res.Resources;
29import android.graphics.Color;
30import android.os.Build;
31import android.os.Handler;
32import android.os.RemoteException;
33import android.os.UserHandle;
34import android.os.UserManager;
35import android.os.storage.StorageManager;
36import android.provider.Settings;
37import android.security.Credentials;
38import android.security.KeyChain;
39import android.security.KeyChain.KeyChainConnection;
40import android.util.Log;
41
42import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
43import com.android.internal.notification.SystemNotificationChannels;
44import com.android.internal.R;
45
46import java.io.ByteArrayInputStream;
47import java.io.IOException;
48import java.security.cert.CertificateException;
49import java.security.cert.CertificateFactory;
50import java.security.cert.X509Certificate;
51import java.util.List;
52import java.util.Set;
53
54public 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