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