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.text.TextUtils;
28import android.util.Log;
29import com.android.org.bouncycastle.asn1.ASN1InputStream;
30import com.android.org.bouncycastle.asn1.ASN1Sequence;
31import com.android.org.bouncycastle.asn1.DEROctetString;
32import com.android.org.bouncycastle.asn1.x509.BasicConstraints;
33import java.io.ByteArrayInputStream;
34import java.io.IOException;
35import java.security.KeyFactory;
36import java.security.KeyStore.PasswordProtection;
37import java.security.KeyStore.PrivateKeyEntry;
38import java.security.KeyStore;
39import java.security.NoSuchAlgorithmException;
40import java.security.PrivateKey;
41import java.security.cert.Certificate;
42import java.security.cert.CertificateEncodingException;
43import java.security.cert.CertificateException;
44import java.security.cert.CertificateFactory;
45import java.security.cert.X509Certificate;
46import java.security.spec.InvalidKeySpecException;
47import java.security.spec.PKCS8EncodedKeySpec;
48import java.util.ArrayList;
49import java.util.Enumeration;
50import java.util.HashMap;
51import java.util.List;
52
53/**
54 * A helper class for accessing the raw data in the intent extra and handling
55 * certificates.
56 */
57class 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
72    CredentialHelper() {
73    }
74
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
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
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
134    X509Certificate getUserCertificate() {
135        return mUserCert;
136    }
137
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
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
178    boolean hasPkcs12KeyStore() {
179        return mBundle.containsKey(KeyChain.EXTRA_PKCS12);
180    }
181
182    boolean hasKeyPair() {
183        return mBundle.containsKey(Credentials.EXTRA_PUBLIC_KEY)
184                && mBundle.containsKey(Credentials.EXTRA_PRIVATE_KEY);
185    }
186
187    boolean hasUserCertificate() {
188        return (mUserCert != null);
189    }
190
191    boolean hasCaCerts() {
192        return !mCaCerts.isEmpty();
193    }
194
195    boolean hasAnyForSystemInstall() {
196        return (mUserKey != null) || hasUserCertificate() || hasCaCerts();
197    }
198
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
210    boolean containsAnyRawData() {
211        return !mBundle.isEmpty();
212    }
213
214    byte[] getData(String key) {
215        return mBundle.get(key);
216    }
217
218    void putPkcs12Data(byte[] data) {
219        mBundle.put(KeyChain.EXTRA_PKCS12, data);
220    }
221
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
243    void setName(String name) {
244        mName = name;
245    }
246
247    String getName() {
248        return mName;
249    }
250
251    void setInstallAsUid(int uid) {
252        mUid = uid;
253    }
254
255    boolean isInstallAsUidSet() {
256        return mUid != -1;
257    }
258
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
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
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
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
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