1 /* 2 * Copyright (C) 2012 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 android.security; 18 19 import com.android.org.conscrypt.OpenSSLEngine; 20 import com.android.org.conscrypt.OpenSSLKeyHolder; 21 22 import android.util.Log; 23 24 import java.io.ByteArrayInputStream; 25 import java.io.IOException; 26 import java.io.InputStream; 27 import java.io.OutputStream; 28 import java.security.InvalidKeyException; 29 import java.security.Key; 30 import java.security.KeyStore.Entry; 31 import java.security.KeyStore.PrivateKeyEntry; 32 import java.security.KeyStore.ProtectionParameter; 33 import java.security.KeyStore; 34 import java.security.KeyStoreException; 35 import java.security.KeyStoreSpi; 36 import java.security.NoSuchAlgorithmException; 37 import java.security.PrivateKey; 38 import java.security.UnrecoverableKeyException; 39 import java.security.cert.Certificate; 40 import java.security.cert.CertificateEncodingException; 41 import java.security.cert.CertificateException; 42 import java.security.cert.CertificateFactory; 43 import java.security.cert.X509Certificate; 44 import java.util.ArrayList; 45 import java.util.Collection; 46 import java.util.Collections; 47 import java.util.Date; 48 import java.util.Enumeration; 49 import java.util.HashSet; 50 import java.util.Iterator; 51 import java.util.Set; 52 53 /** 54 * A java.security.KeyStore interface for the Android KeyStore. An instance of 55 * it can be created via the {@link java.security.KeyStore#getInstance(String) 56 * KeyStore.getInstance("AndroidKeyStore")} interface. This returns a 57 * java.security.KeyStore backed by this "AndroidKeyStore" implementation. 58 * <p> 59 * This is built on top of Android's keystore daemon. The convention of alias 60 * use is: 61 * <p> 62 * PrivateKeyEntry will have a Credentials.USER_PRIVATE_KEY as the private key, 63 * Credentials.USER_CERTIFICATE as the first certificate in the chain (the one 64 * that corresponds to the private key), and then a Credentials.CA_CERTIFICATE 65 * entry which will have the rest of the chain concatenated in BER format. 66 * <p> 67 * TrustedCertificateEntry will just have a Credentials.CA_CERTIFICATE entry 68 * with a single certificate. 69 * 70 * @hide 71 */ 72 public class AndroidKeyStore extends KeyStoreSpi { 73 public static final String NAME = "AndroidKeyStore"; 74 75 private android.security.KeyStore mKeyStore; 76 77 @Override 78 public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, 79 UnrecoverableKeyException { 80 if (!isKeyEntry(alias)) { 81 return null; 82 } 83 84 final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore"); 85 try { 86 return engine.getPrivateKeyById(Credentials.USER_PRIVATE_KEY + alias); 87 } catch (InvalidKeyException e) { 88 UnrecoverableKeyException t = new UnrecoverableKeyException("Can't get key"); 89 t.initCause(e); 90 throw t; 91 } 92 } 93 94 @Override 95 public Certificate[] engineGetCertificateChain(String alias) { 96 if (alias == null) { 97 throw new NullPointerException("alias == null"); 98 } 99 100 final X509Certificate leaf = (X509Certificate) engineGetCertificate(alias); 101 if (leaf == null) { 102 return null; 103 } 104 105 final Certificate[] caList; 106 107 final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); 108 if (caBytes != null) { 109 final Collection<X509Certificate> caChain = toCertificates(caBytes); 110 111 caList = new Certificate[caChain.size() + 1]; 112 113 final Iterator<X509Certificate> it = caChain.iterator(); 114 int i = 1; 115 while (it.hasNext()) { 116 caList[i++] = it.next(); 117 } 118 } else { 119 caList = new Certificate[1]; 120 } 121 122 caList[0] = leaf; 123 124 return caList; 125 } 126 127 @Override 128 public Certificate engineGetCertificate(String alias) { 129 if (alias == null) { 130 throw new NullPointerException("alias == null"); 131 } 132 133 byte[] certificate = mKeyStore.get(Credentials.USER_CERTIFICATE + alias); 134 if (certificate != null) { 135 return toCertificate(certificate); 136 } 137 138 certificate = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); 139 if (certificate != null) { 140 return toCertificate(certificate); 141 } 142 143 return null; 144 } 145 146 private static X509Certificate toCertificate(byte[] bytes) { 147 try { 148 final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 149 return (X509Certificate) certFactory 150 .generateCertificate(new ByteArrayInputStream(bytes)); 151 } catch (CertificateException e) { 152 Log.w(NAME, "Couldn't parse certificate in keystore", e); 153 return null; 154 } 155 } 156 157 @SuppressWarnings("unchecked") 158 private static Collection<X509Certificate> toCertificates(byte[] bytes) { 159 try { 160 final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 161 return (Collection<X509Certificate>) certFactory 162 .generateCertificates(new ByteArrayInputStream(bytes)); 163 } catch (CertificateException e) { 164 Log.w(NAME, "Couldn't parse certificates in keystore", e); 165 return new ArrayList<X509Certificate>(); 166 } 167 } 168 169 private Date getModificationDate(String alias) { 170 final long epochMillis = mKeyStore.getmtime(alias); 171 if (epochMillis == -1L) { 172 return null; 173 } 174 175 return new Date(epochMillis); 176 } 177 178 @Override 179 public Date engineGetCreationDate(String alias) { 180 if (alias == null) { 181 throw new NullPointerException("alias == null"); 182 } 183 184 Date d = getModificationDate(Credentials.USER_PRIVATE_KEY + alias); 185 if (d != null) { 186 return d; 187 } 188 189 d = getModificationDate(Credentials.USER_CERTIFICATE + alias); 190 if (d != null) { 191 return d; 192 } 193 194 return getModificationDate(Credentials.CA_CERTIFICATE + alias); 195 } 196 197 @Override 198 public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) 199 throws KeyStoreException { 200 if ((password != null) && (password.length > 0)) { 201 throw new KeyStoreException("entries cannot be protected with passwords"); 202 } 203 204 if (key instanceof PrivateKey) { 205 setPrivateKeyEntry(alias, (PrivateKey) key, chain, null); 206 } else { 207 throw new KeyStoreException("Only PrivateKeys are supported"); 208 } 209 } 210 211 private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain, 212 KeyStoreParameter params) throws KeyStoreException { 213 byte[] keyBytes = null; 214 215 final String pkeyAlias; 216 if (key instanceof OpenSSLKeyHolder) { 217 pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias(); 218 } else { 219 pkeyAlias = null; 220 } 221 222 final boolean shouldReplacePrivateKey; 223 if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) { 224 final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length()); 225 if (!alias.equals(keySubalias)) { 226 throw new KeyStoreException("Can only replace keys with same alias: " + alias 227 + " != " + keySubalias); 228 } 229 230 shouldReplacePrivateKey = false; 231 } else { 232 // Make sure the PrivateKey format is the one we support. 233 final String keyFormat = key.getFormat(); 234 if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) { 235 throw new KeyStoreException( 236 "Only PrivateKeys that can be encoded into PKCS#8 are supported"); 237 } 238 239 // Make sure we can actually encode the key. 240 keyBytes = key.getEncoded(); 241 if (keyBytes == null) { 242 throw new KeyStoreException("PrivateKey has no encoding"); 243 } 244 245 shouldReplacePrivateKey = true; 246 } 247 248 // Make sure the chain exists since this is a PrivateKey 249 if ((chain == null) || (chain.length == 0)) { 250 throw new KeyStoreException("Must supply at least one Certificate with PrivateKey"); 251 } 252 253 // Do chain type checking. 254 X509Certificate[] x509chain = new X509Certificate[chain.length]; 255 for (int i = 0; i < chain.length; i++) { 256 if (!"X.509".equals(chain[i].getType())) { 257 throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #" 258 + i); 259 } 260 261 if (!(chain[i] instanceof X509Certificate)) { 262 throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #" 263 + i); 264 } 265 266 x509chain[i] = (X509Certificate) chain[i]; 267 } 268 269 final byte[] userCertBytes; 270 try { 271 userCertBytes = x509chain[0].getEncoded(); 272 } catch (CertificateEncodingException e) { 273 throw new KeyStoreException("Couldn't encode certificate #1", e); 274 } 275 276 /* 277 * If we have a chain, store it in the CA certificate slot for this 278 * alias as concatenated DER-encoded certificates. These can be 279 * deserialized by {@link CertificateFactory#generateCertificates}. 280 */ 281 final byte[] chainBytes; 282 if (chain.length > 1) { 283 /* 284 * The chain is passed in as {user_cert, ca_cert_1, ca_cert_2, ...} 285 * so we only need the certificates starting at index 1. 286 */ 287 final byte[][] certsBytes = new byte[x509chain.length - 1][]; 288 int totalCertLength = 0; 289 for (int i = 0; i < certsBytes.length; i++) { 290 try { 291 certsBytes[i] = x509chain[i + 1].getEncoded(); 292 totalCertLength += certsBytes[i].length; 293 } catch (CertificateEncodingException e) { 294 throw new KeyStoreException("Can't encode Certificate #" + i, e); 295 } 296 } 297 298 /* 299 * Serialize this into one byte array so we can later call 300 * CertificateFactory#generateCertificates to recover them. 301 */ 302 chainBytes = new byte[totalCertLength]; 303 int outputOffset = 0; 304 for (int i = 0; i < certsBytes.length; i++) { 305 final int certLength = certsBytes[i].length; 306 System.arraycopy(certsBytes[i], 0, chainBytes, outputOffset, certLength); 307 outputOffset += certLength; 308 certsBytes[i] = null; 309 } 310 } else { 311 chainBytes = null; 312 } 313 314 /* 315 * Make sure we clear out all the appropriate types before trying to 316 * write. 317 */ 318 if (shouldReplacePrivateKey) { 319 Credentials.deleteAllTypesForAlias(mKeyStore, alias); 320 } else { 321 Credentials.deleteCertificateTypesForAlias(mKeyStore, alias); 322 } 323 324 final int flags = (params == null) ? 0 : params.getFlags(); 325 326 if (shouldReplacePrivateKey 327 && !mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes, 328 android.security.KeyStore.UID_SELF, flags)) { 329 Credentials.deleteAllTypesForAlias(mKeyStore, alias); 330 throw new KeyStoreException("Couldn't put private key in keystore"); 331 } else if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertBytes, 332 android.security.KeyStore.UID_SELF, flags)) { 333 Credentials.deleteAllTypesForAlias(mKeyStore, alias); 334 throw new KeyStoreException("Couldn't put certificate #1 in keystore"); 335 } else if (chainBytes != null 336 && !mKeyStore.put(Credentials.CA_CERTIFICATE + alias, chainBytes, 337 android.security.KeyStore.UID_SELF, flags)) { 338 Credentials.deleteAllTypesForAlias(mKeyStore, alias); 339 throw new KeyStoreException("Couldn't put certificate chain in keystore"); 340 } 341 } 342 343 @Override 344 public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain) 345 throws KeyStoreException { 346 throw new KeyStoreException("Operation not supported because key encoding is unknown"); 347 } 348 349 @Override 350 public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException { 351 if (isKeyEntry(alias)) { 352 throw new KeyStoreException("Entry exists and is not a trusted certificate"); 353 } 354 355 // We can't set something to null. 356 if (cert == null) { 357 throw new NullPointerException("cert == null"); 358 } 359 360 final byte[] encoded; 361 try { 362 encoded = cert.getEncoded(); 363 } catch (CertificateEncodingException e) { 364 throw new KeyStoreException(e); 365 } 366 367 if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded, 368 android.security.KeyStore.UID_SELF, android.security.KeyStore.FLAG_NONE)) { 369 throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?"); 370 } 371 } 372 373 @Override 374 public void engineDeleteEntry(String alias) throws KeyStoreException { 375 if (!isKeyEntry(alias) && !isCertificateEntry(alias)) { 376 return; 377 } 378 379 if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) { 380 throw new KeyStoreException("No such entry " + alias); 381 } 382 } 383 384 private Set<String> getUniqueAliases() { 385 final String[] rawAliases = mKeyStore.saw(""); 386 if (rawAliases == null) { 387 return new HashSet<String>(); 388 } 389 390 final Set<String> aliases = new HashSet<String>(rawAliases.length); 391 for (String alias : rawAliases) { 392 final int idx = alias.indexOf('_'); 393 if ((idx == -1) || (alias.length() <= idx)) { 394 Log.e(NAME, "invalid alias: " + alias); 395 continue; 396 } 397 398 aliases.add(new String(alias.substring(idx + 1))); 399 } 400 401 return aliases; 402 } 403 404 @Override 405 public Enumeration<String> engineAliases() { 406 return Collections.enumeration(getUniqueAliases()); 407 } 408 409 @Override 410 public boolean engineContainsAlias(String alias) { 411 if (alias == null) { 412 throw new NullPointerException("alias == null"); 413 } 414 415 return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias) 416 || mKeyStore.contains(Credentials.USER_CERTIFICATE + alias) 417 || mKeyStore.contains(Credentials.CA_CERTIFICATE + alias); 418 } 419 420 @Override 421 public int engineSize() { 422 return getUniqueAliases().size(); 423 } 424 425 @Override 426 public boolean engineIsKeyEntry(String alias) { 427 return isKeyEntry(alias); 428 } 429 430 private boolean isKeyEntry(String alias) { 431 if (alias == null) { 432 throw new NullPointerException("alias == null"); 433 } 434 435 return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias); 436 } 437 438 private boolean isCertificateEntry(String alias) { 439 if (alias == null) { 440 throw new NullPointerException("alias == null"); 441 } 442 443 return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias); 444 } 445 446 @Override 447 public boolean engineIsCertificateEntry(String alias) { 448 return !isKeyEntry(alias) && isCertificateEntry(alias); 449 } 450 451 @Override 452 public String engineGetCertificateAlias(Certificate cert) { 453 if (cert == null) { 454 return null; 455 } 456 457 final Set<String> nonCaEntries = new HashSet<String>(); 458 459 /* 460 * First scan the PrivateKeyEntry types. The KeyStoreSpi documentation 461 * says to only compare the first certificate in the chain which is 462 * equivalent to the USER_CERTIFICATE prefix for the Android keystore 463 * convention. 464 */ 465 final String[] certAliases = mKeyStore.saw(Credentials.USER_CERTIFICATE); 466 if (certAliases != null) { 467 for (String alias : certAliases) { 468 final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias); 469 if (certBytes == null) { 470 continue; 471 } 472 473 final Certificate c = toCertificate(certBytes); 474 nonCaEntries.add(alias); 475 476 if (cert.equals(c)) { 477 return alias; 478 } 479 } 480 } 481 482 /* 483 * Look at all the TrustedCertificateEntry types. Skip all the 484 * PrivateKeyEntry we looked at above. 485 */ 486 final String[] caAliases = mKeyStore.saw(Credentials.CA_CERTIFICATE); 487 if (certAliases != null) { 488 for (String alias : caAliases) { 489 if (nonCaEntries.contains(alias)) { 490 continue; 491 } 492 493 final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); 494 if (certBytes == null) { 495 continue; 496 } 497 498 final Certificate c = 499 toCertificate(mKeyStore.get(Credentials.CA_CERTIFICATE + alias)); 500 if (cert.equals(c)) { 501 return alias; 502 } 503 } 504 } 505 506 return null; 507 } 508 509 @Override 510 public void engineStore(OutputStream stream, char[] password) throws IOException, 511 NoSuchAlgorithmException, CertificateException { 512 throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream"); 513 } 514 515 @Override 516 public void engineLoad(InputStream stream, char[] password) throws IOException, 517 NoSuchAlgorithmException, CertificateException { 518 if (stream != null) { 519 throw new IllegalArgumentException("InputStream not supported"); 520 } 521 522 if (password != null) { 523 throw new IllegalArgumentException("password not supported"); 524 } 525 526 // Unfortunate name collision. 527 mKeyStore = android.security.KeyStore.getInstance(); 528 } 529 530 @Override 531 public void engineSetEntry(String alias, Entry entry, ProtectionParameter param) 532 throws KeyStoreException { 533 if (entry == null) { 534 throw new KeyStoreException("entry == null"); 535 } 536 537 if (engineContainsAlias(alias)) { 538 engineDeleteEntry(alias); 539 } 540 541 if (entry instanceof KeyStore.TrustedCertificateEntry) { 542 KeyStore.TrustedCertificateEntry trE = (KeyStore.TrustedCertificateEntry) entry; 543 engineSetCertificateEntry(alias, trE.getTrustedCertificate()); 544 return; 545 } 546 547 if (param != null && !(param instanceof KeyStoreParameter)) { 548 throw new KeyStoreException( 549 "protParam should be android.security.KeyStoreParameter; was: " 550 + param.getClass().getName()); 551 } 552 553 if (entry instanceof PrivateKeyEntry) { 554 PrivateKeyEntry prE = (PrivateKeyEntry) entry; 555 setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(), 556 (KeyStoreParameter) param); 557 return; 558 } 559 560 throw new KeyStoreException( 561 "Entry must be a PrivateKeyEntry or TrustedCertificateEntry; was " + entry); 562 } 563 564 } 565