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