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.text.TextUtils; 28 import android.util.Log; 29 import com.android.org.bouncycastle.asn1.ASN1InputStream; 30 import com.android.org.bouncycastle.asn1.ASN1Sequence; 31 import com.android.org.bouncycastle.asn1.DEROctetString; 32 import com.android.org.bouncycastle.asn1.x509.BasicConstraints; 33 import java.io.ByteArrayInputStream; 34 import java.io.IOException; 35 import java.security.KeyFactory; 36 import java.security.KeyStore.PasswordProtection; 37 import java.security.KeyStore.PrivateKeyEntry; 38 import java.security.KeyStore; 39 import java.security.NoSuchAlgorithmException; 40 import java.security.PrivateKey; 41 import java.security.cert.Certificate; 42 import java.security.cert.CertificateEncodingException; 43 import java.security.cert.CertificateException; 44 import java.security.cert.CertificateFactory; 45 import java.security.cert.X509Certificate; 46 import java.security.spec.InvalidKeySpecException; 47 import java.security.spec.PKCS8EncodedKeySpec; 48 import java.util.ArrayList; 49 import java.util.Enumeration; 50 import java.util.HashMap; 51 import java.util.List; 52 53 /** 54 * A helper class for accessing the raw data in the intent extra and handling 55 * certificates. 56 */ 57 class CredentialHelper { 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 CredentialHelper()71 CredentialHelper() { 72 } 73 CredentialHelper(Intent intent)74 CredentialHelper(Intent intent) { 75 Bundle bundle = intent.getExtras(); 76 if (bundle == null) { 77 return; 78 } 79 80 String name = bundle.getString(KeyChain.EXTRA_NAME); 81 bundle.remove(KeyChain.EXTRA_NAME); 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 onSaveStates(Bundle outStates)95 synchronized void onSaveStates(Bundle outStates) { 96 try { 97 outStates.putSerializable(DATA_KEY, mBundle); 98 outStates.putString(KeyChain.EXTRA_NAME, 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 onRestoreStates(Bundle savedStates)116 void onRestoreStates(Bundle savedStates) { 117 mBundle = (HashMap) savedStates.getSerializable(DATA_KEY); 118 mName = savedStates.getString(KeyChain.EXTRA_NAME); 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 getUserCertificate()130 X509Certificate getUserCertificate() { 131 return mUserCert; 132 } 133 parseCert(byte[] bytes)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 isCa(X509Certificate cert)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 BasicConstraints.getInstance(sequence).isCA(); 169 } catch (IOException e) { 170 return false; 171 } 172 } 173 hasPkcs12KeyStore()174 boolean hasPkcs12KeyStore() { 175 return mBundle.containsKey(KeyChain.EXTRA_PKCS12); 176 } 177 hasKeyPair()178 boolean hasKeyPair() { 179 return mBundle.containsKey(Credentials.EXTRA_PUBLIC_KEY) 180 && mBundle.containsKey(Credentials.EXTRA_PRIVATE_KEY); 181 } 182 hasUserCertificate()183 boolean hasUserCertificate() { 184 return (mUserCert != null); 185 } 186 hasCaCerts()187 boolean hasCaCerts() { 188 return !mCaCerts.isEmpty(); 189 } 190 hasAnyForSystemInstall()191 boolean hasAnyForSystemInstall() { 192 return (mUserKey != null) || hasUserCertificate() || hasCaCerts(); 193 } 194 setPrivateKey(byte[] bytes)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 containsAnyRawData()206 boolean containsAnyRawData() { 207 return !mBundle.isEmpty(); 208 } 209 getData(String key)210 byte[] getData(String key) { 211 return mBundle.get(key); 212 } 213 putPkcs12Data(byte[] data)214 void putPkcs12Data(byte[] data) { 215 mBundle.put(KeyChain.EXTRA_PKCS12, data); 216 } 217 getDescription(Context context)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 setName(String name)239 void setName(String name) { 240 mName = name; 241 } 242 getName()243 String getName() { 244 return mName; 245 } 246 createSystemInstallIntent()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.EXTRA_USER_PRIVATE_KEY_NAME, 255 Credentials.USER_PRIVATE_KEY + mName); 256 intent.putExtra(Credentials.EXTRA_USER_PRIVATE_KEY_DATA, 257 mUserKey.getEncoded()); 258 } 259 if (mUserCert != null) { 260 intent.putExtra(Credentials.EXTRA_USER_CERTIFICATE_NAME, 261 Credentials.USER_CERTIFICATE + mName); 262 intent.putExtra(Credentials.EXTRA_USER_CERTIFICATE_DATA, 263 Credentials.convertToPem(mUserCert)); 264 } 265 if (!mCaCerts.isEmpty()) { 266 intent.putExtra(Credentials.EXTRA_CA_CERTIFICATES_NAME, 267 Credentials.CA_CERTIFICATE + mName); 268 X509Certificate[] caCerts 269 = mCaCerts.toArray(new X509Certificate[mCaCerts.size()]); 270 intent.putExtra(Credentials.EXTRA_CA_CERTIFICATES_DATA, 271 Credentials.convertToPem(caCerts)); 272 } 273 return intent; 274 } catch (IOException e) { 275 throw new AssertionError(e); 276 } catch (CertificateEncodingException e) { 277 throw new AssertionError(e); 278 } 279 } 280 installCaCertsToKeyChain(IKeyChainService keyChainService)281 boolean installCaCertsToKeyChain(IKeyChainService keyChainService) { 282 for (X509Certificate caCert : mCaCerts) { 283 byte[] bytes = null; 284 try { 285 bytes = caCert.getEncoded(); 286 } catch (CertificateEncodingException e) { 287 throw new AssertionError(e); 288 } 289 if (bytes != null) { 290 try { 291 keyChainService.installCaCertificate(bytes); 292 } catch (RemoteException e) { 293 Log.w(TAG, "installCaCertsToKeyChain(): " + e); 294 return false; 295 } 296 } 297 } 298 return true; 299 } 300 extractPkcs12(String password)301 boolean extractPkcs12(String password) { 302 try { 303 return extractPkcs12Internal(password); 304 } catch (Exception e) { 305 Log.w(TAG, "extractPkcs12(): " + e, e); 306 return false; 307 } 308 } 309 extractPkcs12Internal(String password)310 private boolean extractPkcs12Internal(String password) 311 throws Exception { 312 // TODO: add test about this 313 java.security.KeyStore keystore = java.security.KeyStore.getInstance("PKCS12"); 314 PasswordProtection passwordProtection = new PasswordProtection(password.toCharArray()); 315 keystore.load(new ByteArrayInputStream(getData(KeyChain.EXTRA_PKCS12)), 316 passwordProtection.getPassword()); 317 318 Enumeration<String> aliases = keystore.aliases(); 319 if (!aliases.hasMoreElements()) { 320 return false; 321 } 322 323 while (aliases.hasMoreElements()) { 324 String alias = aliases.nextElement(); 325 KeyStore.Entry entry = keystore.getEntry(alias, passwordProtection); 326 Log.d(TAG, "extracted alias = " + alias + ", entry=" + entry.getClass()); 327 328 if (entry instanceof PrivateKeyEntry) { 329 if (TextUtils.isEmpty(mName)) { 330 mName = alias; 331 } 332 return installFrom((PrivateKeyEntry) entry); 333 } 334 } 335 return true; 336 } 337 installFrom(PrivateKeyEntry entry)338 private synchronized boolean installFrom(PrivateKeyEntry entry) { 339 mUserKey = entry.getPrivateKey(); 340 mUserCert = (X509Certificate) entry.getCertificate(); 341 342 Certificate[] certs = entry.getCertificateChain(); 343 Log.d(TAG, "# certs extracted = " + certs.length); 344 mCaCerts = new ArrayList<X509Certificate>(certs.length); 345 for (Certificate c : certs) { 346 X509Certificate cert = (X509Certificate) c; 347 if (isCa(cert)) { 348 mCaCerts.add(cert); 349 } 350 } 351 Log.d(TAG, "# ca certs extracted = " + mCaCerts.size()); 352 353 return true; 354 } 355 } 356