diff --git a/api/current.txt b/api/current.txt index a29cdba4cea18..8a7fb7962bf47 100644 --- a/api/current.txt +++ b/api/current.txt @@ -19988,6 +19988,10 @@ package android.sax { 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 { 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); diff --git a/keystore/java/android/security/AndroidKeyPairGenerator.java b/keystore/java/android/security/AndroidKeyPairGenerator.java new file mode 100644 index 0000000000000..c42001b79705d --- /dev/null +++ b/keystore/java/android/security/AndroidKeyPairGenerator.java @@ -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. + *
+ * 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. + *
+ * 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(); + } +} diff --git a/keystore/java/android/security/AndroidKeyPairGeneratorSpec.java b/keystore/java/android/security/AndroidKeyPairGeneratorSpec.java new file mode 100644 index 0000000000000..311359ca43684 --- /dev/null +++ b/keystore/java/android/security/AndroidKeyPairGeneratorSpec.java @@ -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 + * Android KeyStore + * facility. + */ +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. + *
+ * 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. + *
+ * 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; + } +} diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java index a629f8dfa6769..e19217f25a2df 100644 --- a/keystore/java/android/security/AndroidKeyStore.java +++ b/keystore/java/android/security/AndroidKeyStore.java @@ -46,9 +46,8 @@ import java.util.Iterator; import java.util.Set; /** - * A java.security.KeyStore interface for the Android KeyStore. This class is - * hidden from the Android API, but an instance of it can be created via the - * {@link java.security.KeyStore#getInstance(String) + * A java.security.KeyStore interface for the Android KeyStore. An instance of + * it can be created via the {@link java.security.KeyStore#getInstance(String) * KeyStore.getInstance("AndroidKeyStore")} interface. This returns a * java.security.KeyStore backed by this "AndroidKeyStore" implementation. *
@@ -277,7 +276,7 @@ public class AndroidKeyStore extends KeyStoreSpi {
* Make sure we clear out all the types we know about before trying to
* write.
*/
- deleteAllTypesForAlias(alias);
+ Credentials.deleteAllTypesForAlias(mKeyStore, alias);
if (!mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes)) {
throw new KeyStoreException("Couldn't put private key in keystore");
@@ -315,26 +314,11 @@ public class AndroidKeyStore extends KeyStoreSpi {
@Override
public void engineDeleteEntry(String alias) throws KeyStoreException {
- if (!deleteAllTypesForAlias(alias)) {
+ if (!Credentials.deleteAllTypesForAlias(mKeyStore, 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 Provides access to a few facilities of the Android security
+ subsystems. For information on how to use this facility, see the Android
+ KeyStore facility guide.