Merge "Android Keystore keys are no longer backed by Conscrypt." into mnc-dev
This commit is contained in:
@@ -29,15 +29,14 @@ import android.os.Looper;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.security.keystore.KeyInfo;
|
||||
import android.security.keystore.AndroidKeyStoreProvider;
|
||||
import android.security.keystore.KeyProperties;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.Closeable;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.Principal;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
@@ -47,7 +46,6 @@ import java.util.Locale;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import com.android.org.conscrypt.OpenSSLEngine;
|
||||
import com.android.org.conscrypt.TrustedCertificateStore;
|
||||
|
||||
/**
|
||||
@@ -90,8 +88,6 @@ import com.android.org.conscrypt.TrustedCertificateStore;
|
||||
// TODO reference intent for credential installation when public
|
||||
public final class KeyChain {
|
||||
|
||||
private static final String TAG = "KeyChain";
|
||||
|
||||
/**
|
||||
* @hide Also used by KeyChainService implementation
|
||||
*/
|
||||
@@ -372,15 +368,14 @@ public final class KeyChain {
|
||||
if (keyId == null) {
|
||||
throw new KeyChainException("keystore had a problem");
|
||||
}
|
||||
|
||||
final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
|
||||
return engine.getPrivateKeyById(keyId);
|
||||
return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
|
||||
KeyStore.getInstance(), keyId);
|
||||
} catch (RemoteException e) {
|
||||
throw new KeyChainException(e);
|
||||
} catch (RuntimeException e) {
|
||||
// only certain RuntimeExceptions can be propagated across the IKeyChainService call
|
||||
throw new KeyChainException(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
} catch (UnrecoverableKeyException e) {
|
||||
throw new KeyChainException(e);
|
||||
} finally {
|
||||
keyChainConnection.close();
|
||||
|
||||
@@ -331,7 +331,9 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
|
||||
if (keyType == null) {
|
||||
throw new NullPointerException("keyType == null");
|
||||
} else {
|
||||
if (KeyStore.getKeyTypeForAlgorithm(keyType) == -1) {
|
||||
try {
|
||||
KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(keyType);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new NoSuchAlgorithmException("Unsupported key type: " + keyType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ package android.security;
|
||||
import android.app.ActivityThread;
|
||||
import android.app.Application;
|
||||
import android.app.KeyguardManager;
|
||||
import com.android.org.conscrypt.NativeConstants;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
@@ -38,7 +37,6 @@ import android.security.keymaster.OperationResult;
|
||||
import android.security.keystore.KeyExpiredException;
|
||||
import android.security.keystore.KeyNotYetValidException;
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.security.keystore.UserNotAuthenticatedException;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -136,16 +134,6 @@ public class KeyStore {
|
||||
return mToken;
|
||||
}
|
||||
|
||||
public static int getKeyTypeForAlgorithm(@KeyProperties.KeyAlgorithmEnum String keyType) {
|
||||
if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyType)) {
|
||||
return NativeConstants.EVP_PKEY_RSA;
|
||||
} else if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyType)) {
|
||||
return NativeConstants.EVP_PKEY_EC;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public State state(int userId) {
|
||||
final int ret;
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.keystore;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.interfaces.ECKey;
|
||||
import java.security.spec.ECParameterSpec;
|
||||
|
||||
/**
|
||||
* EC private key (instance of {@link PrivateKey} and {@link ECKey}) backed by keystore.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class AndroidKeyStoreECPrivateKey extends AndroidKeyStorePrivateKey implements ECKey {
|
||||
private final ECParameterSpec mParams;
|
||||
|
||||
public AndroidKeyStoreECPrivateKey(String alias, ECParameterSpec params) {
|
||||
super(alias, KeyProperties.KEY_ALGORITHM_EC);
|
||||
mParams = params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ECParameterSpec getParams() {
|
||||
return mParams;
|
||||
}
|
||||
}
|
||||
@@ -52,4 +52,42 @@ public class AndroidKeyStoreKey implements Key {
|
||||
// This key does not export its key material
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((mAlgorithm == null) ? 0 : mAlgorithm.hashCode());
|
||||
result = prime * result + ((mAlias == null) ? 0 : mAlias.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
AndroidKeyStoreKey other = (AndroidKeyStoreKey) obj;
|
||||
if (mAlgorithm == null) {
|
||||
if (other.mAlgorithm != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!mAlgorithm.equals(other.mAlgorithm)) {
|
||||
return false;
|
||||
}
|
||||
if (mAlias == null) {
|
||||
if (other.mAlias != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!mAlias.equals(other.mAlias)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import android.annotation.Nullable;
|
||||
import android.security.Credentials;
|
||||
import android.security.KeyPairGeneratorSpec;
|
||||
import android.security.KeyStore;
|
||||
import android.security.keymaster.ExportResult;
|
||||
import android.security.keymaster.KeyCharacteristics;
|
||||
import android.security.keymaster.KeymasterArguments;
|
||||
import android.security.keymaster.KeymasterDefs;
|
||||
@@ -44,29 +43,24 @@ import com.android.org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
|
||||
import com.android.org.bouncycastle.jce.X509Principal;
|
||||
import com.android.org.bouncycastle.jce.provider.X509CertificateObject;
|
||||
import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
|
||||
import com.android.org.conscrypt.OpenSSLEngine;
|
||||
|
||||
import libcore.util.EmptyArray;
|
||||
|
||||
import java.math.BigInteger;
|
||||
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.ProviderException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.RSAKeyGenParameterSpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
@@ -147,6 +141,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
|
||||
SUPPORTED_EC_NIST_CURVE_NAMES.addAll(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.keySet());
|
||||
Collections.sort(SUPPORTED_EC_NIST_CURVE_NAMES);
|
||||
}
|
||||
|
||||
private final int mOriginalKeymasterAlgorithm;
|
||||
|
||||
private KeyStore mKeyStore;
|
||||
@@ -431,24 +426,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
|
||||
args.addInts(KeymasterDefs.KM_TAG_PADDING, mKeymasterSignaturePaddings);
|
||||
args.addInts(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests);
|
||||
|
||||
// TODO: Remove the digest and padding NONE workaround below once Android Keystore returns
|
||||
// keys which are backed by AndroidKeyStoreBCWorkaround provider instead of Conscrypt. The
|
||||
// workaround is needed because Conscrypt (via keystore-engine) uses old KeyStore API which
|
||||
// translates into digest NONE and padding NONE in the new API. keystore-engine cannot be
|
||||
// updated to pass in the correct padding and digest values because it uses
|
||||
// OpenSSL/BoringSSL engine which performs digesting and padding prior before invoking
|
||||
// KeyStore API.
|
||||
if (!com.android.internal.util.ArrayUtils.contains(
|
||||
mKeymasterDigests, KeymasterDefs.KM_DIGEST_NONE)) {
|
||||
args.addInt(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE);
|
||||
}
|
||||
if ((!com.android.internal.util.ArrayUtils.contains(
|
||||
mKeymasterSignaturePaddings, KeymasterDefs.KM_PAD_NONE))
|
||||
&& (!com.android.internal.util.ArrayUtils.contains(
|
||||
mKeymasterEncryptionPaddings, KeymasterDefs.KM_PAD_NONE))) {
|
||||
args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
|
||||
}
|
||||
|
||||
KeymasterUtils.addUserAuthArgs(args,
|
||||
mSpec.isUserAuthenticationRequired(),
|
||||
mSpec.getUserAuthenticationValidityDurationSeconds());
|
||||
@@ -483,40 +460,23 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
|
||||
"Failed to generate key pair", KeyStore.getKeyStoreException(errorCode));
|
||||
}
|
||||
|
||||
final PrivateKey privKey;
|
||||
final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
|
||||
KeyPair result;
|
||||
try {
|
||||
privKey = engine.getPrivateKeyById(privateKeyAlias);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new ProviderException("Failed to obtain generated private key", e);
|
||||
result = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
|
||||
mKeyStore, privateKeyAlias);
|
||||
} catch (UnrecoverableKeyException e) {
|
||||
throw new ProviderException("Failed to load generated key pair from keystore", e);
|
||||
}
|
||||
|
||||
ExportResult exportResult =
|
||||
mKeyStore.exportKey(
|
||||
privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null);
|
||||
if (exportResult == null) {
|
||||
throw new KeyStoreConnectException();
|
||||
} else if (exportResult.resultCode != KeyStore.NO_ERROR) {
|
||||
if (!mJcaKeyAlgorithm.equalsIgnoreCase(result.getPrivate().getAlgorithm())) {
|
||||
throw new ProviderException(
|
||||
"Failed to obtain X.509 form of generated public key",
|
||||
KeyStore.getKeyStoreException(exportResult.resultCode));
|
||||
}
|
||||
final byte[] pubKeyBytes = exportResult.exportData;
|
||||
|
||||
final PublicKey pubKey;
|
||||
try {
|
||||
final KeyFactory keyFact = KeyFactory.getInstance(mJcaKeyAlgorithm);
|
||||
pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new ProviderException(
|
||||
"Failed to obtain " + mJcaKeyAlgorithm + " KeyFactory", e);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new ProviderException("Invalid X.509 encoding of generated public key", e);
|
||||
"Generated key pair algorithm does not match requested algorithm: "
|
||||
+ result.getPrivate().getAlgorithm() + " vs " + mJcaKeyAlgorithm);
|
||||
}
|
||||
|
||||
final X509Certificate cert;
|
||||
try {
|
||||
cert = generateSelfSignedCertificate(privKey, pubKey);
|
||||
cert = generateSelfSignedCertificate(result.getPrivate(), result.getPublic());
|
||||
} catch (Exception e) {
|
||||
throw new ProviderException("Failed to generate self-signed certificate", e);
|
||||
}
|
||||
@@ -539,7 +499,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
|
||||
KeyStore.getKeyStoreException(insertErrorCode));
|
||||
}
|
||||
|
||||
KeyPair result = new KeyPair(pubKey, privKey);
|
||||
success = true;
|
||||
return result;
|
||||
} finally {
|
||||
|
||||
@@ -16,11 +16,28 @@
|
||||
|
||||
package android.security.keystore;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.security.KeyStore;
|
||||
import android.security.keymaster.ExportResult;
|
||||
import android.security.keymaster.KeyCharacteristics;
|
||||
import android.security.keymaster.KeymasterDefs;
|
||||
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.ProviderException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Security;
|
||||
import java.security.Signature;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.interfaces.ECKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.interfaces.RSAKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.List;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.Mac;
|
||||
@@ -146,4 +163,145 @@ public class AndroidKeyStoreProvider extends Provider {
|
||||
}
|
||||
return ((KeyStoreCryptoOperation) spi).getOperationHandle();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static AndroidKeyStorePublicKey getAndroidKeyStorePublicKey(
|
||||
@NonNull String alias,
|
||||
@NonNull @KeyProperties.KeyAlgorithmEnum String keyAlgorithm,
|
||||
@NonNull byte[] x509EncodedForm) {
|
||||
PublicKey publicKey;
|
||||
try {
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
|
||||
publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedForm));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new ProviderException(
|
||||
"Failed to obtain " + keyAlgorithm + " KeyFactory", e);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new ProviderException("Invalid X.509 encoding of public key", e);
|
||||
}
|
||||
if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
|
||||
return new AndroidKeyStoreECPublicKey(alias, (ECPublicKey) publicKey);
|
||||
} else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
|
||||
return new AndroidKeyStoreRSAPublicKey(alias, (RSAPublicKey) publicKey);
|
||||
} else {
|
||||
throw new ProviderException("Unsupported Android Keystore public key algorithm: "
|
||||
+ keyAlgorithm);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static AndroidKeyStorePrivateKey getAndroidKeyStorePrivateKey(
|
||||
@NonNull AndroidKeyStorePublicKey publicKey) {
|
||||
String keyAlgorithm = publicKey.getAlgorithm();
|
||||
if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
|
||||
return new AndroidKeyStoreECPrivateKey(
|
||||
publicKey.getAlias(), ((ECKey) publicKey).getParams());
|
||||
} else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
|
||||
return new AndroidKeyStoreRSAPrivateKey(
|
||||
publicKey.getAlias(), ((RSAKey) publicKey).getModulus());
|
||||
} else {
|
||||
throw new ProviderException("Unsupported Android Keystore public key algorithm: "
|
||||
+ keyAlgorithm);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore(
|
||||
@NonNull KeyStore keyStore, @NonNull String privateKeyAlias)
|
||||
throws UnrecoverableKeyException {
|
||||
KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
|
||||
int errorCode = keyStore.getKeyCharacteristics(
|
||||
privateKeyAlias, null, null, keyCharacteristics);
|
||||
if (errorCode != KeyStore.NO_ERROR) {
|
||||
throw (UnrecoverableKeyException)
|
||||
new UnrecoverableKeyException("Failed to obtain information about private key")
|
||||
.initCause(KeyStore.getKeyStoreException(errorCode));
|
||||
}
|
||||
ExportResult exportResult = keyStore.exportKey(
|
||||
privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null);
|
||||
if (exportResult.resultCode != KeyStore.NO_ERROR) {
|
||||
throw (UnrecoverableKeyException)
|
||||
new UnrecoverableKeyException("Failed to obtain X.509 form of public key")
|
||||
.initCause(KeyStore.getKeyStoreException(errorCode));
|
||||
}
|
||||
final byte[] x509EncodedPublicKey = exportResult.exportData;
|
||||
|
||||
int keymasterAlgorithm = keyCharacteristics.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1);
|
||||
if (keymasterAlgorithm == -1) {
|
||||
throw new UnrecoverableKeyException("Key algorithm unknown");
|
||||
}
|
||||
|
||||
String jcaKeyAlgorithm;
|
||||
try {
|
||||
jcaKeyAlgorithm = KeyProperties.KeyAlgorithm.fromKeymasterAsymmetricKeyAlgorithm(
|
||||
keymasterAlgorithm);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw (UnrecoverableKeyException)
|
||||
new UnrecoverableKeyException("Failed to load private key")
|
||||
.initCause(e);
|
||||
}
|
||||
|
||||
return AndroidKeyStoreProvider.getAndroidKeyStorePublicKey(
|
||||
privateKeyAlias, jcaKeyAlgorithm, x509EncodedPublicKey);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore(
|
||||
@NonNull KeyStore keyStore, @NonNull String privateKeyAlias)
|
||||
throws UnrecoverableKeyException {
|
||||
AndroidKeyStorePublicKey publicKey =
|
||||
loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias);
|
||||
AndroidKeyStorePrivateKey privateKey =
|
||||
AndroidKeyStoreProvider.getAndroidKeyStorePrivateKey(publicKey);
|
||||
return new KeyPair(publicKey, privateKey);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore(
|
||||
@NonNull KeyStore keyStore, @NonNull String privateKeyAlias)
|
||||
throws UnrecoverableKeyException {
|
||||
KeyPair keyPair = loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias);
|
||||
return (AndroidKeyStorePrivateKey) keyPair.getPrivate();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static AndroidKeyStoreSecretKey loadAndroidKeyStoreSecretKeyFromKeystore(
|
||||
@NonNull KeyStore keyStore, @NonNull String secretKeyAlias)
|
||||
throws UnrecoverableKeyException {
|
||||
KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
|
||||
int errorCode = keyStore.getKeyCharacteristics(
|
||||
secretKeyAlias, null, null, keyCharacteristics);
|
||||
if (errorCode != KeyStore.NO_ERROR) {
|
||||
throw (UnrecoverableKeyException)
|
||||
new UnrecoverableKeyException("Failed to obtain information about key")
|
||||
.initCause(KeyStore.getKeyStoreException(errorCode));
|
||||
}
|
||||
|
||||
int keymasterAlgorithm = keyCharacteristics.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1);
|
||||
if (keymasterAlgorithm == -1) {
|
||||
throw new UnrecoverableKeyException("Key algorithm unknown");
|
||||
}
|
||||
|
||||
List<Integer> keymasterDigests =
|
||||
keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST);
|
||||
int keymasterDigest;
|
||||
if (keymasterDigests.isEmpty()) {
|
||||
keymasterDigest = -1;
|
||||
} else {
|
||||
// More than one digest can be permitted for this key. Use the first one to form the
|
||||
// JCA key algorithm name.
|
||||
keymasterDigest = keymasterDigests.get(0);
|
||||
}
|
||||
|
||||
@KeyProperties.KeyAlgorithmEnum String keyAlgorithmString;
|
||||
try {
|
||||
keyAlgorithmString = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm(
|
||||
keymasterAlgorithm, keymasterDigest);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw (UnrecoverableKeyException)
|
||||
new UnrecoverableKeyException("Unsupported secret key type").initCause(e);
|
||||
}
|
||||
|
||||
return new AndroidKeyStoreSecretKey(secretKeyAlias, keyAlgorithmString);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package android.security.keystore;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* {@link PublicKey} backed by Android Keystore.
|
||||
@@ -41,4 +42,30 @@ public class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implements Publ
|
||||
public byte[] getEncoded() {
|
||||
return ArrayUtils.cloneIfNotEmpty(mEncoded);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + Arrays.hashCode(mEncoded);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!super.equals(obj)) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
AndroidKeyStorePublicKey other = (AndroidKeyStorePublicKey) obj;
|
||||
if (!Arrays.equals(mEncoded, other.mEncoded)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.keystore;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.interfaces.RSAKey;
|
||||
|
||||
/**
|
||||
* RSA private key (instance of {@link PrivateKey} and {@link RSAKey}) backed by keystore.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class AndroidKeyStoreRSAPrivateKey extends AndroidKeyStorePrivateKey implements RSAKey {
|
||||
|
||||
private final BigInteger mModulus;
|
||||
|
||||
public AndroidKeyStoreRSAPrivateKey(String alias, BigInteger modulus) {
|
||||
super(alias, KeyProperties.KEY_ALGORITHM_RSA);
|
||||
mModulus = modulus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger getModulus() {
|
||||
return mModulus;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import android.security.keymaster.KeymasterArguments;
|
||||
import android.security.keymaster.KeymasterDefs;
|
||||
import android.security.keymaster.OperationResult;
|
||||
|
||||
import com.android.org.conscrypt.util.EmptyArray;
|
||||
import libcore.util.EmptyArray;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidKeyException;
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
|
||||
package android.security.keystore;
|
||||
|
||||
import com.android.org.conscrypt.OpenSSLEngine;
|
||||
import com.android.org.conscrypt.OpenSSLKeyHolder;
|
||||
|
||||
import libcore.util.EmptyArray;
|
||||
|
||||
import android.security.Credentials;
|
||||
@@ -35,7 +32,6 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyStore.Entry;
|
||||
import java.security.KeyStore.PrivateKeyEntry;
|
||||
@@ -45,6 +41,7 @@ import java.security.KeyStoreException;
|
||||
import java.security.KeyStoreSpi;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
@@ -59,7 +56,6 @@ import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
@@ -92,59 +88,17 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
|
||||
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
|
||||
UnrecoverableKeyException {
|
||||
if (isPrivateKeyEntry(alias)) {
|
||||
final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
|
||||
try {
|
||||
return engine.getPrivateKeyById(Credentials.USER_PRIVATE_KEY + alias);
|
||||
} catch (InvalidKeyException e) {
|
||||
UnrecoverableKeyException t = new UnrecoverableKeyException("Can't get key");
|
||||
t.initCause(e);
|
||||
throw t;
|
||||
}
|
||||
String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
|
||||
return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
|
||||
mKeyStore, privateKeyAlias);
|
||||
} else if (isSecretKeyEntry(alias)) {
|
||||
KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
|
||||
String keyAliasInKeystore = Credentials.USER_SECRET_KEY + alias;
|
||||
int errorCode = mKeyStore.getKeyCharacteristics(
|
||||
keyAliasInKeystore, null, null, keyCharacteristics);
|
||||
if (errorCode != KeyStore.NO_ERROR) {
|
||||
throw (UnrecoverableKeyException)
|
||||
new UnrecoverableKeyException("Failed to load information about key")
|
||||
.initCause(mKeyStore.getInvalidKeyException(alias, errorCode));
|
||||
}
|
||||
|
||||
int keymasterAlgorithm =
|
||||
keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1);
|
||||
if (keymasterAlgorithm == -1) {
|
||||
keymasterAlgorithm =
|
||||
keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1);
|
||||
}
|
||||
if (keymasterAlgorithm == -1) {
|
||||
throw new UnrecoverableKeyException("Key algorithm unknown");
|
||||
}
|
||||
|
||||
List<Integer> keymasterDigests =
|
||||
keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST);
|
||||
int keymasterDigest;
|
||||
if (keymasterDigests.isEmpty()) {
|
||||
keymasterDigest = -1;
|
||||
} else {
|
||||
// More than one digest can be permitted for this key. Use the first one to form the
|
||||
// JCA key algorithm name.
|
||||
keymasterDigest = keymasterDigests.get(0);
|
||||
}
|
||||
|
||||
@KeyProperties.KeyAlgorithmEnum String keyAlgorithmString;
|
||||
try {
|
||||
keyAlgorithmString = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm(
|
||||
keymasterAlgorithm, keymasterDigest);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw (UnrecoverableKeyException)
|
||||
new UnrecoverableKeyException("Unsupported secret key type").initCause(e);
|
||||
}
|
||||
|
||||
return new AndroidKeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmString);
|
||||
String secretKeyAlias = Credentials.USER_SECRET_KEY + alias;
|
||||
return AndroidKeyStoreProvider.loadAndroidKeyStoreSecretKeyFromKeystore(
|
||||
mKeyStore, secretKeyAlias);
|
||||
} else {
|
||||
// Key not found
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -188,22 +142,36 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
|
||||
|
||||
byte[] certificate = mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
|
||||
if (certificate != null) {
|
||||
return toCertificate(certificate);
|
||||
return wrapIntoKeyStoreCertificate(
|
||||
Credentials.USER_PRIVATE_KEY + alias, toCertificate(certificate));
|
||||
}
|
||||
|
||||
certificate = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
|
||||
if (certificate != null) {
|
||||
return toCertificate(certificate);
|
||||
return wrapIntoKeyStoreCertificate(
|
||||
Credentials.USER_PRIVATE_KEY + alias, toCertificate(certificate));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the provided cerificate into {@link KeyStoreX509Certificate} so that the public key
|
||||
* returned by the certificate contains information about the alias of the private key in
|
||||
* keystore. This is needed so that Android Keystore crypto operations using public keys can
|
||||
* find out which key alias to use. These operations cannot work without an alias.
|
||||
*/
|
||||
private static KeyStoreX509Certificate wrapIntoKeyStoreCertificate(
|
||||
String privateKeyAlias, X509Certificate certificate) {
|
||||
return (certificate != null)
|
||||
? new KeyStoreX509Certificate(privateKeyAlias, certificate) : null;
|
||||
}
|
||||
|
||||
private static X509Certificate toCertificate(byte[] bytes) {
|
||||
try {
|
||||
final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||
return (X509Certificate) certFactory
|
||||
.generateCertificate(new ByteArrayInputStream(bytes));
|
||||
return (X509Certificate) certFactory.generateCertificate(
|
||||
new ByteArrayInputStream(bytes));
|
||||
} catch (CertificateException e) {
|
||||
Log.w(NAME, "Couldn't parse certificate in keystore", e);
|
||||
return null;
|
||||
@@ -214,8 +182,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
|
||||
private static Collection<X509Certificate> toCertificates(byte[] bytes) {
|
||||
try {
|
||||
final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||
return (Collection<X509Certificate>) certFactory
|
||||
.generateCertificates(new ByteArrayInputStream(bytes));
|
||||
return (Collection<X509Certificate>) certFactory.generateCertificates(
|
||||
new ByteArrayInputStream(bytes));
|
||||
} catch (CertificateException e) {
|
||||
Log.w(NAME, "Couldn't parse certificates in keystore", e);
|
||||
return new ArrayList<X509Certificate>();
|
||||
@@ -406,9 +374,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
|
||||
}
|
||||
|
||||
final String pkeyAlias;
|
||||
if (key instanceof OpenSSLKeyHolder) {
|
||||
pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias();
|
||||
} else if (key instanceof AndroidKeyStorePrivateKey) {
|
||||
if (key instanceof AndroidKeyStorePrivateKey) {
|
||||
pkeyAlias = ((AndroidKeyStoreKey) key).getAlias();
|
||||
} else {
|
||||
pkeyAlias = null;
|
||||
@@ -851,6 +817,19 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
|
||||
if (cert == null) {
|
||||
return null;
|
||||
}
|
||||
if (!"X.509".equalsIgnoreCase(cert.getType())) {
|
||||
// Only X.509 certificates supported
|
||||
return null;
|
||||
}
|
||||
byte[] targetCertBytes;
|
||||
try {
|
||||
targetCertBytes = cert.getEncoded();
|
||||
} catch (CertificateEncodingException e) {
|
||||
return null;
|
||||
}
|
||||
if (targetCertBytes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Set<String> nonCaEntries = new HashSet<String>();
|
||||
|
||||
@@ -868,10 +847,9 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
|
||||
continue;
|
||||
}
|
||||
|
||||
final Certificate c = toCertificate(certBytes);
|
||||
nonCaEntries.add(alias);
|
||||
|
||||
if (cert.equals(c)) {
|
||||
if (Arrays.equals(certBytes, targetCertBytes)) {
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
@@ -893,9 +871,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
|
||||
continue;
|
||||
}
|
||||
|
||||
final Certificate c =
|
||||
toCertificate(mKeyStore.get(Credentials.CA_CERTIFICATE + alias));
|
||||
if (cert.equals(c)) {
|
||||
if (Arrays.equals(certBytes, targetCertBytes)) {
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
@@ -954,4 +930,25 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link X509Certificate} which returns {@link AndroidKeyStorePublicKey} from
|
||||
* {@link #getPublicKey()}. This is so that crypto operations on these public keys contain
|
||||
* can find out which keystore private key entry to use. This is needed so that Android Keystore
|
||||
* crypto operations using public keys can find out which key alias to use. These operations
|
||||
* require an alias.
|
||||
*/
|
||||
static class KeyStoreX509Certificate extends DelegatingX509Certificate {
|
||||
private final String mPrivateKeyAlias;
|
||||
KeyStoreX509Certificate(String privateKeyAlias, X509Certificate delegate) {
|
||||
super(delegate);
|
||||
mPrivateKeyAlias = privateKeyAlias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey getPublicKey() {
|
||||
PublicKey original = super.getPublicKey();
|
||||
return AndroidKeyStoreProvider.getAndroidKeyStorePublicKey(
|
||||
mPrivateKeyAlias, original.getAlgorithm(), original.getEncoded());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.keystore;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.Principal;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
class DelegatingX509Certificate extends X509Certificate {
|
||||
private final X509Certificate mDelegate;
|
||||
|
||||
DelegatingX509Certificate(X509Certificate delegate) {
|
||||
mDelegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getCriticalExtensionOIDs() {
|
||||
return mDelegate.getCriticalExtensionOIDs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getExtensionValue(String oid) {
|
||||
return mDelegate.getExtensionValue(oid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getNonCriticalExtensionOIDs() {
|
||||
return mDelegate.getNonCriticalExtensionOIDs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasUnsupportedCriticalExtension() {
|
||||
return mDelegate.hasUnsupportedCriticalExtension();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkValidity() throws CertificateExpiredException,
|
||||
CertificateNotYetValidException {
|
||||
mDelegate.checkValidity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkValidity(Date date) throws CertificateExpiredException,
|
||||
CertificateNotYetValidException {
|
||||
mDelegate.checkValidity(date);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBasicConstraints() {
|
||||
return mDelegate.getBasicConstraints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getIssuerDN() {
|
||||
return mDelegate.getIssuerDN();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] getIssuerUniqueID() {
|
||||
return mDelegate.getIssuerUniqueID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] getKeyUsage() {
|
||||
return mDelegate.getKeyUsage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getNotAfter() {
|
||||
return mDelegate.getNotAfter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getNotBefore() {
|
||||
return mDelegate.getNotBefore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger getSerialNumber() {
|
||||
return mDelegate.getSerialNumber();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSigAlgName() {
|
||||
return mDelegate.getSigAlgName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSigAlgOID() {
|
||||
return mDelegate.getSigAlgOID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSigAlgParams() {
|
||||
return mDelegate.getSigAlgParams();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSignature() {
|
||||
return mDelegate.getSignature();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getSubjectDN() {
|
||||
return mDelegate.getSubjectDN();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] getSubjectUniqueID() {
|
||||
return mDelegate.getSubjectUniqueID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getTBSCertificate() throws CertificateEncodingException {
|
||||
return mDelegate.getTBSCertificate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return mDelegate.getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoded() throws CertificateEncodingException {
|
||||
return mDelegate.getEncoded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey getPublicKey() {
|
||||
return mDelegate.getPublicKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mDelegate.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(PublicKey key)
|
||||
throws CertificateException,
|
||||
NoSuchAlgorithmException,
|
||||
InvalidKeyException,
|
||||
NoSuchProviderException,
|
||||
SignatureException {
|
||||
mDelegate.verify(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(PublicKey key, String sigProvider)
|
||||
throws CertificateException,
|
||||
NoSuchAlgorithmException,
|
||||
InvalidKeyException,
|
||||
NoSuchProviderException,
|
||||
SignatureException {
|
||||
mDelegate.verify(key, sigProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getExtendedKeyUsage() throws CertificateParsingException {
|
||||
return mDelegate.getExtendedKeyUsage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<List<?>> getIssuerAlternativeNames() throws CertificateParsingException {
|
||||
return mDelegate.getIssuerAlternativeNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public X500Principal getIssuerX500Principal() {
|
||||
return mDelegate.getIssuerX500Principal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<List<?>> getSubjectAlternativeNames() throws CertificateParsingException {
|
||||
return mDelegate.getSubjectAlternativeNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public X500Principal getSubjectX500Principal() {
|
||||
return mDelegate.getSubjectX500Principal();
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import android.app.Activity;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.Process;
|
||||
import android.os.ServiceManager;
|
||||
import android.security.keymaster.ExportResult;
|
||||
import android.security.keymaster.KeyCharacteristics;
|
||||
import android.security.keymaster.KeymasterArguments;
|
||||
@@ -34,13 +33,9 @@ import android.test.suitebuilder.annotation.MediumTest;
|
||||
import com.android.org.conscrypt.NativeConstants;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.security.spec.RSAKeyGenParameterSpec;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.Base64;
|
||||
|
||||
/**
|
||||
* Junit / Instrumentation test case for KeyStore class
|
||||
*
|
||||
|
||||
@@ -26,17 +26,21 @@ import android.test.AndroidTestCase;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
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.security.interfaces.ECKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.interfaces.RSAKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.security.spec.RSAKeyGenParameterSpec;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
@@ -158,6 +162,26 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
|
||||
}
|
||||
|
||||
public void testKeyPairGenerator_GenerateKeyPair_EC_Unencrypted_Success() throws Exception {
|
||||
KeyPairGenerator generator = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
|
||||
generator.initialize(new KeyGenParameterSpec.Builder(
|
||||
TEST_ALIAS_1,
|
||||
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
|
||||
.setCertificateSubject(TEST_DN_1)
|
||||
.setCertificateSerialNumber(TEST_SERIAL_1)
|
||||
.setCertificateNotBefore(NOW)
|
||||
.setCertificateNotAfter(NOW_PLUS_10_YEARS)
|
||||
.setDigests(KeyProperties.DIGEST_SHA256)
|
||||
.build());
|
||||
|
||||
final KeyPair pair = generator.generateKeyPair();
|
||||
assertNotNull("The KeyPair returned should not be null", pair);
|
||||
|
||||
assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 256, null, TEST_DN_1, TEST_SERIAL_1, NOW,
|
||||
NOW_PLUS_10_YEARS);
|
||||
}
|
||||
|
||||
public void testKeyPairGenerator_Legacy_GenerateKeyPair_EC_Unencrypted_Success()
|
||||
throws Exception {
|
||||
mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
|
||||
.setAlias(TEST_ALIAS_1)
|
||||
.setKeyType("EC")
|
||||
@@ -328,19 +352,40 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
|
||||
assertNotNull("The PrivateKey for the KeyPair should be not null", privKey);
|
||||
assertEquals(keyType, privKey.getAlgorithm());
|
||||
|
||||
if ("EC".equalsIgnoreCase(keyType)) {
|
||||
assertTrue("EC private key must be instanceof ECKey: " + privKey.getClass().getName(),
|
||||
privKey instanceof ECKey);
|
||||
assertEquals("Private and public key must have the same EC parameters",
|
||||
((ECKey) pubKey).getParams(), ((ECKey) privKey).getParams());
|
||||
} else if ("RSA".equalsIgnoreCase(keyType)) {
|
||||
assertTrue("RSA private key must be instance of RSAKey: "
|
||||
+ privKey.getClass().getName(),
|
||||
privKey instanceof RSAKey);
|
||||
assertEquals("Private and public key must have the same RSA modulus",
|
||||
((RSAKey) pubKey).getModulus(), ((RSAKey) privKey).getModulus());
|
||||
}
|
||||
|
||||
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));
|
||||
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(
|
||||
"Public key used to sign certificate should have the same algorithm as in KeyPair",
|
||||
pubKey.getAlgorithm(), x509userCert.getPublicKey().getAlgorithm());
|
||||
|
||||
assertEquals("PublicKey used to sign certificate should match one returned in KeyPair",
|
||||
pubKey, x509userCert.getPublicKey());
|
||||
pubKey,
|
||||
AndroidKeyStoreProvider.getAndroidKeyStorePublicKey(
|
||||
Credentials.USER_PRIVATE_KEY + alias,
|
||||
x509userCert.getPublicKey().getAlgorithm(),
|
||||
x509userCert.getPublicKey().getEncoded()));
|
||||
|
||||
assertEquals("The Subject DN should be the one passed into the params", dn,
|
||||
x509userCert.getSubjectDN());
|
||||
@@ -357,7 +402,10 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
|
||||
assertDateEquals("The notAfter date should be the one passed into the params", end,
|
||||
x509userCert.getNotAfter());
|
||||
|
||||
// Assert that the cert's signature verifies using the public key from generated KeyPair
|
||||
x509userCert.verify(pubKey);
|
||||
// Assert that the cert's signature verifies using the public key from the cert itself.
|
||||
x509userCert.verify(x509userCert.getPublicKey());
|
||||
|
||||
final byte[] caCerts = mAndroidKeyStore.get(Credentials.CA_CERTIFICATE + alias);
|
||||
assertNull("A list of CA certificates should not exist for the generated entry", caCerts);
|
||||
@@ -368,6 +416,8 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
|
||||
final byte[] pubKeyBytes = exportResult.exportData;
|
||||
assertNotNull("The keystore should return the public key for the generated key",
|
||||
pubKeyBytes);
|
||||
assertTrue("Public key X.509 format should be as expected",
|
||||
Arrays.equals(pubKey.getEncoded(), pubKeyBytes));
|
||||
}
|
||||
|
||||
private static void assertDateEquals(String message, Date date1, Date date2) throws Exception {
|
||||
|
||||
@@ -19,38 +19,31 @@ package android.security.keystore;
|
||||
import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
|
||||
|
||||
import com.android.org.conscrypt.NativeConstants;
|
||||
import com.android.org.conscrypt.OpenSSLEngine;
|
||||
|
||||
import android.security.Credentials;
|
||||
import android.security.KeyStore;
|
||||
import android.security.KeyStoreParameter;
|
||||
import android.security.keymaster.ExportResult;
|
||||
import android.security.keymaster.KeymasterDefs;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyStore.Entry;
|
||||
import java.security.KeyStore.PrivateKeyEntry;
|
||||
import java.security.KeyStore.TrustedCertificateEntry;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.interfaces.ECKey;
|
||||
import java.security.interfaces.RSAKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
@@ -1203,14 +1196,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
|
||||
|
||||
private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, PrivateKey expectedKey,
|
||||
Certificate expectedCert, Collection<Certificate> expectedChain) throws Exception {
|
||||
if (expectedKey instanceof ECPrivateKey) {
|
||||
if (expectedKey instanceof ECKey) {
|
||||
assertEquals("Returned PrivateKey should be what we inserted",
|
||||
((ECPrivateKey) expectedKey).getParams().getCurve(),
|
||||
((ECPublicKey) keyEntry.getCertificate().getPublicKey()).getParams().getCurve());
|
||||
} else if (expectedKey instanceof RSAPrivateKey) {
|
||||
((ECKey) expectedKey).getParams().getCurve(),
|
||||
((ECKey) keyEntry.getCertificate().getPublicKey()).getParams().getCurve());
|
||||
} else if (expectedKey instanceof RSAKey) {
|
||||
assertEquals("Returned PrivateKey should be what we inserted",
|
||||
((RSAPrivateKey) expectedKey).getModulus(),
|
||||
((RSAPrivateKey) keyEntry.getPrivateKey()).getModulus());
|
||||
((RSAKey) expectedKey).getModulus(),
|
||||
((RSAKey) keyEntry.getPrivateKey()).getModulus());
|
||||
}
|
||||
|
||||
assertEquals("Returned Certificate should be what we inserted", expectedCert,
|
||||
@@ -1263,15 +1256,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
|
||||
Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
|
||||
assertNotNull("Key should exist", key);
|
||||
|
||||
assertTrue("Should be a RSAPrivateKey", key instanceof RSAPrivateKey);
|
||||
|
||||
RSAPrivateKey actualKey = (RSAPrivateKey) key;
|
||||
assertTrue("Should be a PrivateKey", key instanceof PrivateKey);
|
||||
assertTrue("Should be a RSAKey", key instanceof RSAKey);
|
||||
|
||||
KeyFactory keyFact = KeyFactory.getInstance("RSA");
|
||||
PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
|
||||
|
||||
assertEquals("Inserted key should be same as retrieved key",
|
||||
((RSAPrivateKey) expectedKey).getModulus(), actualKey.getModulus());
|
||||
((RSAKey) expectedKey).getModulus(), ((RSAKey) key).getModulus());
|
||||
}
|
||||
|
||||
public void testKeyStore_GetKey_NoPassword_Unencrypted_Success() throws Exception {
|
||||
@@ -1287,15 +1279,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
|
||||
Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
|
||||
assertNotNull("Key should exist", key);
|
||||
|
||||
assertTrue("Should be a RSAPrivateKey", key instanceof RSAPrivateKey);
|
||||
|
||||
RSAPrivateKey actualKey = (RSAPrivateKey) key;
|
||||
assertTrue("Should be a PrivateKey", key instanceof PrivateKey);
|
||||
assertTrue("Should be a RSAKey", key instanceof RSAKey);
|
||||
|
||||
KeyFactory keyFact = KeyFactory.getInstance("RSA");
|
||||
PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
|
||||
|
||||
assertEquals("Inserted key should be same as retrieved key",
|
||||
((RSAPrivateKey) expectedKey).getModulus(), actualKey.getModulus());
|
||||
((RSAKey) expectedKey).getModulus(), ((RSAKey) key).getModulus());
|
||||
}
|
||||
|
||||
public void testKeyStore_GetKey_Certificate_Encrypted_Failure() throws Exception {
|
||||
@@ -1926,31 +1917,11 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
|
||||
Date notAfter) throws Exception {
|
||||
final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
ExportResult exportResult =
|
||||
keyStore.exportKey(privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null);
|
||||
assertEquals(KeyStore.NO_ERROR, exportResult.resultCode);
|
||||
final byte[] pubKeyBytes = exportResult.exportData;
|
||||
|
||||
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);
|
||||
}
|
||||
KeyPair keyPair = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
|
||||
keyStore, privateKeyAlias);
|
||||
|
||||
final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
|
||||
certGen.setPublicKey(pubKey);
|
||||
certGen.setPublicKey(keyPair.getPublic());
|
||||
certGen.setSerialNumber(serialNumber);
|
||||
certGen.setSubjectDN(subjectDN);
|
||||
certGen.setIssuerDN(subjectDN);
|
||||
@@ -1958,7 +1929,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase {
|
||||
certGen.setNotAfter(notAfter);
|
||||
certGen.setSignatureAlgorithm("sha1WithRSA");
|
||||
|
||||
final X509Certificate cert = certGen.generate(privKey);
|
||||
final X509Certificate cert = certGen.generate(keyPair.getPrivate());
|
||||
|
||||
return cert;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user