• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.keychain;
18 
19 import static android.app.admin.SecurityLog.TAG_CERT_AUTHORITY_INSTALLED;
20 import static android.app.admin.SecurityLog.TAG_CERT_AUTHORITY_REMOVED;
21 
22 import android.app.BroadcastOptions;
23 import android.app.IntentService;
24 import android.app.admin.SecurityLog;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.PackageManager;
28 import android.content.pm.StringParceledListSlice;
29 import android.os.Binder;
30 import android.os.Build;
31 import android.os.IBinder;
32 import android.os.UserHandle;
33 import android.security.Credentials;
34 import android.security.IKeyChainService;
35 import android.security.KeyChain;
36 import android.security.KeyStore;
37 import android.security.keymaster.KeymasterArguments;
38 import android.security.keymaster.KeymasterCertificateChain;
39 import android.security.keystore.AttestationUtils;
40 import android.security.keystore.DeviceIdAttestationException;
41 import android.security.keystore.KeyGenParameterSpec;
42 import android.security.keystore.ParcelableKeyGenParameterSpec;
43 import android.text.TextUtils;
44 import android.util.Log;
45 
46 import com.android.internal.annotations.VisibleForTesting;
47 import com.android.keychain.internal.GrantsDatabase;
48 import com.android.org.conscrypt.TrustedCertificateStore;
49 
50 import java.io.ByteArrayInputStream;
51 import java.io.IOException;
52 import java.security.InvalidAlgorithmParameterException;
53 import java.security.KeyPair;
54 import java.security.KeyPairGenerator;
55 import java.security.NoSuchAlgorithmException;
56 import java.security.NoSuchProviderException;
57 import java.security.cert.Certificate;
58 import java.security.cert.CertificateEncodingException;
59 import java.security.cert.CertificateException;
60 import java.security.cert.CertificateFactory;
61 import java.security.cert.X509Certificate;
62 import java.util.ArrayList;
63 import java.util.Collections;
64 import java.util.List;
65 
66 import javax.security.auth.x500.X500Principal;
67 
68 public class KeyChainService extends IntentService {
69 
70     private static final String TAG = "KeyChain";
71     private static final String CERT_INSTALLER_PACKAGE = "com.android.certinstaller";
72     private static final String SYSTEM_PACKAGE = "android.uid.system:1000";
73 
74     /** created in onCreate(), closed in onDestroy() */
75     private GrantsDatabase mGrantsDb;
76     private Injector mInjector;
77 
KeyChainService()78     public KeyChainService() {
79         super(KeyChainService.class.getSimpleName());
80         mInjector = new Injector();
81     }
82 
onCreate()83     @Override public void onCreate() {
84         super.onCreate();
85         mGrantsDb = new GrantsDatabase(this);
86     }
87 
88     @Override
onDestroy()89     public void onDestroy() {
90         super.onDestroy();
91         mGrantsDb.destroy();
92         mGrantsDb = null;
93     }
94 
95     private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() {
96         private final KeyStore mKeyStore = KeyStore.getInstance();
97         private final TrustedCertificateStore mTrustedCertificateStore
98                 = new TrustedCertificateStore();
99         private final Context mContext = KeyChainService.this;
100 
101         @Override
102         public String requestPrivateKey(String alias) {
103             checkArgs(alias);
104 
105             final String keystoreAlias = Credentials.USER_PRIVATE_KEY + alias;
106             final int uid = mInjector.getCallingUid();
107             return mKeyStore.grant(keystoreAlias, uid);
108         }
109 
110         @Override public byte[] getCertificate(String alias) {
111             checkArgs(alias);
112             return mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
113         }
114 
115         @Override public byte[] getCaCertificates(String alias) {
116             checkArgs(alias);
117             return mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
118         }
119 
120         @Override public boolean isUserSelectable(String alias) {
121             validateAlias(alias);
122             return mGrantsDb.isUserSelectable(alias);
123         }
124 
125         @Override public void setUserSelectable(String alias, boolean isUserSelectable) {
126             validateAlias(alias);
127             checkSystemCaller();
128             mGrantsDb.setIsUserSelectable(alias, isUserSelectable);
129         }
130 
131         @Override public int generateKeyPair(
132                 String algorithm, ParcelableKeyGenParameterSpec parcelableSpec) {
133             checkSystemCaller();
134             final KeyGenParameterSpec spec = parcelableSpec.getSpec();
135             final String alias = spec.getKeystoreAlias();
136             // Validate the alias here to avoid relying on KeyGenParameterSpec c'tor preventing
137             // the creation of a KeyGenParameterSpec instance with a non-empty alias.
138             if (TextUtils.isEmpty(alias) || spec.getUid() != KeyStore.UID_SELF) {
139                 Log.e(TAG, "Cannot generate key pair with empty alias or specified uid.");
140                 return KeyChain.KEY_GEN_MISSING_ALIAS;
141             }
142 
143             if (spec.getAttestationChallenge() != null) {
144                 Log.e(TAG, "Key generation request should not include an Attestation challenge.");
145                 return KeyChain.KEY_GEN_SUPERFLUOUS_ATTESTATION_CHALLENGE;
146             }
147 
148             try {
149                 KeyPairGenerator generator = KeyPairGenerator.getInstance(
150                         algorithm, "AndroidKeyStore");
151                 // Do not prepend USER_PRIVATE_KEY to the alias because
152                 // AndroidKeyStoreKeyPairGeneratorSpi will helpfully prepend that in
153                 // generateKeyPair.
154                 generator.initialize(spec);
155                 KeyPair kp = generator.generateKeyPair();
156                 if (kp == null) {
157                     Log.e(TAG, "Key generation failed.");
158                     return KeyChain.KEY_GEN_FAILURE;
159                 }
160                 return KeyChain.KEY_GEN_SUCCESS;
161             } catch (NoSuchAlgorithmException e) {
162                 Log.e(TAG, "Invalid algorithm requested", e);
163                 return KeyChain.KEY_GEN_NO_SUCH_ALGORITHM;
164             } catch (InvalidAlgorithmParameterException e) {
165                 Log.e(TAG, "Invalid algorithm params", e);
166                 return KeyChain.KEY_GEN_INVALID_ALGORITHM_PARAMETERS;
167             } catch (NoSuchProviderException e) {
168                 Log.e(TAG, "Could not find Keystore.", e);
169                 return KeyChain.KEY_GEN_NO_KEYSTORE_PROVIDER;
170             }
171         }
172 
173         @Override public int attestKey(
174                 String alias, byte[] attestationChallenge,
175                 int[] idAttestationFlags,
176                 KeymasterCertificateChain attestationChain) {
177             checkSystemCaller();
178             validateAlias(alias);
179 
180             if (attestationChallenge == null) {
181                 Log.e(TAG, String.format("Missing attestation challenge for alias %s", alias));
182                 return KeyChain.KEY_ATTESTATION_MISSING_CHALLENGE;
183             }
184 
185             final KeymasterArguments attestArgs;
186             try {
187                 attestArgs = AttestationUtils.prepareAttestationArguments(
188                         mContext, idAttestationFlags, attestationChallenge);
189             } catch (DeviceIdAttestationException e) {
190                 Log.e(TAG, "Failed collecting attestation data", e);
191                 return KeyChain.KEY_ATTESTATION_CANNOT_COLLECT_DATA;
192             }
193             final String keystoreAlias = Credentials.USER_PRIVATE_KEY + alias;
194             final int errorCode = mKeyStore.attestKey(keystoreAlias, attestArgs, attestationChain);
195             if (errorCode != KeyStore.NO_ERROR) {
196                 Log.e(TAG, String.format("Failure attesting for key %s: %d", alias, errorCode));
197                 if (errorCode == KeyStore.CANNOT_ATTEST_IDS) {
198                     return KeyChain.KEY_ATTESTATION_CANNOT_ATTEST_IDS;
199                 } else {
200                     // General failure, cannot discern which.
201                     return KeyChain.KEY_ATTESTATION_FAILURE;
202                 }
203             }
204 
205             return KeyChain.KEY_ATTESTATION_SUCCESS;
206         }
207 
208         @Override public boolean setKeyPairCertificate(String alias, byte[] userCertificate,
209                 byte[] userCertificateChain) {
210             checkSystemCaller();
211             if (!mKeyStore.isUnlocked()) {
212                 Log.e(TAG, "Keystore is " + mKeyStore.state().toString() + ". Credentials cannot"
213                         + " be installed until device is unlocked");
214                 return false;
215             }
216 
217             if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertificate,
218                         KeyStore.UID_SELF, KeyStore.FLAG_NONE)) {
219                 Log.e(TAG, "Failed to import user certificate " + userCertificate);
220                 return false;
221             }
222 
223             if (userCertificateChain != null && userCertificateChain.length > 0) {
224                 if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, userCertificateChain,
225                             KeyStore.UID_SELF, KeyStore.FLAG_NONE)) {
226                     Log.e(TAG, "Failed to import certificate chain" + userCertificateChain);
227                     if (!mKeyStore.delete(Credentials.USER_CERTIFICATE + alias)) {
228                         Log.e(TAG, "Failed to clean up key chain after certificate chain"
229                                 + " importing failed");
230                     }
231                     return false;
232                 }
233             } else {
234                 if (!mKeyStore.delete(Credentials.CA_CERTIFICATE + alias)) {
235                     Log.e(TAG, "Failed to remove CA certificate chain for alias " + alias);
236                 }
237             }
238             broadcastKeychainChange();
239             broadcastLegacyStorageChange();
240             return true;
241         }
242 
243         private void validateAlias(String alias) {
244             if (alias == null) {
245                 throw new NullPointerException("alias == null");
246             }
247         }
248 
249         private void validateKeyStoreState() {
250             if (!mKeyStore.isUnlocked()) {
251                 throw new IllegalStateException("keystore is "
252                         + mKeyStore.state().toString());
253             }
254         }
255 
256         private void checkArgs(String alias) {
257             validateAlias(alias);
258             validateKeyStoreState();
259 
260             final int callingUid = mInjector.getCallingUid();
261             if (!mGrantsDb.hasGrant(callingUid, alias)) {
262                 throw new IllegalStateException("uid " + callingUid
263                         + " doesn't have permission to access the requested alias");
264             }
265         }
266 
267         @Override public String installCaCertificate(byte[] caCertificate) {
268             checkCertInstallerOrSystemCaller();
269             final String alias;
270             String subjectForAudit = null;
271             try {
272                 final X509Certificate cert = parseCertificate(caCertificate);
273                 if (mInjector.isSecurityLoggingEnabled()) {
274                     subjectForAudit =
275                             cert.getSubjectX500Principal().getName(X500Principal.CANONICAL);
276                 }
277                 synchronized (mTrustedCertificateStore) {
278                     mTrustedCertificateStore.installCertificate(cert);
279                     alias = mTrustedCertificateStore.getCertificateAlias(cert);
280                 }
281             } catch (IOException | CertificateException e) {
282                 if (subjectForAudit != null) {
283                     mInjector.writeSecurityEvent(
284                             TAG_CERT_AUTHORITY_INSTALLED, 0 /*result*/, subjectForAudit);
285                 }
286                 throw new IllegalStateException(e);
287             }
288             if (subjectForAudit != null) {
289                 mInjector.writeSecurityEvent(
290                         TAG_CERT_AUTHORITY_INSTALLED, 1 /*result*/, subjectForAudit);
291             }
292             broadcastLegacyStorageChange();
293             broadcastTrustStoreChange();
294             return alias;
295         }
296 
297         /**
298          * Install a key pair to the keystore.
299          *
300          * @param privateKey The private key associated with the client certificate
301          * @param userCertificate The client certificate to be installed
302          * @param userCertificateChain The rest of the chain for the client certificate
303          * @param alias The alias under which the key pair is installed
304          * @return Whether the operation succeeded or not.
305          */
306         @Override public boolean installKeyPair(byte[] privateKey, byte[] userCertificate,
307                 byte[] userCertificateChain, String alias) {
308             checkCertInstallerOrSystemCaller();
309             if (!mKeyStore.isUnlocked()) {
310                 Log.e(TAG, "Keystore is " + mKeyStore.state().toString() + ". Credentials cannot"
311                         + " be installed until device is unlocked");
312                 return false;
313             }
314             if (!removeKeyPair(alias)) {
315                 return false;
316             }
317             if (!mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, privateKey, -1,
318                     KeyStore.FLAG_ENCRYPTED)) {
319                 Log.e(TAG, "Failed to import private key " + alias);
320                 return false;
321             }
322             if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertificate, -1,
323                     KeyStore.FLAG_ENCRYPTED)) {
324                 Log.e(TAG, "Failed to import user certificate " + userCertificate);
325                 if (!mKeyStore.delete(Credentials.USER_PRIVATE_KEY + alias)) {
326                     Log.e(TAG, "Failed to delete private key after certificate importing failed");
327                 }
328                 return false;
329             }
330             if (userCertificateChain != null && userCertificateChain.length > 0) {
331                 if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, userCertificateChain, -1,
332                         KeyStore.FLAG_ENCRYPTED)) {
333                     Log.e(TAG, "Failed to import certificate chain" + userCertificateChain);
334                     if (!removeKeyPair(alias)) {
335                         Log.e(TAG, "Failed to clean up key chain after certificate chain"
336                                 + " importing failed");
337                     }
338                     return false;
339                 }
340             }
341             broadcastKeychainChange();
342             broadcastLegacyStorageChange();
343             return true;
344         }
345 
346         @Override public boolean removeKeyPair(String alias) {
347             checkCertInstallerOrSystemCaller();
348             if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) {
349                 return false;
350             }
351             mGrantsDb.removeAliasInformation(alias);
352             broadcastKeychainChange();
353             broadcastLegacyStorageChange();
354             return true;
355         }
356 
357         private X509Certificate parseCertificate(byte[] bytes) throws CertificateException {
358             CertificateFactory cf = CertificateFactory.getInstance("X.509");
359             return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));
360         }
361 
362         @Override public boolean reset() {
363             // only Settings should be able to reset
364             checkSystemCaller();
365             mGrantsDb.removeAllAliasesInformation();
366             boolean ok = true;
367             synchronized (mTrustedCertificateStore) {
368                 // delete user-installed CA certs
369                 for (String alias : mTrustedCertificateStore.aliases()) {
370                     if (TrustedCertificateStore.isUser(alias)) {
371                         if (!deleteCertificateEntry(alias)) {
372                             ok = false;
373                         }
374                     }
375                 }
376             }
377             broadcastTrustStoreChange();
378             broadcastKeychainChange();
379             broadcastLegacyStorageChange();
380             return ok;
381         }
382 
383         @Override public boolean deleteCaCertificate(String alias) {
384             // only Settings should be able to delete
385             checkSystemCaller();
386             boolean ok = true;
387             synchronized (mTrustedCertificateStore) {
388                 ok = deleteCertificateEntry(alias);
389             }
390             broadcastTrustStoreChange();
391             broadcastLegacyStorageChange();
392             return ok;
393         }
394 
395         private boolean deleteCertificateEntry(String alias) {
396             String subjectForAudit = null;
397             if (mInjector.isSecurityLoggingEnabled()) {
398                 final Certificate cert = mTrustedCertificateStore.getCertificate(alias);
399                 if (cert instanceof X509Certificate) {
400                     subjectForAudit = ((X509Certificate) cert)
401                             .getSubjectX500Principal().getName(X500Principal.CANONICAL);
402                 }
403             }
404 
405             try {
406                 mTrustedCertificateStore.deleteCertificateEntry(alias);
407                 if (subjectForAudit != null) {
408                     mInjector.writeSecurityEvent(
409                             TAG_CERT_AUTHORITY_REMOVED, 1 /*result*/, subjectForAudit);
410                 }
411                 return true;
412             } catch (IOException | CertificateException e) {
413                 Log.w(TAG, "Problem removing CA certificate " + alias, e);
414                 if (subjectForAudit != null) {
415                     mInjector.writeSecurityEvent(
416                             TAG_CERT_AUTHORITY_REMOVED, 0 /*result*/, subjectForAudit);
417                 }
418                 return false;
419             }
420         }
421 
422         private void checkCertInstallerOrSystemCaller() {
423             final String caller = callingPackage();
424             if (!SYSTEM_PACKAGE.equals(caller) && !CERT_INSTALLER_PACKAGE.equals(caller)) {
425                 throw new SecurityException("Not system or cert installer package: " + caller);
426             }
427         }
428 
429         private void checkSystemCaller() {
430             final String caller = callingPackage();
431             if (!SYSTEM_PACKAGE.equals(caller)) {
432                 throw new SecurityException("Not system package: " + caller);
433             }
434         }
435 
436         private String callingPackage() {
437             return getPackageManager().getNameForUid(mInjector.getCallingUid());
438         }
439 
440         @Override public boolean hasGrant(int uid, String alias) {
441             checkSystemCaller();
442             return mGrantsDb.hasGrant(uid, alias);
443         }
444 
445         @Override public void setGrant(int uid, String alias, boolean value) {
446             checkSystemCaller();
447             mGrantsDb.setGrant(uid, alias, value);
448             broadcastPermissionChange(uid, alias, value);
449             broadcastLegacyStorageChange();
450         }
451 
452         @Override
453         public StringParceledListSlice getUserCaAliases() {
454             synchronized (mTrustedCertificateStore) {
455                 return new StringParceledListSlice(new ArrayList<String>(
456                         mTrustedCertificateStore.userAliases()));
457             }
458         }
459 
460         @Override
461         public StringParceledListSlice getSystemCaAliases() {
462             synchronized (mTrustedCertificateStore) {
463                 return new StringParceledListSlice(new ArrayList<String>(
464                         mTrustedCertificateStore.allSystemAliases()));
465             }
466         }
467 
468         @Override
469         public boolean containsCaAlias(String alias) {
470             return mTrustedCertificateStore.containsAlias(alias);
471         }
472 
473         @Override
474         public byte[] getEncodedCaCertificate(String alias, boolean includeDeletedSystem) {
475             synchronized (mTrustedCertificateStore) {
476                 X509Certificate certificate = (X509Certificate) mTrustedCertificateStore
477                         .getCertificate(alias, includeDeletedSystem);
478                 if (certificate == null) {
479                     Log.w(TAG, "Could not find CA certificate " + alias);
480                     return null;
481                 }
482                 try {
483                     return certificate.getEncoded();
484                 } catch (CertificateEncodingException e) {
485                     Log.w(TAG, "Error while encoding CA certificate " + alias);
486                     return null;
487                 }
488             }
489         }
490 
491         @Override
492         public List<String> getCaCertificateChainAliases(String rootAlias,
493                 boolean includeDeletedSystem) {
494             synchronized (mTrustedCertificateStore) {
495                 X509Certificate root = (X509Certificate) mTrustedCertificateStore.getCertificate(
496                         rootAlias, includeDeletedSystem);
497                 try {
498                     List<X509Certificate> chain = mTrustedCertificateStore.getCertificateChain(
499                             root);
500                     List<String> aliases = new ArrayList<String>(chain.size());
501                     final int n = chain.size();
502                     for (int i = 0; i < n; ++i) {
503                         String alias = mTrustedCertificateStore.getCertificateAlias(chain.get(i),
504                                 true);
505                         if (alias != null) {
506                             aliases.add(alias);
507                         }
508                     }
509                     return aliases;
510                 } catch (CertificateException e) {
511                     Log.w(TAG, "Error retrieving cert chain for root " + rootAlias);
512                     return Collections.emptyList();
513                 }
514             }
515         }
516     };
517 
onBind(Intent intent)518     @Override public IBinder onBind(Intent intent) {
519         if (IKeyChainService.class.getName().equals(intent.getAction())) {
520             return mIKeyChainService;
521         }
522         return null;
523     }
524 
525     @Override
onHandleIntent(final Intent intent)526     protected void onHandleIntent(final Intent intent) {
527         if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
528             mGrantsDb.purgeOldGrants(getPackageManager());
529         }
530     }
531 
broadcastLegacyStorageChange()532     private void broadcastLegacyStorageChange() {
533         Intent intent = new Intent(KeyChain.ACTION_STORAGE_CHANGED);
534         BroadcastOptions opts = BroadcastOptions.makeBasic();
535         opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.N_MR1);
536         sendBroadcastAsUser(intent, UserHandle.of(UserHandle.myUserId()), null, opts.toBundle());
537     }
538 
broadcastKeychainChange()539     private void broadcastKeychainChange() {
540         Intent intent = new Intent(KeyChain.ACTION_KEYCHAIN_CHANGED);
541         sendBroadcastAsUser(intent, UserHandle.of(UserHandle.myUserId()));
542     }
543 
broadcastTrustStoreChange()544     private void broadcastTrustStoreChange() {
545         Intent intent = new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED);
546         sendBroadcastAsUser(intent, UserHandle.of(UserHandle.myUserId()));
547     }
548 
broadcastPermissionChange(int uid, String alias, boolean access)549     private void broadcastPermissionChange(int uid, String alias, boolean access) {
550         // Since the permission change only impacts one uid only send to that uid's packages.
551         final PackageManager packageManager = getPackageManager();
552         String[] packages = packageManager.getPackagesForUid(uid);
553         if (packages == null) {
554             return;
555         }
556         for (String pckg : packages) {
557             Intent intent = new Intent(KeyChain.ACTION_KEY_ACCESS_CHANGED);
558             intent.putExtra(KeyChain.EXTRA_KEY_ALIAS, alias);
559             intent.putExtra(KeyChain.EXTRA_KEY_ACCESSIBLE, access);
560             intent.setPackage(pckg);
561             sendBroadcastAsUser(intent, UserHandle.of(UserHandle.myUserId()));
562         }
563     }
564 
565     @VisibleForTesting
setInjector(Injector injector)566     void setInjector(Injector injector) {
567         mInjector = injector;
568     }
569 
570     /**
571      * Injector for mocking out dependencies in tests.
572      */
573     @VisibleForTesting
574     static class Injector {
isSecurityLoggingEnabled()575         public boolean isSecurityLoggingEnabled() {
576             return SecurityLog.isLoggingEnabled();
577         }
578 
writeSecurityEvent(int tag, Object... payload)579         public void writeSecurityEvent(int tag, Object... payload) {
580             SecurityLog.writeEvent(tag, payload);
581         }
582 
getCallingUid()583         public int getCallingUid() {
584             return Binder.getCallingUid();
585         }
586     }
587 }
588