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 if (mUserKey != null) { 232 intent.putExtra(Credentials.USER_PRIVATE_KEY + mName, 233 convertToPem(mUserKey)); 234 } 235 if (mUserCert != null) { 236 intent.putExtra(Credentials.USER_CERTIFICATE + mName, 237 convertToPem(mUserCert)); 238 } 239 if (!mCaCerts.isEmpty()) { 240 Object[] caCerts = (Object[]) 241 mCaCerts.toArray(new X509Certificate[mCaCerts.size()]); 242 intent.putExtra(Credentials.CA_CERTIFICATE + mName, 243 convertToPem(caCerts)); 244 } 245 return intent; 246 } 247 248 boolean extractPkcs12(String password) { 249 try { 250 return extractPkcs12Internal(password); 251 } catch (Exception e) { 252 Log.w(TAG, "extractPkcs12(): " + e, e); 253 return false; 254 } 255 } 256 257 private boolean extractPkcs12Internal(String password) 258 throws Exception { 259 // TODO: add test about this 260 java.security.KeyStore keystore = 261 java.security.KeyStore.getInstance("PKCS12"); 262 PasswordProtection passwordProtection = 263 new PasswordProtection(password.toCharArray()); 264 keystore.load(new ByteArrayInputStream(getData(Credentials.PKCS12)), 265 passwordProtection.getPassword()); 266 267 Enumeration<String> aliases = keystore.aliases(); 268 if (!aliases.hasMoreElements()) return false; 269 270 while (aliases.hasMoreElements()) { 271 String alias = aliases.nextElement(); 272 KeyStore.Entry entry = keystore.getEntry(alias, passwordProtection); 273 Log.d(TAG, "extracted alias = " + alias 274 + ", entry=" + entry.getClass()); 275 276 if (entry instanceof PrivateKeyEntry) { 277 mName = alias; 278 return installFrom((PrivateKeyEntry) entry); 279 } 280 } 281 return true; 282 } 283 284 private synchronized boolean installFrom(PrivateKeyEntry entry) { 285 mUserKey = entry.getPrivateKey(); 286 mUserCert = (X509Certificate) entry.getCertificate(); 287 288 Certificate[] certs = entry.getCertificateChain(); 289 Log.d(TAG, "# certs extracted = " + certs.length); 290 List<X509Certificate> caCerts = mCaCerts = 291 new ArrayList<X509Certificate>(certs.length); 292 for (Certificate c : certs) { 293 X509Certificate cert = (X509Certificate) c; 294 if (isCa(cert)) caCerts.add(cert); 295 } 296 Log.d(TAG, "# ca certs extracted = " + mCaCerts.size()); 297 298 return true; 299 } 300 301 private byte[] convertToPem(Object... objects) { 302 try { 303 ByteArrayOutputStream bao = new ByteArrayOutputStream(); 304 OutputStreamWriter osw = new OutputStreamWriter(bao); 305 PEMWriter pw = new PEMWriter(osw); 306 for (Object o : objects) pw.writeObject(o); 307 pw.close(); 308 return bao.toByteArray(); 309 } catch (IOException e) { 310 // should not occur 311 Log.w(TAG, "convertToPem(): " + e); 312 throw new RuntimeException(e); 313 } 314 } 315 } 316