• 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.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             outStates.putInt(Credentials.EXTRA_INSTALL_AS_UID, mUid);
104             if (mUserKey != null) {
105                 outStates.putByteArray(Credentials.USER_PRIVATE_KEY,
106                         mUserKey.getEncoded());
107             }
108             ArrayList<byte[]> certs = new ArrayList<byte[]>(mCaCerts.size() + 1);
109             if (mUserCert != null) {
110                 certs.add(mUserCert.getEncoded());
111             }
112             for (X509Certificate cert : mCaCerts) {
113                 certs.add(cert.getEncoded());
114             }
115             outStates.putByteArray(CERTS_KEY, Util.toBytes(certs));
116         } catch (CertificateEncodingException e) {
117             throw new AssertionError(e);
118         }
119     }
120 
onRestoreStates(Bundle savedStates)121     void onRestoreStates(Bundle savedStates) {
122         mBundle = (HashMap) savedStates.getSerializable(DATA_KEY);
123         mName = savedStates.getString(KeyChain.EXTRA_NAME);
124         mUid = savedStates.getInt(Credentials.EXTRA_INSTALL_AS_UID, -1);
125         byte[] bytes = savedStates.getByteArray(Credentials.USER_PRIVATE_KEY);
126         if (bytes != null) {
127             setPrivateKey(bytes);
128         }
129 
130         ArrayList<byte[]> certs = Util.fromBytes(savedStates.getByteArray(CERTS_KEY));
131         for (byte[] cert : certs) {
132             parseCert(cert);
133         }
134     }
135 
getUserCertificate()136     X509Certificate getUserCertificate() {
137         return mUserCert;
138     }
139 
parseCert(byte[] bytes)140     private void parseCert(byte[] bytes) {
141         if (bytes == null) {
142             return;
143         }
144 
145         try {
146             CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
147             X509Certificate cert = (X509Certificate)
148                     certFactory.generateCertificate(
149                             new ByteArrayInputStream(bytes));
150             if (isCa(cert)) {
151                 Log.d(TAG, "got a CA cert");
152                 mCaCerts.add(cert);
153             } else {
154                 Log.d(TAG, "got a user cert");
155                 mUserCert = cert;
156             }
157         } catch (CertificateException e) {
158             Log.w(TAG, "parseCert(): " + e);
159         }
160     }
161 
isCa(X509Certificate cert)162     private boolean isCa(X509Certificate cert) {
163         try {
164             // TODO: add a test about this
165             byte[] asn1EncodedBytes = cert.getExtensionValue("2.5.29.19");
166             if (asn1EncodedBytes == null) {
167                 return false;
168             }
169             DEROctetString derOctetString = (DEROctetString)
170                     new ASN1InputStream(asn1EncodedBytes).readObject();
171             byte[] octets = derOctetString.getOctets();
172             ASN1Sequence sequence = (ASN1Sequence)
173                     new ASN1InputStream(octets).readObject();
174             return BasicConstraints.getInstance(sequence).isCA();
175         } catch (IOException e) {
176             return false;
177         }
178     }
179 
hasPkcs12KeyStore()180     boolean hasPkcs12KeyStore() {
181         return mBundle.containsKey(KeyChain.EXTRA_PKCS12);
182     }
183 
hasKeyPair()184     boolean hasKeyPair() {
185         return mBundle.containsKey(Credentials.EXTRA_PUBLIC_KEY)
186                 && mBundle.containsKey(Credentials.EXTRA_PRIVATE_KEY);
187     }
188 
hasUserCertificate()189     boolean hasUserCertificate() {
190         return (mUserCert != null);
191     }
192 
hasCaCerts()193     boolean hasCaCerts() {
194         return !mCaCerts.isEmpty();
195     }
196 
hasAnyForSystemInstall()197     boolean hasAnyForSystemInstall() {
198         return (mUserKey != null) || hasUserCertificate() || hasCaCerts();
199     }
200 
setPrivateKey(byte[] bytes)201     void setPrivateKey(byte[] bytes) {
202         try {
203             KeyFactory keyFactory = KeyFactory.getInstance("RSA");
204             mUserKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
205         } catch (NoSuchAlgorithmException e) {
206             throw new AssertionError(e);
207         } catch (InvalidKeySpecException e) {
208             throw new AssertionError(e);
209         }
210     }
211 
containsAnyRawData()212     boolean containsAnyRawData() {
213         return !mBundle.isEmpty();
214     }
215 
getData(String key)216     byte[] getData(String key) {
217         return mBundle.get(key);
218     }
219 
putPkcs12Data(byte[] data)220     void putPkcs12Data(byte[] data) {
221         mBundle.put(KeyChain.EXTRA_PKCS12, data);
222     }
223 
getDescription(Context context)224     CharSequence getDescription(Context context) {
225         // TODO: create more descriptive string
226         StringBuilder sb = new StringBuilder();
227         String newline = "<br>";
228         if (mUserKey != null) {
229             sb.append(context.getString(R.string.one_userkey)).append(newline);
230         }
231         if (mUserCert != null) {
232             sb.append(context.getString(R.string.one_usercrt)).append(newline);
233         }
234         int n = mCaCerts.size();
235         if (n > 0) {
236             if (n == 1) {
237                 sb.append(context.getString(R.string.one_cacrt));
238             } else {
239                 sb.append(context.getString(R.string.n_cacrts, n));
240             }
241         }
242         return Html.fromHtml(sb.toString());
243     }
244 
setName(String name)245     void setName(String name) {
246         mName = name;
247     }
248 
getName()249     String getName() {
250         return mName;
251     }
252 
setInstallAsUid(int uid)253     void setInstallAsUid(int uid) {
254         mUid = uid;
255     }
256 
isInstallAsUidSet()257     boolean isInstallAsUidSet() {
258         return mUid != -1;
259     }
260 
getInstallAsUid()261     int getInstallAsUid() {
262         return mUid;
263     }
264 
createSystemInstallIntent()265     Intent createSystemInstallIntent() {
266         Intent intent = new Intent("com.android.credentials.INSTALL");
267         // To prevent the private key from being sniffed, we explicitly spell
268         // out the intent receiver class.
269         intent.setClassName("com.android.settings", "com.android.settings.CredentialStorage");
270         intent.putExtra(Credentials.EXTRA_INSTALL_AS_UID, mUid);
271         try {
272             if (mUserKey != null) {
273                 intent.putExtra(Credentials.EXTRA_USER_PRIVATE_KEY_NAME,
274                         Credentials.USER_PRIVATE_KEY + mName);
275                 intent.putExtra(Credentials.EXTRA_USER_PRIVATE_KEY_DATA,
276                         mUserKey.getEncoded());
277             }
278             if (mUserCert != null) {
279                 intent.putExtra(Credentials.EXTRA_USER_CERTIFICATE_NAME,
280                         Credentials.USER_CERTIFICATE + mName);
281                 intent.putExtra(Credentials.EXTRA_USER_CERTIFICATE_DATA,
282                         Credentials.convertToPem(mUserCert));
283             }
284             if (!mCaCerts.isEmpty()) {
285                 intent.putExtra(Credentials.EXTRA_CA_CERTIFICATES_NAME,
286                         Credentials.CA_CERTIFICATE + mName);
287                 X509Certificate[] caCerts
288                         = mCaCerts.toArray(new X509Certificate[mCaCerts.size()]);
289                 intent.putExtra(Credentials.EXTRA_CA_CERTIFICATES_DATA,
290                         Credentials.convertToPem(caCerts));
291             }
292             return intent;
293         } catch (IOException e) {
294             throw new AssertionError(e);
295         } catch (CertificateEncodingException e) {
296             throw new AssertionError(e);
297         }
298     }
299 
installCaCertsToKeyChain(IKeyChainService keyChainService)300     boolean installCaCertsToKeyChain(IKeyChainService keyChainService) {
301         for (X509Certificate caCert : mCaCerts) {
302             byte[] bytes = null;
303             try {
304                 bytes = caCert.getEncoded();
305             } catch (CertificateEncodingException e) {
306                 throw new AssertionError(e);
307             }
308             if (bytes != null) {
309                 try {
310                     keyChainService.installCaCertificate(bytes);
311                 } catch (RemoteException e) {
312                     Log.w(TAG, "installCaCertsToKeyChain(): " + e);
313                     return false;
314                 }
315             }
316         }
317         return true;
318     }
319 
extractPkcs12(String password)320     boolean extractPkcs12(String password) {
321         try {
322             return extractPkcs12Internal(password);
323         } catch (Exception e) {
324             Log.w(TAG, "extractPkcs12(): " + e, e);
325             return false;
326         }
327     }
328 
extractPkcs12Internal(String password)329     private boolean extractPkcs12Internal(String password)
330             throws Exception {
331         // TODO: add test about this
332         java.security.KeyStore keystore = java.security.KeyStore.getInstance("PKCS12");
333         PasswordProtection passwordProtection = new PasswordProtection(password.toCharArray());
334         keystore.load(new ByteArrayInputStream(getData(KeyChain.EXTRA_PKCS12)),
335                       passwordProtection.getPassword());
336 
337         Enumeration<String> aliases = keystore.aliases();
338         if (!aliases.hasMoreElements()) {
339             return false;
340         }
341 
342         while (aliases.hasMoreElements()) {
343             String alias = aliases.nextElement();
344             KeyStore.Entry entry = keystore.getEntry(alias, passwordProtection);
345             Log.d(TAG, "extracted alias = " + alias + ", entry=" + entry.getClass());
346 
347             if (entry instanceof PrivateKeyEntry) {
348                 if (TextUtils.isEmpty(mName)) {
349                     mName = alias;
350                 }
351                 return installFrom((PrivateKeyEntry) entry);
352             }
353         }
354         return true;
355     }
356 
installFrom(PrivateKeyEntry entry)357     private synchronized boolean installFrom(PrivateKeyEntry entry) {
358         mUserKey = entry.getPrivateKey();
359         mUserCert = (X509Certificate) entry.getCertificate();
360 
361         Certificate[] certs = entry.getCertificateChain();
362         Log.d(TAG, "# certs extracted = " + certs.length);
363         mCaCerts = new ArrayList<X509Certificate>(certs.length);
364         for (Certificate c : certs) {
365             X509Certificate cert = (X509Certificate) c;
366             if (isCa(cert)) {
367                 mCaCerts.add(cert);
368             }
369         }
370         Log.d(TAG, "# ca certs extracted = " + mCaCerts.size());
371 
372         return true;
373     }
374 }
375