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