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