Merge "Add KeyPairGenerator for Android keystore" into jb-mr1-dev
This commit is contained in:
@@ -19988,6 +19988,10 @@ package android.sax {
|
|||||||
|
|
||||||
package android.security {
|
package android.security {
|
||||||
|
|
||||||
|
public class AndroidKeyPairGeneratorSpec implements java.security.spec.AlgorithmParameterSpec {
|
||||||
|
ctor public AndroidKeyPairGeneratorSpec(android.content.Context, java.lang.String, javax.security.auth.x500.X500Principal, java.math.BigInteger, java.util.Date, java.util.Date);
|
||||||
|
}
|
||||||
|
|
||||||
public final class KeyChain {
|
public final class KeyChain {
|
||||||
ctor public KeyChain();
|
ctor public KeyChain();
|
||||||
method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int, java.lang.String);
|
method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int, java.lang.String);
|
||||||
|
|||||||
163
keystore/java/android/security/AndroidKeyPairGenerator.java
Normal file
163
keystore/java/android/security/AndroidKeyPairGenerator.java
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.security;
|
||||||
|
|
||||||
|
import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
|
||||||
|
|
||||||
|
import org.apache.harmony.xnet.provider.jsse.OpenSSLEngine;
|
||||||
|
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.KeyPairGeneratorSpi;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.cert.CertificateEncodingException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a way to create instances of a KeyPair which will be placed in the
|
||||||
|
* Android keystore service usable only by the application that called it. This
|
||||||
|
* can be used in conjunction with
|
||||||
|
* {@link java.security.KeyStore#getInstance(String)} using the
|
||||||
|
* {@code "AndroidKeyStore"} type.
|
||||||
|
* <p>
|
||||||
|
* This class can not be directly instantiated and must instead be used via the
|
||||||
|
* {@link KeyPairGenerator#getInstance(String)
|
||||||
|
* KeyPairGenerator.getInstance("AndroidKeyPairGenerator")} API.
|
||||||
|
*
|
||||||
|
* {@hide}
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public class AndroidKeyPairGenerator extends KeyPairGeneratorSpi {
|
||||||
|
public static final String NAME = "AndroidKeyPairGenerator";
|
||||||
|
|
||||||
|
private android.security.KeyStore mKeyStore;
|
||||||
|
|
||||||
|
private AndroidKeyPairGeneratorSpec mSpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a KeyPair which is backed by the Android keystore service. You
|
||||||
|
* must call {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)}
|
||||||
|
* with an {@link AndroidKeyPairGeneratorSpec} as the {@code params}
|
||||||
|
* argument before calling this otherwise an {@code IllegalStateException}
|
||||||
|
* will be thrown.
|
||||||
|
* <p>
|
||||||
|
* This will create an entry in the Android keystore service with a
|
||||||
|
* self-signed certificate using the {@code params} specified in the
|
||||||
|
* {@code initialize(params)} call.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException when called before calling
|
||||||
|
* {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)}
|
||||||
|
* @see java.security.KeyPairGeneratorSpi#generateKeyPair()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public KeyPair generateKeyPair() {
|
||||||
|
if (mKeyStore == null || mSpec == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Must call initialize with an AndroidKeyPairGeneratorSpec first");
|
||||||
|
}
|
||||||
|
|
||||||
|
final String alias = mSpec.getKeystoreAlias();
|
||||||
|
|
||||||
|
Credentials.deleteAllTypesForAlias(mKeyStore, alias);
|
||||||
|
|
||||||
|
final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
|
||||||
|
mKeyStore.generate(privateKeyAlias);
|
||||||
|
|
||||||
|
final PrivateKey privKey;
|
||||||
|
final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
|
||||||
|
try {
|
||||||
|
privKey = engine.getPrivateKeyById(privateKeyAlias);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new RuntimeException("Can't get key", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final byte[] pubKeyBytes = mKeyStore.getPubkey(privateKeyAlias);
|
||||||
|
|
||||||
|
final PublicKey pubKey;
|
||||||
|
try {
|
||||||
|
final KeyFactory keyFact = KeyFactory.getInstance("RSA");
|
||||||
|
pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes));
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalStateException("Can't instantiate RSA key generator", e);
|
||||||
|
} catch (InvalidKeySpecException e) {
|
||||||
|
throw new IllegalStateException("keystore returned invalid key encoding", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
|
||||||
|
certGen.setPublicKey(pubKey);
|
||||||
|
certGen.setSerialNumber(mSpec.getSerialNumber());
|
||||||
|
certGen.setSubjectDN(mSpec.getSubjectDN());
|
||||||
|
certGen.setIssuerDN(mSpec.getSubjectDN());
|
||||||
|
certGen.setNotBefore(mSpec.getStartDate());
|
||||||
|
certGen.setNotAfter(mSpec.getEndDate());
|
||||||
|
certGen.setSignatureAlgorithm("sha1WithRSA");
|
||||||
|
|
||||||
|
final X509Certificate cert;
|
||||||
|
try {
|
||||||
|
cert = certGen.generate(privKey);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Credentials.deleteAllTypesForAlias(mKeyStore, alias);
|
||||||
|
throw new IllegalStateException("Can't generate certificate", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] certBytes;
|
||||||
|
try {
|
||||||
|
certBytes = cert.getEncoded();
|
||||||
|
} catch (CertificateEncodingException e) {
|
||||||
|
Credentials.deleteAllTypesForAlias(mKeyStore, alias);
|
||||||
|
throw new IllegalStateException("Can't get encoding of certificate", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, certBytes)) {
|
||||||
|
Credentials.deleteAllTypesForAlias(mKeyStore, alias);
|
||||||
|
throw new IllegalStateException("Can't store certificate in AndroidKeyStore");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new KeyPair(pubKey, privKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(int keysize, SecureRandom random) {
|
||||||
|
throw new IllegalArgumentException("cannot specify keysize with AndroidKeyPairGenerator");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(AlgorithmParameterSpec params, SecureRandom random)
|
||||||
|
throws InvalidAlgorithmParameterException {
|
||||||
|
if (params == null) {
|
||||||
|
throw new InvalidAlgorithmParameterException(
|
||||||
|
"must supply params of type AndroidKeyPairGenericSpec");
|
||||||
|
} else if (!(params instanceof AndroidKeyPairGeneratorSpec)) {
|
||||||
|
throw new InvalidAlgorithmParameterException(
|
||||||
|
"params must be of type AndroidKeyPairGeneratorSpec");
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidKeyPairGeneratorSpec spec = (AndroidKeyPairGeneratorSpec) params;
|
||||||
|
|
||||||
|
mSpec = spec;
|
||||||
|
mKeyStore = android.security.KeyStore.getInstance();
|
||||||
|
}
|
||||||
|
}
|
||||||
144
keystore/java/android/security/AndroidKeyPairGeneratorSpec.java
Normal file
144
keystore/java/android/security/AndroidKeyPairGeneratorSpec.java
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.security;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import javax.security.auth.x500.X500Principal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This provides the required parameters needed for initializing the KeyPair
|
||||||
|
* generator that works with
|
||||||
|
* <a href="{@docRoot}guide/topics/security/keystore.html">Android KeyStore
|
||||||
|
* facility</a>.
|
||||||
|
*/
|
||||||
|
public class AndroidKeyPairGeneratorSpec implements AlgorithmParameterSpec {
|
||||||
|
private final String mKeystoreAlias;
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
private final X500Principal mSubjectDN;
|
||||||
|
|
||||||
|
private final BigInteger mSerialNumber;
|
||||||
|
|
||||||
|
private final Date mStartDate;
|
||||||
|
|
||||||
|
private final Date mEndDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameter specification for the "{@code AndroidKeyPairGenerator}"
|
||||||
|
* instance of the {@link java.security.KeyPairGenerator} API. The
|
||||||
|
* {@code context} passed in may be used to pop up some UI to ask the user
|
||||||
|
* to unlock or initialize the Android keystore facility.
|
||||||
|
* <p>
|
||||||
|
* After generation, the {@code keyStoreAlias} is used with the
|
||||||
|
* {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter)}
|
||||||
|
* interface to retrieve the {@link PrivateKey} and its associated
|
||||||
|
* {@link Certificate} chain.
|
||||||
|
* <p>
|
||||||
|
* The KeyPair generator will create a self-signed certificate with the
|
||||||
|
* properties of {@code subjectDN} as its X.509v3 Subject Distinguished Name
|
||||||
|
* and as its X.509v3 Issuer Distinguished Name, using the specified
|
||||||
|
* {@code serialNumber}, and the validity date starting at {@code startDate}
|
||||||
|
* and ending at {@code endDate}.
|
||||||
|
*
|
||||||
|
* @param context Android context for the activity
|
||||||
|
* @param keyStoreAlias name to use for the generated key in the Android
|
||||||
|
* keystore
|
||||||
|
* @param subjectDN X.509 v3 Subject Distinguished Name
|
||||||
|
* @param serialNumber X509 v3 certificate serial number
|
||||||
|
* @param startDate the start of the self-signed certificate validity period
|
||||||
|
* @param endDate the end date of the self-signed certificate validity
|
||||||
|
* period
|
||||||
|
* @throws IllegalArgumentException when any argument is {@code null} or
|
||||||
|
* {@code endDate} is before {@code startDate}.
|
||||||
|
*/
|
||||||
|
public AndroidKeyPairGeneratorSpec(Context context, String keyStoreAlias,
|
||||||
|
X500Principal subjectDN, BigInteger serialNumber, Date startDate, Date endDate) {
|
||||||
|
if (context == null) {
|
||||||
|
throw new IllegalArgumentException("context == null");
|
||||||
|
} else if (TextUtils.isEmpty(keyStoreAlias)) {
|
||||||
|
throw new IllegalArgumentException("keyStoreAlias must not be empty");
|
||||||
|
} else if (subjectDN == null) {
|
||||||
|
throw new IllegalArgumentException("subjectDN == null");
|
||||||
|
} else if (serialNumber == null) {
|
||||||
|
throw new IllegalArgumentException("serialNumber == null");
|
||||||
|
} else if (startDate == null) {
|
||||||
|
throw new IllegalArgumentException("startDate == null");
|
||||||
|
} else if (endDate == null) {
|
||||||
|
throw new IllegalArgumentException("endDate == null");
|
||||||
|
} else if (endDate.before(startDate)) {
|
||||||
|
throw new IllegalArgumentException("endDate < startDate");
|
||||||
|
}
|
||||||
|
|
||||||
|
mContext = context;
|
||||||
|
mKeystoreAlias = keyStoreAlias;
|
||||||
|
mSubjectDN = subjectDN;
|
||||||
|
mSerialNumber = serialNumber;
|
||||||
|
mStartDate = startDate;
|
||||||
|
mEndDate = endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
String getKeystoreAlias() {
|
||||||
|
return mKeystoreAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
Context getContext() {
|
||||||
|
return mContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
X500Principal getSubjectDN() {
|
||||||
|
return mSubjectDN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
BigInteger getSerialNumber() {
|
||||||
|
return mSerialNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
Date getStartDate() {
|
||||||
|
return mStartDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
Date getEndDate() {
|
||||||
|
return mEndDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,9 +46,8 @@ import java.util.Iterator;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A java.security.KeyStore interface for the Android KeyStore. This class is
|
* A java.security.KeyStore interface for the Android KeyStore. An instance of
|
||||||
* hidden from the Android API, but an instance of it can be created via the
|
* it can be created via the {@link java.security.KeyStore#getInstance(String)
|
||||||
* {@link java.security.KeyStore#getInstance(String)
|
|
||||||
* KeyStore.getInstance("AndroidKeyStore")} interface. This returns a
|
* KeyStore.getInstance("AndroidKeyStore")} interface. This returns a
|
||||||
* java.security.KeyStore backed by this "AndroidKeyStore" implementation.
|
* java.security.KeyStore backed by this "AndroidKeyStore" implementation.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -277,7 +276,7 @@ public class AndroidKeyStore extends KeyStoreSpi {
|
|||||||
* Make sure we clear out all the types we know about before trying to
|
* Make sure we clear out all the types we know about before trying to
|
||||||
* write.
|
* write.
|
||||||
*/
|
*/
|
||||||
deleteAllTypesForAlias(alias);
|
Credentials.deleteAllTypesForAlias(mKeyStore, alias);
|
||||||
|
|
||||||
if (!mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes)) {
|
if (!mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes)) {
|
||||||
throw new KeyStoreException("Couldn't put private key in keystore");
|
throw new KeyStoreException("Couldn't put private key in keystore");
|
||||||
@@ -315,26 +314,11 @@ public class AndroidKeyStore extends KeyStoreSpi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void engineDeleteEntry(String alias) throws KeyStoreException {
|
public void engineDeleteEntry(String alias) throws KeyStoreException {
|
||||||
if (!deleteAllTypesForAlias(alias)) {
|
if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) {
|
||||||
throw new KeyStoreException("No such entry " + alias);
|
throw new KeyStoreException("No such entry " + alias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete all types (private key, certificate, CA certificate) for a
|
|
||||||
* particular {@code alias}. All three can exist for any given alias.
|
|
||||||
* Returns {@code true} if there was at least one of those types.
|
|
||||||
*/
|
|
||||||
private boolean deleteAllTypesForAlias(String alias) {
|
|
||||||
/*
|
|
||||||
* Make sure every type is deleted. There can be all three types, so
|
|
||||||
* don't use a conditional here.
|
|
||||||
*/
|
|
||||||
return mKeyStore.delKey(Credentials.USER_PRIVATE_KEY + alias)
|
|
||||||
| mKeyStore.delete(Credentials.USER_CERTIFICATE + alias)
|
|
||||||
| mKeyStore.delete(Credentials.CA_CERTIFICATE + alias);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<String> getUniqueAliases() {
|
private Set<String> getUniqueAliases() {
|
||||||
final String[] rawAliases = mKeyStore.saw("");
|
final String[] rawAliases = mKeyStore.saw("");
|
||||||
if (rawAliases == null) {
|
if (rawAliases == null) {
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ public class AndroidKeyStoreProvider extends Provider {
|
|||||||
public AndroidKeyStoreProvider() {
|
public AndroidKeyStoreProvider() {
|
||||||
super(PROVIDER_NAME, 1.0, "Android KeyStore security provider");
|
super(PROVIDER_NAME, 1.0, "Android KeyStore security provider");
|
||||||
|
|
||||||
|
// java.security.KeyStore
|
||||||
put("KeyStore." + AndroidKeyStore.NAME, AndroidKeyStore.class.getName());
|
put("KeyStore." + AndroidKeyStore.NAME, AndroidKeyStore.class.getName());
|
||||||
|
|
||||||
|
// java.security.KeyPairGenerator
|
||||||
|
put("KeyPairGenerator." + AndroidKeyPairGenerator.NAME,
|
||||||
|
AndroidKeyPairGenerator.class.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,4 +185,19 @@ public class Credentials {
|
|||||||
Log.w(LOGTAG, e.toString());
|
Log.w(LOGTAG, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all types (private key, certificate, CA certificate) for a
|
||||||
|
* particular {@code alias}. All three can exist for any given alias.
|
||||||
|
* Returns {@code true} if there was at least one of those types.
|
||||||
|
*/
|
||||||
|
static boolean deleteAllTypesForAlias(KeyStore keystore, String alias) {
|
||||||
|
/*
|
||||||
|
* Make sure every type is deleted. There can be all three types, so
|
||||||
|
* don't use a conditional here.
|
||||||
|
*/
|
||||||
|
return keystore.delKey(Credentials.USER_PRIVATE_KEY + alias)
|
||||||
|
| keystore.delete(Credentials.USER_CERTIFICATE + alias)
|
||||||
|
| keystore.delete(Credentials.CA_CERTIFICATE + alias);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
keystore/java/android/security/package.html
Normal file
9
keystore/java/android/security/package.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<HTML>
|
||||||
|
<BODY>
|
||||||
|
<p>Provides access to a few facilities of the Android security
|
||||||
|
subsystems.</p>
|
||||||
|
<p>For information on how to use this facility, see the <a
|
||||||
|
href="{@docRoot}guide/topics/security/keystore.html">Android
|
||||||
|
KeyStore facility</a> guide.</p>
|
||||||
|
</BODY>
|
||||||
|
</HTML>
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.security;
|
||||||
|
|
||||||
|
import android.test.AndroidTestCase;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import javax.security.auth.x500.X500Principal;
|
||||||
|
|
||||||
|
public class AndroidKeyPairGeneratorSpecTest extends AndroidTestCase {
|
||||||
|
private static final String TEST_ALIAS_1 = "test1";
|
||||||
|
|
||||||
|
private static final X500Principal TEST_DN_1 = new X500Principal("CN=test1");
|
||||||
|
|
||||||
|
private static final long NOW_MILLIS = System.currentTimeMillis();
|
||||||
|
|
||||||
|
private static final BigInteger SERIAL_1 = BigInteger.ONE;
|
||||||
|
|
||||||
|
/* We have to round this off because X509v3 doesn't store milliseconds. */
|
||||||
|
private static final Date NOW = new Date(NOW_MILLIS - (NOW_MILLIS % 1000L));
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private static final Date NOW_PLUS_10_YEARS = new Date(NOW.getYear() + 10, 0, 1);
|
||||||
|
|
||||||
|
public void testConstructor_Success() throws Exception {
|
||||||
|
AndroidKeyPairGeneratorSpec spec = new AndroidKeyPairGeneratorSpec(getContext(),
|
||||||
|
TEST_ALIAS_1, TEST_DN_1, SERIAL_1, NOW, NOW_PLUS_10_YEARS);
|
||||||
|
|
||||||
|
assertEquals("Context should be the one specified", getContext(), spec.getContext());
|
||||||
|
|
||||||
|
assertEquals("Alias should be the one specified", TEST_ALIAS_1, spec.getKeystoreAlias());
|
||||||
|
|
||||||
|
assertEquals("subjectDN should be the one specified", TEST_DN_1, spec.getSubjectDN());
|
||||||
|
|
||||||
|
assertEquals("startDate should be the one specified", NOW, spec.getStartDate());
|
||||||
|
|
||||||
|
assertEquals("endDate should be the one specified", NOW_PLUS_10_YEARS, spec.getEndDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testConstructor_NullContext_Failure() throws Exception {
|
||||||
|
try {
|
||||||
|
new AndroidKeyPairGeneratorSpec(null, TEST_ALIAS_1, TEST_DN_1, SERIAL_1, NOW,
|
||||||
|
NOW_PLUS_10_YEARS);
|
||||||
|
fail("Should throw IllegalArgumentException when context is null");
|
||||||
|
} catch (IllegalArgumentException success) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testConstructor_NullKeystoreAlias_Failure() throws Exception {
|
||||||
|
try {
|
||||||
|
new AndroidKeyPairGeneratorSpec(getContext(), null, TEST_DN_1, SERIAL_1, NOW,
|
||||||
|
NOW_PLUS_10_YEARS);
|
||||||
|
fail("Should throw IllegalArgumentException when keystoreAlias is null");
|
||||||
|
} catch (IllegalArgumentException success) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testConstructor_NullSubjectDN_Failure() throws Exception {
|
||||||
|
try {
|
||||||
|
new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, null, SERIAL_1, NOW,
|
||||||
|
NOW_PLUS_10_YEARS);
|
||||||
|
fail("Should throw IllegalArgumentException when subjectDN is null");
|
||||||
|
} catch (IllegalArgumentException success) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testConstructor_NullSerial_Failure() throws Exception {
|
||||||
|
try {
|
||||||
|
new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, null, NOW,
|
||||||
|
NOW_PLUS_10_YEARS);
|
||||||
|
fail("Should throw IllegalArgumentException when startDate is null");
|
||||||
|
} catch (IllegalArgumentException success) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testConstructor_NullStartDate_Failure() throws Exception {
|
||||||
|
try {
|
||||||
|
new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1, null,
|
||||||
|
NOW_PLUS_10_YEARS);
|
||||||
|
fail("Should throw IllegalArgumentException when startDate is null");
|
||||||
|
} catch (IllegalArgumentException success) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testConstructor_NullEndDate_Failure() throws Exception {
|
||||||
|
try {
|
||||||
|
new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1, NOW,
|
||||||
|
null);
|
||||||
|
fail("Should throw IllegalArgumentException when keystoreAlias is null");
|
||||||
|
} catch (IllegalArgumentException success) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testConstructor_EndBeforeStart_Failure() throws Exception {
|
||||||
|
try {
|
||||||
|
new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1,
|
||||||
|
NOW_PLUS_10_YEARS, NOW);
|
||||||
|
fail("Should throw IllegalArgumentException when end is before start");
|
||||||
|
} catch (IllegalArgumentException success) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.security;
|
||||||
|
|
||||||
|
import android.test.AndroidTestCase;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import javax.security.auth.x500.X500Principal;
|
||||||
|
|
||||||
|
public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
|
||||||
|
private android.security.KeyStore mAndroidKeyStore;
|
||||||
|
|
||||||
|
private java.security.KeyPairGenerator mGenerator;
|
||||||
|
|
||||||
|
private static final String TEST_ALIAS_1 = "test1";
|
||||||
|
|
||||||
|
private static final String TEST_ALIAS_2 = "test2";
|
||||||
|
|
||||||
|
private static final X500Principal TEST_DN_1 = new X500Principal("CN=test1");
|
||||||
|
|
||||||
|
private static final X500Principal TEST_DN_2 = new X500Principal("CN=test2");
|
||||||
|
|
||||||
|
private static final BigInteger TEST_SERIAL_1 = BigInteger.ONE;
|
||||||
|
|
||||||
|
private static final BigInteger TEST_SERIAL_2 = BigInteger.valueOf(2L);
|
||||||
|
|
||||||
|
private static final long NOW_MILLIS = System.currentTimeMillis();
|
||||||
|
|
||||||
|
/* We have to round this off because X509v3 doesn't store milliseconds. */
|
||||||
|
private static final Date NOW = new Date(NOW_MILLIS - (NOW_MILLIS % 1000L));
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private static final Date NOW_PLUS_10_YEARS = new Date(NOW.getYear() + 10, 0, 1);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
mAndroidKeyStore = android.security.KeyStore.getInstance();
|
||||||
|
|
||||||
|
assertTrue(mAndroidKeyStore.reset());
|
||||||
|
|
||||||
|
assertEquals(android.security.KeyStore.State.UNINITIALIZED, mAndroidKeyStore.state());
|
||||||
|
|
||||||
|
assertTrue(mAndroidKeyStore.password("1111"));
|
||||||
|
|
||||||
|
assertEquals(android.security.KeyStore.State.UNLOCKED, mAndroidKeyStore.state());
|
||||||
|
|
||||||
|
assertEquals(0, mAndroidKeyStore.saw("").length);
|
||||||
|
|
||||||
|
mGenerator = java.security.KeyPairGenerator.getInstance(AndroidKeyPairGenerator.NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testKeyPairGenerator_Initialize_Params_Success() throws Exception {
|
||||||
|
mGenerator.initialize(new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1,
|
||||||
|
TEST_DN_1, TEST_SERIAL_1, NOW, NOW_PLUS_10_YEARS));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testKeyPairGenerator_Initialize_KeySize_Failure() throws Exception {
|
||||||
|
try {
|
||||||
|
mGenerator.initialize(1024);
|
||||||
|
fail("KeyPairGenerator should not support setting the key size");
|
||||||
|
} catch (IllegalArgumentException success) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testKeyPairGenerator_Initialize_KeySizeAndSecureRandom_Failure() throws Exception {
|
||||||
|
try {
|
||||||
|
mGenerator.initialize(1024, new SecureRandom());
|
||||||
|
fail("KeyPairGenerator should not support setting the key size");
|
||||||
|
} catch (IllegalArgumentException success) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testKeyPairGenerator_Initialize_ParamsAndSecureRandom_Failure() throws Exception {
|
||||||
|
mGenerator.initialize(new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1,
|
||||||
|
TEST_DN_1, TEST_SERIAL_1, NOW, NOW_PLUS_10_YEARS), new SecureRandom());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testKeyPairGenerator_GenerateKeyPair_Success() throws Exception {
|
||||||
|
mGenerator.initialize(new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1,
|
||||||
|
TEST_DN_1, TEST_SERIAL_1, NOW, NOW_PLUS_10_YEARS));
|
||||||
|
|
||||||
|
final KeyPair pair = mGenerator.generateKeyPair();
|
||||||
|
assertNotNull("The KeyPair returned should not be null", pair);
|
||||||
|
|
||||||
|
assertKeyPairCorrect(pair, TEST_ALIAS_1, TEST_DN_1, TEST_SERIAL_1, NOW, NOW_PLUS_10_YEARS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testKeyPairGenerator_GenerateKeyPair_Replaced_Success() throws Exception {
|
||||||
|
// Generate the first key
|
||||||
|
{
|
||||||
|
mGenerator.initialize(new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_1,
|
||||||
|
TEST_DN_1, TEST_SERIAL_1, NOW, NOW_PLUS_10_YEARS));
|
||||||
|
final KeyPair pair1 = mGenerator.generateKeyPair();
|
||||||
|
assertNotNull("The KeyPair returned should not be null", pair1);
|
||||||
|
assertKeyPairCorrect(pair1, TEST_ALIAS_1, TEST_DN_1, TEST_SERIAL_1, NOW,
|
||||||
|
NOW_PLUS_10_YEARS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the original key
|
||||||
|
{
|
||||||
|
mGenerator.initialize(new AndroidKeyPairGeneratorSpec(getContext(), TEST_ALIAS_2,
|
||||||
|
TEST_DN_2, TEST_SERIAL_2, NOW, NOW_PLUS_10_YEARS));
|
||||||
|
final KeyPair pair2 = mGenerator.generateKeyPair();
|
||||||
|
assertNotNull("The KeyPair returned should not be null", pair2);
|
||||||
|
assertKeyPairCorrect(pair2, TEST_ALIAS_2, TEST_DN_2, TEST_SERIAL_2, NOW,
|
||||||
|
NOW_PLUS_10_YEARS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertKeyPairCorrect(KeyPair pair, String alias, X500Principal dn,
|
||||||
|
BigInteger serial, Date start, Date end) throws Exception {
|
||||||
|
final PublicKey pubKey = pair.getPublic();
|
||||||
|
assertNotNull("The PublicKey for the KeyPair should be not null", pubKey);
|
||||||
|
|
||||||
|
final PrivateKey privKey = pair.getPrivate();
|
||||||
|
assertNotNull("The PrivateKey for the KeyPair should be not null", privKey);
|
||||||
|
|
||||||
|
final byte[] userCertBytes = mAndroidKeyStore.get(Credentials.USER_CERTIFICATE + alias);
|
||||||
|
assertNotNull("The user certificate should exist for the generated entry", userCertBytes);
|
||||||
|
|
||||||
|
final CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||||
|
final Certificate userCert = cf
|
||||||
|
.generateCertificate(new ByteArrayInputStream(userCertBytes));
|
||||||
|
|
||||||
|
assertTrue("Certificate should be in X.509 format", userCert instanceof X509Certificate);
|
||||||
|
|
||||||
|
final X509Certificate x509userCert = (X509Certificate) userCert;
|
||||||
|
|
||||||
|
assertEquals("PublicKey used to sign certificate should match one returned in KeyPair",
|
||||||
|
pubKey, x509userCert.getPublicKey());
|
||||||
|
|
||||||
|
assertEquals("The Subject DN should be the one passed into the params", dn,
|
||||||
|
x509userCert.getSubjectDN());
|
||||||
|
|
||||||
|
assertEquals("The Issuer DN should be the same as the Subject DN", dn,
|
||||||
|
x509userCert.getIssuerDN());
|
||||||
|
|
||||||
|
assertEquals("The Serial should be the one passed into the params", serial,
|
||||||
|
x509userCert.getSerialNumber());
|
||||||
|
|
||||||
|
assertEquals("The notBefore date should be the one passed into the params", start,
|
||||||
|
x509userCert.getNotBefore());
|
||||||
|
|
||||||
|
assertEquals("The notAfter date should be the one passed into the params", end,
|
||||||
|
x509userCert.getNotAfter());
|
||||||
|
|
||||||
|
x509userCert.verify(pubKey);
|
||||||
|
|
||||||
|
final byte[] caCerts = mAndroidKeyStore.get(Credentials.CA_CERTIFICATE + alias);
|
||||||
|
assertNull("A list of CA certificates should not exist for the generated entry", caCerts);
|
||||||
|
|
||||||
|
final byte[] pubKeyBytes = mAndroidKeyStore.getPubkey(Credentials.USER_PRIVATE_KEY + alias);
|
||||||
|
assertNotNull("The keystore should return the public key for the generated key",
|
||||||
|
pubKeyBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user