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