1/*
2 * Copyright (C) 2011 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.keychain;
18
19import android.app.BroadcastOptions;
20import android.app.IntentService;
21import android.content.ContentValues;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.content.pm.StringParceledListSlice;
26import android.database.Cursor;
27import android.database.DatabaseUtils;
28import android.database.sqlite.SQLiteDatabase;
29import android.database.sqlite.SQLiteOpenHelper;
30import android.os.Binder;
31import android.os.Build;
32import android.os.IBinder;
33import android.os.Process;
34import android.os.UserHandle;
35import android.security.Credentials;
36import android.security.IKeyChainService;
37import android.security.KeyChain;
38import android.security.KeyStore;
39import android.util.Log;
40import java.io.ByteArrayInputStream;
41import java.io.IOException;
42import java.security.cert.CertificateException;
43import java.security.cert.CertificateEncodingException;
44import java.security.cert.CertificateFactory;
45import java.security.cert.X509Certificate;
46import java.util.Set;
47import java.util.List;
48import java.util.ArrayList;
49import java.util.Collections;
50
51import com.android.org.conscrypt.TrustedCertificateStore;
52
53public class KeyChainService extends IntentService {
54
55    private static final String TAG = "KeyChain";
56
57    private static final String DATABASE_NAME = "grants.db";
58    private static final int DATABASE_VERSION = 1;
59    private static final String TABLE_GRANTS = "grants";
60    private static final String GRANTS_ALIAS = "alias";
61    private static final String GRANTS_GRANTEE_UID = "uid";
62
63    /** created in onCreate(), closed in onDestroy() */
64    public DatabaseHelper mDatabaseHelper;
65
66    private static final String SELECTION_COUNT_OF_MATCHING_GRANTS =
67            "SELECT COUNT(*) FROM " + TABLE_GRANTS
68                    + " WHERE " + GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
69
70    private static final String SELECT_GRANTS_BY_UID_AND_ALIAS =
71            GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
72
73    private static final String SELECTION_GRANTS_BY_UID = GRANTS_GRANTEE_UID + "=?";
74
75    private static final String SELECTION_GRANTS_BY_ALIAS = GRANTS_ALIAS + "=?";
76
77    public KeyChainService() {
78        super(KeyChainService.class.getSimpleName());
79    }
80
81    @Override public void onCreate() {
82        super.onCreate();
83        mDatabaseHelper = new DatabaseHelper(this);
84    }
85
86    @Override
87    public void onDestroy() {
88        super.onDestroy();
89        mDatabaseHelper.close();
90        mDatabaseHelper = null;
91    }
92
93    private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() {
94        private final KeyStore mKeyStore = KeyStore.getInstance();
95        private final TrustedCertificateStore mTrustedCertificateStore
96                = new TrustedCertificateStore();
97
98        @Override
99        public String requestPrivateKey(String alias) {
100            checkArgs(alias);
101
102            final String keystoreAlias = Credentials.USER_PRIVATE_KEY + alias;
103            final int uid = Binder.getCallingUid();
104            if (!mKeyStore.grant(keystoreAlias, uid)) {
105                return null;
106            }
107            final int userHandle = UserHandle.getUserId(uid);
108            final int systemUidForUser = UserHandle.getUid(userHandle, Process.SYSTEM_UID);
109
110            final StringBuilder sb = new StringBuilder();
111            sb.append(systemUidForUser);
112            sb.append('_');
113            sb.append(keystoreAlias);
114
115            return sb.toString();
116        }
117
118        @Override public byte[] getCertificate(String alias) {
119            checkArgs(alias);
120            return mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
121        }
122
123        @Override public byte[] getCaCertificates(String alias) {
124            checkArgs(alias);
125            return mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
126        }
127
128        private void checkArgs(String alias) {
129            if (alias == null) {
130                throw new NullPointerException("alias == null");
131            }
132            if (!mKeyStore.isUnlocked()) {
133                throw new IllegalStateException("keystore is "
134                        + mKeyStore.state().toString());
135            }
136
137            final int callingUid = getCallingUid();
138            if (!hasGrantInternal(mDatabaseHelper.getReadableDatabase(), callingUid, alias)) {
139                throw new IllegalStateException("uid " + callingUid
140                        + " doesn't have permission to access the requested alias");
141            }
142        }
143
144        @Override public String installCaCertificate(byte[] caCertificate) {
145            checkCertInstallerOrSystemCaller();
146            final String alias;
147            try {
148                final X509Certificate cert = parseCertificate(caCertificate);
149                synchronized (mTrustedCertificateStore) {
150                    mTrustedCertificateStore.installCertificate(cert);
151                    alias = mTrustedCertificateStore.getCertificateAlias(cert);
152                }
153            } catch (IOException e) {
154                throw new IllegalStateException(e);
155            } catch (CertificateException e) {
156                throw new IllegalStateException(e);
157            }
158            broadcastLegacyStorageChange();
159            broadcastTrustStoreChange();
160            return alias;
161        }
162
163        /**
164         * Install a key pair to the keystore.
165         *
166         * @param privateKey The private key associated with the client certificate
167         * @param userCertificate The client certificate to be installed
168         * @param userCertificateChain The rest of the chain for the client certificate
169         * @param alias The alias under which the key pair is installed
170         * @return Whether the operation succeeded or not.
171         */
172        @Override public boolean installKeyPair(byte[] privateKey, byte[] userCertificate,
173                byte[] userCertificateChain, String alias) {
174            checkCertInstallerOrSystemCaller();
175            if (!mKeyStore.isUnlocked()) {
176                Log.e(TAG, "Keystore is " + mKeyStore.state().toString() + ". Credentials cannot"
177                        + " be installed until device is unlocked");
178                return false;
179            }
180            if (!removeKeyPair(alias)) {
181                return false;
182            }
183            if (!mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, privateKey, -1,
184                    KeyStore.FLAG_ENCRYPTED)) {
185                Log.e(TAG, "Failed to import private key " + alias);
186                return false;
187            }
188            if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertificate, -1,
189                    KeyStore.FLAG_ENCRYPTED)) {
190                Log.e(TAG, "Failed to import user certificate " + userCertificate);
191                if (!mKeyStore.delete(Credentials.USER_PRIVATE_KEY + alias)) {
192                    Log.e(TAG, "Failed to delete private key after certificate importing failed");
193                }
194                return false;
195            }
196            if (userCertificateChain != null && userCertificateChain.length > 0) {
197                if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, userCertificateChain, -1,
198                        KeyStore.FLAG_ENCRYPTED)) {
199                    Log.e(TAG, "Failed to import certificate chain" + userCertificateChain);
200                    if (!removeKeyPair(alias)) {
201                        Log.e(TAG, "Failed to clean up key chain after certificate chain"
202                                + " importing failed");
203                    }
204                    return false;
205                }
206            }
207            broadcastKeychainChange();
208            broadcastLegacyStorageChange();
209            return true;
210        }
211
212        @Override public boolean removeKeyPair(String alias) {
213            checkCertInstallerOrSystemCaller();
214            if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) {
215                return false;
216            }
217            removeGrantsForAlias(alias);
218            broadcastKeychainChange();
219            broadcastLegacyStorageChange();
220            return true;
221        }
222
223        private X509Certificate parseCertificate(byte[] bytes) throws CertificateException {
224            CertificateFactory cf = CertificateFactory.getInstance("X.509");
225            return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));
226        }
227
228        @Override public boolean reset() {
229            // only Settings should be able to reset
230            checkSystemCaller();
231            removeAllGrants(mDatabaseHelper.getWritableDatabase());
232            boolean ok = true;
233            synchronized (mTrustedCertificateStore) {
234                // delete user-installed CA certs
235                for (String alias : mTrustedCertificateStore.aliases()) {
236                    if (TrustedCertificateStore.isUser(alias)) {
237                        if (!deleteCertificateEntry(alias)) {
238                            ok = false;
239                        }
240                    }
241                }
242            }
243            broadcastTrustStoreChange();
244            broadcastKeychainChange();
245            broadcastLegacyStorageChange();
246            return ok;
247        }
248
249        @Override public boolean deleteCaCertificate(String alias) {
250            // only Settings should be able to delete
251            checkSystemCaller();
252            boolean ok = true;
253            synchronized (mTrustedCertificateStore) {
254                ok = deleteCertificateEntry(alias);
255            }
256            broadcastTrustStoreChange();
257            broadcastLegacyStorageChange();
258            return ok;
259        }
260
261        private boolean deleteCertificateEntry(String alias) {
262            try {
263                mTrustedCertificateStore.deleteCertificateEntry(alias);
264                return true;
265            } catch (IOException e) {
266                Log.w(TAG, "Problem removing CA certificate " + alias, e);
267                return false;
268            } catch (CertificateException e) {
269                Log.w(TAG, "Problem removing CA certificate " + alias, e);
270                return false;
271            }
272        }
273
274        private void checkCertInstallerOrSystemCaller() {
275            String actual = checkCaller("com.android.certinstaller");
276            if (actual == null) {
277                return;
278            }
279            checkSystemCaller();
280        }
281        private void checkSystemCaller() {
282            String actual = checkCaller("android.uid.system:1000");
283            if (actual != null) {
284                throw new IllegalStateException(actual);
285            }
286        }
287        /**
288         * Returns null if actually caller is expected, otherwise return bad package to report
289         */
290        private String checkCaller(String expectedPackage) {
291            String actualPackage = getPackageManager().getNameForUid(getCallingUid());
292            return (!expectedPackage.equals(actualPackage)) ? actualPackage : null;
293        }
294
295        @Override public boolean hasGrant(int uid, String alias) {
296            checkSystemCaller();
297            return hasGrantInternal(mDatabaseHelper.getReadableDatabase(), uid, alias);
298        }
299
300        @Override public void setGrant(int uid, String alias, boolean value) {
301            checkSystemCaller();
302            setGrantInternal(mDatabaseHelper.getWritableDatabase(), uid, alias, value);
303            broadcastPermissionChange(uid, alias, value);
304            broadcastLegacyStorageChange();
305        }
306
307        @Override
308        public StringParceledListSlice getUserCaAliases() {
309            synchronized (mTrustedCertificateStore) {
310                return new StringParceledListSlice(new ArrayList<String>(
311                        mTrustedCertificateStore.userAliases()));
312            }
313        }
314
315        @Override
316        public StringParceledListSlice getSystemCaAliases() {
317            synchronized (mTrustedCertificateStore) {
318                return new StringParceledListSlice(new ArrayList<String>(
319                        mTrustedCertificateStore.allSystemAliases()));
320            }
321        }
322
323        @Override
324        public boolean containsCaAlias(String alias) {
325            return mTrustedCertificateStore.containsAlias(alias);
326        }
327
328        @Override
329        public byte[] getEncodedCaCertificate(String alias, boolean includeDeletedSystem) {
330            synchronized (mTrustedCertificateStore) {
331                X509Certificate certificate = (X509Certificate) mTrustedCertificateStore
332                        .getCertificate(alias, includeDeletedSystem);
333                if (certificate == null) {
334                    Log.w(TAG, "Could not find CA certificate " + alias);
335                    return null;
336                }
337                try {
338                    return certificate.getEncoded();
339                } catch (CertificateEncodingException e) {
340                    Log.w(TAG, "Error while encoding CA certificate " + alias);
341                    return null;
342                }
343            }
344        }
345
346        @Override
347        public List<String> getCaCertificateChainAliases(String rootAlias,
348                boolean includeDeletedSystem) {
349            synchronized (mTrustedCertificateStore) {
350                X509Certificate root = (X509Certificate) mTrustedCertificateStore.getCertificate(
351                        rootAlias, includeDeletedSystem);
352                try {
353                    List<X509Certificate> chain = mTrustedCertificateStore.getCertificateChain(
354                            root);
355                    List<String> aliases = new ArrayList<String>(chain.size());
356                    final int n = chain.size();
357                    for (int i = 0; i < n; ++i) {
358                        String alias = mTrustedCertificateStore.getCertificateAlias(chain.get(i),
359                                true);
360                        if (alias != null) {
361                            aliases.add(alias);
362                        }
363                    }
364                    return aliases;
365                } catch (CertificateException e) {
366                    Log.w(TAG, "Error retrieving cert chain for root " + rootAlias);
367                    return Collections.emptyList();
368                }
369            }
370        }
371    };
372
373    private boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) {
374        final long numMatches = DatabaseUtils.longForQuery(db, SELECTION_COUNT_OF_MATCHING_GRANTS,
375                new String[]{String.valueOf(uid), alias});
376        return numMatches > 0;
377    }
378
379    private void setGrantInternal(final SQLiteDatabase db,
380            final int uid, final String alias, final boolean value) {
381        if (value) {
382            if (!hasGrantInternal(db, uid, alias)) {
383                final ContentValues values = new ContentValues();
384                values.put(GRANTS_ALIAS, alias);
385                values.put(GRANTS_GRANTEE_UID, uid);
386                db.insert(TABLE_GRANTS, GRANTS_ALIAS, values);
387            }
388        } else {
389            db.delete(TABLE_GRANTS, SELECT_GRANTS_BY_UID_AND_ALIAS,
390                    new String[]{String.valueOf(uid), alias});
391        }
392    }
393
394    private void removeGrantsForAlias(String alias) {
395        final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
396        db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_ALIAS, new String[] {alias});
397    }
398
399    private void removeAllGrants(final SQLiteDatabase db) {
400        db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
401    }
402
403    private class DatabaseHelper extends SQLiteOpenHelper {
404        public DatabaseHelper(Context context) {
405            super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
406        }
407
408        @Override
409        public void onCreate(final SQLiteDatabase db) {
410            db.execSQL("CREATE TABLE " + TABLE_GRANTS + " (  "
411                    + GRANTS_ALIAS + " STRING NOT NULL,  "
412                    + GRANTS_GRANTEE_UID + " INTEGER NOT NULL,  "
413                    + "UNIQUE (" + GRANTS_ALIAS + "," + GRANTS_GRANTEE_UID + "))");
414        }
415
416        @Override
417        public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
418            Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
419
420            if (oldVersion == 1) {
421                // the first upgrade step goes here
422                oldVersion++;
423            }
424        }
425    }
426
427    @Override public IBinder onBind(Intent intent) {
428        if (IKeyChainService.class.getName().equals(intent.getAction())) {
429            return mIKeyChainService;
430        }
431        return null;
432    }
433
434    @Override
435    protected void onHandleIntent(final Intent intent) {
436        if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
437            purgeOldGrants();
438        }
439    }
440
441    private void purgeOldGrants() {
442        final PackageManager packageManager = getPackageManager();
443        final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
444        Cursor cursor = null;
445        db.beginTransaction();
446        try {
447            cursor = db.query(TABLE_GRANTS,
448                    new String[]{GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null);
449            while (cursor.moveToNext()) {
450                final int uid = cursor.getInt(0);
451                final boolean packageExists = packageManager.getPackagesForUid(uid) != null;
452                if (packageExists) {
453                    continue;
454                }
455                Log.d(TAG, "deleting grants for UID " + uid
456                        + " because its package is no longer installed");
457                db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_UID,
458                        new String[]{Integer.toString(uid)});
459            }
460            db.setTransactionSuccessful();
461        } finally {
462            if (cursor != null) {
463                cursor.close();
464            }
465            db.endTransaction();
466        }
467    }
468
469    private void broadcastLegacyStorageChange() {
470        Intent intent = new Intent(KeyChain.ACTION_STORAGE_CHANGED);
471        BroadcastOptions opts = BroadcastOptions.makeBasic();
472        opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.N_MR1);
473        sendBroadcastAsUser(intent, UserHandle.of(UserHandle.myUserId()), null, opts.toBundle());
474    }
475
476    private void broadcastKeychainChange() {
477        Intent intent = new Intent(KeyChain.ACTION_KEYCHAIN_CHANGED);
478        sendBroadcastAsUser(intent, UserHandle.of(UserHandle.myUserId()));
479    }
480
481    private void broadcastTrustStoreChange() {
482        Intent intent = new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED);
483        sendBroadcastAsUser(intent, UserHandle.of(UserHandle.myUserId()));
484    }
485
486    private void broadcastPermissionChange(int uid, String alias, boolean access) {
487        // Since the permission change only impacts one uid only send to that uid's packages.
488        final PackageManager packageManager = getPackageManager();
489        String[] packages = packageManager.getPackagesForUid(uid);
490        if (packages == null) {
491            return;
492        }
493        for (String pckg : packages) {
494            Intent intent = new Intent(KeyChain.ACTION_KEY_ACCESS_CHANGED);
495            intent.putExtra(KeyChain.EXTRA_KEY_ALIAS, alias);
496            intent.putExtra(KeyChain.EXTRA_KEY_ACCESSIBLE, access);
497            intent.setPackage(pckg);
498            sendBroadcastAsUser(intent, UserHandle.of(UserHandle.myUserId()));
499        }
500    }
501}
502