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