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