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