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