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