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