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