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