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.IntentService;
20import android.content.ContentValues;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.PackageManager;
24import android.content.pm.ParceledListSlice;
25import android.database.Cursor;
26import android.database.DatabaseUtils;
27import android.database.sqlite.SQLiteDatabase;
28import android.database.sqlite.SQLiteOpenHelper;
29import android.os.Binder;
30import android.os.IBinder;
31import android.os.Process;
32import android.os.UserHandle;
33import android.os.UserManager;
34import android.security.Credentials;
35import android.security.IKeyChainService;
36import android.security.KeyChain;
37import android.security.KeyStore;
38import android.util.Log;
39import com.android.internal.util.ParcelableString;
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    public KeyChainService() {
76        super(KeyChainService.class.getSimpleName());
77    }
78
79    @Override public void onCreate() {
80        super.onCreate();
81        mDatabaseHelper = new DatabaseHelper(this);
82    }
83
84    @Override
85    public void onDestroy() {
86        super.onDestroy();
87        mDatabaseHelper.close();
88        mDatabaseHelper = null;
89    }
90
91    private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() {
92        private final KeyStore mKeyStore = KeyStore.getInstance();
93        private final TrustedCertificateStore mTrustedCertificateStore
94                = new TrustedCertificateStore();
95
96        @Override
97        public String requestPrivateKey(String alias) {
98            checkArgs(alias);
99
100            final String keystoreAlias = Credentials.USER_PRIVATE_KEY + alias;
101            final int uid = Binder.getCallingUid();
102            if (!mKeyStore.grant(keystoreAlias, uid)) {
103                return null;
104            }
105            final int userHandle = UserHandle.getUserId(uid);
106            final int systemUidForUser = UserHandle.getUid(userHandle, Process.SYSTEM_UID);
107
108            final StringBuilder sb = new StringBuilder();
109            sb.append(systemUidForUser);
110            sb.append('_');
111            sb.append(keystoreAlias);
112
113            return sb.toString();
114        }
115
116        @Override public byte[] getCertificate(String alias) {
117            checkArgs(alias);
118            return mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
119        }
120
121        private void checkArgs(String alias) {
122            if (alias == null) {
123                throw new NullPointerException("alias == null");
124            }
125            if (!mKeyStore.isUnlocked()) {
126                throw new IllegalStateException("keystore is "
127                        + mKeyStore.state().toString());
128            }
129
130            final int callingUid = getCallingUid();
131            if (!hasGrantInternal(mDatabaseHelper.getReadableDatabase(), callingUid, alias)) {
132                throw new IllegalStateException("uid " + callingUid
133                        + " doesn't have permission to access the requested alias");
134            }
135        }
136
137        @Override public void installCaCertificate(byte[] caCertificate) {
138            checkCertInstallerOrSystemCaller();
139            checkUserRestriction();
140            try {
141                synchronized (mTrustedCertificateStore) {
142                    mTrustedCertificateStore.installCertificate(parseCertificate(caCertificate));
143                }
144            } catch (IOException e) {
145                throw new IllegalStateException(e);
146            } catch (CertificateException e) {
147                throw new IllegalStateException(e);
148            }
149            broadcastStorageChange();
150        }
151
152        @Override public boolean installKeyPair(byte[] privateKey, byte[] userCertificate,
153                String alias) {
154            checkCertInstallerOrSystemCaller();
155            if (!mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, privateKey, -1,
156                    KeyStore.FLAG_ENCRYPTED)) {
157                Log.e(TAG, "Failed to import private key " + alias);
158                return false;
159            }
160            if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertificate, -1,
161                    KeyStore.FLAG_ENCRYPTED)) {
162                Log.e(TAG, "Failed to import user certificate " + userCertificate);
163                if (!mKeyStore.delete(Credentials.USER_PRIVATE_KEY + alias)) {
164                    Log.e(TAG, "Failed to delete private key after certificate importing failed");
165                }
166                return false;
167            }
168            broadcastStorageChange();
169            return true;
170        }
171
172        private X509Certificate parseCertificate(byte[] bytes) throws CertificateException {
173            CertificateFactory cf = CertificateFactory.getInstance("X.509");
174            return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));
175        }
176
177        @Override public boolean reset() {
178            // only Settings should be able to reset
179            checkSystemCaller();
180            checkUserRestriction();
181            removeAllGrants(mDatabaseHelper.getWritableDatabase());
182            boolean ok = true;
183            synchronized (mTrustedCertificateStore) {
184                // delete user-installed CA certs
185                for (String alias : mTrustedCertificateStore.aliases()) {
186                    if (TrustedCertificateStore.isUser(alias)) {
187                        if (!deleteCertificateEntry(alias)) {
188                            ok = false;
189                        }
190                    }
191                }
192            }
193            broadcastStorageChange();
194            return ok;
195        }
196
197        @Override public boolean deleteCaCertificate(String alias) {
198            // only Settings should be able to delete
199            checkSystemCaller();
200            checkUserRestriction();
201            boolean ok = true;
202            synchronized (mTrustedCertificateStore) {
203                ok = deleteCertificateEntry(alias);
204            }
205            broadcastStorageChange();
206            return ok;
207        }
208
209        private boolean deleteCertificateEntry(String alias) {
210            try {
211                mTrustedCertificateStore.deleteCertificateEntry(alias);
212                return true;
213            } catch (IOException e) {
214                Log.w(TAG, "Problem removing CA certificate " + alias, e);
215                return false;
216            } catch (CertificateException e) {
217                Log.w(TAG, "Problem removing CA certificate " + alias, e);
218                return false;
219            }
220        }
221
222        private void checkCertInstallerOrSystemCaller() {
223            String actual = checkCaller("com.android.certinstaller");
224            if (actual == null) {
225                return;
226            }
227            checkSystemCaller();
228        }
229        private void checkSystemCaller() {
230            String actual = checkCaller("android.uid.system:1000");
231            if (actual != null) {
232                throw new IllegalStateException(actual);
233            }
234        }
235        private void checkUserRestriction() {
236            UserManager um = (UserManager) getSystemService(USER_SERVICE);
237            if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
238                throw new SecurityException("User cannot modify credentials");
239            }
240        }
241        /**
242         * Returns null if actually caller is expected, otherwise return bad package to report
243         */
244        private String checkCaller(String expectedPackage) {
245            String actualPackage = getPackageManager().getNameForUid(getCallingUid());
246            return (!expectedPackage.equals(actualPackage)) ? actualPackage : null;
247        }
248
249        @Override public boolean hasGrant(int uid, String alias) {
250            checkSystemCaller();
251            return hasGrantInternal(mDatabaseHelper.getReadableDatabase(), uid, alias);
252        }
253
254        @Override public void setGrant(int uid, String alias, boolean value) {
255            checkSystemCaller();
256            setGrantInternal(mDatabaseHelper.getWritableDatabase(), uid, alias, value);
257            broadcastStorageChange();
258        }
259
260        private ParceledListSlice<ParcelableString> makeAliasesParcelableSynchronised(
261                Set<String> aliasSet) {
262            List<ParcelableString> aliases = new ArrayList<ParcelableString>(aliasSet.size());
263            for (String alias : aliasSet) {
264                ParcelableString parcelableString = new ParcelableString();
265                parcelableString.string = alias;
266                aliases.add(parcelableString);
267            }
268            return new ParceledListSlice<ParcelableString>(aliases);
269        }
270
271        @Override
272        public ParceledListSlice<ParcelableString> getUserCaAliases() {
273            synchronized (mTrustedCertificateStore) {
274                Set<String> aliasSet = mTrustedCertificateStore.userAliases();
275                return makeAliasesParcelableSynchronised(aliasSet);
276            }
277        }
278
279        @Override
280        public ParceledListSlice<ParcelableString> getSystemCaAliases() {
281            synchronized (mTrustedCertificateStore) {
282                Set<String> aliasSet = mTrustedCertificateStore.allSystemAliases();
283                return makeAliasesParcelableSynchronised(aliasSet);
284            }
285        }
286
287        @Override
288        public boolean containsCaAlias(String alias) {
289            return mTrustedCertificateStore.containsAlias(alias);
290        }
291
292        @Override
293        public byte[] getEncodedCaCertificate(String alias, boolean includeDeletedSystem) {
294            synchronized (mTrustedCertificateStore) {
295                X509Certificate certificate = (X509Certificate) mTrustedCertificateStore
296                        .getCertificate(alias, includeDeletedSystem);
297                if (certificate == null) {
298                    Log.w(TAG, "Could not find CA certificate " + alias);
299                    return null;
300                }
301                try {
302                    return certificate.getEncoded();
303                } catch (CertificateEncodingException e) {
304                    Log.w(TAG, "Error while encoding CA certificate " + alias);
305                    return null;
306                }
307            }
308        }
309
310        @Override
311        public List<String> getCaCertificateChainAliases(String rootAlias,
312                boolean includeDeletedSystem) {
313            synchronized (mTrustedCertificateStore) {
314                X509Certificate root = (X509Certificate) mTrustedCertificateStore.getCertificate(
315                        rootAlias, includeDeletedSystem);
316                try {
317                    List<X509Certificate> chain = mTrustedCertificateStore.getCertificateChain(
318                            root);
319                    List<String> aliases = new ArrayList<String>(chain.size());
320                    final int n = chain.size();
321                    for (int i = 0; i < n; ++i) {
322                        String alias = mTrustedCertificateStore.getCertificateAlias(chain.get(i),
323                                true);
324                        if (alias != null) {
325                            aliases.add(alias);
326                        }
327                    }
328                    return aliases;
329                } catch (CertificateException e) {
330                    Log.w(TAG, "Error retrieving cert chain for root " + rootAlias);
331                    return Collections.emptyList();
332                }
333            }
334        }
335    };
336
337    private boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) {
338        final long numMatches = DatabaseUtils.longForQuery(db, SELECTION_COUNT_OF_MATCHING_GRANTS,
339                new String[]{String.valueOf(uid), alias});
340        return numMatches > 0;
341    }
342
343    private void setGrantInternal(final SQLiteDatabase db,
344            final int uid, final String alias, final boolean value) {
345        if (value) {
346            if (!hasGrantInternal(db, uid, alias)) {
347                final ContentValues values = new ContentValues();
348                values.put(GRANTS_ALIAS, alias);
349                values.put(GRANTS_GRANTEE_UID, uid);
350                db.insert(TABLE_GRANTS, GRANTS_ALIAS, values);
351            }
352        } else {
353            db.delete(TABLE_GRANTS, SELECT_GRANTS_BY_UID_AND_ALIAS,
354                    new String[]{String.valueOf(uid), alias});
355        }
356    }
357
358    private void removeAllGrants(final SQLiteDatabase db) {
359        db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
360    }
361
362    private class DatabaseHelper extends SQLiteOpenHelper {
363        public DatabaseHelper(Context context) {
364            super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
365        }
366
367        @Override
368        public void onCreate(final SQLiteDatabase db) {
369            db.execSQL("CREATE TABLE " + TABLE_GRANTS + " (  "
370                    + GRANTS_ALIAS + " STRING NOT NULL,  "
371                    + GRANTS_GRANTEE_UID + " INTEGER NOT NULL,  "
372                    + "UNIQUE (" + GRANTS_ALIAS + "," + GRANTS_GRANTEE_UID + "))");
373        }
374
375        @Override
376        public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
377            Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
378
379            if (oldVersion == 1) {
380                // the first upgrade step goes here
381                oldVersion++;
382            }
383        }
384    }
385
386    @Override public IBinder onBind(Intent intent) {
387        if (IKeyChainService.class.getName().equals(intent.getAction())) {
388            return mIKeyChainService;
389        }
390        return null;
391    }
392
393    @Override
394    protected void onHandleIntent(final Intent intent) {
395        if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
396            purgeOldGrants();
397        }
398    }
399
400    private void purgeOldGrants() {
401        final PackageManager packageManager = getPackageManager();
402        final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
403        Cursor cursor = null;
404        db.beginTransaction();
405        try {
406            cursor = db.query(TABLE_GRANTS,
407                    new String[]{GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null);
408            while (cursor.moveToNext()) {
409                final int uid = cursor.getInt(0);
410                final boolean packageExists = packageManager.getPackagesForUid(uid) != null;
411                if (packageExists) {
412                    continue;
413                }
414                Log.d(TAG, "deleting grants for UID " + uid
415                        + " because its package is no longer installed");
416                db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_UID,
417                        new String[]{Integer.toString(uid)});
418            }
419            db.setTransactionSuccessful();
420        } finally {
421            if (cursor != null) {
422                cursor.close();
423            }
424            db.endTransaction();
425        }
426    }
427
428    private void broadcastStorageChange() {
429        Intent intent = new Intent(KeyChain.ACTION_STORAGE_CHANGED);
430        sendBroadcastAsUser(intent, new UserHandle(UserHandle.myUserId()));
431    }
432
433}
434