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.content.Context;
20 import android.content.Intent;
21 import android.os.Bundle;
22 import android.os.RemoteException;
23 import android.security.Credentials;
24 import android.security.KeyChain;
25 import android.security.IKeyChainService;
26 import android.text.Html;
27 import android.text.TextUtils;
28 import android.util.Log;
29 import com.android.org.bouncycastle.asn1.ASN1InputStream;
30 import com.android.org.bouncycastle.asn1.ASN1Sequence;
31 import com.android.org.bouncycastle.asn1.DEROctetString;
32 import com.android.org.bouncycastle.asn1.x509.BasicConstraints;
33 import java.io.ByteArrayInputStream;
34 import java.io.IOException;
35 import java.security.KeyFactory;
36 import java.security.KeyStore.PasswordProtection;
37 import java.security.KeyStore.PrivateKeyEntry;
38 import java.security.KeyStore;
39 import java.security.NoSuchAlgorithmException;
40 import java.security.PrivateKey;
41 import java.security.cert.Certificate;
42 import java.security.cert.CertificateEncodingException;
43 import java.security.cert.CertificateException;
44 import java.security.cert.CertificateFactory;
45 import java.security.cert.X509Certificate;
46 import java.security.spec.InvalidKeySpecException;
47 import java.security.spec.PKCS8EncodedKeySpec;
48 import java.util.ArrayList;
49 import java.util.Enumeration;
50 import java.util.HashMap;
51 import java.util.List;
52 
53 /**
54  * A helper class for accessing the raw data in the intent extra and handling
55  * certificates.
56  */
57 class CredentialHelper {
58     private static final String DATA_KEY = "data";
59     private static final String CERTS_KEY = "crts";
60 
61     private static final String TAG = "CredentialHelper";
62 
63     // keep raw data from intent's extra
64     private HashMap<String, byte[]> mBundle = new HashMap<String, byte[]>();
65 
66     private String mName = "";
67     private int mUid = -1;
68     private PrivateKey mUserKey;
69     private X509Certificate mUserCert;
70     private List<X509Certificate> mCaCerts = new ArrayList<X509Certificate>();
71 
CredentialHelper()72     CredentialHelper() {
73     }
74 
CredentialHelper(Intent intent)75     CredentialHelper(Intent intent) {
76         Bundle bundle = intent.getExtras();
77         if (bundle == null) {
78             return;
79         }
80 
81         String name = bundle.getString(KeyChain.EXTRA_NAME);
82         bundle.remove(KeyChain.EXTRA_NAME);
83         if (name != null) {
84             mName = name;
85         }
86 
87         mUid = bundle.getInt(Credentials.EXTRA_INSTALL_AS_UID, -1);
88         bundle.remove(Credentials.EXTRA_INSTALL_AS_UID);
89 
90         Log.d(TAG, "# extras: " + bundle.size());
91         for (String key : bundle.keySet()) {
92             byte[] bytes = bundle.getByteArray(key);
93             Log.d(TAG, "   " + key + ": " + ((bytes == null) ? -1 : bytes.length));
94             mBundle.put(key, bytes);
95         }
96         parseCert(getData(KeyChain.EXTRA_CERTIFICATE));
97     }
98 
onSaveStates(Bundle outStates)99     synchronized void onSaveStates(Bundle outStates) {
100         try {
101             outStates.putSerializable(DATA_KEY, mBundle);
102             outStates.putString(KeyChain.EXTRA_NAME, mName);
103             if (mUserKey != null) {
104                 outStates.putByteArray(Credentials.USER_PRIVATE_KEY,
105                         mUserKey.getEncoded());
106             }
107             ArrayList<byte[]> certs = new ArrayList<byte[]>(mCaCerts.size() + 1);
108             if (mUserCert != null) {
109                 certs.add(mUserCert.getEncoded());
110             }
111             for (X509Certificate cert : mCaCerts) {
112                 certs.add(cert.getEncoded());
113             }
114             outStates.putByteArray(CERTS_KEY, Util.toBytes(certs));
115         } catch (CertificateEncodingException e) {
116             throw new AssertionError(e);
117         }
118     }
119 
onRestoreStates(Bundle savedStates)120     void onRestoreStates(Bundle savedStates) {
121         mBundle = (HashMap) savedStates.getSerializable(DATA_KEY);
122         mName = savedStates.getString(KeyChain.EXTRA_NAME);
123         byte[] bytes = savedStates.getByteArray(Credentials.USER_PRIVATE_KEY);
124         if (bytes != null) {
125             setPrivateKey(bytes);
126         }
127 
128         ArrayList<byte[]> certs = Util.fromBytes(savedStates.getByteArray(CERTS_KEY));
129         for (byte[] cert : certs) {
130             parseCert(cert);
131         }
132     }
133 
getUserCertificate()134     X509Certificate getUserCertificate() {
135         return mUserCert;
136     }
137 
parseCert(byte[] bytes)138     private void parseCert(byte[] bytes) {
139         if (bytes == null) {
140             return;
141         }
142 
143         try {
144             CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
145             X509Certificate cert = (X509Certificate)
146                     certFactory.generateCertificate(
147                             new ByteArrayInputStream(bytes));
148             if (isCa(cert)) {
149                 Log.d(TAG, "got a CA cert");
150                 mCaCerts.add(cert);
151             } else {
152                 Log.d(TAG, "got a user cert");
153                 mUserCert = cert;
154             }
155         } catch (CertificateException e) {
156             Log.w(TAG, "parseCert(): " + e);
157         }
158     }
159 
isCa(X509Certificate cert)160     private boolean isCa(X509Certificate cert) {
161         try {
162             // TODO: add a test about this
163             byte[] asn1EncodedBytes = cert.getExtensionValue("2.5.29.19");
164             if (asn1EncodedBytes == null) {
165                 return false;
166             }
167             DEROctetString derOctetString = (DEROctetString)
168                     new ASN1InputStream(asn1EncodedBytes).readObject();
169             byte[] octets = derOctetString.getOctets();
170             ASN1Sequence sequence = (ASN1Sequence)
171                     new ASN1InputStream(octets).readObject();
172             return BasicConstraints.getInstance(sequence).isCA();
173         } catch (IOException e) {
174             return false;
175         }
176     }
177 
hasPkcs12KeyStore()178     boolean hasPkcs12KeyStore() {
179         return mBundle.containsKey(KeyChain.EXTRA_PKCS12);
180     }
181 
hasKeyPair()182     boolean hasKeyPair() {
183         return mBundle.containsKey(Credentials.EXTRA_PUBLIC_KEY)
184                 && mBundle.containsKey(Credentials.EXTRA_PRIVATE_KEY);
185     }
186 
hasUserCertificate()187     boolean hasUserCertificate() {
188         return (mUserCert != null);
189     }
190 
hasCaCerts()191     boolean hasCaCerts() {
192         return !mCaCerts.isEmpty();
193     }
194 
hasAnyForSystemInstall()195     boolean hasAnyForSystemInstall() {
196         return (mUserKey != null) || hasUserCertificate() || hasCaCerts();
197     }
198 
setPrivateKey(byte[] bytes)199     void setPrivateKey(byte[] bytes) {
200         try {
201             KeyFactory keyFactory = KeyFactory.getInstance("RSA");
202             mUserKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
203         } catch (NoSuchAlgorithmException e) {
204             throw new AssertionError(e);
205         } catch (InvalidKeySpecException e) {
206             throw new AssertionError(e);
207         }
208     }
209 
containsAnyRawData()210     boolean containsAnyRawData() {
211         return !mBundle.isEmpty();
212     }
213 
getData(String key)214     byte[] getData(String key) {
215         return mBundle.get(key);
216     }
217 
putPkcs12Data(byte[] data)218     void putPkcs12Data(byte[] data) {
219         mBundle.put(KeyChain.EXTRA_PKCS12, data);
220     }
221 
getDescription(Context context)222     CharSequence getDescription(Context context) {
223         // TODO: create more descriptive string
224         StringBuilder sb = new StringBuilder();
225         String newline = "<br>";
226         if (mUserKey != null) {
227             sb.append(context.getString(R.string.one_userkey)).append(newline);
228         }
229         if (mUserCert != null) {
230             sb.append(context.getString(R.string.one_usercrt)).append(newline);
231         }
232         int n = mCaCerts.size();
233         if (n > 0) {
234             if (n == 1) {
235                 sb.append(context.getString(R.string.one_cacrt));
236             } else {
237                 sb.append(context.getString(R.string.n_cacrts, n));
238             }
239         }
240         return Html.fromHtml(sb.toString());
241     }
242 
setName(String name)243     void setName(String name) {
244         mName = name;
245     }
246 
getName()247     String getName() {
248         return mName;
249     }
250 
setInstallAsUid(int uid)251     void setInstallAsUid(int uid) {
252         mUid = uid;
253     }
254 
isInstallAsUidSet()255     boolean isInstallAsUidSet() {
256         return mUid != -1;
257     }
258 
createSystemInstallIntent()259     Intent createSystemInstallIntent() {
260         Intent intent = new Intent("com.android.credentials.INSTALL");
261         // To prevent the private key from being sniffed, we explicitly spell
262         // out the intent receiver class.
263         intent.setClassName("com.android.settings", "com.android.settings.CredentialStorage");
264         intent.putExtra(Credentials.EXTRA_INSTALL_AS_UID, mUid);
265         try {
266             if (mUserKey != null) {
267                 intent.putExtra(Credentials.EXTRA_USER_PRIVATE_KEY_NAME,
268                         Credentials.USER_PRIVATE_KEY + mName);
269                 intent.putExtra(Credentials.EXTRA_USER_PRIVATE_KEY_DATA,
270                         mUserKey.getEncoded());
271             }
272             if (mUserCert != null) {
273                 intent.putExtra(Credentials.EXTRA_USER_CERTIFICATE_NAME,
274                         Credentials.USER_CERTIFICATE + mName);
275                 intent.putExtra(Credentials.EXTRA_USER_CERTIFICATE_DATA,
276                         Credentials.convertToPem(mUserCert));
277             }
278             if (!mCaCerts.isEmpty()) {
279                 intent.putExtra(Credentials.EXTRA_CA_CERTIFICATES_NAME,
280                         Credentials.CA_CERTIFICATE + mName);
281                 X509Certificate[] caCerts
282                         = mCaCerts.toArray(new X509Certificate[mCaCerts.size()]);
283                 intent.putExtra(Credentials.EXTRA_CA_CERTIFICATES_DATA,
284                         Credentials.convertToPem(caCerts));
285             }
286             return intent;
287         } catch (IOException e) {
288             throw new AssertionError(e);
289         } catch (CertificateEncodingException e) {
290             throw new AssertionError(e);
291         }
292     }
293 
installCaCertsToKeyChain(IKeyChainService keyChainService)294     boolean installCaCertsToKeyChain(IKeyChainService keyChainService) {
295         for (X509Certificate caCert : mCaCerts) {
296             byte[] bytes = null;
297             try {
298                 bytes = caCert.getEncoded();
299             } catch (CertificateEncodingException e) {
300                 throw new AssertionError(e);
301             }
302             if (bytes != null) {
303                 try {
304                     keyChainService.installCaCertificate(bytes);
305                 } catch (RemoteException e) {
306                     Log.w(TAG, "installCaCertsToKeyChain(): " + e);
307                     return false;
308                 }
309             }
310         }
311         return true;
312     }
313 
extractPkcs12(String password)314     boolean extractPkcs12(String password) {
315         try {
316             return extractPkcs12Internal(password);
317         } catch (Exception e) {
318             Log.w(TAG, "extractPkcs12(): " + e, e);
319             return false;
320         }
321     }
322 
extractPkcs12Internal(String password)323     private boolean extractPkcs12Internal(String password)
324             throws Exception {
325         // TODO: add test about this
326         java.security.KeyStore keystore = java.security.KeyStore.getInstance("PKCS12");
327         PasswordProtection passwordProtection = new PasswordProtection(password.toCharArray());
328         keystore.load(new ByteArrayInputStream(getData(KeyChain.EXTRA_PKCS12)),
329                       passwordProtection.getPassword());
330 
331         Enumeration<String> aliases = keystore.aliases();
332         if (!aliases.hasMoreElements()) {
333             return false;
334         }
335 
336         while (aliases.hasMoreElements()) {
337             String alias = aliases.nextElement();
338             KeyStore.Entry entry = keystore.getEntry(alias, passwordProtection);
339             Log.d(TAG, "extracted alias = " + alias + ", entry=" + entry.getClass());
340 
341             if (entry instanceof PrivateKeyEntry) {
342                 if (TextUtils.isEmpty(mName)) {
343                     mName = alias;
344                 }
345                 return installFrom((PrivateKeyEntry) entry);
346             }
347         }
348         return true;
349     }
350 
installFrom(PrivateKeyEntry entry)351     private synchronized boolean installFrom(PrivateKeyEntry entry) {
352         mUserKey = entry.getPrivateKey();
353         mUserCert = (X509Certificate) entry.getCertificate();
354 
355         Certificate[] certs = entry.getCertificateChain();
356         Log.d(TAG, "# certs extracted = " + certs.length);
357         mCaCerts = new ArrayList<X509Certificate>(certs.length);
358         for (Certificate c : certs) {
359             X509Certificate cert = (X509Certificate) c;
360             if (isCa(cert)) {
361                 mCaCerts.add(cert);
362             }
363         }
364         Log.d(TAG, "# ca certs extracted = " + mCaCerts.size());
365 
366         return true;
367     }
368 }
369