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