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