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.keystore;
18
19import android.annotation.NonNull;
20import android.security.KeyStore;
21import android.security.keymaster.ExportResult;
22import android.security.keymaster.KeyCharacteristics;
23import android.security.keymaster.KeymasterDefs;
24
25import java.io.IOException;
26import java.security.KeyFactory;
27import java.security.KeyPair;
28import java.security.KeyStoreException;
29import java.security.NoSuchAlgorithmException;
30import java.security.NoSuchProviderException;
31import java.security.Provider;
32import java.security.ProviderException;
33import java.security.PublicKey;
34import java.security.Security;
35import java.security.Signature;
36import java.security.UnrecoverableKeyException;
37import java.security.cert.CertificateException;
38import java.security.interfaces.ECKey;
39import java.security.interfaces.ECPublicKey;
40import java.security.interfaces.RSAKey;
41import java.security.interfaces.RSAPublicKey;
42import java.security.spec.InvalidKeySpecException;
43import java.security.spec.X509EncodedKeySpec;
44import java.util.List;
45
46import javax.crypto.Cipher;
47import javax.crypto.Mac;
48
49/**
50 * A provider focused on providing JCA interfaces for the Android KeyStore.
51 *
52 * @hide
53 */
54public class AndroidKeyStoreProvider extends Provider {
55    public static final String PROVIDER_NAME = "AndroidKeyStore";
56
57    // IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these
58    // classes when this provider is instantiated and installed early on during each app's
59    // initialization process.
60    //
61    // Crypto operations operating on the AndroidKeyStore keys must not be offered by this provider.
62    // Instead, they need to be offered by AndroidKeyStoreBCWorkaroundProvider. See its Javadoc
63    // for details.
64
65    private static final String PACKAGE_NAME = "android.security.keystore";
66
67    public AndroidKeyStoreProvider() {
68        super(PROVIDER_NAME, 1.0, "Android KeyStore security provider");
69
70        // java.security.KeyStore
71        put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStoreSpi");
72
73        // java.security.KeyPairGenerator
74        put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
75        put("KeyPairGenerator.RSA", PACKAGE_NAME +  ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
76
77        // java.security.KeyFactory
78        putKeyFactoryImpl("EC");
79        putKeyFactoryImpl("RSA");
80
81        // javax.crypto.KeyGenerator
82        put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES");
83        put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA1");
84        put("KeyGenerator.HmacSHA224", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA224");
85        put("KeyGenerator.HmacSHA256", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA256");
86        put("KeyGenerator.HmacSHA384", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA384");
87        put("KeyGenerator.HmacSHA512", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA512");
88
89        // java.security.SecretKeyFactory
90        putSecretKeyFactoryImpl("AES");
91        putSecretKeyFactoryImpl("HmacSHA1");
92        putSecretKeyFactoryImpl("HmacSHA224");
93        putSecretKeyFactoryImpl("HmacSHA256");
94        putSecretKeyFactoryImpl("HmacSHA384");
95        putSecretKeyFactoryImpl("HmacSHA512");
96    }
97
98    /**
99     * Installs a new instance of this provider (and the
100     * {@link AndroidKeyStoreBCWorkaroundProvider}).
101     */
102    public static void install() {
103        Provider[] providers = Security.getProviders();
104        int bcProviderIndex = -1;
105        for (int i = 0; i < providers.length; i++) {
106            Provider provider = providers[i];
107            if ("BC".equals(provider.getName())) {
108                bcProviderIndex = i;
109                break;
110            }
111        }
112
113        Security.addProvider(new AndroidKeyStoreProvider());
114        Provider workaroundProvider = new AndroidKeyStoreBCWorkaroundProvider();
115        if (bcProviderIndex != -1) {
116            // Bouncy Castle provider found -- install the workaround provider above it.
117            // insertProviderAt uses 1-based positions.
118            Security.insertProviderAt(workaroundProvider, bcProviderIndex + 1);
119        } else {
120            // Bouncy Castle provider not found -- install the workaround provider at lowest
121            // priority.
122            Security.addProvider(workaroundProvider);
123        }
124    }
125
126    private void putSecretKeyFactoryImpl(String algorithm) {
127        put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreSecretKeyFactorySpi");
128    }
129
130    private void putKeyFactoryImpl(String algorithm) {
131        put("KeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreKeyFactorySpi");
132    }
133
134    /**
135     * Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto
136     * primitive.
137     *
138     * <p>The following primitives are supported: {@link Cipher} and {@link Mac}.
139     *
140     * @return KeyStore operation handle or {@code 0} if the provided primitive's KeyStore operation
141     *         is not in progress.
142     *
143     * @throws IllegalArgumentException if the provided primitive is not supported or is not backed
144     *         by AndroidKeyStore provider.
145     * @throws IllegalStateException if the provided primitive is not initialized.
146     */
147    public static long getKeyStoreOperationHandle(Object cryptoPrimitive) {
148        if (cryptoPrimitive == null) {
149            throw new NullPointerException();
150        }
151        Object spi;
152        if (cryptoPrimitive instanceof Signature) {
153            spi = ((Signature) cryptoPrimitive).getCurrentSpi();
154        } else if (cryptoPrimitive instanceof Mac) {
155            spi = ((Mac) cryptoPrimitive).getCurrentSpi();
156        } else if (cryptoPrimitive instanceof Cipher) {
157            spi = ((Cipher) cryptoPrimitive).getCurrentSpi();
158        } else {
159            throw new IllegalArgumentException("Unsupported crypto primitive: " + cryptoPrimitive
160                    + ". Supported: Signature, Mac, Cipher");
161        }
162        if (spi == null) {
163            throw new IllegalStateException("Crypto primitive not initialized");
164        } else if (!(spi instanceof KeyStoreCryptoOperation)) {
165            throw new IllegalArgumentException(
166                    "Crypto primitive not backed by AndroidKeyStore provider: " + cryptoPrimitive
167                    + ", spi: " + spi);
168        }
169        return ((KeyStoreCryptoOperation) spi).getOperationHandle();
170    }
171
172    @NonNull
173    public static AndroidKeyStorePublicKey getAndroidKeyStorePublicKey(
174            @NonNull String alias,
175            int uid,
176            @NonNull @KeyProperties.KeyAlgorithmEnum String keyAlgorithm,
177            @NonNull byte[] x509EncodedForm) {
178        PublicKey publicKey;
179        try {
180            KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
181            publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedForm));
182        } catch (NoSuchAlgorithmException e) {
183            throw new ProviderException(
184                    "Failed to obtain " + keyAlgorithm + " KeyFactory", e);
185        } catch (InvalidKeySpecException e) {
186            throw new ProviderException("Invalid X.509 encoding of public key", e);
187        }
188        if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
189            return new AndroidKeyStoreECPublicKey(alias, uid, (ECPublicKey) publicKey);
190        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
191            return new AndroidKeyStoreRSAPublicKey(alias, uid, (RSAPublicKey) publicKey);
192        } else {
193            throw new ProviderException("Unsupported Android Keystore public key algorithm: "
194                    + keyAlgorithm);
195        }
196    }
197
198    @NonNull
199    public static AndroidKeyStorePrivateKey getAndroidKeyStorePrivateKey(
200            @NonNull AndroidKeyStorePublicKey publicKey) {
201        String keyAlgorithm = publicKey.getAlgorithm();
202        if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
203            return new AndroidKeyStoreECPrivateKey(
204                    publicKey.getAlias(), publicKey.getUid(), ((ECKey) publicKey).getParams());
205        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
206            return new AndroidKeyStoreRSAPrivateKey(
207                    publicKey.getAlias(), publicKey.getUid(), ((RSAKey) publicKey).getModulus());
208        } else {
209            throw new ProviderException("Unsupported Android Keystore public key algorithm: "
210                    + keyAlgorithm);
211        }
212    }
213
214    @NonNull
215    public static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore(
216            @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid)
217            throws UnrecoverableKeyException {
218        KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
219        int errorCode = keyStore.getKeyCharacteristics(
220                privateKeyAlias, null, null, uid, keyCharacteristics);
221        if (errorCode != KeyStore.NO_ERROR) {
222            throw (UnrecoverableKeyException)
223                    new UnrecoverableKeyException("Failed to obtain information about private key")
224                    .initCause(KeyStore.getKeyStoreException(errorCode));
225        }
226        ExportResult exportResult = keyStore.exportKey(
227                privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null, uid);
228        if (exportResult.resultCode != KeyStore.NO_ERROR) {
229            throw (UnrecoverableKeyException)
230                    new UnrecoverableKeyException("Failed to obtain X.509 form of public key")
231                    .initCause(KeyStore.getKeyStoreException(errorCode));
232        }
233        final byte[] x509EncodedPublicKey = exportResult.exportData;
234
235        Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM);
236        if (keymasterAlgorithm == null) {
237            throw new UnrecoverableKeyException("Key algorithm unknown");
238        }
239
240        String jcaKeyAlgorithm;
241        try {
242            jcaKeyAlgorithm = KeyProperties.KeyAlgorithm.fromKeymasterAsymmetricKeyAlgorithm(
243                    keymasterAlgorithm);
244        } catch (IllegalArgumentException e) {
245            throw (UnrecoverableKeyException)
246                    new UnrecoverableKeyException("Failed to load private key")
247                    .initCause(e);
248        }
249
250        return AndroidKeyStoreProvider.getAndroidKeyStorePublicKey(
251                privateKeyAlias, uid, jcaKeyAlgorithm, x509EncodedPublicKey);
252    }
253
254    @NonNull
255    public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore(
256            @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid)
257            throws UnrecoverableKeyException {
258        AndroidKeyStorePublicKey publicKey =
259                loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias, uid);
260        AndroidKeyStorePrivateKey privateKey =
261                AndroidKeyStoreProvider.getAndroidKeyStorePrivateKey(publicKey);
262        return new KeyPair(publicKey, privateKey);
263    }
264
265    @NonNull
266    public static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore(
267            @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid)
268            throws UnrecoverableKeyException {
269        KeyPair keyPair = loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias, uid);
270        return (AndroidKeyStorePrivateKey) keyPair.getPrivate();
271    }
272
273    @NonNull
274    public static AndroidKeyStoreSecretKey loadAndroidKeyStoreSecretKeyFromKeystore(
275            @NonNull KeyStore keyStore, @NonNull String secretKeyAlias, int uid)
276            throws UnrecoverableKeyException {
277        KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
278        int errorCode = keyStore.getKeyCharacteristics(
279                secretKeyAlias, null, null, uid, keyCharacteristics);
280        if (errorCode != KeyStore.NO_ERROR) {
281            throw (UnrecoverableKeyException)
282                    new UnrecoverableKeyException("Failed to obtain information about key")
283                            .initCause(KeyStore.getKeyStoreException(errorCode));
284        }
285
286        Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM);
287        if (keymasterAlgorithm == null) {
288            throw new UnrecoverableKeyException("Key algorithm unknown");
289        }
290
291        List<Integer> keymasterDigests = keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_DIGEST);
292        int keymasterDigest;
293        if (keymasterDigests.isEmpty()) {
294            keymasterDigest = -1;
295        } else {
296            // More than one digest can be permitted for this key. Use the first one to form the
297            // JCA key algorithm name.
298            keymasterDigest = keymasterDigests.get(0);
299        }
300
301        @KeyProperties.KeyAlgorithmEnum String keyAlgorithmString;
302        try {
303            keyAlgorithmString = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm(
304                    keymasterAlgorithm, keymasterDigest);
305        } catch (IllegalArgumentException e) {
306            throw (UnrecoverableKeyException)
307                    new UnrecoverableKeyException("Unsupported secret key type").initCause(e);
308        }
309
310        return new AndroidKeyStoreSecretKey(secretKeyAlias, uid, keyAlgorithmString);
311    }
312
313    /**
314     * Returns an {@code AndroidKeyStore} {@link java.security.KeyStore}} of the specified UID.
315     * The {@code KeyStore} contains keys and certificates owned by that UID. Such cross-UID
316     * access is permitted to a few system UIDs and only to a few other UIDs (e.g., Wi-Fi, VPN)
317     * all of which are system.
318     *
319     * <p>Note: the returned {@code KeyStore} is already initialized/loaded. Thus, there is
320     * no need to invoke {@code load} on it.
321     */
322    @NonNull
323    public static java.security.KeyStore getKeyStoreForUid(int uid)
324            throws KeyStoreException, NoSuchProviderException {
325        java.security.KeyStore result =
326                java.security.KeyStore.getInstance("AndroidKeyStore", PROVIDER_NAME);
327        try {
328            result.load(new AndroidKeyStoreLoadStoreParameter(uid));
329        } catch (NoSuchAlgorithmException | CertificateException | IOException e) {
330            throw new KeyStoreException(
331                    "Failed to load AndroidKeyStore KeyStore for UID " + uid, e);
332        }
333        return result;
334    }
335}
336