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 com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
20
21import com.android.org.conscrypt.NativeCrypto;
22import com.android.org.conscrypt.OpenSSLEngine;
23
24import java.security.InvalidAlgorithmParameterException;
25import java.security.InvalidKeyException;
26import java.security.KeyFactory;
27import java.security.KeyPair;
28import java.security.KeyPairGenerator;
29import java.security.KeyPairGeneratorSpi;
30import java.security.NoSuchAlgorithmException;
31import java.security.PrivateKey;
32import java.security.PublicKey;
33import java.security.SecureRandom;
34import java.security.cert.CertificateEncodingException;
35import java.security.cert.X509Certificate;
36import java.security.spec.AlgorithmParameterSpec;
37import java.security.spec.DSAParameterSpec;
38import java.security.spec.ECParameterSpec;
39import java.security.spec.InvalidKeySpecException;
40import java.security.spec.RSAKeyGenParameterSpec;
41import java.security.spec.X509EncodedKeySpec;
42
43/**
44 * Provides a way to create instances of a KeyPair which will be placed in the
45 * Android keystore service usable only by the application that called it. This
46 * can be used in conjunction with
47 * {@link java.security.KeyStore#getInstance(String)} using the
48 * {@code "AndroidKeyStore"} type.
49 * <p>
50 * This class can not be directly instantiated and must instead be used via the
51 * {@link KeyPairGenerator#getInstance(String)
52 * KeyPairGenerator.getInstance("AndroidKeyPairGenerator")} API.
53 *
54 * {@hide}
55 */
56public class AndroidKeyPairGenerator extends KeyPairGeneratorSpi {
57    private android.security.KeyStore mKeyStore;
58
59    private KeyPairGeneratorSpec mSpec;
60
61    /**
62     * Generate a KeyPair which is backed by the Android keystore service. You
63     * must call {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)}
64     * with an {@link KeyPairGeneratorSpec} as the {@code params}
65     * argument before calling this otherwise an {@code IllegalStateException}
66     * will be thrown.
67     * <p>
68     * This will create an entry in the Android keystore service with a
69     * self-signed certificate using the {@code params} specified in the
70     * {@code initialize(params)} call.
71     *
72     * @throws IllegalStateException when called before calling
73     *             {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)}
74     * @see java.security.KeyPairGeneratorSpi#generateKeyPair()
75     */
76    @Override
77    public KeyPair generateKeyPair() {
78        if (mKeyStore == null || mSpec == null) {
79            throw new IllegalStateException(
80                    "Must call initialize with an android.security.KeyPairGeneratorSpec first");
81        }
82
83        if (((mSpec.getFlags() & KeyStore.FLAG_ENCRYPTED) != 0)
84                && (mKeyStore.state() != KeyStore.State.UNLOCKED)) {
85            throw new IllegalStateException(
86                    "Android keystore must be in initialized and unlocked state "
87                            + "if encryption is required");
88        }
89
90        final String alias = mSpec.getKeystoreAlias();
91
92        Credentials.deleteAllTypesForAlias(mKeyStore, alias);
93
94        final int keyType = KeyStore.getKeyTypeForAlgorithm(mSpec.getKeyType());
95        byte[][] args = getArgsForKeyType(keyType, mSpec.getAlgorithmParameterSpec());
96
97        final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
98        if (!mKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, keyType,
99                mSpec.getKeySize(), mSpec.getFlags(), args)) {
100            throw new IllegalStateException("could not generate key in keystore");
101        }
102
103        final PrivateKey privKey;
104        final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
105        try {
106            privKey = engine.getPrivateKeyById(privateKeyAlias);
107        } catch (InvalidKeyException e) {
108            throw new RuntimeException("Can't get key", e);
109        }
110
111        final byte[] pubKeyBytes = mKeyStore.getPubkey(privateKeyAlias);
112
113        final PublicKey pubKey;
114        try {
115            final KeyFactory keyFact = KeyFactory.getInstance(mSpec.getKeyType());
116            pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes));
117        } catch (NoSuchAlgorithmException e) {
118            throw new IllegalStateException("Can't instantiate key generator", e);
119        } catch (InvalidKeySpecException e) {
120            throw new IllegalStateException("keystore returned invalid key encoding", e);
121        }
122
123        final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
124        certGen.setPublicKey(pubKey);
125        certGen.setSerialNumber(mSpec.getSerialNumber());
126        certGen.setSubjectDN(mSpec.getSubjectDN());
127        certGen.setIssuerDN(mSpec.getSubjectDN());
128        certGen.setNotBefore(mSpec.getStartDate());
129        certGen.setNotAfter(mSpec.getEndDate());
130        certGen.setSignatureAlgorithm(getDefaultSignatureAlgorithmForKeyType(mSpec.getKeyType()));
131
132        final X509Certificate cert;
133        try {
134            cert = certGen.generate(privKey);
135        } catch (Exception e) {
136            Credentials.deleteAllTypesForAlias(mKeyStore, alias);
137            throw new IllegalStateException("Can't generate certificate", e);
138        }
139
140        byte[] certBytes;
141        try {
142            certBytes = cert.getEncoded();
143        } catch (CertificateEncodingException e) {
144            Credentials.deleteAllTypesForAlias(mKeyStore, alias);
145            throw new IllegalStateException("Can't get encoding of certificate", e);
146        }
147
148        if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, certBytes, KeyStore.UID_SELF,
149                mSpec.getFlags())) {
150            Credentials.deleteAllTypesForAlias(mKeyStore, alias);
151            throw new IllegalStateException("Can't store certificate in AndroidKeyStore");
152        }
153
154        return new KeyPair(pubKey, privKey);
155    }
156
157    private static String getDefaultSignatureAlgorithmForKeyType(String keyType) {
158        if ("RSA".equalsIgnoreCase(keyType)) {
159            return "sha256WithRSA";
160        } else if ("DSA".equalsIgnoreCase(keyType)) {
161            return "sha1WithDSA";
162        } else if ("EC".equalsIgnoreCase(keyType)) {
163            return "sha256WithECDSA";
164        } else {
165            throw new IllegalArgumentException("Unsupported key type " + keyType);
166        }
167    }
168
169    private static byte[][] getArgsForKeyType(int keyType, AlgorithmParameterSpec spec) {
170        switch (keyType) {
171            case NativeCrypto.EVP_PKEY_RSA:
172                if (spec instanceof RSAKeyGenParameterSpec) {
173                    RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) spec;
174                    return new byte[][] { rsaSpec.getPublicExponent().toByteArray() };
175                }
176                break;
177            case NativeCrypto.EVP_PKEY_DSA:
178                if (spec instanceof DSAParameterSpec) {
179                    DSAParameterSpec dsaSpec = (DSAParameterSpec) spec;
180                    return new byte[][] { dsaSpec.getG().toByteArray(),
181                            dsaSpec.getP().toByteArray(), dsaSpec.getQ().toByteArray() };
182                }
183                break;
184        }
185        return null;
186    }
187
188    @Override
189    public void initialize(int keysize, SecureRandom random) {
190        throw new IllegalArgumentException("cannot specify keysize with AndroidKeyPairGenerator");
191    }
192
193    @Override
194    public void initialize(AlgorithmParameterSpec params, SecureRandom random)
195            throws InvalidAlgorithmParameterException {
196        if (params == null) {
197            throw new InvalidAlgorithmParameterException(
198                    "must supply params of type android.security.KeyPairGeneratorSpec");
199        } else if (!(params instanceof KeyPairGeneratorSpec)) {
200            throw new InvalidAlgorithmParameterException(
201                    "params must be of type android.security.KeyPairGeneratorSpec");
202        }
203
204        KeyPairGeneratorSpec spec = (KeyPairGeneratorSpec) params;
205
206        mSpec = spec;
207        mKeyStore = android.security.KeyStore.getInstance();
208    }
209}
210