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