1 /*
2  * Copyright (C) 2009 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.certinstaller;
18 
19 import static android.security.KeyStore.UID_SELF;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.app.KeyguardManager;
23 import android.app.admin.DevicePolicyManager;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.os.Bundle;
28 import android.os.Process;
29 import android.os.RemoteException;
30 import android.os.UserHandle;
31 import android.security.Credentials;
32 import android.security.IKeyChainService;
33 import android.security.KeyChain;
34 import android.text.Html;
35 import android.text.TextUtils;
36 import android.util.Log;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.org.bouncycastle.asn1.ASN1InputStream;
40 import com.android.org.bouncycastle.asn1.ASN1Sequence;
41 import com.android.org.bouncycastle.asn1.DEROctetString;
42 import com.android.org.bouncycastle.asn1.x509.BasicConstraints;
43 import com.android.org.conscrypt.TrustedCertificateStore;
44 
45 import java.io.ByteArrayInputStream;
46 import java.io.IOException;
47 import java.security.KeyFactory;
48 import java.security.KeyStore;
49 import java.security.KeyStore.PasswordProtection;
50 import java.security.KeyStore.PrivateKeyEntry;
51 import java.security.NoSuchAlgorithmException;
52 import java.security.PrivateKey;
53 import java.security.cert.Certificate;
54 import java.security.cert.CertificateEncodingException;
55 import java.security.cert.CertificateException;
56 import java.security.cert.CertificateFactory;
57 import java.security.cert.X509Certificate;
58 import java.security.spec.InvalidKeySpecException;
59 import java.security.spec.PKCS8EncodedKeySpec;
60 import java.util.ArrayList;
61 import java.util.Enumeration;
62 import java.util.HashMap;
63 import java.util.List;
64 import java.util.Map;
65 
66 /**
67  * A helper class for accessing the raw data in the intent extra and handling
68  * certificates.
69  */
70 class CredentialHelper {
71     private static final String DATA_KEY = "data";
72     private static final String CERTS_KEY = "crts";
73     private static final String USER_KEY_ALGORITHM = "user_key_algorithm";
74     private static final String SETTINGS_PACKAGE = "com.android.settings";
75 
76     private static final String TAG = "CredentialHelper";
77 
78     // keep raw data from intent's extra
79     private HashMap<String, byte[]> mBundle = new HashMap<String, byte[]>();
80 
81     private String mName = "";
82     private String mCertUsageSelected = "";
83     private String mReferrer = "";
84     private int mUid = Process.INVALID_UID;
85     private PrivateKey mUserKey;
86     private X509Certificate mUserCert;
87     private List<X509Certificate> mCaCerts = new ArrayList<X509Certificate>();
88 
CredentialHelper()89     CredentialHelper() {
90     }
91 
92     /**
93      * @param byteMap keeps raw data from intent's extra
94      * @param name
95      * @param referrer
96      * @param certUsageSelected used to assign mUid according to certificate usage
97      * @param uid is ignored unless certUsageSelected is null
98      */
CredentialHelper(@onNull Map<String, byte[]> byteMap, @Nullable String name, @Nullable String referrer, @Nullable String certUsageSelected, int uid)99     CredentialHelper(@NonNull Map<String, byte[]> byteMap, @Nullable String name,
100             @Nullable String referrer, @Nullable String certUsageSelected, int uid) {
101         if (name != null) {
102             mName = name;
103         }
104 
105         if (referrer != null) {
106             mReferrer = referrer;
107         }
108 
109         if (certUsageSelected != null) {
110             setCertUsageSelectedAndUid(certUsageSelected);
111         } else {
112             mUid = uid;
113         }
114 
115         for (String key : byteMap.keySet()) {
116             byte[] bytes = byteMap.get(key);
117             Log.d(TAG, "   " + key + ": " + ((bytes == null) ? -1 : bytes.length));
118             mBundle.put(key, bytes);
119         }
120         parseCert(getData(KeyChain.EXTRA_CERTIFICATE));
121     }
122 
onSaveStates(Bundle outStates)123     synchronized void onSaveStates(Bundle outStates) {
124         try {
125             outStates.putSerializable(DATA_KEY, mBundle);
126             outStates.putString(KeyChain.EXTRA_NAME, mName);
127             outStates.putInt(Credentials.EXTRA_INSTALL_AS_UID, mUid);
128             if (mUserKey != null) {
129                 Log.d(TAG, "Key algorithm: " + mUserKey.getAlgorithm());
130                 outStates.putString(USER_KEY_ALGORITHM, mUserKey.getAlgorithm());
131                 outStates.putByteArray(Credentials.USER_PRIVATE_KEY,
132                         mUserKey.getEncoded());
133             }
134             ArrayList<byte[]> certs = new ArrayList<byte[]>(mCaCerts.size() + 1);
135             if (mUserCert != null) {
136                 certs.add(mUserCert.getEncoded());
137             }
138             for (X509Certificate cert : mCaCerts) {
139                 certs.add(cert.getEncoded());
140             }
141             outStates.putByteArray(CERTS_KEY, Util.toBytes(certs));
142         } catch (CertificateEncodingException e) {
143             throw new AssertionError(e);
144         }
145     }
146 
onRestoreStates(Bundle savedStates)147     void onRestoreStates(Bundle savedStates) {
148         mBundle = (HashMap) savedStates.getSerializable(DATA_KEY);
149         mName = savedStates.getString(KeyChain.EXTRA_NAME);
150         mUid = savedStates.getInt(Credentials.EXTRA_INSTALL_AS_UID, Process.INVALID_UID);
151         String userKeyAlgorithm = savedStates.getString(USER_KEY_ALGORITHM);
152         byte[] userKeyBytes = savedStates.getByteArray(Credentials.USER_PRIVATE_KEY);
153         Log.d(TAG, "Loaded key algorithm: " + userKeyAlgorithm);
154         if (userKeyAlgorithm != null && userKeyBytes != null) {
155             setPrivateKey(userKeyAlgorithm, userKeyBytes);
156         }
157 
158         ArrayList<byte[]> certs = Util.fromBytes(savedStates.getByteArray(CERTS_KEY));
159         for (byte[] cert : certs) {
160             parseCert(cert);
161         }
162     }
163 
getUserCertificate()164     X509Certificate getUserCertificate() {
165         return mUserCert;
166     }
167 
parseCert(byte[] bytes)168     private void parseCert(byte[] bytes) {
169         if (bytes == null) {
170             return;
171         }
172 
173         try {
174             CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
175             X509Certificate cert = (X509Certificate)
176                     certFactory.generateCertificate(
177                             new ByteArrayInputStream(bytes));
178             if (isCa(cert)) {
179                 Log.d(TAG, "got a CA cert");
180                 mCaCerts.add(cert);
181             } else {
182                 Log.d(TAG, "got a user cert");
183                 mUserCert = cert;
184             }
185         } catch (CertificateException e) {
186             Log.w(TAG, "parseCert(): " + e);
187         }
188     }
189 
isCa(X509Certificate cert)190     private boolean isCa(X509Certificate cert) {
191         try {
192             // TODO: add a test about this
193             byte[] asn1EncodedBytes = cert.getExtensionValue("2.5.29.19");
194             if (asn1EncodedBytes == null) {
195                 return false;
196             }
197             DEROctetString derOctetString = (DEROctetString)
198                     new ASN1InputStream(asn1EncodedBytes).readObject();
199             byte[] octets = derOctetString.getOctets();
200             ASN1Sequence sequence = (ASN1Sequence)
201                     new ASN1InputStream(octets).readObject();
202             return BasicConstraints.getInstance(sequence).isCA();
203         } catch (IOException e) {
204             return false;
205         }
206     }
207 
hasPkcs12KeyStore()208     boolean hasPkcs12KeyStore() {
209         return mBundle.containsKey(KeyChain.EXTRA_PKCS12);
210     }
211 
hasPrivateKey()212     boolean hasPrivateKey() {
213         return mBundle.containsKey(Credentials.EXTRA_PRIVATE_KEY);
214     }
215 
getUidFromCertificateUsage(String certUsage)216     int getUidFromCertificateUsage(String certUsage) {
217         if (Credentials.CERTIFICATE_USAGE_WIFI.equals(certUsage)) {
218             return Process.WIFI_UID;
219         } else {
220             return UID_SELF;
221         }
222     }
223 
hasUserCertificate()224     boolean hasUserCertificate() {
225         return (mUserCert != null);
226     }
227 
hasCaCerts()228     boolean hasCaCerts() {
229         return !mCaCerts.isEmpty();
230     }
231 
hasAnyForSystemInstall()232     boolean hasAnyForSystemInstall() {
233         return (mUserKey != null) || hasUserCertificate() || hasCaCerts();
234     }
235 
setPrivateKey(String algorithm, byte[] bytes)236     void setPrivateKey(String algorithm, byte[] bytes) {
237         try {
238             KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
239             mUserKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
240         } catch (NoSuchAlgorithmException e) {
241             throw new AssertionError(e);
242         } catch (InvalidKeySpecException e) {
243             throw new AssertionError(e);
244         }
245     }
246 
containsAnyRawData()247     boolean containsAnyRawData() {
248         return !mBundle.isEmpty();
249     }
250 
getData(String key)251     byte[] getData(String key) {
252         return mBundle.get(key);
253     }
254 
putPkcs12Data(byte[] data)255     void putPkcs12Data(byte[] data) {
256         mBundle.put(KeyChain.EXTRA_PKCS12, data);
257     }
258 
getDescription(Context context)259     CharSequence getDescription(Context context) {
260         // TODO: create more descriptive string
261         StringBuilder sb = new StringBuilder();
262         String newline = "<br>";
263         if (mUserKey != null) {
264             sb.append(context.getString(R.string.one_userkey)).append(newline);
265             sb.append(context.getString(R.string.userkey_type)).append(mUserKey.getAlgorithm())
266                     .append(newline);
267         }
268         if (mUserCert != null) {
269             sb.append(context.getString(R.string.one_usercrt)).append(newline);
270         }
271         int n = mCaCerts.size();
272         if (n > 0) {
273             if (n == 1) {
274                 sb.append(context.getString(R.string.one_cacrt));
275             } else {
276                 sb.append(context.getString(R.string.n_cacrts, n));
277             }
278         }
279         return Html.fromHtml(sb.toString());
280     }
281 
setName(String name)282     void setName(String name) {
283         mName = name;
284     }
285 
getName()286     String getName() {
287         return mName;
288     }
289 
setCertUsageSelectedAndUid(String certUsageSelected)290     void setCertUsageSelectedAndUid(String certUsageSelected) {
291         mCertUsageSelected = certUsageSelected;
292         mUid = getUidFromCertificateUsage(certUsageSelected);
293     }
294 
getCertUsageSelected()295     String getCertUsageSelected() {
296         return mCertUsageSelected;
297     }
298 
calledBySettings()299     boolean calledBySettings() {
300         return mReferrer != null && mReferrer.equals(SETTINGS_PACKAGE);
301     }
302 
createSystemInstallIntent(final Context context)303     Intent createSystemInstallIntent(final Context context) {
304         Intent intent = new Intent("com.android.credentials.INSTALL");
305         // To prevent the private key from being sniffed, we explicitly spell
306         // out the intent receiver class.
307         intent.setComponent(ComponentName.unflattenFromString(
308                 context.getString(R.string.config_system_install_component)));
309         intent.putExtra(Credentials.EXTRA_INSTALL_AS_UID, mUid);
310         intent.putExtra(Credentials.EXTRA_USER_KEY_ALIAS, mName);
311         try {
312             if (mUserKey != null) {
313                 intent.putExtra(Credentials.EXTRA_USER_PRIVATE_KEY_DATA,
314                         mUserKey.getEncoded());
315             }
316             if (mUserCert != null) {
317                 intent.putExtra(Credentials.EXTRA_USER_CERTIFICATE_DATA,
318                         Credentials.convertToPem(mUserCert));
319             }
320             if (!mCaCerts.isEmpty()) {
321                 X509Certificate[] caCerts
322                         = mCaCerts.toArray(new X509Certificate[mCaCerts.size()]);
323                 intent.putExtra(Credentials.EXTRA_CA_CERTIFICATES_DATA,
324                         Credentials.convertToPem(caCerts));
325             }
326             return intent;
327         } catch (IOException e) {
328             throw new AssertionError(e);
329         } catch (CertificateEncodingException e) {
330             throw new AssertionError(e);
331         }
332     }
333 
installVpnAndAppsTrustAnchors(Context context, IKeyChainService keyChainService)334     boolean installVpnAndAppsTrustAnchors(Context context, IKeyChainService keyChainService) {
335         final TrustedCertificateStore trustedCertificateStore = new TrustedCertificateStore();
336         for (X509Certificate caCert : mCaCerts) {
337             byte[] bytes = null;
338             try {
339                 bytes = caCert.getEncoded();
340             } catch (CertificateEncodingException e) {
341                 throw new AssertionError(e);
342             }
343             if (bytes != null) {
344                 try {
345                     keyChainService.installCaCertificate(bytes);
346                 } catch (RemoteException e) {
347                     Log.w(TAG, "installCaCertsToKeyChain(): " + e);
348                     return false;
349                 }
350 
351                 String alias = trustedCertificateStore.getCertificateAlias(caCert);
352                 if (alias == null) {
353                     Log.e(TAG, "alias is null");
354                     return false;
355                 }
356 
357                 maybeApproveCaCert(context, alias);
358             }
359         }
360         return true;
361     }
362 
maybeApproveCaCert(Context context, String alias)363     private void maybeApproveCaCert(Context context, String alias) {
364         final KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
365         if (keyguardManager.isDeviceSecure(UserHandle.myUserId())) {
366             // Since the cert is installed by real user, the cert is approved by the user
367             final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
368             dpm.approveCaCert(alias, UserHandle.myUserId(), true);
369         }
370     }
371 
hasPassword()372     boolean hasPassword() {
373         if (!hasPkcs12KeyStore()) {
374             return false;
375         }
376         try {
377             return loadPkcs12Internal(new PasswordProtection(new char[] {})) == null;
378         } catch (Exception e) {
379             return true;
380         }
381     }
382 
extractPkcs12(String password)383     boolean extractPkcs12(String password) {
384         try {
385             return extractPkcs12Internal(new PasswordProtection(password.toCharArray()));
386         } catch (Exception e) {
387             Log.w(TAG, "extractPkcs12(): " + e, e);
388             return false;
389         }
390     }
391 
extractPkcs12Internal(PasswordProtection password)392     private boolean extractPkcs12Internal(PasswordProtection password)
393             throws Exception {
394         // TODO: add test about this
395         java.security.KeyStore keystore = loadPkcs12Internal(password);
396 
397         Enumeration<String> aliases = keystore.aliases();
398         if (!aliases.hasMoreElements()) {
399             Log.e(TAG, "PKCS12 file has no elements");
400             return false;
401         }
402 
403         while (aliases.hasMoreElements()) {
404             String alias = aliases.nextElement();
405             if (keystore.isKeyEntry(alias)) {
406                 KeyStore.Entry entry = keystore.getEntry(alias, password);
407                 Log.d(TAG, "extracted alias = " + alias + ", entry=" + entry.getClass());
408 
409                 if (entry instanceof PrivateKeyEntry) {
410                     if (TextUtils.isEmpty(mName)) {
411                         mName = alias;
412                     }
413                     return installFrom((PrivateKeyEntry) entry);
414                 }
415             } else {
416                 // KeyStore.getEntry with non-null ProtectionParameter can only be invoked on
417                 // PrivateKeyEntry or SecretKeyEntry.
418                 // See https://docs.oracle.com/javase/8/docs/api/java/security/KeyStore.html
419                 Log.d(TAG, "Skip non-key entry, alias = " + alias);
420             }
421         }
422         return true;
423     }
424 
loadPkcs12Internal(PasswordProtection password)425     private java.security.KeyStore loadPkcs12Internal(PasswordProtection password)
426             throws Exception {
427         java.security.KeyStore keystore = java.security.KeyStore.getInstance("PKCS12");
428         keystore.load(new ByteArrayInputStream(getData(KeyChain.EXTRA_PKCS12)),
429                       password.getPassword());
430         return keystore;
431     }
432 
installFrom(PrivateKeyEntry entry)433     private synchronized boolean installFrom(PrivateKeyEntry entry) {
434         mUserKey = entry.getPrivateKey();
435         mUserCert = (X509Certificate) entry.getCertificate();
436 
437         Certificate[] certs = entry.getCertificateChain();
438         Log.d(TAG, "# certs extracted = " + certs.length);
439         mCaCerts = new ArrayList<X509Certificate>(certs.length);
440         for (Certificate c : certs) {
441             X509Certificate cert = (X509Certificate) c;
442             if (isCa(cert)) {
443                 mCaCerts.add(cert);
444             }
445         }
446         Log.d(TAG, "# ca certs extracted = " + mCaCerts.size());
447 
448         return true;
449     }
450 
451     /**
452      * Returns true if this credential contains _only_ CA certificates to be used as trust anchors
453      * for VPN and apps.
454      */
hasOnlyVpnAndAppsTrustAnchors()455     public boolean hasOnlyVpnAndAppsTrustAnchors() {
456         if (!hasCaCerts()) {
457             return false;
458         }
459         if (mUid != UID_SELF) {
460             // VPN and Apps trust anchors can only be installed under UID_SELF
461             return false;
462         }
463 
464         if (mUserKey != null) {
465             // We are installing a key pair for client authentication, its CA
466             // should have nothing to do with VPN and apps trust anchors.
467             return false;
468         } else {
469             return true;
470         }
471     }
472 
getReferrer()473     public String getReferrer() {
474         return mReferrer;
475     }
476 
477     @VisibleForTesting
getUid()478     public int getUid() {
479         return mUid;
480     }
481 }
482