• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.googlecode.android_scripting.facade;
18 
19 import android.os.Environment;
20 import android.security.Credentials;
21 import android.security.KeyStore;
22 import android.util.Log;
23 
24 import com.android.internal.net.VpnProfile;
25 import com.android.org.bouncycastle.asn1.ASN1InputStream;
26 import com.android.org.bouncycastle.asn1.ASN1Sequence;
27 import com.android.org.bouncycastle.asn1.DEROctetString;
28 import com.android.org.bouncycastle.asn1.x509.BasicConstraints;
29 
30 import junit.framework.Assert;
31 
32 import libcore.io.Streams;
33 
34 import java.io.ByteArrayInputStream;
35 import java.io.File;
36 import java.io.FileInputStream;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.security.KeyStoreException;
40 import java.security.NoSuchAlgorithmException;
41 import java.security.KeyStore.PasswordProtection;
42 import java.security.KeyStore.PrivateKeyEntry;
43 import java.security.PrivateKey;
44 import java.security.UnrecoverableEntryException;
45 import java.security.cert.Certificate;
46 import java.security.cert.CertificateEncodingException;
47 import java.security.cert.CertificateException;
48 import java.security.cert.X509Certificate;
49 import java.util.ArrayList;
50 import java.util.Collections;
51 import java.util.Enumeration;
52 import java.util.List;
53 
54 /**
55  * Certificate installer helper to extract information from a provided file
56  * and install certificates to keystore.
57  */
58 public class CertInstallerHelper {
59     private static final String TAG = "CertInstallerHelper";
60     /* Define a password to unlock keystore after it is reset */
61     private static final String CERT_STORE_PASSWORD = "password";
62     private final int mUid = KeyStore.UID_SELF;
63     private PrivateKey mUserKey;  // private key
64     private X509Certificate mUserCert;  // user certificate
65     private List<X509Certificate> mCaCerts = new ArrayList<X509Certificate>();
66     private KeyStore mKeyStore = KeyStore.getInstance();
67 
68     /**
69      * Unlock keystore and set password
70      */
CertInstallerHelper()71     public CertInstallerHelper() {
72         for (String key : mKeyStore.list("")) {
73             mKeyStore.delete(key, KeyStore.UID_SELF);
74         }
75         mKeyStore.onUserPasswordChanged(CERT_STORE_PASSWORD);
76     }
77 
extractCertificate(String certFile, String password)78     private void extractCertificate(String certFile, String password) {
79         InputStream in = null;
80         final byte[] raw;
81         java.security.KeyStore keystore = null;
82         try {
83             // Read .p12 file from SDCARD and extract with password
84             in = new FileInputStream(new File(
85                     Environment.getExternalStorageDirectory(), certFile));
86             raw = Streams.readFully(in);
87 
88             keystore = java.security.KeyStore.getInstance("PKCS12");
89             PasswordProtection passwordProtection = new PasswordProtection(password.toCharArray());
90             keystore.load(new ByteArrayInputStream(raw), passwordProtection.getPassword());
91 
92             // Install certificates and private keys
93             Enumeration<String> aliases = keystore.aliases();
94             if (!aliases.hasMoreElements()) {
95                 Assert.fail("key store failed to put in keychain");
96             }
97             ArrayList<String> aliasesList = Collections.list(aliases);
98             // The keystore is initialized for each test case, there will
99             // be only one alias in the keystore
100             Assert.assertEquals(1, aliasesList.size());
101             String alias = aliasesList.get(0);
102             java.security.KeyStore.Entry entry = keystore.getEntry(alias, passwordProtection);
103             Log.d(TAG, "extracted alias = " + alias + ", entry=" + entry.getClass());
104 
105             if (entry instanceof PrivateKeyEntry) {
106                 Assert.assertTrue(installFrom((PrivateKeyEntry) entry));
107             }
108         } catch (IOException e) {
109             Assert.fail("Failed to read certficate: " + e);
110         } catch (KeyStoreException e) {
111             Log.e(TAG, "failed to extract certificate" + e);
112         } catch (NoSuchAlgorithmException e) {
113             Log.e(TAG, "failed to extract certificate" + e);
114         } catch (CertificateException e) {
115             Log.e(TAG, "failed to extract certificate" + e);
116         } catch (UnrecoverableEntryException e) {
117             Log.e(TAG, "failed to extract certificate" + e);
118         }
119         finally {
120             if (in != null) {
121                 try {
122                     in.close();
123                 } catch (IOException e) {
124                     Log.e(TAG, "close FileInputStream error: " + e);
125                 }
126             }
127         }
128     }
129 
130     /**
131      * Extract private keys, user certificates and ca certificates
132      */
installFrom(PrivateKeyEntry entry)133     private synchronized boolean installFrom(PrivateKeyEntry entry) {
134         mUserKey = entry.getPrivateKey();
135         mUserCert = (X509Certificate) entry.getCertificate();
136 
137         Certificate[] certs = entry.getCertificateChain();
138         Log.d(TAG, "# certs extracted = " + certs.length);
139         mCaCerts = new ArrayList<X509Certificate>(certs.length);
140         for (Certificate c : certs) {
141             X509Certificate cert = (X509Certificate) c;
142             if (isCa(cert)) {
143                 mCaCerts.add(cert);
144             }
145         }
146         Log.d(TAG, "# ca certs extracted = " + mCaCerts.size());
147         return true;
148     }
149 
isCa(X509Certificate cert)150     private boolean isCa(X509Certificate cert) {
151         try {
152             byte[] asn1EncodedBytes = cert.getExtensionValue("2.5.29.19");
153             if (asn1EncodedBytes == null) {
154                 return false;
155             }
156             DEROctetString derOctetString = (DEROctetString)
157                     new ASN1InputStream(asn1EncodedBytes).readObject();
158             byte[] octets = derOctetString.getOctets();
159             ASN1Sequence sequence = (ASN1Sequence)
160                     new ASN1InputStream(octets).readObject();
161             return BasicConstraints.getInstance(sequence).isCA();
162         } catch (IOException e) {
163             return false;
164         }
165     }
166 
167     /**
168      * Extract certificate from the given file, and install it to keystore
169      * @param name certificate name
170      * @param certFile .p12 file which includes certificates
171      * @param password password to extract the .p12 file
172      */
installCertificate(VpnProfile profile, String certFile, String password)173     public void installCertificate(VpnProfile profile, String certFile, String password) {
174         // extract private keys, certificates from the provided file
175         extractCertificate(certFile, password);
176         // install certificate to the keystore
177         int flags = KeyStore.FLAG_ENCRYPTED;
178         try {
179             if (mUserKey != null) {
180                 Log.v(TAG, "has private key");
181                 String key = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert;
182                 byte[] value = mUserKey.getEncoded();
183 
184                 if (!mKeyStore.importKey(key, value, mUid, flags)) {
185                     Log.e(TAG, "Failed to install " + key + " as user " + mUid);
186                     return;
187                 }
188                 Log.v(TAG, "install " + key + " as user " + mUid + " is successful");
189             }
190 
191             if (mUserCert != null) {
192                 String certName = Credentials.USER_CERTIFICATE + profile.ipsecUserCert;
193                 byte[] certData = Credentials.convertToPem(mUserCert);
194 
195                 if (!mKeyStore.put(certName, certData, mUid, flags)) {
196                     Log.e(TAG, "Failed to install " + certName + " as user " + mUid);
197                     return;
198                 }
199                 Log.v(TAG, "install " + certName + " as user" + mUid + " is successful.");
200             }
201 
202             if (!mCaCerts.isEmpty()) {
203                 String caListName = Credentials.CA_CERTIFICATE + profile.ipsecCaCert;
204                 X509Certificate[] caCerts = mCaCerts.toArray(new X509Certificate[mCaCerts.size()]);
205                 byte[] caListData = Credentials.convertToPem(caCerts);
206 
207                 if (!mKeyStore.put(caListName, caListData, mUid, flags)) {
208                     Log.e(TAG, "Failed to install " + caListName + " as user " + mUid);
209                     return;
210                 }
211                 Log.v(TAG, " install " + caListName + " as user " + mUid + " is successful");
212             }
213         } catch (CertificateEncodingException e) {
214             Log.e(TAG, "Exception while convert certificates to pem " + e);
215             throw new AssertionError(e);
216         } catch (IOException e) {
217             Log.e(TAG, "IOException while convert to pem: " + e);
218         }
219     }
220 
getUid()221     public int getUid() {
222         return mUid;
223     }
224 }
225