Skip to content

Commit db02671

Browse files
committed
Add KeyPairGenerator for Android keystore
This allows end-users to generate keys in the keystore without the private part of the key ever needing to leave the device. The generation process also generates a self-signed certificate. Change-Id: I114ffb8e0cbe3b1edaae7e69e8aa578cb835efc9
1 parent e29df16 commit db02671

File tree

9 files changed

+642
-20
lines changed

9 files changed

+642
-20
lines changed

api/current.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19967,6 +19967,10 @@ package android.sax {
1996719967

1996819968
package android.security {
1996919969

19970+
public class AndroidKeyPairGeneratorSpec implements java.security.spec.AlgorithmParameterSpec {
19971+
ctor public AndroidKeyPairGeneratorSpec(android.content.Context, java.lang.String, javax.security.auth.x500.X500Principal, java.math.BigInteger, java.util.Date, java.util.Date);
19972+
}
19973+
1997019974
public final class KeyChain {
1997119975
ctor public KeyChain();
1997219976
method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int, java.lang.String);
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
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 com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
20+
21+
import org.apache.harmony.xnet.provider.jsse.OpenSSLEngine;
22+
23+
import java.security.InvalidAlgorithmParameterException;
24+
import java.security.InvalidKeyException;
25+
import java.security.KeyFactory;
26+
import java.security.KeyPair;
27+
import java.security.KeyPairGenerator;
28+
import java.security.KeyPairGeneratorSpi;
29+
import java.security.NoSuchAlgorithmException;
30+
import java.security.PrivateKey;
31+
import java.security.PublicKey;
32+
import java.security.SecureRandom;
33+
import java.security.cert.CertificateEncodingException;
34+
import java.security.cert.X509Certificate;
35+
import java.security.spec.AlgorithmParameterSpec;
36+
import java.security.spec.InvalidKeySpecException;
37+
import java.security.spec.X509EncodedKeySpec;
38+
39+
/**
40+
* Provides a way to create instances of a KeyPair which will be placed in the
41+
* Android keystore service usable only by the application that called it. This
42+
* can be used in conjunction with
43+
* {@link java.security.KeyStore#getInstance(String)} using the
44+
* {@code "AndroidKeyStore"} type.
45+
* <p>
46+
* This class can not be directly instantiated and must instead be used via the
47+
* {@link KeyPairGenerator#getInstance(String)
48+
* KeyPairGenerator.getInstance("AndroidKeyPairGenerator")} API.
49+
*
50+
* {@hide}
51+
*/
52+
@SuppressWarnings("deprecation")
53+
public class AndroidKeyPairGenerator extends KeyPairGeneratorSpi {
54+
public static final String NAME = "AndroidKeyPairGenerator";
55+
56+
private android.security.KeyStore mKeyStore;
57+
58+
private AndroidKeyPairGeneratorSpec mSpec;
59+
60+
/**
61+
* Generate a KeyPair which is backed by the Android keystore service. You
62+
* must call {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)}
63+
* with an {@link AndroidKeyPairGeneratorSpec} as the {@code params}
64+
* argument before calling this otherwise an {@code IllegalStateException}
65+
* will be thrown.
66+
* <p>
67+
* This will create an entry in the Android keystore service with a
68+
* self-signed certificate using the {@code params} specified in the
69+
* {@code initialize(params)} call.
70+
*
71+
* @throws IllegalStateException when called before calling
72+
* {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)}
73+
* @see java.security.KeyPairGeneratorSpi#generateKeyPair()
74+
*/
75+
@Override
76+
public KeyPair generateKeyPair() {
77+
if (mKeyStore == null || mSpec == null) {
78+
throw new IllegalStateException(
79+
"Must call initialize with an AndroidKeyPairGeneratorSpec first");
80+
}
81+
82+
final String alias = mSpec.getKeystoreAlias();
83+
84+
Credentials.deleteAllTypesForAlias(mKeyStore, alias);
85+
86+
final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
87+
mKeyStore.generate(privateKeyAlias);
88+
89+
final PrivateKey privKey;
90+
final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
91+
try {
92+
privKey = engine.getPrivateKeyById(privateKeyAlias);
93+
} catch (InvalidKeyException e) {
94+
throw new RuntimeException("Can't get key", e);
95+
}
96+
97+
final byte[] pubKeyBytes = mKeyStore.getPubkey(privateKeyAlias);
98+
99+
final PublicKey pubKey;
100+
try {
101+
final KeyFactory keyFact = KeyFactory.getInstance("RSA");
102+
pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes));
103+
} catch (NoSuchAlgorithmException e) {
104+
throw new IllegalStateException("Can't instantiate RSA key generator", e);
105+
} catch (InvalidKeySpecException e) {
106+
throw new IllegalStateException("keystore returned invalid key encoding", e);
107+
}
108+
109+
final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
110+
certGen.setPublicKey(pubKey);
111+
certGen.setSerialNumber(mSpec.getSerialNumber());
112+
certGen.setSubjectDN(mSpec.getSubjectDN());
113+
certGen.setIssuerDN(mSpec.getSubjectDN());
114+
certGen.setNotBefore(mSpec.getStartDate());
115+
certGen.setNotAfter(mSpec.getEndDate());
116+
certGen.setSignatureAlgorithm("sha1WithRSA");
117+
118+
final X509Certificate cert;
119+
try {
120+
cert = certGen.generate(privKey);
121+
} catch (Exception e) {
122+
Credentials.deleteAllTypesForAlias(mKeyStore, alias);
123+
throw new IllegalStateException("Can't generate certificate", e);
124+
}
125+
126+
byte[] certBytes;
127+
try {
128+
certBytes = cert.getEncoded();
129+
} catch (CertificateEncodingException e) {
130+
Credentials.deleteAllTypesForAlias(mKeyStore, alias);
131+
throw new IllegalStateException("Can't get encoding of certificate", e);
132+
}
133+
134+
if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, certBytes)) {
135+
Credentials.deleteAllTypesForAlias(mKeyStore, alias);
136+
throw new IllegalStateException("Can't store certificate in AndroidKeyStore");
137+
}
138+
139+
return new KeyPair(pubKey, privKey);
140+
}
141+
142+
@Override
143+
public void initialize(int keysize, SecureRandom random) {
144+
throw new IllegalArgumentException("cannot specify keysize with AndroidKeyPairGenerator");
145+
}
146+
147+
@Override
148+
public void initialize(AlgorithmParameterSpec params, SecureRandom random)
149+
throws InvalidAlgorithmParameterException {
150+
if (params == null) {
151+
throw new InvalidAlgorithmParameterException(
152+
"must supply params of type AndroidKeyPairGenericSpec");
153+
} else if (!(params instanceof AndroidKeyPairGeneratorSpec)) {
154+
throw new InvalidAlgorithmParameterException(
155+
"params must be of type AndroidKeyPairGeneratorSpec");
156+
}
157+
158+
AndroidKeyPairGeneratorSpec spec = (AndroidKeyPairGeneratorSpec) params;
159+
160+
mSpec = spec;
161+
mKeyStore = android.security.KeyStore.getInstance();
162+
}
163+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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 android.content.Context;
20+
import android.text.TextUtils;
21+
22+
import java.math.BigInteger;
23+
import java.security.PrivateKey;
24+
import java.security.cert.Certificate;
25+
import java.security.spec.AlgorithmParameterSpec;
26+
import java.util.Date;
27+
28+
import javax.security.auth.x500.X500Principal;
29+
30+
/**
31+
* This provides the required parameters needed for initializing the KeyPair
32+
* generator that works with
33+
* <a href="{@docRoot}guide/topics/security/keystore.html">Android KeyStore
34+
* facility</a>.
35+
*/
36+
public class AndroidKeyPairGeneratorSpec implements AlgorithmParameterSpec {
37+
private final String mKeystoreAlias;
38+
39+
private final Context mContext;
40+
41+
private final X500Principal mSubjectDN;
42+
43+
private final BigInteger mSerialNumber;
44+
45+
private final Date mStartDate;
46+
47+
private final Date mEndDate;
48+
49+
/**
50+
* Parameter specification for the "{@code AndroidKeyPairGenerator}"
51+
* instance of the {@link java.security.KeyPairGenerator} API. The
52+
* {@code context} passed in may be used to pop up some UI to ask the user
53+
* to unlock or initialize the Android keystore facility.
54+
* <p>
55+
* After generation, the {@code keyStoreAlias} is used with the
56+
* {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter)}
57+
* interface to retrieve the {@link PrivateKey} and its associated
58+
* {@link Certificate} chain.
59+
* <p>
60+
* The KeyPair generator will create a self-signed certificate with the
61+
* properties of {@code subjectDN} as its X.509v3 Subject Distinguished Name
62+
* and as its X.509v3 Issuer Distinguished Name, using the specified
63+
* {@code serialNumber}, and the validity date starting at {@code startDate}
64+
* and ending at {@code endDate}.
65+
*
66+
* @param context Android context for the activity
67+
* @param keyStoreAlias name to use for the generated key in the Android
68+
* keystore
69+
* @param subjectDN X.509 v3 Subject Distinguished Name
70+
* @param serialNumber X509 v3 certificate serial number
71+
* @param startDate the start of the self-signed certificate validity period
72+
* @param endDate the end date of the self-signed certificate validity
73+
* period
74+
* @throws IllegalArgumentException when any argument is {@code null} or
75+
* {@code endDate} is before {@code startDate}.
76+
*/
77+
public AndroidKeyPairGeneratorSpec(Context context, String keyStoreAlias,
78+
X500Principal subjectDN, BigInteger serialNumber, Date startDate, Date endDate) {
79+
if (context == null) {
80+
throw new IllegalArgumentException("context == null");
81+
} else if (TextUtils.isEmpty(keyStoreAlias)) {
82+
throw new IllegalArgumentException("keyStoreAlias must not be empty");
83+
} else if (subjectDN == null) {
84+
throw new IllegalArgumentException("subjectDN == null");
85+
} else if (serialNumber == null) {
86+
throw new IllegalArgumentException("serialNumber == null");
87+
} else if (startDate == null) {
88+
throw new IllegalArgumentException("startDate == null");
89+
} else if (endDate == null) {
90+
throw new IllegalArgumentException("endDate == null");
91+
} else if (endDate.before(startDate)) {
92+
throw new IllegalArgumentException("endDate < startDate");
93+
}
94+
95+
mContext = context;
96+
mKeystoreAlias = keyStoreAlias;
97+
mSubjectDN = subjectDN;
98+
mSerialNumber = serialNumber;
99+
mStartDate = startDate;
100+
mEndDate = endDate;
101+
}
102+
103+
/**
104+
* @hide
105+
*/
106+
String getKeystoreAlias() {
107+
return mKeystoreAlias;
108+
}
109+
110+
/**
111+
* @hide
112+
*/
113+
Context getContext() {
114+
return mContext;
115+
}
116+
117+
/**
118+
* @hide
119+
*/
120+
X500Principal getSubjectDN() {
121+
return mSubjectDN;
122+
}
123+
124+
/**
125+
* @hide
126+
*/
127+
BigInteger getSerialNumber() {
128+
return mSerialNumber;
129+
}
130+
131+
/**
132+
* @hide
133+
*/
134+
Date getStartDate() {
135+
return mStartDate;
136+
}
137+
138+
/**
139+
* @hide
140+
*/
141+
Date getEndDate() {
142+
return mEndDate;
143+
}
144+
}

