Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 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 | |
Kenny Root | 12e7522 | 2013-04-23 22:34:24 -0700 | [diff] [blame] | 19 | import com.android.org.conscrypt.OpenSSLEngine; |
| 20 | import com.android.org.conscrypt.OpenSSLKeyHolder; |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 21 | |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 22 | import libcore.util.EmptyArray; |
| 23 | |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 24 | import android.security.keymaster.KeyCharacteristics; |
| 25 | import android.security.keymaster.KeymasterArguments; |
| 26 | import android.security.keymaster.KeymasterDefs; |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 27 | import android.util.Log; |
| 28 | |
| 29 | import java.io.ByteArrayInputStream; |
| 30 | import java.io.IOException; |
| 31 | import java.io.InputStream; |
| 32 | import java.io.OutputStream; |
| 33 | import java.security.InvalidKeyException; |
| 34 | import java.security.Key; |
Kenny Root | 2eeda72 | 2013-04-10 11:30:58 -0700 | [diff] [blame] | 35 | import java.security.KeyStore.Entry; |
| 36 | import java.security.KeyStore.PrivateKeyEntry; |
| 37 | import java.security.KeyStore.ProtectionParameter; |
| 38 | import java.security.KeyStore; |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 39 | import java.security.KeyStore.SecretKeyEntry; |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 40 | import java.security.KeyStoreException; |
| 41 | import java.security.KeyStoreSpi; |
| 42 | import java.security.NoSuchAlgorithmException; |
| 43 | import java.security.PrivateKey; |
| 44 | import java.security.UnrecoverableKeyException; |
| 45 | import java.security.cert.Certificate; |
| 46 | import java.security.cert.CertificateEncodingException; |
| 47 | import java.security.cert.CertificateException; |
| 48 | import java.security.cert.CertificateFactory; |
| 49 | import java.security.cert.X509Certificate; |
| 50 | import java.util.ArrayList; |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 51 | import java.util.Arrays; |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 52 | import java.util.Collection; |
| 53 | import java.util.Collections; |
| 54 | import java.util.Date; |
| 55 | import java.util.Enumeration; |
| 56 | import java.util.HashSet; |
| 57 | import java.util.Iterator; |
Alex Klyubin | 5eacd77 | 2015-04-14 19:00:35 -0700 | [diff] [blame] | 58 | import java.util.List; |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 59 | import java.util.Set; |
| 60 | |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 61 | import javax.crypto.SecretKey; |
| 62 | |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 63 | /** |
Kenny Root | db02671 | 2012-08-20 10:48:46 -0700 | [diff] [blame] | 64 | * A java.security.KeyStore interface for the Android KeyStore. An instance of |
| 65 | * it can be created via the {@link java.security.KeyStore#getInstance(String) |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 66 | * KeyStore.getInstance("AndroidKeyStore")} interface. This returns a |
| 67 | * java.security.KeyStore backed by this "AndroidKeyStore" implementation. |
| 68 | * <p> |
| 69 | * This is built on top of Android's keystore daemon. The convention of alias |
| 70 | * use is: |
| 71 | * <p> |
| 72 | * PrivateKeyEntry will have a Credentials.USER_PRIVATE_KEY as the private key, |
| 73 | * Credentials.USER_CERTIFICATE as the first certificate in the chain (the one |
| 74 | * that corresponds to the private key), and then a Credentials.CA_CERTIFICATE |
| 75 | * entry which will have the rest of the chain concatenated in BER format. |
| 76 | * <p> |
| 77 | * TrustedCertificateEntry will just have a Credentials.CA_CERTIFICATE entry |
| 78 | * with a single certificate. |
| 79 | * |
| 80 | * @hide |
| 81 | */ |
| 82 | public class AndroidKeyStore extends KeyStoreSpi { |
| 83 | public static final String NAME = "AndroidKeyStore"; |
| 84 | |
| 85 | private android.security.KeyStore mKeyStore; |
| 86 | |
| 87 | @Override |
| 88 | public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, |
| 89 | UnrecoverableKeyException { |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 90 | if (isPrivateKeyEntry(alias)) { |
| 91 | final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore"); |
| 92 | try { |
| 93 | return engine.getPrivateKeyById(Credentials.USER_PRIVATE_KEY + alias); |
| 94 | } catch (InvalidKeyException e) { |
| 95 | UnrecoverableKeyException t = new UnrecoverableKeyException("Can't get key"); |
| 96 | t.initCause(e); |
| 97 | throw t; |
| 98 | } |
| 99 | } else if (isSecretKeyEntry(alias)) { |
| 100 | KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); |
| 101 | String keyAliasInKeystore = Credentials.USER_SECRET_KEY + alias; |
| 102 | int errorCode = mKeyStore.getKeyCharacteristics( |
| 103 | keyAliasInKeystore, null, null, keyCharacteristics); |
| 104 | if ((errorCode != KeymasterDefs.KM_ERROR_OK) |
| 105 | && (errorCode != android.security.KeyStore.NO_ERROR)) { |
| 106 | throw new UnrecoverableKeyException("Failed to load information about key." |
| 107 | + " Error code: " + errorCode); |
| 108 | } |
| 109 | |
| 110 | int keymasterAlgorithm = |
| 111 | keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1); |
| 112 | if (keymasterAlgorithm == -1) { |
| 113 | keymasterAlgorithm = |
| 114 | keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1); |
| 115 | } |
| 116 | if (keymasterAlgorithm == -1) { |
| 117 | throw new UnrecoverableKeyException("Key algorithm unknown"); |
| 118 | } |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 119 | |
Alex Klyubin | 5eacd77 | 2015-04-14 19:00:35 -0700 | [diff] [blame] | 120 | List<Integer> keymasterDigests = |
| 121 | keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST); |
| 122 | int keymasterDigest; |
| 123 | if (keymasterDigests.isEmpty()) { |
| 124 | keymasterDigest = -1; |
| 125 | } else { |
| 126 | // More than one digest can be permitted for this key. Use the first one to form the |
| 127 | // JCA key algorithm name. |
| 128 | keymasterDigest = keymasterDigests.get(0); |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 129 | } |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 130 | |
| 131 | String keyAlgorithmString; |
| 132 | try { |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 133 | keyAlgorithmString = KeymasterUtils.getJcaSecretKeyAlgorithm( |
| 134 | keymasterAlgorithm, keymasterDigest); |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 135 | } catch (IllegalArgumentException e) { |
| 136 | throw (UnrecoverableKeyException) |
| 137 | new UnrecoverableKeyException("Unsupported secret key type").initCause(e); |
| 138 | } |
| 139 | |
| 140 | return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmString); |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 141 | } |
| 142 | |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 143 | return null; |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 144 | } |
| 145 | |
| 146 | @Override |
| 147 | public Certificate[] engineGetCertificateChain(String alias) { |
Kenny Root | a4640c0 | 2012-08-31 13:38:11 -0700 | [diff] [blame] | 148 | if (alias == null) { |
| 149 | throw new NullPointerException("alias == null"); |
| 150 | } |
| 151 | |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 152 | final X509Certificate leaf = (X509Certificate) engineGetCertificate(alias); |
| 153 | if (leaf == null) { |
| 154 | return null; |
| 155 | } |
| 156 | |
| 157 | final Certificate[] caList; |
| 158 | |
| 159 | final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); |
| 160 | if (caBytes != null) { |
| 161 | final Collection<X509Certificate> caChain = toCertificates(caBytes); |
| 162 | |
| 163 | caList = new Certificate[caChain.size() + 1]; |
| 164 | |
| 165 | final Iterator<X509Certificate> it = caChain.iterator(); |
| 166 | int i = 1; |
| 167 | while (it.hasNext()) { |
| 168 | caList[i++] = it.next(); |
| 169 | } |
| 170 | } else { |
| 171 | caList = new Certificate[1]; |
| 172 | } |
| 173 | |
| 174 | caList[0] = leaf; |
| 175 | |
| 176 | return caList; |
| 177 | } |
| 178 | |
| 179 | @Override |
| 180 | public Certificate engineGetCertificate(String alias) { |
Kenny Root | a4640c0 | 2012-08-31 13:38:11 -0700 | [diff] [blame] | 181 | if (alias == null) { |
| 182 | throw new NullPointerException("alias == null"); |
| 183 | } |
| 184 | |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 185 | byte[] certificate = mKeyStore.get(Credentials.USER_CERTIFICATE + alias); |
| 186 | if (certificate != null) { |
| 187 | return toCertificate(certificate); |
| 188 | } |
| 189 | |
| 190 | certificate = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); |
| 191 | if (certificate != null) { |
| 192 | return toCertificate(certificate); |
| 193 | } |
| 194 | |
| 195 | return null; |
| 196 | } |
| 197 | |
| 198 | private static X509Certificate toCertificate(byte[] bytes) { |
| 199 | try { |
| 200 | final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); |
| 201 | return (X509Certificate) certFactory |
| 202 | .generateCertificate(new ByteArrayInputStream(bytes)); |
| 203 | } catch (CertificateException e) { |
| 204 | Log.w(NAME, "Couldn't parse certificate in keystore", e); |
| 205 | return null; |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | @SuppressWarnings("unchecked") |
| 210 | private static Collection<X509Certificate> toCertificates(byte[] bytes) { |
| 211 | try { |
| 212 | final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); |
| 213 | return (Collection<X509Certificate>) certFactory |
| 214 | .generateCertificates(new ByteArrayInputStream(bytes)); |
| 215 | } catch (CertificateException e) { |
| 216 | Log.w(NAME, "Couldn't parse certificates in keystore", e); |
| 217 | return new ArrayList<X509Certificate>(); |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | private Date getModificationDate(String alias) { |
| 222 | final long epochMillis = mKeyStore.getmtime(alias); |
| 223 | if (epochMillis == -1L) { |
| 224 | return null; |
| 225 | } |
| 226 | |
| 227 | return new Date(epochMillis); |
| 228 | } |
| 229 | |
| 230 | @Override |
| 231 | public Date engineGetCreationDate(String alias) { |
Kenny Root | a4640c0 | 2012-08-31 13:38:11 -0700 | [diff] [blame] | 232 | if (alias == null) { |
| 233 | throw new NullPointerException("alias == null"); |
| 234 | } |
| 235 | |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 236 | Date d = getModificationDate(Credentials.USER_PRIVATE_KEY + alias); |
| 237 | if (d != null) { |
| 238 | return d; |
| 239 | } |
| 240 | |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 241 | d = getModificationDate(Credentials.USER_SECRET_KEY + alias); |
| 242 | if (d != null) { |
| 243 | return d; |
| 244 | } |
| 245 | |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 246 | d = getModificationDate(Credentials.USER_CERTIFICATE + alias); |
| 247 | if (d != null) { |
| 248 | return d; |
| 249 | } |
| 250 | |
| 251 | return getModificationDate(Credentials.CA_CERTIFICATE + alias); |
| 252 | } |
| 253 | |
| 254 | @Override |
| 255 | public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) |
| 256 | throws KeyStoreException { |
| 257 | if ((password != null) && (password.length > 0)) { |
| 258 | throw new KeyStoreException("entries cannot be protected with passwords"); |
| 259 | } |
| 260 | |
| 261 | if (key instanceof PrivateKey) { |
Kenny Root | 2eeda72 | 2013-04-10 11:30:58 -0700 | [diff] [blame] | 262 | setPrivateKeyEntry(alias, (PrivateKey) key, chain, null); |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 263 | } else if (key instanceof SecretKey) { |
| 264 | setSecretKeyEntry(alias, (SecretKey) key, null); |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 265 | } else { |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 266 | throw new KeyStoreException("Only PrivateKey and SecretKey are supported"); |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 267 | } |
| 268 | } |
| 269 | |
Kenny Root | 2eeda72 | 2013-04-10 11:30:58 -0700 | [diff] [blame] | 270 | private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain, |
Kenny Root | 1c219f6 | 2013-04-18 17:57:03 -0700 | [diff] [blame] | 271 | KeyStoreParameter params) throws KeyStoreException { |
Kenny Root | 802768d | 2012-08-21 15:23:35 -0700 | [diff] [blame] | 272 | byte[] keyBytes = null; |
| 273 | |
| 274 | final String pkeyAlias; |
Kenny Root | cc1fc6b | 2013-01-22 13:25:38 -0800 | [diff] [blame] | 275 | if (key instanceof OpenSSLKeyHolder) { |
| 276 | pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias(); |
Kenny Root | 802768d | 2012-08-21 15:23:35 -0700 | [diff] [blame] | 277 | } else { |
| 278 | pkeyAlias = null; |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 279 | } |
| 280 | |
Kenny Root | 802768d | 2012-08-21 15:23:35 -0700 | [diff] [blame] | 281 | final boolean shouldReplacePrivateKey; |
| 282 | if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) { |
| 283 | final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length()); |
| 284 | if (!alias.equals(keySubalias)) { |
| 285 | throw new KeyStoreException("Can only replace keys with same alias: " + alias |
| 286 | + " != " + keySubalias); |
| 287 | } |
| 288 | |
| 289 | shouldReplacePrivateKey = false; |
| 290 | } else { |
| 291 | // Make sure the PrivateKey format is the one we support. |
| 292 | final String keyFormat = key.getFormat(); |
| 293 | if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) { |
| 294 | throw new KeyStoreException( |
| 295 | "Only PrivateKeys that can be encoded into PKCS#8 are supported"); |
| 296 | } |
| 297 | |
| 298 | // Make sure we can actually encode the key. |
| 299 | keyBytes = key.getEncoded(); |
| 300 | if (keyBytes == null) { |
| 301 | throw new KeyStoreException("PrivateKey has no encoding"); |
| 302 | } |
| 303 | |
| 304 | shouldReplacePrivateKey = true; |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 305 | } |
| 306 | |
| 307 | // Make sure the chain exists since this is a PrivateKey |
| 308 | if ((chain == null) || (chain.length == 0)) { |
| 309 | throw new KeyStoreException("Must supply at least one Certificate with PrivateKey"); |
| 310 | } |
| 311 | |
| 312 | // Do chain type checking. |
| 313 | X509Certificate[] x509chain = new X509Certificate[chain.length]; |
| 314 | for (int i = 0; i < chain.length; i++) { |
| 315 | if (!"X.509".equals(chain[i].getType())) { |
| 316 | throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #" |
| 317 | + i); |
| 318 | } |
| 319 | |
| 320 | if (!(chain[i] instanceof X509Certificate)) { |
| 321 | throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #" |
| 322 | + i); |
| 323 | } |
| 324 | |
| 325 | x509chain[i] = (X509Certificate) chain[i]; |
| 326 | } |
| 327 | |
| 328 | final byte[] userCertBytes; |
| 329 | try { |
| 330 | userCertBytes = x509chain[0].getEncoded(); |
| 331 | } catch (CertificateEncodingException e) { |
| 332 | throw new KeyStoreException("Couldn't encode certificate #1", e); |
| 333 | } |
| 334 | |
| 335 | /* |
| 336 | * If we have a chain, store it in the CA certificate slot for this |
| 337 | * alias as concatenated DER-encoded certificates. These can be |
| 338 | * deserialized by {@link CertificateFactory#generateCertificates}. |
| 339 | */ |
| 340 | final byte[] chainBytes; |
| 341 | if (chain.length > 1) { |
| 342 | /* |
| 343 | * The chain is passed in as {user_cert, ca_cert_1, ca_cert_2, ...} |
| 344 | * so we only need the certificates starting at index 1. |
| 345 | */ |
| 346 | final byte[][] certsBytes = new byte[x509chain.length - 1][]; |
| 347 | int totalCertLength = 0; |
| 348 | for (int i = 0; i < certsBytes.length; i++) { |
| 349 | try { |
| 350 | certsBytes[i] = x509chain[i + 1].getEncoded(); |
| 351 | totalCertLength += certsBytes[i].length; |
| 352 | } catch (CertificateEncodingException e) { |
| 353 | throw new KeyStoreException("Can't encode Certificate #" + i, e); |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | /* |
| 358 | * Serialize this into one byte array so we can later call |
| 359 | * CertificateFactory#generateCertificates to recover them. |
| 360 | */ |
| 361 | chainBytes = new byte[totalCertLength]; |
| 362 | int outputOffset = 0; |
| 363 | for (int i = 0; i < certsBytes.length; i++) { |
| 364 | final int certLength = certsBytes[i].length; |
| 365 | System.arraycopy(certsBytes[i], 0, chainBytes, outputOffset, certLength); |
| 366 | outputOffset += certLength; |
| 367 | certsBytes[i] = null; |
| 368 | } |
| 369 | } else { |
| 370 | chainBytes = null; |
| 371 | } |
| 372 | |
| 373 | /* |
Kenny Root | 802768d | 2012-08-21 15:23:35 -0700 | [diff] [blame] | 374 | * Make sure we clear out all the appropriate types before trying to |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 375 | * write. |
| 376 | */ |
Kenny Root | 802768d | 2012-08-21 15:23:35 -0700 | [diff] [blame] | 377 | if (shouldReplacePrivateKey) { |
| 378 | Credentials.deleteAllTypesForAlias(mKeyStore, alias); |
| 379 | } else { |
| 380 | Credentials.deleteCertificateTypesForAlias(mKeyStore, alias); |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 381 | Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias); |
Kenny Root | 802768d | 2012-08-21 15:23:35 -0700 | [diff] [blame] | 382 | } |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 383 | |
Kenny Root | 2eeda72 | 2013-04-10 11:30:58 -0700 | [diff] [blame] | 384 | final int flags = (params == null) ? 0 : params.getFlags(); |
| 385 | |
Kenny Root | 802768d | 2012-08-21 15:23:35 -0700 | [diff] [blame] | 386 | if (shouldReplacePrivateKey |
Kenny Root | 2eeda72 | 2013-04-10 11:30:58 -0700 | [diff] [blame] | 387 | && !mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes, |
| 388 | android.security.KeyStore.UID_SELF, flags)) { |
Kenny Root | 802768d | 2012-08-21 15:23:35 -0700 | [diff] [blame] | 389 | Credentials.deleteAllTypesForAlias(mKeyStore, alias); |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 390 | throw new KeyStoreException("Couldn't put private key in keystore"); |
Kenny Root | 2eeda72 | 2013-04-10 11:30:58 -0700 | [diff] [blame] | 391 | } else if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertBytes, |
| 392 | android.security.KeyStore.UID_SELF, flags)) { |
Kenny Root | 802768d | 2012-08-21 15:23:35 -0700 | [diff] [blame] | 393 | Credentials.deleteAllTypesForAlias(mKeyStore, alias); |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 394 | throw new KeyStoreException("Couldn't put certificate #1 in keystore"); |
| 395 | } else if (chainBytes != null |
Kenny Root | 2eeda72 | 2013-04-10 11:30:58 -0700 | [diff] [blame] | 396 | && !mKeyStore.put(Credentials.CA_CERTIFICATE + alias, chainBytes, |
| 397 | android.security.KeyStore.UID_SELF, flags)) { |
Kenny Root | 802768d | 2012-08-21 15:23:35 -0700 | [diff] [blame] | 398 | Credentials.deleteAllTypesForAlias(mKeyStore, alias); |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 399 | throw new KeyStoreException("Couldn't put certificate chain in keystore"); |
| 400 | } |
| 401 | } |
| 402 | |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 403 | private void setSecretKeyEntry(String entryAlias, SecretKey key, KeyStoreParameter params) |
| 404 | throws KeyStoreException { |
| 405 | if (key instanceof KeyStoreSecretKey) { |
| 406 | // KeyStore-backed secret key. It cannot be duplicated into another entry and cannot |
| 407 | // overwrite its own entry. |
| 408 | String keyAliasInKeystore = ((KeyStoreSecretKey) key).getAlias(); |
| 409 | if (keyAliasInKeystore == null) { |
| 410 | throw new KeyStoreException("KeyStore-backed secret key does not have an alias"); |
| 411 | } |
| 412 | if (!keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) { |
| 413 | throw new KeyStoreException("KeyStore-backed secret key has invalid alias: " |
| 414 | + keyAliasInKeystore); |
| 415 | } |
| 416 | String keyEntryAlias = |
| 417 | keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length()); |
| 418 | if (!entryAlias.equals(keyEntryAlias)) { |
| 419 | throw new KeyStoreException("Can only replace KeyStore-backed keys with same" |
| 420 | + " alias: " + entryAlias + " != " + keyEntryAlias); |
| 421 | } |
| 422 | // This is the entry where this key is already stored. No need to do anything. |
| 423 | if (params != null) { |
| 424 | throw new KeyStoreException("Modifying KeyStore-backed key using protection" |
| 425 | + " parameters not supported"); |
| 426 | } |
| 427 | return; |
| 428 | } |
| 429 | |
| 430 | if (params == null) { |
| 431 | throw new KeyStoreException( |
| 432 | "Protection parameters must be specified when importing a symmetric key"); |
| 433 | } |
| 434 | |
| 435 | // Not a KeyStore-backed secret key -- import its key material into keystore. |
| 436 | String keyExportFormat = key.getFormat(); |
| 437 | if (keyExportFormat == null) { |
| 438 | throw new KeyStoreException( |
| 439 | "Only secret keys that export their key material are supported"); |
| 440 | } else if (!"RAW".equals(keyExportFormat)) { |
| 441 | throw new KeyStoreException( |
| 442 | "Unsupported secret key material export format: " + keyExportFormat); |
| 443 | } |
| 444 | byte[] keyMaterial = key.getEncoded(); |
| 445 | if (keyMaterial == null) { |
| 446 | throw new KeyStoreException("Key did not export its key material despite supporting" |
| 447 | + " RAW format export"); |
| 448 | } |
| 449 | |
| 450 | String keyAlgorithmString = key.getAlgorithm(); |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 451 | int keymasterAlgorithm; |
| 452 | int keymasterDigest; |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 453 | try { |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 454 | keymasterAlgorithm = KeymasterUtils.getKeymasterAlgorithmFromJcaSecretKeyAlgorithm( |
| 455 | keyAlgorithmString); |
| 456 | keymasterDigest = |
| 457 | KeymasterUtils.getKeymasterDigestfromJcaSecretKeyAlgorithm(keyAlgorithmString); |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 458 | } catch (IllegalArgumentException e) { |
| 459 | throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString); |
| 460 | } |
| 461 | |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 462 | KeymasterArguments args = new KeymasterArguments(); |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 463 | args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, keymasterAlgorithm); |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 464 | |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 465 | int[] keymasterDigests; |
Alex Klyubin | c46e9e7 | 2015-04-06 15:36:25 -0700 | [diff] [blame] | 466 | if (params.isDigestsSpecified()) { |
| 467 | // Digest(s) specified in parameters |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 468 | keymasterDigests = |
| 469 | KeymasterUtils.getKeymasterDigestsFromJcaDigestAlgorithms(params.getDigests()); |
| 470 | if (keymasterDigest != -1) { |
Alex Klyubin | c46e9e7 | 2015-04-06 15:36:25 -0700 | [diff] [blame] | 471 | // Digest also specified in the JCA key algorithm name. |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 472 | if (!com.android.internal.util.ArrayUtils.contains( |
| 473 | keymasterDigests, keymasterDigest)) { |
Alex Klyubin | c46e9e7 | 2015-04-06 15:36:25 -0700 | [diff] [blame] | 474 | throw new KeyStoreException("Key digest mismatch" |
| 475 | + ". Key: " + keyAlgorithmString |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 476 | + ", parameter spec: " + Arrays.asList(params.getDigests())); |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 477 | } |
| 478 | } |
| 479 | } else { |
Alex Klyubin | c46e9e7 | 2015-04-06 15:36:25 -0700 | [diff] [blame] | 480 | // No digest specified in parameters |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 481 | if (keymasterDigest != -1) { |
Alex Klyubin | c46e9e7 | 2015-04-06 15:36:25 -0700 | [diff] [blame] | 482 | // Digest specified in the JCA key algorithm name. |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 483 | keymasterDigests = new int[] {keymasterDigest}; |
Alex Klyubin | c46e9e7 | 2015-04-06 15:36:25 -0700 | [diff] [blame] | 484 | } else { |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 485 | keymasterDigests = EmptyArray.INT; |
Alex Klyubin | c46e9e7 | 2015-04-06 15:36:25 -0700 | [diff] [blame] | 486 | } |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 487 | } |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 488 | args.addInts(KeymasterDefs.KM_TAG_DIGEST, keymasterDigests); |
| 489 | if (keymasterDigests.length > 0) { |
Alex Klyubin | c46e9e7 | 2015-04-06 15:36:25 -0700 | [diff] [blame] | 490 | // TODO: Remove MAC length constraint once Keymaster API no longer requires it. |
| 491 | // This code will blow up if mode than one digest is specified. |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 492 | int digestOutputSizeBytes = |
| 493 | KeymasterUtils.getDigestOutputSizeBytes(keymasterDigests[0]); |
| 494 | if (digestOutputSizeBytes != -1) { |
Alex Klyubin | 4ab8ea4 | 2015-03-27 16:53:44 -0700 | [diff] [blame] | 495 | // TODO: Switch to bits instead of bytes, once this is fixed in Keymaster |
| 496 | args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes); |
| 497 | } |
| 498 | } |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 499 | if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { |
| 500 | if (keymasterDigests.length == 0) { |
Alex Klyubin | c46e9e7 | 2015-04-06 15:36:25 -0700 | [diff] [blame] | 501 | throw new KeyStoreException("At least one digest algorithm must be specified" |
| 502 | + " for key algorithm " + keyAlgorithmString); |
Alex Klyubin | b406f24 | 2015-03-31 13:39:38 -0700 | [diff] [blame] | 503 | } |
| 504 | } |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 505 | |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 506 | @KeyStoreKeyProperties.PurposeEnum int purposes = params.getPurposes(); |
| 507 | int[] keymasterBlockModes = KeymasterUtils.getKeymasterBlockModesFromJcaBlockModes( |
| 508 | params.getBlockModes()); |
| 509 | if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) |
Alex Klyubin | f853f64 | 2015-04-08 13:36:22 -0700 | [diff] [blame] | 510 | && (params.isRandomizedEncryptionRequired())) { |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 511 | for (int keymasterBlockMode : keymasterBlockModes) { |
| 512 | if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible(keymasterBlockMode)) { |
| 513 | throw new KeyStoreException( |
| 514 | "Randomized encryption (IND-CPA) required but may be violated by block" |
| 515 | + " mode: " |
| 516 | + KeymasterUtils.getJcaBlockModeFromKeymasterBlockMode( |
| 517 | keymasterBlockMode) |
| 518 | + ". See KeyStoreParameter documentation."); |
| 519 | } |
Alex Klyubin | f853f64 | 2015-04-08 13:36:22 -0700 | [diff] [blame] | 520 | } |
| 521 | } |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 522 | for (int keymasterPurpose : KeyStoreKeyProperties.Purpose.allToKeymaster(purposes)) { |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 523 | args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose); |
| 524 | } |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 525 | args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes); |
| 526 | int[] keymasterPaddings = ArrayUtils.concat( |
| 527 | KeymasterUtils.getKeymasterPaddingsFromJcaEncryptionPaddings( |
| 528 | params.getEncryptionPaddings()), |
| 529 | KeymasterUtils.getKeymasterPaddingsFromJcaSignaturePaddings( |
| 530 | params.getSignaturePaddings())); |
| 531 | args.addInts(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings); |
Alex Klyubin | c46e9e7 | 2015-04-06 15:36:25 -0700 | [diff] [blame] | 532 | if (params.getUserAuthenticators() == 0) { |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 533 | args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); |
| 534 | } else { |
Alex Klyubin | c8e5574 | 2015-03-31 19:50:13 -0700 | [diff] [blame] | 535 | args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 536 | KeyStoreKeyProperties.UserAuthenticator.allToKeymaster( |
Alex Klyubin | c8e5574 | 2015-03-31 19:50:13 -0700 | [diff] [blame] | 537 | params.getUserAuthenticators())); |
Alex Klyubin | 10a9f17 | 2015-04-16 13:41:19 -0700 | [diff] [blame] | 538 | long secureUserId = GateKeeper.getSecureUserId(); |
| 539 | if (secureUserId == 0) { |
| 540 | throw new IllegalStateException("Secure lock screen must be enabled" |
| 541 | + " to import keys requiring user authentication"); |
| 542 | } |
| 543 | args.addLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, secureUserId); |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 544 | } |
Alex Klyubin | 2ea13d4 | 2015-04-01 14:41:28 -0700 | [diff] [blame] | 545 | if (params.isInvalidatedOnNewFingerprintEnrolled()) { |
| 546 | // TODO: Add the invalidate on fingerprint enrolled constraint once Keymaster supports |
| 547 | // that. |
| 548 | } |
Alex Klyubin | c46e9e7 | 2015-04-06 15:36:25 -0700 | [diff] [blame] | 549 | if (params.getUserAuthenticationValidityDurationSeconds() != -1) { |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 550 | args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT, |
| 551 | params.getUserAuthenticationValidityDurationSeconds()); |
| 552 | } |
Alex Klyubin | 5045b71 | 2015-03-31 20:19:54 -0700 | [diff] [blame] | 553 | args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, |
| 554 | (params.getKeyValidityStart() != null) |
| 555 | ? params.getKeyValidityStart() : new Date(0)); |
| 556 | args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, |
| 557 | (params.getKeyValidityForOriginationEnd() != null) |
| 558 | ? params.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE)); |
| 559 | args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, |
| 560 | (params.getKeyValidityForConsumptionEnd() != null) |
| 561 | ? params.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE)); |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 562 | |
| 563 | // TODO: Remove this once keymaster does not require us to specify the size of imported key. |
| 564 | args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keyMaterial.length * 8); |
| 565 | |
Alex Klyubin | 5927c9f | 2015-04-10 13:28:03 -0700 | [diff] [blame] | 566 | if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) |
Alex Klyubin | f853f64 | 2015-04-08 13:36:22 -0700 | [diff] [blame] | 567 | && (!params.isRandomizedEncryptionRequired())) { |
| 568 | // Permit caller-provided IV when encrypting with this key |
Alex Klyubin | b406f24 | 2015-03-31 13:39:38 -0700 | [diff] [blame] | 569 | args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); |
| 570 | } |
| 571 | |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 572 | Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias); |
| 573 | String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias; |
| 574 | int errorCode = mKeyStore.importKey( |
| 575 | keyAliasInKeystore, |
| 576 | args, |
| 577 | KeymasterDefs.KM_KEY_FORMAT_RAW, |
| 578 | keyMaterial, |
| 579 | params.getFlags(), |
| 580 | new KeyCharacteristics()); |
| 581 | if (errorCode != android.security.KeyStore.NO_ERROR) { |
| 582 | throw new KeyStoreException("Failed to import secret key. Keystore error code: " |
| 583 | + errorCode); |
| 584 | } |
| 585 | } |
| 586 | |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 587 | @Override |
| 588 | public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain) |
| 589 | throws KeyStoreException { |
Kenny Root | a4640c0 | 2012-08-31 13:38:11 -0700 | [diff] [blame] | 590 | throw new KeyStoreException("Operation not supported because key encoding is unknown"); |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 591 | } |
| 592 | |
| 593 | @Override |
| 594 | public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException { |
| 595 | if (isKeyEntry(alias)) { |
| 596 | throw new KeyStoreException("Entry exists and is not a trusted certificate"); |
| 597 | } |
| 598 | |
Kenny Root | a4640c0 | 2012-08-31 13:38:11 -0700 | [diff] [blame] | 599 | // We can't set something to null. |
| 600 | if (cert == null) { |
| 601 | throw new NullPointerException("cert == null"); |
| 602 | } |
| 603 | |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 604 | final byte[] encoded; |
| 605 | try { |
| 606 | encoded = cert.getEncoded(); |
| 607 | } catch (CertificateEncodingException e) { |
| 608 | throw new KeyStoreException(e); |
| 609 | } |
| 610 | |
Kenny Root | 2eeda72 | 2013-04-10 11:30:58 -0700 | [diff] [blame] | 611 | if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded, |
| 612 | android.security.KeyStore.UID_SELF, android.security.KeyStore.FLAG_NONE)) { |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 613 | throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?"); |
| 614 | } |
| 615 | } |
| 616 | |
| 617 | @Override |
| 618 | public void engineDeleteEntry(String alias) throws KeyStoreException { |
Kenny Root | a4640c0 | 2012-08-31 13:38:11 -0700 | [diff] [blame] | 619 | if (!isKeyEntry(alias) && !isCertificateEntry(alias)) { |
| 620 | return; |
| 621 | } |
| 622 | |
Kenny Root | db02671 | 2012-08-20 10:48:46 -0700 | [diff] [blame] | 623 | if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) { |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 624 | throw new KeyStoreException("No such entry " + alias); |
| 625 | } |
| 626 | } |
| 627 | |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 628 | private Set<String> getUniqueAliases() { |
| 629 | final String[] rawAliases = mKeyStore.saw(""); |
| 630 | if (rawAliases == null) { |
| 631 | return new HashSet<String>(); |
| 632 | } |
| 633 | |
| 634 | final Set<String> aliases = new HashSet<String>(rawAliases.length); |
| 635 | for (String alias : rawAliases) { |
| 636 | final int idx = alias.indexOf('_'); |
| 637 | if ((idx == -1) || (alias.length() <= idx)) { |
| 638 | Log.e(NAME, "invalid alias: " + alias); |
| 639 | continue; |
| 640 | } |
| 641 | |
| 642 | aliases.add(new String(alias.substring(idx + 1))); |
| 643 | } |
| 644 | |
| 645 | return aliases; |
| 646 | } |
| 647 | |
| 648 | @Override |
| 649 | public Enumeration<String> engineAliases() { |
| 650 | return Collections.enumeration(getUniqueAliases()); |
| 651 | } |
| 652 | |
| 653 | @Override |
| 654 | public boolean engineContainsAlias(String alias) { |
Kenny Root | a4640c0 | 2012-08-31 13:38:11 -0700 | [diff] [blame] | 655 | if (alias == null) { |
| 656 | throw new NullPointerException("alias == null"); |
| 657 | } |
| 658 | |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 659 | return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias) |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 660 | || mKeyStore.contains(Credentials.USER_SECRET_KEY + alias) |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 661 | || mKeyStore.contains(Credentials.USER_CERTIFICATE + alias) |
| 662 | || mKeyStore.contains(Credentials.CA_CERTIFICATE + alias); |
| 663 | } |
| 664 | |
| 665 | @Override |
| 666 | public int engineSize() { |
| 667 | return getUniqueAliases().size(); |
| 668 | } |
| 669 | |
| 670 | @Override |
| 671 | public boolean engineIsKeyEntry(String alias) { |
| 672 | return isKeyEntry(alias); |
| 673 | } |
| 674 | |
| 675 | private boolean isKeyEntry(String alias) { |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 676 | return isPrivateKeyEntry(alias) || isSecretKeyEntry(alias); |
| 677 | } |
| 678 | |
| 679 | private boolean isPrivateKeyEntry(String alias) { |
Kenny Root | a4640c0 | 2012-08-31 13:38:11 -0700 | [diff] [blame] | 680 | if (alias == null) { |
| 681 | throw new NullPointerException("alias == null"); |
| 682 | } |
| 683 | |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 684 | return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias); |
| 685 | } |
| 686 | |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 687 | private boolean isSecretKeyEntry(String alias) { |
| 688 | if (alias == null) { |
| 689 | throw new NullPointerException("alias == null"); |
| 690 | } |
| 691 | |
| 692 | return mKeyStore.contains(Credentials.USER_SECRET_KEY + alias); |
| 693 | } |
| 694 | |
Kenny Root | a4640c0 | 2012-08-31 13:38:11 -0700 | [diff] [blame] | 695 | private boolean isCertificateEntry(String alias) { |
| 696 | if (alias == null) { |
| 697 | throw new NullPointerException("alias == null"); |
| 698 | } |
| 699 | |
| 700 | return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias); |
| 701 | } |
| 702 | |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 703 | @Override |
| 704 | public boolean engineIsCertificateEntry(String alias) { |
Kenny Root | a4640c0 | 2012-08-31 13:38:11 -0700 | [diff] [blame] | 705 | return !isKeyEntry(alias) && isCertificateEntry(alias); |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 706 | } |
| 707 | |
| 708 | @Override |
| 709 | public String engineGetCertificateAlias(Certificate cert) { |
| 710 | if (cert == null) { |
| 711 | return null; |
| 712 | } |
| 713 | |
| 714 | final Set<String> nonCaEntries = new HashSet<String>(); |
| 715 | |
| 716 | /* |
| 717 | * First scan the PrivateKeyEntry types. The KeyStoreSpi documentation |
| 718 | * says to only compare the first certificate in the chain which is |
| 719 | * equivalent to the USER_CERTIFICATE prefix for the Android keystore |
| 720 | * convention. |
| 721 | */ |
| 722 | final String[] certAliases = mKeyStore.saw(Credentials.USER_CERTIFICATE); |
Kenny Root | 78ad849 | 2013-02-13 17:02:57 -0800 | [diff] [blame] | 723 | if (certAliases != null) { |
| 724 | for (String alias : certAliases) { |
| 725 | final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias); |
| 726 | if (certBytes == null) { |
| 727 | continue; |
| 728 | } |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 729 | |
Kenny Root | 78ad849 | 2013-02-13 17:02:57 -0800 | [diff] [blame] | 730 | final Certificate c = toCertificate(certBytes); |
| 731 | nonCaEntries.add(alias); |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 732 | |
Kenny Root | 78ad849 | 2013-02-13 17:02:57 -0800 | [diff] [blame] | 733 | if (cert.equals(c)) { |
| 734 | return alias; |
| 735 | } |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 736 | } |
| 737 | } |
| 738 | |
| 739 | /* |
| 740 | * Look at all the TrustedCertificateEntry types. Skip all the |
| 741 | * PrivateKeyEntry we looked at above. |
| 742 | */ |
| 743 | final String[] caAliases = mKeyStore.saw(Credentials.CA_CERTIFICATE); |
Kenny Root | 78ad849 | 2013-02-13 17:02:57 -0800 | [diff] [blame] | 744 | if (certAliases != null) { |
| 745 | for (String alias : caAliases) { |
| 746 | if (nonCaEntries.contains(alias)) { |
| 747 | continue; |
| 748 | } |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 749 | |
Kenny Root | 78ad849 | 2013-02-13 17:02:57 -0800 | [diff] [blame] | 750 | final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias); |
| 751 | if (certBytes == null) { |
| 752 | continue; |
| 753 | } |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 754 | |
Kenny Root | 78ad849 | 2013-02-13 17:02:57 -0800 | [diff] [blame] | 755 | final Certificate c = |
| 756 | toCertificate(mKeyStore.get(Credentials.CA_CERTIFICATE + alias)); |
| 757 | if (cert.equals(c)) { |
| 758 | return alias; |
| 759 | } |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 760 | } |
| 761 | } |
| 762 | |
| 763 | return null; |
| 764 | } |
| 765 | |
| 766 | @Override |
| 767 | public void engineStore(OutputStream stream, char[] password) throws IOException, |
| 768 | NoSuchAlgorithmException, CertificateException { |
| 769 | throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream"); |
| 770 | } |
| 771 | |
| 772 | @Override |
| 773 | public void engineLoad(InputStream stream, char[] password) throws IOException, |
| 774 | NoSuchAlgorithmException, CertificateException { |
| 775 | if (stream != null) { |
| 776 | throw new IllegalArgumentException("InputStream not supported"); |
| 777 | } |
| 778 | |
| 779 | if (password != null) { |
| 780 | throw new IllegalArgumentException("password not supported"); |
| 781 | } |
| 782 | |
| 783 | // Unfortunate name collision. |
| 784 | mKeyStore = android.security.KeyStore.getInstance(); |
| 785 | } |
| 786 | |
Kenny Root | 2eeda72 | 2013-04-10 11:30:58 -0700 | [diff] [blame] | 787 | @Override |
| 788 | public void engineSetEntry(String alias, Entry entry, ProtectionParameter param) |
| 789 | throws KeyStoreException { |
| 790 | if (entry == null) { |
| 791 | throw new KeyStoreException("entry == null"); |
| 792 | } |
| 793 | |
| 794 | if (engineContainsAlias(alias)) { |
| 795 | engineDeleteEntry(alias); |
| 796 | } |
| 797 | |
| 798 | if (entry instanceof KeyStore.TrustedCertificateEntry) { |
| 799 | KeyStore.TrustedCertificateEntry trE = (KeyStore.TrustedCertificateEntry) entry; |
| 800 | engineSetCertificateEntry(alias, trE.getTrustedCertificate()); |
| 801 | return; |
| 802 | } |
| 803 | |
Kenny Root | 1c219f6 | 2013-04-18 17:57:03 -0700 | [diff] [blame] | 804 | if (param != null && !(param instanceof KeyStoreParameter)) { |
| 805 | throw new KeyStoreException( |
| 806 | "protParam should be android.security.KeyStoreParameter; was: " |
Kenny Root | 2eeda72 | 2013-04-10 11:30:58 -0700 | [diff] [blame] | 807 | + param.getClass().getName()); |
| 808 | } |
| 809 | |
| 810 | if (entry instanceof PrivateKeyEntry) { |
| 811 | PrivateKeyEntry prE = (PrivateKeyEntry) entry; |
| 812 | setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(), |
Kenny Root | 1c219f6 | 2013-04-18 17:57:03 -0700 | [diff] [blame] | 813 | (KeyStoreParameter) param); |
Alex Klyubin | baf2838 | 2015-03-26 14:46:55 -0700 | [diff] [blame] | 814 | } else if (entry instanceof SecretKeyEntry) { |
| 815 | SecretKeyEntry secE = (SecretKeyEntry) entry; |
| 816 | setSecretKeyEntry(alias, secE.getSecretKey(), (KeyStoreParameter) param); |
| 817 | } else { |
| 818 | throw new KeyStoreException( |
| 819 | "Entry must be a PrivateKeyEntry, SecretKeyEntry or TrustedCertificateEntry" |
| 820 | + "; was " + entry); |
Kenny Root | 2eeda72 | 2013-04-10 11:30:58 -0700 | [diff] [blame] | 821 | } |
Kenny Root | 2eeda72 | 2013-04-10 11:30:58 -0700 | [diff] [blame] | 822 | } |
| 823 | |
Kenny Root | e29df16 | 2012-08-10 08:28:37 -0700 | [diff] [blame] | 824 | } |