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.app.admin.DevicePolicyManager; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.PackageManager; 23 import android.os.Bundle; 24 import android.os.RemoteException; 25 import android.os.UserHandle; 26 import android.security.Credentials; 27 import android.security.KeyChain; 28 import android.security.IKeyChainService; 29 import android.text.Html; 30 import android.text.TextUtils; 31 import android.util.Log; 32 import com.android.org.bouncycastle.asn1.ASN1InputStream; 33 import com.android.org.bouncycastle.asn1.ASN1Sequence; 34 import com.android.org.bouncycastle.asn1.DEROctetString; 35 import com.android.org.bouncycastle.asn1.x509.BasicConstraints; 36 import com.android.org.conscrypt.TrustedCertificateStore; 37 38 import java.io.ByteArrayInputStream; 39 import java.io.IOException; 40 import java.security.KeyFactory; 41 import java.security.KeyStore.PasswordProtection; 42 import java.security.KeyStore.PrivateKeyEntry; 43 import java.security.KeyStore; 44 import java.security.NoSuchAlgorithmException; 45 import java.security.PrivateKey; 46 import java.security.cert.Certificate; 47 import java.security.cert.CertificateEncodingException; 48 import java.security.cert.CertificateException; 49 import java.security.cert.CertificateFactory; 50 import java.security.cert.X509Certificate; 51 import java.security.spec.InvalidKeySpecException; 52 import java.security.spec.PKCS8EncodedKeySpec; 53 import java.util.ArrayList; 54 import java.util.Enumeration; 55 import java.util.HashMap; 56 import java.util.List; 57 58 /** 59 * A helper class for accessing the raw data in the intent extra and handling 60 * certificates. 61 */ 62 class CredentialHelper { 63 private static final String DATA_KEY = "data"; 64 private static final String CERTS_KEY = "crts"; 65 66 private static final String TAG = "CredentialHelper"; 67 68 // keep raw data from intent's extra 69 private HashMap<String, byte[]> mBundle = new HashMap<String, byte[]>(); 70 71 private String mName = ""; 72 private int mUid = -1; 73 private PrivateKey mUserKey; 74 private X509Certificate mUserCert; 75 private List<X509Certificate> mCaCerts = new ArrayList<X509Certificate>(); 76 CredentialHelper()77 CredentialHelper() { 78 } 79 CredentialHelper(Intent intent)80 CredentialHelper(Intent intent) { 81 Bundle bundle = intent.getExtras(); 82 if (bundle == null) { 83 return; 84 } 85 86 String name = bundle.getString(KeyChain.EXTRA_NAME); 87 bundle.remove(KeyChain.EXTRA_NAME); 88 if (name != null) { 89 mName = name; 90 } 91 92 mUid = bundle.getInt(Credentials.EXTRA_INSTALL_AS_UID, -1); 93 bundle.remove(Credentials.EXTRA_INSTALL_AS_UID); 94 95 Log.d(TAG, "# extras: " + bundle.size()); 96 for (String key : bundle.keySet()) { 97 byte[] bytes = bundle.getByteArray(key); 98 Log.d(TAG, " " + key + ": " + ((bytes == null) ? -1 : bytes.length)); 99 mBundle.put(key, bytes); 100 } 101 parseCert(getData(KeyChain.EXTRA_CERTIFICATE)); 102 } 103 onSaveStates(Bundle outStates)104 synchronized void onSaveStates(Bundle outStates) { 105 try { 106 outStates.putSerializable(DATA_KEY, mBundle); 107 outStates.putString(KeyChain.EXTRA_NAME, mName); 108 outStates.putInt(Credentials.EXTRA_INSTALL_AS_UID, mUid); 109 if (mUserKey != null) { 110 outStates.putByteArray(Credentials.USER_PRIVATE_KEY, 111 mUserKey.getEncoded()); 112 } 113 ArrayList<byte[]> certs = new ArrayList<byte[]>(mCaCerts.size() + 1); 114 if (mUserCert != null) { 115 certs.add(mUserCert.getEncoded()); 116 } 117 for (X509Certificate cert : mCaCerts) { 118 certs.add(cert.getEncoded()); 119 } 120 outStates.putByteArray(CERTS_KEY, Util.toBytes(certs)); 121 } catch (CertificateEncodingException e) { 122 throw new AssertionError(e); 123 } 124 } 125 onRestoreStates(Bundle savedStates)126 void onRestoreStates(Bundle savedStates) { 127 mBundle = (HashMap) savedStates.getSerializable(DATA_KEY); 128 mName = savedStates.getString(KeyChain.EXTRA_NAME); 129 mUid = savedStates.getInt(Credentials.EXTRA_INSTALL_AS_UID, -1); 130 byte[] bytes = savedStates.getByteArray(Credentials.USER_PRIVATE_KEY); 131 if (bytes != null) { 132 setPrivateKey(bytes); 133 } 134 135 ArrayList<byte[]> certs = Util.fromBytes(savedStates.getByteArray(CERTS_KEY)); 136 for (byte[] cert : certs) { 137 parseCert(cert); 138 } 139 } 140 getUserCertificate()141 X509Certificate getUserCertificate() { 142 return mUserCert; 143 } 144 parseCert(byte[] bytes)145 private void parseCert(byte[] bytes) { 146 if (bytes == null) { 147 return; 148 } 149 150 try { 151 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 152 X509Certificate cert = (X509Certificate) 153 certFactory.generateCertificate( 154 new ByteArrayInputStream(bytes)); 155 if (isCa(cert)) { 156 Log.d(TAG, "got a CA cert"); 157 mCaCerts.add(cert); 158 } else { 159 Log.d(TAG, "got a user cert"); 160 mUserCert = cert; 161 } 162 } catch (CertificateException e) { 163 Log.w(TAG, "parseCert(): " + e); 164 } 165 } 166 isCa(X509Certificate cert)167 private boolean isCa(X509Certificate cert) { 168 try { 169 // TODO: add a test about this 170 byte[] asn1EncodedBytes = cert.getExtensionValue("2.5.29.19"); 171 if (asn1EncodedBytes == null) { 172 return false; 173 } 174 DEROctetString derOctetString = (DEROctetString) 175 new ASN1InputStream(asn1EncodedBytes).readObject(); 176 byte[] octets = derOctetString.getOctets(); 177 ASN1Sequence sequence = (ASN1Sequence) 178 new ASN1InputStream(octets).readObject(); 179 return BasicConstraints.getInstance(sequence).isCA(); 180 } catch (IOException e) { 181 return false; 182 } 183 } 184 hasPkcs12KeyStore()185 boolean hasPkcs12KeyStore() { 186 return mBundle.containsKey(KeyChain.EXTRA_PKCS12); 187 } 188 hasKeyPair()189 boolean hasKeyPair() { 190 return mBundle.containsKey(Credentials.EXTRA_PUBLIC_KEY) 191 && mBundle.containsKey(Credentials.EXTRA_PRIVATE_KEY); 192 } 193 hasUserCertificate()194 boolean hasUserCertificate() { 195 return (mUserCert != null); 196 } 197 hasCaCerts()198 boolean hasCaCerts() { 199 return !mCaCerts.isEmpty(); 200 } 201 hasAnyForSystemInstall()202 boolean hasAnyForSystemInstall() { 203 return (mUserKey != null) || hasUserCertificate() || hasCaCerts(); 204 } 205 setPrivateKey(byte[] bytes)206 void setPrivateKey(byte[] bytes) { 207 try { 208 KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 209 mUserKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes)); 210 } catch (NoSuchAlgorithmException e) { 211 throw new AssertionError(e); 212 } catch (InvalidKeySpecException e) { 213 throw new AssertionError(e); 214 } 215 } 216 containsAnyRawData()217 boolean containsAnyRawData() { 218 return !mBundle.isEmpty(); 219 } 220 getData(String key)221 byte[] getData(String key) { 222 return mBundle.get(key); 223 } 224 putPkcs12Data(byte[] data)225 void putPkcs12Data(byte[] data) { 226 mBundle.put(KeyChain.EXTRA_PKCS12, data); 227 } 228 getDescription(Context context)229 CharSequence getDescription(Context context) { 230 // TODO: create more descriptive string 231 StringBuilder sb = new StringBuilder(); 232 String newline = "<br>"; 233 if (mUserKey != null) { 234 sb.append(context.getString(R.string.one_userkey)).append(newline); 235 } 236 if (mUserCert != null) { 237 sb.append(context.getString(R.string.one_usercrt)).append(newline); 238 } 239 int n = mCaCerts.size(); 240 if (n > 0) { 241 if (n == 1) { 242 sb.append(context.getString(R.string.one_cacrt)); 243 } else { 244 sb.append(context.getString(R.string.n_cacrts, n)); 245 } 246 } 247 return Html.fromHtml(sb.toString()); 248 } 249 setName(String name)250 void setName(String name) { 251 mName = name; 252 } 253 getName()254 String getName() { 255 return mName; 256 } 257 setInstallAsUid(int uid)258 void setInstallAsUid(int uid) { 259 mUid = uid; 260 } 261 isInstallAsUidSet()262 boolean isInstallAsUidSet() { 263 return mUid != -1; 264 } 265 getInstallAsUid()266 int getInstallAsUid() { 267 return mUid; 268 } 269 createSystemInstallIntent(final Context context)270 Intent createSystemInstallIntent(final Context context) { 271 Intent intent = new Intent("com.android.credentials.INSTALL"); 272 // To prevent the private key from being sniffed, we explicitly spell 273 // out the intent receiver class. 274 if (!isWear(context)) { 275 intent.setClassName(Util.SETTINGS_PACKAGE, "com.android.settings.CredentialStorage"); 276 } else { 277 intent.setClassName("com.google.android.apps.wearable.settings", 278 "com.google.android.clockwork.settings.CredentialStorage"); 279 } 280 intent.putExtra(Credentials.EXTRA_INSTALL_AS_UID, mUid); 281 try { 282 if (mUserKey != null) { 283 intent.putExtra(Credentials.EXTRA_USER_PRIVATE_KEY_NAME, 284 Credentials.USER_PRIVATE_KEY + mName); 285 intent.putExtra(Credentials.EXTRA_USER_PRIVATE_KEY_DATA, 286 mUserKey.getEncoded()); 287 } 288 if (mUserCert != null) { 289 intent.putExtra(Credentials.EXTRA_USER_CERTIFICATE_NAME, 290 Credentials.USER_CERTIFICATE + mName); 291 intent.putExtra(Credentials.EXTRA_USER_CERTIFICATE_DATA, 292 Credentials.convertToPem(mUserCert)); 293 } 294 if (!mCaCerts.isEmpty()) { 295 intent.putExtra(Credentials.EXTRA_CA_CERTIFICATES_NAME, 296 Credentials.CA_CERTIFICATE + mName); 297 X509Certificate[] caCerts 298 = mCaCerts.toArray(new X509Certificate[mCaCerts.size()]); 299 intent.putExtra(Credentials.EXTRA_CA_CERTIFICATES_DATA, 300 Credentials.convertToPem(caCerts)); 301 } 302 return intent; 303 } catch (IOException e) { 304 throw new AssertionError(e); 305 } catch (CertificateEncodingException e) { 306 throw new AssertionError(e); 307 } 308 } 309 installVpnAndAppsTrustAnchors(Context context, IKeyChainService keyChainService)310 boolean installVpnAndAppsTrustAnchors(Context context, IKeyChainService keyChainService) { 311 final TrustedCertificateStore trustedCertificateStore = new TrustedCertificateStore(); 312 final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); 313 for (X509Certificate caCert : mCaCerts) { 314 byte[] bytes = null; 315 try { 316 bytes = caCert.getEncoded(); 317 } catch (CertificateEncodingException e) { 318 throw new AssertionError(e); 319 } 320 if (bytes != null) { 321 try { 322 keyChainService.installCaCertificate(bytes); 323 } catch (RemoteException e) { 324 Log.w(TAG, "installCaCertsToKeyChain(): " + e); 325 return false; 326 } 327 328 String alias = trustedCertificateStore.getCertificateAlias(caCert); 329 if (alias == null) { 330 Log.e(TAG, "alias is null"); 331 return false; 332 } 333 334 // Since the cert is installed by real user, the cert is approved by the user 335 dpm.approveCaCert(alias, UserHandle.myUserId(), true); 336 } 337 } 338 return true; 339 } 340 hasPassword()341 boolean hasPassword() { 342 if (!hasPkcs12KeyStore()) { 343 return false; 344 } 345 try { 346 return loadPkcs12Internal(new PasswordProtection(new char[] {})) == null; 347 } catch (Exception e) { 348 return true; 349 } 350 } 351 extractPkcs12(String password)352 boolean extractPkcs12(String password) { 353 try { 354 return extractPkcs12Internal(new PasswordProtection(password.toCharArray())); 355 } catch (Exception e) { 356 Log.w(TAG, "extractPkcs12(): " + e, e); 357 return false; 358 } 359 } 360 extractPkcs12Internal(PasswordProtection password)361 private boolean extractPkcs12Internal(PasswordProtection password) 362 throws Exception { 363 // TODO: add test about this 364 java.security.KeyStore keystore = loadPkcs12Internal(password); 365 366 Enumeration<String> aliases = keystore.aliases(); 367 if (!aliases.hasMoreElements()) { 368 Log.e(TAG, "PKCS12 file has no elements"); 369 return false; 370 } 371 372 while (aliases.hasMoreElements()) { 373 String alias = aliases.nextElement(); 374 if (keystore.isKeyEntry(alias)) { 375 KeyStore.Entry entry = keystore.getEntry(alias, password); 376 Log.d(TAG, "extracted alias = " + alias + ", entry=" + entry.getClass()); 377 378 if (entry instanceof PrivateKeyEntry) { 379 if (TextUtils.isEmpty(mName)) { 380 mName = alias; 381 } 382 return installFrom((PrivateKeyEntry) entry); 383 } 384 } else { 385 // KeyStore.getEntry with non-null ProtectionParameter can only be invoked on 386 // PrivateKeyEntry or SecretKeyEntry. 387 // See https://docs.oracle.com/javase/8/docs/api/java/security/KeyStore.html 388 Log.d(TAG, "Skip non-key entry, alias = " + alias); 389 } 390 } 391 return true; 392 } 393 loadPkcs12Internal(PasswordProtection password)394 private java.security.KeyStore loadPkcs12Internal(PasswordProtection password) 395 throws Exception { 396 java.security.KeyStore keystore = java.security.KeyStore.getInstance("PKCS12"); 397 keystore.load(new ByteArrayInputStream(getData(KeyChain.EXTRA_PKCS12)), 398 password.getPassword()); 399 return keystore; 400 } 401 installFrom(PrivateKeyEntry entry)402 private synchronized boolean installFrom(PrivateKeyEntry entry) { 403 mUserKey = entry.getPrivateKey(); 404 mUserCert = (X509Certificate) entry.getCertificate(); 405 406 Certificate[] certs = entry.getCertificateChain(); 407 Log.d(TAG, "# certs extracted = " + certs.length); 408 mCaCerts = new ArrayList<X509Certificate>(certs.length); 409 for (Certificate c : certs) { 410 X509Certificate cert = (X509Certificate) c; 411 if (isCa(cert)) { 412 mCaCerts.add(cert); 413 } 414 } 415 Log.d(TAG, "# ca certs extracted = " + mCaCerts.size()); 416 417 return true; 418 } 419 isWear(final Context context)420 private static boolean isWear(final Context context) { 421 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); 422 } 423 424 /** 425 * Returns whether this credential contains CA certificates to be used as trust anchors 426 * for VPN and apps. 427 */ includesVpnAndAppsTrustAnchors()428 public boolean includesVpnAndAppsTrustAnchors() { 429 if (!hasCaCerts()) { 430 return false; 431 } 432 if (getInstallAsUid() != android.security.KeyStore.UID_SELF) { 433 // VPN and Apps trust anchors can only be installed under UID_SELF 434 return false; 435 } 436 437 if (mUserKey != null) { 438 // We are installing a key pair for client authentication, its CA 439 // should have nothing to do with VPN and apps trust anchors. 440 return false; 441 } else { 442 return true; 443 } 444 } 445 } 446