keystore/java/android/security/AndroidKeyStore.java

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,8 @@
4646
import java.util.Set;
4747

4848
/**
49-
* A java.security.KeyStore interface for the Android KeyStore. This class is
50-
* hidden from the Android API, but an instance of it can be created via the
51-
* {@link java.security.KeyStore#getInstance(String)
49+
* A java.security.KeyStore interface for the Android KeyStore. An instance of
50+
* it can be created via the {@link java.security.KeyStore#getInstance(String)
5251
* KeyStore.getInstance("AndroidKeyStore")} interface. This returns a
5352
* java.security.KeyStore backed by this "AndroidKeyStore" implementation.
5453
* <p>
@@ -277,7 +276,7 @@ private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chai
277276
* Make sure we clear out all the types we know about before trying to
278277
* write.
279278
*/
280-
deleteAllTypesForAlias(alias);
279+
Credentials.deleteAllTypesForAlias(mKeyStore, alias);
281280

282281
if (!mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes)) {
283282
throw new KeyStoreException("Couldn't put private key in keystore");
@@ -315,26 +314,11 @@ public void engineSetCertificateEntry(String alias, Certificate cert) throws Key
315314

316315
@Override
317316
public void engineDeleteEntry(String alias) throws KeyStoreException {
318-
if (!deleteAllTypesForAlias(alias)) {
317+
if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) {
319318
throw new KeyStoreException("No such entry " + alias);
320319
}
321320
}
322321

323-
/**
324-
* Delete all types (private key, certificate, CA certificate) for a
325-
* particular {@code alias}. All three can exist for any given alias.
326-
* Returns {@code true} if there was at least one of those types.
327-
*/
328-
private boolean deleteAllTypesForAlias(String alias) {
329-
/*
330-
* Make sure every type is deleted. There can be all three types, so
331-
* don't use a conditional here.
332-
*/
333-
return mKeyStore.delKey(Credentials.USER_PRIVATE_KEY + alias)
334-
| mKeyStore.delete(Credentials.USER_CERTIFICATE + alias)
335-
| mKeyStore.delete(Credentials.CA_CERTIFICATE + alias);
336-
}
337-
338322
private Set<String> getUniqueAliases() {
339323
final String[] rawAliases = mKeyStore.saw("");
340324
if (rawAliases == null) {

keystore/java/android/security/AndroidKeyStoreProvider.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ public class AndroidKeyStoreProvider extends Provider {
2929
public AndroidKeyStoreProvider() {
3030
super(PROVIDER_NAME, 1.0, "Android KeyStore security provider");
3131

32+
// java.security.KeyStore
3233
put("KeyStore." + AndroidKeyStore.NAME, AndroidKeyStore.class.getName());
34+
35+
// java.security.KeyPairGenerator
36+
put("KeyPairGenerator." + AndroidKeyPairGenerator.NAME,
37+
AndroidKeyPairGenerator.class.getName());
3338
}
3439
}

0 commit comments

Comments
 (0)