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