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.database.Cursor;
25import android.database.DatabaseUtils;
26import android.database.sqlite.SQLiteDatabase;
27import android.database.sqlite.SQLiteOpenHelper;
28import android.os.Binder;
29import android.os.IBinder;
30import android.os.Process;
31import android.security.Credentials;
32import android.security.IKeyChainService;
33import android.security.KeyChain;
34import android.security.KeyStore;
35import android.util.Log;
36import java.io.ByteArrayInputStream;
37import java.io.IOException;
38import java.security.cert.CertificateException;
39import java.security.cert.CertificateFactory;
40import java.security.cert.X509Certificate;
41
42import com.android.org.conscrypt.TrustedCertificateStore;
43
44public class KeyChainService extends IntentService {
45
46    private static final String TAG = "KeyChain";
47
48    private static final String DATABASE_NAME = "grants.db";
49    private static final int DATABASE_VERSION = 1;
50    private static final String TABLE_GRANTS = "grants";
51    private static final String GRANTS_ALIAS = "alias";
52    private static final String GRANTS_GRANTEE_UID = "uid";
53
54    /** created in onCreate(), closed in onDestroy() */
55    public DatabaseHelper mDatabaseHelper;
56
57    private static final String SELECTION_COUNT_OF_MATCHING_GRANTS =
58            "SELECT COUNT(*) FROM " + TABLE_GRANTS
59                    + " WHERE " + GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
60
61    private static final String SELECT_GRANTS_BY_UID_AND_ALIAS =
62            GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
63
64    private static final String SELECTION_GRANTS_BY_UID = GRANTS_GRANTEE_UID + "=?";
65
66    public KeyChainService() {
67        super(KeyChainService.class.getSimpleName());
68    }
69
70    @Override public void onCreate() {
71        super.onCreate();
72        mDatabaseHelper = new DatabaseHelper(this);
73    }
74
75    @Override
76    public void onDestroy() {
77        super.onDestroy();
78        mDatabaseHelper.close();
79        mDatabaseHelper = null;
80    }
81
82    private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() {
83        private final KeyStore mKeyStore = KeyStore.getInstance();
84        private final TrustedCertificateStore mTrustedCertificateStore
85                = new TrustedCertificateStore();
86
87        @Override
88        public String requestPrivateKey(String alias) {
89            checkArgs(alias);
90
91            final String keystoreAlias = Credentials.USER_PRIVATE_KEY + alias;
92            final int uid = Binder.getCallingUid();
93            if (!mKeyStore.grant(keystoreAlias, uid)) {
94                return null;
95            }
96
97            final StringBuilder sb = new StringBuilder();
98            sb.append(Process.SYSTEM_UID);
99            sb.append('_');
100            sb.append(keystoreAlias);
101
102            return sb.toString();
103        }
104
105        @Override public byte[] getCertificate(String alias) {
106            checkArgs(alias);
107            return mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
108        }
109
110        private void checkArgs(String alias) {
111            if (alias == null) {
112                throw new NullPointerException("alias == null");
113            }
114            if (!mKeyStore.isUnlocked()) {
115                throw new IllegalStateException("keystore is "
116                        + mKeyStore.state().toString());
117            }
118
119            final int callingUid = getCallingUid();
120            if (!hasGrantInternal(mDatabaseHelper.getReadableDatabase(), callingUid, alias)) {
121                throw new IllegalStateException("uid " + callingUid
122                        + " doesn't have permission to access the requested alias");
123            }
124        }
125
126        @Override public void installCaCertificate(byte[] caCertificate) {
127            checkCertInstallerOrSystemCaller();
128            try {
129                synchronized (mTrustedCertificateStore) {
130                    mTrustedCertificateStore.installCertificate(parseCertificate(caCertificate));
131                }
132            } catch (IOException e) {
133                throw new IllegalStateException(e);
134            } catch (CertificateException e) {
135                throw new IllegalStateException(e);
136            }
137            broadcastStorageChange();
138        }
139
140        private X509Certificate parseCertificate(byte[] bytes) throws CertificateException {
141            CertificateFactory cf = CertificateFactory.getInstance("X.509");
142            return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));
143        }
144
145        @Override public boolean reset() {
146            // only Settings should be able to reset
147            checkSystemCaller();
148            removeAllGrants(mDatabaseHelper.getWritableDatabase());
149            boolean ok = true;
150            synchronized (mTrustedCertificateStore) {
151                // delete user-installed CA certs
152                for (String alias : mTrustedCertificateStore.aliases()) {
153                    if (TrustedCertificateStore.isUser(alias)) {
154                        if (!deleteCertificateEntry(alias)) {
155                            ok = false;
156                        }
157                    }
158                }
159            }
160            broadcastStorageChange();
161            return ok;
162        }
163
164        @Override public boolean deleteCaCertificate(String alias) {
165            // only Settings should be able to delete
166            checkSystemCaller();
167            boolean ok = true;
168            synchronized (mTrustedCertificateStore) {
169                ok = deleteCertificateEntry(alias);
170            }
171            broadcastStorageChange();
172            return ok;
173        }
174
175        private boolean deleteCertificateEntry(String alias) {
176            try {
177                mTrustedCertificateStore.deleteCertificateEntry(alias);
178                return true;
179            } catch (IOException e) {
180                Log.w(TAG, "Problem removing CA certificate " + alias, e);
181                return false;
182            } catch (CertificateException e) {
183                Log.w(TAG, "Problem removing CA certificate " + alias, e);
184                return false;
185            }
186        }
187
188        private void checkCertInstallerOrSystemCaller() {
189            String actual = checkCaller("com.android.certinstaller");
190            if (actual == null) {
191                return;
192            }
193            checkSystemCaller();
194        }
195        private void checkSystemCaller() {
196            String actual = checkCaller("android.uid.system:1000");
197            if (actual != null) {
198                throw new IllegalStateException(actual);
199            }
200        }
201        /**
202         * Returns null if actually caller is expected, otherwise return bad package to report
203         */
204        private String checkCaller(String expectedPackage) {
205            String actualPackage = getPackageManager().getNameForUid(getCallingUid());
206            return (!expectedPackage.equals(actualPackage)) ? actualPackage : null;
207        }
208
209        @Override public boolean hasGrant(int uid, String alias) {
210            checkSystemCaller();
211            return hasGrantInternal(mDatabaseHelper.getReadableDatabase(), uid, alias);
212        }
213
214        @Override public void setGrant(int uid, String alias, boolean value) {
215            checkSystemCaller();
216            setGrantInternal(mDatabaseHelper.getWritableDatabase(), uid, alias, value);
217            broadcastStorageChange();
218        }
219    };
220
221    private boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) {
222        final long numMatches = DatabaseUtils.longForQuery(db, SELECTION_COUNT_OF_MATCHING_GRANTS,
223                new String[]{String.valueOf(uid), alias});
224        return numMatches > 0;
225    }
226
227    private void setGrantInternal(final SQLiteDatabase db,
228            final int uid, final String alias, final boolean value) {
229        if (value) {
230            if (!hasGrantInternal(db, uid, alias)) {
231                final ContentValues values = new ContentValues();
232                values.put(GRANTS_ALIAS, alias);
233                values.put(GRANTS_GRANTEE_UID, uid);
234                db.insert(TABLE_GRANTS, GRANTS_ALIAS, values);
235            }
236        } else {
237            db.delete(TABLE_GRANTS, SELECT_GRANTS_BY_UID_AND_ALIAS,
238                    new String[]{String.valueOf(uid), alias});
239        }
240    }
241
242    private void removeAllGrants(final SQLiteDatabase db) {
243        db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
244    }
245
246    private class DatabaseHelper extends SQLiteOpenHelper {
247        public DatabaseHelper(Context context) {
248            super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
249        }
250
251        @Override
252        public void onCreate(final SQLiteDatabase db) {
253            db.execSQL("CREATE TABLE " + TABLE_GRANTS + " (  "
254                    + GRANTS_ALIAS + " STRING NOT NULL,  "
255                    + GRANTS_GRANTEE_UID + " INTEGER NOT NULL,  "
256                    + "UNIQUE (" + GRANTS_ALIAS + "," + GRANTS_GRANTEE_UID + "))");
257        }
258
259        @Override
260        public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
261            Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
262
263            if (oldVersion == 1) {
264                // the first upgrade step goes here
265                oldVersion++;
266            }
267        }
268    }
269
270    @Override public IBinder onBind(Intent intent) {
271        if (IKeyChainService.class.getName().equals(intent.getAction())) {
272            return mIKeyChainService;
273        }
274        return null;
275    }
276
277    @Override
278    protected void onHandleIntent(final Intent intent) {
279        if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
280            purgeOldGrants();
281        }
282    }
283
284    private void purgeOldGrants() {
285        final PackageManager packageManager = getPackageManager();
286        final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
287        Cursor cursor = null;
288        db.beginTransaction();
289        try {
290            cursor = db.query(TABLE_GRANTS,
291                    new String[]{GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null);
292            while (cursor.moveToNext()) {
293                final int uid = cursor.getInt(0);
294                final boolean packageExists = packageManager.getPackagesForUid(uid) != null;
295                if (packageExists) {
296                    continue;
297                }
298                Log.d(TAG, "deleting grants for UID " + uid
299                        + " because its package is no longer installed");
300                db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_UID,
301                        new String[]{Integer.toString(uid)});
302            }
303            db.setTransactionSuccessful();
304        } finally {
305            if (cursor != null) {
306                cursor.close();
307            }
308            db.endTransaction();
309        }
310    }
311
312    private void broadcastStorageChange() {
313        Intent intent = new Intent(KeyChain.ACTION_STORAGE_CHANGED);
314        sendBroadcast(intent);
315    }
316
317}
318