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