From d25a0350d1f99dc46ab65687512ead62094eebd7 Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Fri, 6 Oct 2023 08:38:00 -0400 Subject: [PATCH] Rework BouncyCastleSecureArea. First of all, rename this to SoftwareSecureArea since there is no need to leak the fact that we're currently using BouncyCastle which could change in the future. Instead of always using self-signed keys, make it possible to specify the attestation key to use at key creation time. Without this we cannot easily support curves which cannot be used for signing (for example X25519). Modify holder app to create an attestation root on demand. Introduce attestation extension so SoftwareSecureArea keys also can convey the attestation challenge. The OID for this has been reserved (it's 1.3.6.1.4.1.11129.2.1.39) and we can extend this if needed. Add code for dealing with this and use it in SoftwareSecureArea. Show the curve of DeviceKey in the reader app. Make sure we use the BouncyCastle library bundled with the app instead of the Conscrypt-based implementation that may come with the OS. Do this for both wallet and reader app. This has been manually test with mdocs using both ECDSA and MAC authentication with all curves except Ed25519, X25519, Ed448, and X448. These curves still have some serialization problems, we'll revisit this in a future PR. The app still uses the name "Bouncy Castle", we'll address that in a future PR. Also update to the latest available BouncyCastle, version 1.70. Test: Manually tested. Test: All unit tests pass. --- .../com/android/identity/wallet/HolderApp.kt | 6 + .../AuthConfirmationFragment.kt | 4 +- .../identity/wallet/util/ProvisioningUtil.kt | 53 ++- .../com/android/mdl/appreader/VerifierApp.kt | 6 + .../fragment/ShowDocumentFragment.kt | 2 + .../readercertgen/CertificateGenerator.java | 2 - gradle/libs.versions.toml | 2 +- .../securearea/AttestationExtension.kt | 79 ++++ .../identity/securearea/SecureArea.java | 4 +- ...ecureArea.java => SoftwareSecureArea.java} | 374 +++++++++++------- .../credential/CredentialStoreTest.java | 50 +-- .../credential/CredentialUtilTest.java | 8 +- .../response/DeviceResponseGeneratorTest.java | 8 +- ...aTest.java => SoftwareSecureAreaTest.java} | 187 +++++---- 14 files changed, 518 insertions(+), 267 deletions(-) create mode 100644 identity/src/main/java/com/android/identity/securearea/AttestationExtension.kt rename identity/src/main/java/com/android/identity/securearea/{BouncyCastleSecureArea.java => SoftwareSecureArea.java} (69%) rename identity/src/test/java/com/android/identity/securearea/{BouncyCastleSecureAreaTest.java => SoftwareSecureAreaTest.java} (78%) diff --git a/appholder/src/main/java/com/android/identity/wallet/HolderApp.kt b/appholder/src/main/java/com/android/identity/wallet/HolderApp.kt index 44ba212e9..b2ab88269 100644 --- a/appholder/src/main/java/com/android/identity/wallet/HolderApp.kt +++ b/appholder/src/main/java/com/android/identity/wallet/HolderApp.kt @@ -6,12 +6,18 @@ import com.android.identity.util.Logger import com.android.identity.wallet.util.PeriodicKeysRefreshWorkRequest import com.android.identity.wallet.util.PreferencesHelper import com.google.android.material.color.DynamicColors +import org.bouncycastle.jce.provider.BouncyCastleProvider +import java.security.Security class HolderApp: Application() { override fun onCreate() { super.onCreate() Logger.setLogPrinter(AndroidLogPrinter()) + // This is needed to prefer BouncyCastle bundled with the app instead of the Conscrypt + // based implementation included in the OS itself. + Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME) + Security.addProvider(BouncyCastleProvider()) DynamicColors.applyToActivitiesIfAvailable(this) PreferencesHelper.initialize(this) PeriodicKeysRefreshWorkRequest(this).schedulePeriodicKeysRefreshing() diff --git a/appholder/src/main/java/com/android/identity/wallet/authconfirmation/AuthConfirmationFragment.kt b/appholder/src/main/java/com/android/identity/wallet/authconfirmation/AuthConfirmationFragment.kt index 39680f63c..0df5c4433 100644 --- a/appholder/src/main/java/com/android/identity/wallet/authconfirmation/AuthConfirmationFragment.kt +++ b/appholder/src/main/java/com/android/identity/wallet/authconfirmation/AuthConfirmationFragment.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.android.identity.android.securearea.AndroidKeystoreSecureArea -import com.android.identity.securearea.BouncyCastleSecureArea +import com.android.identity.securearea.SoftwareSecureArea import com.android.identity.securearea.SecureArea.ALGORITHM_ES256 import com.android.identity.wallet.R import com.android.identity.wallet.authprompt.UserAuthPromptBuilder @@ -161,7 +161,7 @@ class AuthConfirmationFragment : BottomSheetDialogFragment() { } private fun onPassphraseProvided(passphrase: String) { - val unlockData = BouncyCastleSecureArea.KeyUnlockData(passphrase) + val unlockData = SoftwareSecureArea.KeyUnlockData(passphrase) val result = viewModel.sendResponseForSelection(unlockData) onSendResponseResult(result) } diff --git a/appholder/src/main/java/com/android/identity/wallet/util/ProvisioningUtil.kt b/appholder/src/main/java/com/android/identity/wallet/util/ProvisioningUtil.kt index 12d295457..7b341f4ec 100644 --- a/appholder/src/main/java/com/android/identity/wallet/util/ProvisioningUtil.kt +++ b/appholder/src/main/java/com/android/identity/wallet/util/ProvisioningUtil.kt @@ -12,10 +12,12 @@ import com.android.identity.internal.Util import com.android.identity.mdoc.mso.MobileSecurityObjectGenerator import com.android.identity.mdoc.mso.StaticAuthDataGenerator import com.android.identity.mdoc.util.MdocUtil -import com.android.identity.securearea.BouncyCastleSecureArea import com.android.identity.securearea.SecureArea +import com.android.identity.securearea.SecureArea.KeyLockedException import com.android.identity.securearea.SecureArea.KeyPurpose import com.android.identity.securearea.SecureAreaRepository +import com.android.identity.securearea.SoftwareSecureArea +import com.android.identity.storage.EphemeralStorageEngine import com.android.identity.util.Timestamp import com.android.identity.wallet.document.DocumentInformation import com.android.identity.wallet.document.KeysAndCertificates @@ -25,6 +27,8 @@ import com.android.identity.wallet.selfsigned.ProvisionInfo import com.android.identity.wallet.util.DocumentData.MICOV_DOCTYPE import com.android.identity.wallet.util.DocumentData.MVR_DOCTYPE import java.io.File +import java.security.KeyPair +import java.security.PrivateKey import java.security.cert.X509Certificate import java.time.Instant import java.time.ZoneId @@ -45,16 +49,40 @@ class ProvisioningUtil private constructor( private val androidKeystoreSecureArea: SecureArea get() = AndroidKeystoreSecureArea(context, storageEngine) - private val bouncyCastleSecureArea: SecureArea - get() = BouncyCastleSecureArea(storageEngine) + private val softwareSecureArea: SecureArea + get() = SoftwareSecureArea(storageEngine) val credentialStore by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { val keystoreEngineRepository = SecureAreaRepository() keystoreEngineRepository.addImplementation(androidKeystoreSecureArea) - keystoreEngineRepository.addImplementation(bouncyCastleSecureArea) + keystoreEngineRepository.addImplementation(softwareSecureArea) CredentialStore(storageEngine, keystoreEngineRepository) } + private lateinit var softwareAttestationKey: PrivateKey + private lateinit var softwareAttestationKeySignatureAlgorithm: String + private lateinit var softwareAttestationKeyCertification: List + + private fun initSoftwareAttestationKey() { + val secureArea = SoftwareSecureArea(EphemeralStorageEngine()) + val now = Timestamp.now() + secureArea.createKey( + "SoftwareAttestationRoot", + SoftwareSecureArea.CreateKeySettings.Builder("".toByteArray()) + .setEcCurve(SecureArea.EC_CURVE_P256) + .setKeyPurposes(SecureArea.KEY_PURPOSE_SIGN) + .setSubject("CN=Software Attestation Root") + .setValidityPeriod( + now, + Timestamp.ofEpochMilli(now.toEpochMilli() + 10L * 86400 * 365 * 1000) + ) + .build() + ) + softwareAttestationKey = secureArea.getPrivateKey("SoftwareAttestationRoot", null) + softwareAttestationKeySignatureAlgorithm = "SHA256withECDSA" + softwareAttestationKeyCertification = secureArea.getKeyInfo("SoftwareAttestationRoot").attestation + } + fun provisionSelfSigned( nameSpacedData: NameSpacedData, provisionInfo: ProvisionInfo, @@ -217,8 +245,8 @@ class ProvisioningUtil private constructor( ) } - is BouncyCastleSecureArea -> { - val keyInfo = credential.credentialSecureArea.getKeyInfo(credential.credentialKeyAlias) as BouncyCastleSecureArea.KeyInfo + is SoftwareSecureArea -> { + val keyInfo = credential.credentialSecureArea.getKeyInfo(credential.credentialKeyAlias) as SoftwareSecureArea.KeyInfo createBouncyCastleKeystoreSettings( mDocAuthOption = AddSelfSignedScreenState.MdocAuthStateOption.valueOf(mDocAuthOption), ecCurve = keyInfo.ecCurve @@ -266,13 +294,18 @@ class ProvisioningUtil private constructor( passphrase: String? = null, mDocAuthOption: AddSelfSignedScreenState.MdocAuthStateOption, ecCurve: Int - ): BouncyCastleSecureArea.CreateKeySettings { + ): SoftwareSecureArea.CreateKeySettings { + if (!this::softwareAttestationKey.isInitialized) { + initSoftwareAttestationKey() + } val keyPurpose = mDocAuthOption.toKeyPurpose() - val builder = BouncyCastleSecureArea.CreateKeySettings.Builder() + val builder = SoftwareSecureArea.CreateKeySettings.Builder("DoNotCare".toByteArray()) + .setAttestationKey(softwareAttestationKey, + softwareAttestationKeySignatureAlgorithm, softwareAttestationKeyCertification) .setPassphraseRequired(passphrase != null, passphrase) .setKeyPurposes(keyPurpose) .setEcCurve(ecCurve) - .setKeyPurposes(SecureArea.KEY_PURPOSE_SIGN or SecureArea.KEY_PURPOSE_AGREE_KEY) + .setKeyPurposes(mDocAuthOption.toKeyPurpose()) return builder.build() } @@ -351,7 +384,7 @@ class ProvisioningUtil private constructor( private fun SecureArea.toSecureAreaState(): SecureAreaImplementationState { return when (this) { is AndroidKeystoreSecureArea -> SecureAreaImplementationState.Android - is BouncyCastleSecureArea -> SecureAreaImplementationState.BouncyCastle + is SoftwareSecureArea -> SecureAreaImplementationState.BouncyCastle else -> throw IllegalStateException("Unknown Secure Area Implementation") } } diff --git a/appverifier/src/main/java/com/android/mdl/appreader/VerifierApp.kt b/appverifier/src/main/java/com/android/mdl/appreader/VerifierApp.kt index cffc77b47..ea13ae640 100644 --- a/appverifier/src/main/java/com/android/mdl/appreader/VerifierApp.kt +++ b/appverifier/src/main/java/com/android/mdl/appreader/VerifierApp.kt @@ -6,6 +6,8 @@ import com.android.identity.util.Logger import androidx.preference.PreferenceManager import com.android.mdl.appreader.settings.UserPreferences import com.google.android.material.color.DynamicColors +import org.bouncycastle.jce.provider.BouncyCastleProvider +import java.security.Security class VerifierApp : Application() { @@ -17,6 +19,10 @@ class VerifierApp : Application() { override fun onCreate() { super.onCreate() Logger.setLogPrinter(AndroidLogPrinter()) + // This is needed to prefer BouncyCastle bundled with the app instead of the Conscrypt + // based implementation included in the OS itself. + Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME) + Security.addProvider(BouncyCastleProvider()) DynamicColors.applyToActivitiesIfAvailable(this) userPreferencesInstance = userPreferences Logger.setDebugEnabled(userPreferences.isDebugLoggingEnabled()) diff --git a/appverifier/src/main/java/com/android/mdl/appreader/fragment/ShowDocumentFragment.kt b/appverifier/src/main/java/com/android/mdl/appreader/fragment/ShowDocumentFragment.kt index d8b3ecc41..c1717de00 100644 --- a/appverifier/src/main/java/com/android/mdl/appreader/fragment/ShowDocumentFragment.kt +++ b/appverifier/src/main/java/com/android/mdl/appreader/fragment/ShowDocumentFragment.kt @@ -16,6 +16,7 @@ import androidx.activity.OnBackPressedCallback import androidx.annotation.AttrRes import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController +import com.android.identity.internal.Util import com.android.identity.mdoc.response.DeviceResponseParser import com.android.identity.securearea.SecureArea import com.android.identity.securearea.SecureArea.EcCurve @@ -262,6 +263,7 @@ class ShowDocumentFragment : Fragment() { // Just show the SHA-1 of DeviceKey since all we're interested in here is whether // we saw the same key earlier. sb.append("
DeviceKey
") + sb.append("${getFormattedCheck(true)}Curve: ${curveNameFor(Util.getCurve(doc.deviceKey))}
") val deviceKeySha1 = FormatUtil.encodeToString( MessageDigest.getInstance("SHA-1").digest(doc.deviceKey.encoded) ) diff --git a/appverifier/src/main/java/com/android/mdl/appreader/readercertgen/CertificateGenerator.java b/appverifier/src/main/java/com/android/mdl/appreader/readercertgen/CertificateGenerator.java index de36a1902..57a79613c 100644 --- a/appverifier/src/main/java/com/android/mdl/appreader/readercertgen/CertificateGenerator.java +++ b/appverifier/src/main/java/com/android/mdl/appreader/readercertgen/CertificateGenerator.java @@ -38,8 +38,6 @@ private CertificateGenerator() { static X509Certificate generateCertificate(DataMaterial data, CertificateMaterial certMaterial, KeyMaterial keyMaterial) throws CertIOException, CertificateException, OperatorCreationException { - Provider bcProvider = new BouncyCastleProvider(); - Security.addProvider(bcProvider); Optional issuerCert = keyMaterial.issuerCertificate(); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c0ffaa946..5db784754 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,7 @@ biometrics = "1.2.0-alpha05" cbor = "0.9" exif = "1.3.6" - bouncy-castle = "1.67" + bouncy-castle = "1.70" sonar-gradle-plugin = "3.4.0.2513" jacoco = "0.47.0" navigation = "2.6.0" diff --git a/identity/src/main/java/com/android/identity/securearea/AttestationExtension.kt b/identity/src/main/java/com/android/identity/securearea/AttestationExtension.kt new file mode 100644 index 000000000..8df4d948b --- /dev/null +++ b/identity/src/main/java/com/android/identity/securearea/AttestationExtension.kt @@ -0,0 +1,79 @@ +package com.android.identity.securearea + +import co.nstant.`in`.cbor.CborBuilder +import co.nstant.`in`.cbor.CborDecoder +import co.nstant.`in`.cbor.CborEncoder +import co.nstant.`in`.cbor.CborException +import co.nstant.`in`.cbor.model.ByteString +import co.nstant.`in`.cbor.model.DataItem +import co.nstant.`in`.cbor.model.Map +import co.nstant.`in`.cbor.model.UnicodeString +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream + +/** + * X.509 Extension which may be used by [SecureArea] implementations. + * + * The main purpose of the extension is to define a place to include the + * challenge passed at key creation time, for freshness. + * + * If used, the extension must be put in X.509 certificate for the created + * key (that is, included in the first certificate in the attestation for the key) + * at the OID defined by [ATTESTATION_OID] and the payload should be an + * OCTET STRING containing the bytes of the CBOR conforming to the following CDDL: + * + * ``` + * Attestation = { + * "challenge" : bstr, ; contains the challenge + * } + * ``` + * + * This map may be extended in the future with additional fields. + */ +object AttestationExtension { + /** + * The OID for the attestation extension. + */ + const val ATTESTATION_OID = "1.3.6.1.4.1.11129.2.1.39" + + /** + * Generates the payload of the attestation extension. + * + * @param challenge the challenge to include + * @return the bytes of the CBOR for the extension. + */ + @JvmStatic + fun encode(challenge: ByteArray): ByteArray { + val baos = ByteArrayOutputStream() + try { + CborEncoder(baos).nonCanonical().encode( + CborBuilder() + .addMap() + .put("challenge", challenge) + .end() + .build().get(0) + ) + } catch (e: CborException) { + throw IllegalStateException("Unexpected failure encoding data", e) + } + return baos.toByteArray() + } + + /** + * Extracts the challenge from the attestation extension. + * + * @param attestationExtension the bytes of the CBOR for the extension. + * @return the challenge value. + */ + @JvmStatic + fun decode(attestationExtension: ByteArray): ByteArray { + val dataItems: List = try { + val bais = ByteArrayInputStream(attestationExtension) + CborDecoder(bais).decode() + } catch (e: CborException) { + throw IllegalArgumentException("Error decoding CBOR", e) + } + return ((dataItems[0] as Map).get(UnicodeString("challenge")) as ByteString).bytes + } + +} \ No newline at end of file diff --git a/identity/src/main/java/com/android/identity/securearea/SecureArea.java b/identity/src/main/java/com/android/identity/securearea/SecureArea.java index d255c3b56..306c164fc 100644 --- a/identity/src/main/java/com/android/identity/securearea/SecureArea.java +++ b/identity/src/main/java/com/android/identity/securearea/SecureArea.java @@ -227,7 +227,7 @@ public interface SecureArea { /** * Class with information about a key. * - *

Concrete {@link SecureArea} implementations may subclass this to provide + *

Concrete {@link SecureArea} implementations may subclass this to provide additional * implementation-specific information about the key. */ class KeyInfo { @@ -247,7 +247,7 @@ protected KeyInfo(@NonNull List attestation, } /** - * Gets the attestation for a key. + * Gets the attestation for the key. * * @return A list of certificates representing a certificate chain. */ diff --git a/identity/src/main/java/com/android/identity/securearea/BouncyCastleSecureArea.java b/identity/src/main/java/com/android/identity/securearea/SoftwareSecureArea.java similarity index 69% rename from identity/src/main/java/com/android/identity/securearea/BouncyCastleSecureArea.java rename to identity/src/main/java/com/android/identity/securearea/SoftwareSecureArea.java index f3b24a315..4fe67da79 100644 --- a/identity/src/main/java/com/android/identity/securearea/BouncyCastleSecureArea.java +++ b/identity/src/main/java/com/android/identity/securearea/SoftwareSecureArea.java @@ -24,7 +24,9 @@ import com.android.identity.internal.Util; import com.android.identity.storage.StorageEngine; +import com.android.identity.util.Timestamp; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -79,8 +81,7 @@ import co.nstant.in.cbor.model.UnicodeString; /** - * An implementation of {@link SecureArea} using the - * Bouncy Castle library. + * An implementation of {@link SecureArea} in software. * *

This implementation supports all the curves and algorithms defined by {@link SecureArea} * and also supports passphrase-protected keys. Key material is stored using the @@ -88,20 +89,24 @@ * AES-GCM * with 256-bit keys with the key derived from the passphrase using * HKDF. + * + *

This is currently implemented using the + * Bouncy Castle library but this implementation + * detail may change in the future. */ -public class BouncyCastleSecureArea implements SecureArea { - private static final String TAG = "BouncyCastleSA"; // limit to <= 23 chars +public class SoftwareSecureArea implements SecureArea { + private static final String TAG = "SoftwareSecureArea"; private final StorageEngine mStorageEngine; // Prefix for storage items. - private static final String PREFIX = "IC_BouncyCastleSA_"; + private static final String PREFIX = "IC_SoftwareSecureArea_key_"; /** - * Creates a new Bouncy Castle secure area. + * Creates a new software-backed secure area. * * @param storageEngine the storage engine to use for storing key material. */ - public BouncyCastleSecureArea(@NonNull StorageEngine storageEngine) { + public SoftwareSecureArea(@NonNull StorageEngine storageEngine) { mStorageEngine = storageEngine; } @@ -109,56 +114,55 @@ public BouncyCastleSecureArea(@NonNull StorageEngine storageEngine) { public void createKey(@NonNull String alias, @NonNull SecureArea.CreateKeySettings createKeySettings) { CreateKeySettings settings = (CreateKeySettings) createKeySettings; - ArrayList certificateChain = new ArrayList<>(); KeyPairGenerator kpg; - String certSigningSignatureAlgorithm; try { kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider()); + String selfSigningSignatureAlgorithm; switch (settings.getEcCurve()) { case EC_CURVE_P256: kpg.initialize(new ECGenParameterSpec("secp256r1")); - certSigningSignatureAlgorithm = "SHA256withECDSA"; + selfSigningSignatureAlgorithm = "SHA256withECDSA"; break; case EC_CURVE_P384: kpg.initialize(new ECGenParameterSpec("secp384r1")); - certSigningSignatureAlgorithm = "SHA384withECDSA"; + selfSigningSignatureAlgorithm = "SHA384withECDSA"; break; case EC_CURVE_P521: kpg.initialize(new ECGenParameterSpec("secp521r1")); - certSigningSignatureAlgorithm = "SHA512withECDSA"; + selfSigningSignatureAlgorithm = "SHA512withECDSA"; break; case EC_CURVE_BRAINPOOLP256R1: kpg.initialize(new ECGenParameterSpec("brainpoolP256r1")); - certSigningSignatureAlgorithm = "SHA256withECDSA"; + selfSigningSignatureAlgorithm = "SHA256withECDSA"; break; case EC_CURVE_BRAINPOOLP320R1: kpg.initialize(new ECGenParameterSpec("brainpoolP320r1")); - certSigningSignatureAlgorithm = "SHA256withECDSA"; + selfSigningSignatureAlgorithm = "SHA256withECDSA"; break; case EC_CURVE_BRAINPOOLP384R1: kpg.initialize(new ECGenParameterSpec("brainpoolP384r1")); - certSigningSignatureAlgorithm = "SHA384withECDSA"; + selfSigningSignatureAlgorithm = "SHA384withECDSA"; break; case EC_CURVE_BRAINPOOLP512R1: kpg.initialize(new ECGenParameterSpec("brainpoolP512r1")); - certSigningSignatureAlgorithm = "SHA512withECDSA"; + selfSigningSignatureAlgorithm = "SHA512withECDSA"; break; case EC_CURVE_ED25519: kpg = KeyPairGenerator.getInstance("Ed25519", new BouncyCastleProvider()); - certSigningSignatureAlgorithm = "Ed25519"; + selfSigningSignatureAlgorithm = "Ed25519"; break; case EC_CURVE_ED448: kpg = KeyPairGenerator.getInstance("Ed448", new BouncyCastleProvider()); - certSigningSignatureAlgorithm = "Ed448"; + selfSigningSignatureAlgorithm = "Ed448"; break; case EC_CURVE_X25519: kpg = KeyPairGenerator.getInstance("x25519", new BouncyCastleProvider()); - certSigningSignatureAlgorithm = "Ed25519"; + selfSigningSignatureAlgorithm = null; // Not possible to self-sign break; case EC_CURVE_X448: kpg = KeyPairGenerator.getInstance("x448", new BouncyCastleProvider()); - certSigningSignatureAlgorithm = "Ed448"; + selfSigningSignatureAlgorithm = null; // Not possible to self-sign break; default: throw new IllegalArgumentException( @@ -170,10 +174,6 @@ public void createKey(@NonNull String alias, MapBuilder map = builder.addMap(); map.put("curve", settings.getEcCurve()); map.put("keyPurposes", settings.getKeyPurposes()); - String attestationKeyAlias = settings.getAttestationKeyAlias(); - if (attestationKeyAlias != null) { - map.put("attestationKeyAlias", attestationKeyAlias); - } map.put("passphraseRequired", settings.getPassphraseRequired()); if (!settings.getPassphraseRequired()) { map.put("privateKey", keyPair.getPrivate().getEncoded()); @@ -198,35 +198,59 @@ public void createKey(@NonNull String alias, } } - X500Name issuer = new X500Name("CN=Android Identity Credential BC KS Impl"); - X500Name subject = new X500Name("CN=Android Identity Credential BC KS Impl"); - Date now = new Date(); - Date expirationDate = new Date(now.getTime() + MILLISECONDS.convert(365, DAYS)); + X500Name subject = new X500Name(settings.getSubject() != null ? settings.getSubject() : + "CN=SoftwareSecureArea Key"); + X500Name issuer; + PrivateKey certSigningKey; + String signatureAlgorithm; + + // If an attestation key isn't available, self-sign the certificate (if possible) + if (settings.getAttestationKey() != null) { + issuer = new X500Name(settings.getAttestationKeyCertification().get(0).getSubjectX500Principal().getName()); + certSigningKey = settings.getAttestationKey(); + signatureAlgorithm = settings.getAttestationKeySignatureAlgorithm(); + } else { + issuer = subject; + certSigningKey = keyPair.getPrivate(); + if (selfSigningSignatureAlgorithm == null) { + throw new IllegalStateException( + "Self-signing not possible with this curve, use an attestation key"); + } + signatureAlgorithm = selfSigningSignatureAlgorithm; + } + + Date validFrom = new Date(); + if (settings.getValidFrom() != null) { + validFrom = new Date(settings.getValidFrom().toEpochMilli()); + } + Date validUntil = new Date(new Date().getTime() + MILLISECONDS.convert(365, DAYS)); + if (settings.getValidUntil() != null) { + validUntil = new Date(settings.getValidUntil().toEpochMilli()); + } BigInteger serial = BigInteger.ONE; JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(issuer, serial, - now, - expirationDate, + validFrom, + validUntil, subject, keyPair.getPublic()); + certBuilder.addExtension( + new ASN1ObjectIdentifier(AttestationExtension.ATTESTATION_OID), + false, + AttestationExtension.encode(settings.getAttestationChallenge())); ContentSigner signer; - if (attestationKeyAlias == null) { - if ((settings.getKeyPurposes() & KEY_PURPOSE_SIGN) == 0) { - throw new IllegalArgumentException( - "Cannot self-sign certificate for a key without signing purpose. " - + "Use an attestation key."); - } - signer = new JcaContentSignerBuilder(certSigningSignatureAlgorithm) - .build(keyPair.getPrivate()); - } else { - signer = getSignerForAlias(attestationKeyAlias); - } + signer = new JcaContentSignerBuilder(signatureAlgorithm) + .build(certSigningKey); byte[] encodedCert = certBuilder.build(signer).getEncoded(); CertificateFactory cf = CertificateFactory.getInstance("X.509"); ByteArrayInputStream bais = new ByteArrayInputStream(encodedCert); + ArrayList certificateChain = new ArrayList<>(); certificateChain.add((X509Certificate) cf.generateCertificate(bais)); + if (settings.getAttestationKeyCertification() != null) { + certificateChain.addAll(settings.getAttestationKeyCertification()); + } ArrayBuilder> attestationBuilder = map.putArray("attestation"); for (X509Certificate certificate : certificateChain) { @@ -248,70 +272,8 @@ public void createKey(@NonNull String alias, } } - private @NonNull ContentSigner getSignerForAlias(@NonNull String attestationKeyAlias) { - KeyData attestationKeyData; - try { - attestationKeyData = loadKey(attestationKeyAlias, null); - } catch (KeyLockedException e) { - // TODO: we might revisit this and make it possible to pass KeyUnlockData - // for the attestation key through CreateKeySettings and make ecKeyCreate - // throw. - throw new IllegalArgumentException("Attestation key cannot be locked"); - } - - if ((attestationKeyData.keyPurposes & KEY_PURPOSE_SIGN) == 0) { - throw new IllegalArgumentException( - "Cannot sign certificate using a key without signing purpose."); - } - - // Use the "natural" signature algorithm for the curve... - String signatureAlgorithmName; - switch (attestationKeyData.curve) { - case SecureArea.EC_CURVE_P256: - signatureAlgorithmName = "SHA256withECDSA"; - break; - case SecureArea.EC_CURVE_P384: - signatureAlgorithmName = "SHA384withECDSA"; - break; - case SecureArea.EC_CURVE_P521: - signatureAlgorithmName = "SHA512withECDSA"; - break; - case SecureArea.EC_CURVE_BRAINPOOLP256R1: - signatureAlgorithmName = "SHA256withECDSA"; - break; - case SecureArea.EC_CURVE_BRAINPOOLP320R1: - signatureAlgorithmName = "SHA256withECDSA"; - break; - case SecureArea.EC_CURVE_BRAINPOOLP384R1: - signatureAlgorithmName = "SHA384withECDSA"; - break; - case SecureArea.EC_CURVE_BRAINPOOLP512R1: - signatureAlgorithmName = "SHA512withECDSA"; - break; - case SecureArea.EC_CURVE_ED25519: - signatureAlgorithmName = "EdDSA"; - break; - case SecureArea.EC_CURVE_ED448: - signatureAlgorithmName = "EdDSA"; - break; - - default: - case SecureArea.EC_CURVE_X25519: - case SecureArea.EC_CURVE_X448: - throw new IllegalStateException("Invalid curve " + attestationKeyData.curve - + "for attestation signing"); - } - try { - return new JcaContentSignerBuilder(signatureAlgorithmName) - .build(attestationKeyData.privateKey); - } catch (OperatorCreationException e) { - throw new IllegalStateException("Unexpected exception", e); - } - } - private SecretKey derivePrivateKeyEncryptionKey(@NonNull byte[] encodedPublicKey, @NonNull String passphrase) { - // TODO: replace with Argon2 byte[] info = "ICPrivateKeyEncryption1".getBytes(StandardCharsets.UTF_8); byte[] derivedKey = Util.computeHkdf("HmacSha256", passphrase.getBytes(StandardCharsets.UTF_8), @@ -332,18 +294,19 @@ private static class KeyData { PrivateKey privateKey; } - private @NonNull KeyData loadKey(@NonNull String alias, + private @NonNull KeyData loadKey(@NonNull String prefix, + @NonNull String alias, @Nullable SecureArea.KeyUnlockData keyUnlockData) throws KeyLockedException { KeyData ret = new KeyData(); String passphrase = null; if (keyUnlockData != null) { - BouncyCastleSecureArea.KeyUnlockData unlockData = (BouncyCastleSecureArea.KeyUnlockData) keyUnlockData; + SoftwareSecureArea.KeyUnlockData unlockData = (SoftwareSecureArea.KeyUnlockData) keyUnlockData; passphrase = unlockData.mPassphrase; } - byte[] data = mStorageEngine.get(PREFIX + alias); + byte[] data = mStorageEngine.get(prefix + alias); if (data == null) { throw new IllegalArgumentException("No key with given alias"); } @@ -409,6 +372,22 @@ private static class KeyData { return ret; } + /** + * Gets the underlying private key. + * + * @param alias the alias for the key. + * @param keyUnlockData unlock data, or {@code null}. + * @return a {@link PrivateKey}. + * @throws SecureArea.KeyLockedException + */ + @NonNull + public PrivateKey getPrivateKey(@NonNull String alias, + @Nullable SecureArea.KeyUnlockData keyUnlockData) + throws SecureArea.KeyLockedException { + KeyData keyData = loadKey(PREFIX, alias, keyUnlockData); + return keyData.privateKey; + } + @NonNull @Override public byte[] sign(@NonNull String alias, @@ -416,7 +395,7 @@ public byte[] sign(@NonNull String alias, @NonNull byte[] dataToSign, @Nullable SecureArea.KeyUnlockData keyUnlockData) throws SecureArea.KeyLockedException { - KeyData keyData = loadKey(alias, keyUnlockData); + KeyData keyData = loadKey(PREFIX, alias, keyUnlockData); if ((keyData.keyPurposes & KEY_PURPOSE_SIGN) == 0) { throw new IllegalArgumentException("Key does not have purpose KEY_PURPOSE_SIGN"); } @@ -448,7 +427,7 @@ public byte[] sign(@NonNull String alias, } try { - Signature s = Signature.getInstance(signatureAlgorithmName); + Signature s = Signature.getInstance(signatureAlgorithmName, new BouncyCastleProvider()); s.initSign(keyData.privateKey); s.update(dataToSign); return s.sign(); @@ -464,7 +443,7 @@ public byte[] sign(@NonNull String alias, @NonNull PublicKey otherKey, @Nullable SecureArea.KeyUnlockData keyUnlockData) throws KeyLockedException { - KeyData keyData = loadKey(alias, keyUnlockData); + KeyData keyData = loadKey(PREFIX, alias, keyUnlockData); if ((keyData.keyPurposes & KEY_PURPOSE_AGREE_KEY) == 0) { throw new IllegalArgumentException("Key does not have purpose KEY_PURPOSE_AGREE_KEY"); } @@ -481,19 +460,17 @@ public byte[] sign(@NonNull String alias, } /** - * Bouncy Castle specific class for information about an EC key. + * Specialization of {@link SecureArea.KeyInfo} specific to software-backed keys. */ public static class KeyInfo extends SecureArea.KeyInfo { private final boolean mIsPassphraseProtected; - private final @Nullable String mAttestationKeyAlias; KeyInfo(@NonNull List attestation, @KeyPurpose int keyPurposes, @EcCurve int ecCurve, boolean isHardwareBacked, - boolean isPassphraseProtected, @Nullable String attestationKeyAlias) { + boolean isPassphraseProtected) { super(attestation, keyPurposes, ecCurve, isHardwareBacked); mIsPassphraseProtected = isPassphraseProtected; - mAttestationKeyAlias = attestationKeyAlias; } /** @@ -504,15 +481,6 @@ public static class KeyInfo extends SecureArea.KeyInfo { public boolean isPassphraseProtected() { return mIsPassphraseProtected; } - - /** - * Gets the attestation key alias, if any. - * - * @return the attestation key alias or {@code null} if not using an attestation key. - */ - public @Nullable String getAttestationKeyAlias() { - return mAttestationKeyAlias; - } } @Override @@ -539,10 +507,6 @@ public boolean isPassphraseProtected() { @EcCurve int ecCurve = (int) Util.cborMapExtractNumber(map, "curve"); @KeyPurpose int keyPurposes = (int) Util.cborMapExtractNumber(map, "keyPurposes"); boolean passphraseRequired = Util.cborMapExtractBoolean(map, "passphraseRequired"); - String attestationKeyAlias = null; - if (Util.cborMapHasKey(map, "attestationKeyAlias")) { - attestationKeyAlias = Util.cborMapExtractString(map, "attestationKeyAlias"); - } DataItem attestationDataItem = map.get(new UnicodeString("attestation")); if (!(attestationDataItem instanceof Array)) { @@ -561,7 +525,7 @@ public boolean isPassphraseProtected() { } return new KeyInfo(attestation, keyPurposes, ecCurve, false, - passphraseRequired, attestationKeyAlias); + passphraseRequired); } /** @@ -586,23 +550,41 @@ public KeyUnlockData(@NonNull String passphrase) { * Class used to indicate key creation settings. */ public static class CreateKeySettings extends SecureArea.CreateKeySettings { - private final String mAttestationKeyAlias; - private @KeyPurpose int mKeyPurposes; + private final @KeyPurpose int mKeyPurposes; private final @EcCurve int mEcCurve; private final boolean mPassphraseRequired; private final String mPassphrase; + private final String mSubject; + private final Timestamp mValidFrom; + private final Timestamp mValidUntil; + private final byte[] mAttestationChallenge; + private PrivateKey mAttestationKey; + private String mAttestationKeySignatureAlgorithm; + private List mAttestationKeyCertification; private CreateKeySettings(boolean passphraseRequired, @NonNull String passphrase, @EcCurve int ecCurve, @KeyPurpose int keyPurposes, - @Nullable String attestationKeyAlias) { - super(BouncyCastleSecureArea.class); + @NonNull byte[] attestationChallenge, + @Nullable String subject, + @Nullable Timestamp validFrom, + @Nullable Timestamp validUntil, + @Nullable PrivateKey attestationKey, + @Nullable String attestationKeySignatureAlgorithm, + @Nullable List attestationKeyCertification) { + super(SoftwareSecureArea.class); mPassphraseRequired = passphraseRequired; mPassphrase = passphrase; mEcCurve = ecCurve; mKeyPurposes = keyPurposes; - mAttestationKeyAlias = attestationKeyAlias; + mAttestationChallenge = attestationChallenge; + mSubject = subject; + mValidFrom = validFrom; + mValidUntil = validUntil; + mAttestationKey = attestationKey; + mAttestationKeySignatureAlgorithm = attestationKeySignatureAlgorithm; + mAttestationKeyCertification = attestationKeyCertification; } /** @@ -642,14 +624,67 @@ public boolean getPassphraseRequired() { } /** - * Gets the attestation key alias, if any. + * Gets the attestation challenge. + * + * @return the attestation challenge. + */ + public @NonNull byte[] getAttestationChallenge() { + return mAttestationChallenge; + } + + /** + * Gets the subject for the key. + * + * @return The subject for the key or {@code null} if not set. + */ + public @Nullable String getSubject() { + return mSubject; + } + + /** + * Gets the point in time before which the key is not valid. + * + * @return the point in time before which the key is not valid or {@code null} if not set. + */ + public @Nullable Timestamp getValidFrom() { + return mValidFrom; + } + + /** + * Gets the point in time after which the key is not valid. + * + * @return the point in time after which the key is not valid or {@code null} if not set. + */ + public @Nullable Timestamp getValidUntil() { + return mValidUntil; + } + + /** + * Gets the attestation key to use to attest to the key, if any. + * + * @return the attestation key or {@code null} if not set. + */ + public @Nullable PrivateKey getAttestationKey() { + return mAttestationKey; + } + + /** + * Gets the signature algorithmto use to attest to the key, if any. * - * @return The attestation key alias or {@code null}. + * @return the signature algorithm or {@code null} if not set. */ - public @Nullable String getAttestationKeyAlias() { - return mAttestationKeyAlias; + public @Nullable String getAttestationKeySignatureAlgorithm() { + return mAttestationKeySignatureAlgorithm; } + /** + * Gets the certification for the attestation key, if any. + * + * @return the certification for the attestation key or {@code null} if not set. + */ + public @Nullable List getAttestationKeyCertification() { + return mAttestationKeyCertification; + } /** * A builder for {@link CreateKeySettings}. @@ -659,7 +694,42 @@ public static class Builder { private @EcCurve int mEcCurve = EC_CURVE_P256; private boolean mPassphraseRequired; private String mPassphrase = ""; - private String mAttestationKeyAlias; + private String mSubject; + private Timestamp mValidFrom; + private Timestamp mValidUntil; + private final byte[] mAttestationChallenge; + private PrivateKey mAttestationKey; + private String mAttestationKeySignatureAlgorithm; + private List mAttestationKeyCertification; + + /** + * Constructor. + * + * @param attestationChallenge challenge to include in attestation for the key. + */ + public Builder(@NonNull byte[] attestationChallenge) { + mAttestationChallenge = attestationChallenge; + } + + /** + * Sets the attestation key to use for attesting to the key. + * + *

If not set, the attestation will be a single self-signed certificate. + * + * @param attestationKey the attestation key. + * @param attestationKeySignatureAlgorithm the signature algorithm to use. + * @param attestationKeyCertification the certification for the attestation key. + * @return the builder. + */ + public @NonNull + Builder setAttestationKey(@NonNull PrivateKey attestationKey, + @NonNull String attestationKeySignatureAlgorithm, + @NonNull List attestationKeyCertification) { + mAttestationKey = attestationKey; + mAttestationKeySignatureAlgorithm = attestationKeySignatureAlgorithm; + mAttestationKeyCertification = attestationKeyCertification; + return this; + } /** * Sets the key purpose. @@ -709,18 +779,29 @@ public static class Builder { } /** - * Sets the alias of a key to use sign the returned X509 certificate. + * Sets the subject of the key, to be included in the attestation. * - *

If this is not set, the key itself will be used to sign the X509 certificate - * which will only work if the curve used can be used for signing. + * @param subject subject field + * @return the builder. + */ + public @NonNull Builder setSubject(@Nullable String subject) { + mSubject = subject; + return this; + } + + /** + * Sets the key validity period. * - *

By default this isn't set. + *

By default the key validity period is unbounded. * - * @param attestationKeyAlias the attest key alias or {@code null}. + * @param validFrom the point in time before which the key is not valid. + * @param validUntil the point in time after which the key is not valid. * @return the builder. */ - public @NonNull Builder setAttestationKeyAlias(@Nullable String attestationKeyAlias) { - mAttestationKeyAlias = attestationKeyAlias; + public @NonNull Builder setValidityPeriod(@NonNull Timestamp validFrom, + @NonNull Timestamp validUntil) { + mValidFrom = validFrom; + mValidUntil = validUntil; return this; } @@ -731,7 +812,10 @@ public static class Builder { */ public @NonNull CreateKeySettings build() { return new CreateKeySettings(mPassphraseRequired, mPassphrase, mEcCurve, - mKeyPurposes, mAttestationKeyAlias); + mKeyPurposes, mAttestationChallenge, + mSubject, mValidFrom, mValidUntil, + mAttestationKey, mAttestationKeySignatureAlgorithm, + mAttestationKeyCertification); } } } diff --git a/identity/src/test/java/com/android/identity/credential/CredentialStoreTest.java b/identity/src/test/java/com/android/identity/credential/CredentialStoreTest.java index 6ec57a7db..c1e26eceb 100644 --- a/identity/src/test/java/com/android/identity/credential/CredentialStoreTest.java +++ b/identity/src/test/java/com/android/identity/credential/CredentialStoreTest.java @@ -17,7 +17,7 @@ package com.android.identity.credential; import com.android.identity.internal.Util; -import com.android.identity.securearea.BouncyCastleSecureArea; +import com.android.identity.securearea.SoftwareSecureArea; import com.android.identity.securearea.SecureArea; import com.android.identity.securearea.SecureAreaRepository; import com.android.identity.storage.EphemeralStorageEngine; @@ -46,8 +46,10 @@ public void setup() { mStorageEngine = new EphemeralStorageEngine(); mSecureAreaRepository = new SecureAreaRepository(); - mSecureArea = new BouncyCastleSecureArea(mStorageEngine); + mSecureArea = new SoftwareSecureArea(mStorageEngine); mSecureAreaRepository.addImplementation(mSecureArea); + + } @Test @@ -61,7 +63,7 @@ public void testListCredentials() { for (int n = 0; n < 10; n++) { credentialStore.createCredential( "testCred" + n, - new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build()); } Assert.assertEquals(10, credentialStore.listCredentials().size()); credentialStore.deleteCredential("testCred1"); @@ -84,7 +86,7 @@ public void testCreationDeletion() { Credential credential = credentialStore.createCredential( "testCredential", - new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build()); Assert.assertEquals("testCredential", credential.getName()); List certChain = credential.getAttestation(); Assert.assertTrue(certChain.size() >= 1); @@ -103,7 +105,7 @@ public void testCreationDeletion() { // Check creating a credential with an existing name overwrites the existing one credential = credentialStore.createCredential( "testCredential", - new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build()); Assert.assertEquals("testCredential", credential.getName()); // At least the leaf certificate should be different List certChain3 = credential.getAttestation(); @@ -125,7 +127,7 @@ public void testNameSpacedData() { Credential credential = credentialStore.createCredential( "testCredential", - new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build()); // After creation, NameSpacedData is present but empty. Assert.assertEquals(0, credential.getNameSpacedData().getNameSpaceNames().size()); @@ -158,7 +160,7 @@ public void testAuthenticationKeyUsage() { Credential credential = credentialStore.createCredential( "testCredential", - new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build()); Timestamp timeBeforeValidity = Timestamp.ofEpochMilli(40); Timestamp timeValidityBegin = Timestamp.ofEpochMilli(50); @@ -176,7 +178,7 @@ public void testAuthenticationKeyUsage() { // Create ten authentication keys... for (int n = 0; n < 10; n++) { credential.createPendingAuthenticationKey( - new BouncyCastleSecureArea.CreateKeySettings.Builder().build(), + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build(), null); } Assert.assertEquals(0, credential.getAuthenticationKeys().size()); @@ -247,7 +249,7 @@ public void testAuthenticationKeyUsage() { // Create and certify five replacements for (n = 0; n < 5; n++) { credential.createPendingAuthenticationKey( - new BouncyCastleSecureArea.CreateKeySettings.Builder().build(), + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build(), null); } Assert.assertEquals(10, credential.getAuthenticationKeys().size()); @@ -302,7 +304,7 @@ public void testAuthenticationKeyPersistence() { Credential credential = credentialStore.createCredential( "testCredential", - new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build()); Assert.assertEquals(0, credential.getAuthenticationKeys().size()); Assert.assertEquals(0, credential.getPendingAuthenticationKeys().size()); @@ -310,7 +312,7 @@ public void testAuthenticationKeyPersistence() { // Create ten pending auth keys and certify four of them for (n = 0; n < 4; n++) { credential.createPendingAuthenticationKey( - new BouncyCastleSecureArea.CreateKeySettings.Builder().build(), + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build(), null); } Assert.assertEquals(0, credential.getAuthenticationKeys().size()); @@ -334,7 +336,7 @@ public void testAuthenticationKeyPersistence() { Assert.assertEquals(0, credential.getPendingAuthenticationKeys().size()); for (n = 0; n < 6; n++) { credential.createPendingAuthenticationKey( - new BouncyCastleSecureArea.CreateKeySettings.Builder().build(), + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build(), null); } Assert.assertEquals(4, credential.getAuthenticationKeys().size()); @@ -379,7 +381,7 @@ public void testAuthenticationKeyValidity() { Credential credential = credentialStore.createCredential( "testCredential", - new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build()); // We want to check the behavior for when the holder has a birthday and the issuer // carefully sends half the MSOs to be used before the birthday (with age_in_years set to @@ -400,7 +402,7 @@ public void testAuthenticationKeyValidity() { int n; for (n = 0; n < 10; n++) { credential.createPendingAuthenticationKey( - new BouncyCastleSecureArea.CreateKeySettings.Builder().build(), + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build(), null); } Assert.assertEquals(10, credential.getPendingAuthenticationKeys().size()); @@ -456,7 +458,7 @@ public void testApplicationData() { Credential credential = credentialStore.createCredential( "testCredential", - new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build()); // After creation, NameSpacedData is present but empty. Assert.assertEquals(0, credential.getNameSpacedData().getNameSpaceNames().size()); @@ -506,15 +508,15 @@ public void testAuthKeyApplicationData() { Credential credential = credentialStore.createCredential( "testCredential", - new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build()); SecureArea.CreateKeySettings authKeySettings = - new BouncyCastleSecureArea.CreateKeySettings.Builder() + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]) .build(); for (int n = 0; n < 10; n++) { Credential.PendingAuthenticationKey pendingAuthKey = credential.createPendingAuthenticationKey( - new BouncyCastleSecureArea.CreateKeySettings.Builder().build(), + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build(), null); String value = String.format(Locale.US, "bar%02d", n); ApplicationData pendingAppData = pendingAuthKey.getApplicationData(); @@ -591,18 +593,18 @@ public void testAuthKeyReplacement() { Credential credential = credentialStore.createCredential( "testCredential", - new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build()); Assert.assertEquals(0, credential.getAuthenticationKeys().size()); Assert.assertEquals(0, credential.getPendingAuthenticationKeys().size()); SecureArea.CreateKeySettings authKeySettings = - new BouncyCastleSecureArea.CreateKeySettings.Builder() + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]) .build(); for (int n = 0; n < 10; n++) { Credential.PendingAuthenticationKey pendingAuthKey = credential.createPendingAuthenticationKey( - new BouncyCastleSecureArea.CreateKeySettings.Builder().build(), + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build(), null); pendingAuthKey.certify(new byte[] {0, (byte) n}, Timestamp.ofEpochMilli(100), @@ -616,7 +618,7 @@ public void testAuthKeyReplacement() { Assert.assertArrayEquals(new byte[] {0, 5}, keyToReplace.getIssuerProvidedData()); Credential.PendingAuthenticationKey pendingAuthKey = credential.createPendingAuthenticationKey( - new BouncyCastleSecureArea.CreateKeySettings.Builder().build(), + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build(), keyToReplace); // ... it's not replaced until certify() is called Assert.assertEquals(1, credential.getPendingAuthenticationKeys().size()); @@ -653,7 +655,7 @@ public void testAuthKeyReplacement() { Credential.AuthenticationKey toBeReplaced = credential.getAuthenticationKeys().get(0); Credential.PendingAuthenticationKey replacement = credential.createPendingAuthenticationKey( - new BouncyCastleSecureArea.CreateKeySettings.Builder().build(), + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build(), toBeReplaced); Assert.assertEquals(toBeReplaced, replacement.getReplacementFor()); Assert.assertEquals(replacement, toBeReplaced.getReplacement()); @@ -663,7 +665,7 @@ public void testAuthKeyReplacement() { // Similarly, test the case where the key to be replaced is prematurely deleted. // The replacement key should no longer indicate it's a replacement key. replacement = credential.createPendingAuthenticationKey( - new BouncyCastleSecureArea.CreateKeySettings.Builder().build(), + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build(), toBeReplaced); Assert.assertEquals(toBeReplaced, replacement.getReplacementFor()); Assert.assertEquals(replacement, toBeReplaced.getReplacement()); diff --git a/identity/src/test/java/com/android/identity/credential/CredentialUtilTest.java b/identity/src/test/java/com/android/identity/credential/CredentialUtilTest.java index 690f5bb34..4f83480b0 100644 --- a/identity/src/test/java/com/android/identity/credential/CredentialUtilTest.java +++ b/identity/src/test/java/com/android/identity/credential/CredentialUtilTest.java @@ -16,7 +16,7 @@ package com.android.identity.credential; -import com.android.identity.securearea.BouncyCastleSecureArea; +import com.android.identity.securearea.SoftwareSecureArea; import com.android.identity.securearea.SecureArea; import com.android.identity.securearea.SecureAreaRepository; import com.android.identity.storage.EphemeralStorageEngine; @@ -39,7 +39,7 @@ public void setup() { mStorageEngine = new EphemeralStorageEngine(); mSecureAreaRepository = new SecureAreaRepository(); - mSecureArea = new BouncyCastleSecureArea(mStorageEngine); + mSecureArea = new SoftwareSecureArea(mStorageEngine); mSecureAreaRepository.addImplementation(mSecureArea); } @@ -51,13 +51,13 @@ public void testManagedAuthenticationKeyHelper() { Credential credential = credentialStore.createCredential( "testCredential", - new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build()); Assert.assertEquals(0, credential.getAuthenticationKeys().size()); Assert.assertEquals(0, credential.getPendingAuthenticationKeys().size()); SecureArea.CreateKeySettings authKeySettings = - new BouncyCastleSecureArea.CreateKeySettings.Builder() + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]) .build(); int numAuthKeys = 10; diff --git a/identity/src/test/java/com/android/identity/mdoc/response/DeviceResponseGeneratorTest.java b/identity/src/test/java/com/android/identity/mdoc/response/DeviceResponseGeneratorTest.java index 06814f0ca..a4169d08a 100644 --- a/identity/src/test/java/com/android/identity/mdoc/response/DeviceResponseGeneratorTest.java +++ b/identity/src/test/java/com/android/identity/mdoc/response/DeviceResponseGeneratorTest.java @@ -21,7 +21,7 @@ import com.android.identity.credential.CredentialStore; import com.android.identity.credential.NameSpacedData; import com.android.identity.internal.Util; -import com.android.identity.securearea.BouncyCastleSecureArea; +import com.android.identity.securearea.SoftwareSecureArea; import com.android.identity.securearea.SecureArea; import com.android.identity.securearea.SecureAreaRepository; import com.android.identity.mdoc.mso.MobileSecurityObjectGenerator; @@ -109,7 +109,7 @@ public void setup() throws Exception { mStorageEngine = new EphemeralStorageEngine(); mSecureAreaRepository = new SecureAreaRepository(); - mSecureArea = new BouncyCastleSecureArea(mStorageEngine); + mSecureArea = new SoftwareSecureArea(mStorageEngine); mSecureAreaRepository.addImplementation(mSecureArea); provisionCredential(); @@ -123,7 +123,7 @@ private void provisionCredential() throws Exception { // Create the credential... mCredential = credentialStore.createCredential( "testCredential", - new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build()); NameSpacedData nameSpacedData = new NameSpacedData.Builder() .putEntryString("ns1", "foo1", "bar1") .putEntryString("ns1", "foo2", "bar2") @@ -141,7 +141,7 @@ private void provisionCredential() throws Exception { mTimeValidityEnd = Timestamp.ofEpochMilli(nowMillis + 10 * 86400 * 1000); Credential.PendingAuthenticationKey pendingAuthKey = mCredential.createPendingAuthenticationKey( - new BouncyCastleSecureArea.CreateKeySettings.Builder() + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]) .setKeyPurposes(SecureArea.KEY_PURPOSE_SIGN | SecureArea.KEY_PURPOSE_AGREE_KEY) .build(), diff --git a/identity/src/test/java/com/android/identity/securearea/BouncyCastleSecureAreaTest.java b/identity/src/test/java/com/android/identity/securearea/SoftwareSecureAreaTest.java similarity index 78% rename from identity/src/test/java/com/android/identity/securearea/BouncyCastleSecureAreaTest.java rename to identity/src/test/java/com/android/identity/securearea/SoftwareSecureAreaTest.java index 46c8688d0..3cc5f3fb8 100644 --- a/identity/src/test/java/com/android/identity/securearea/BouncyCastleSecureAreaTest.java +++ b/identity/src/test/java/com/android/identity/securearea/SoftwareSecureAreaTest.java @@ -16,46 +16,96 @@ package com.android.identity.securearea; +import androidx.annotation.NonNull; + import com.android.identity.storage.EphemeralStorageEngine; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1OctetString; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; +import java.security.PrivateKey; import java.security.Security; import java.security.Signature; import java.security.SignatureException; import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.ECGenParameterSpec; +import java.util.ArrayList; +import java.util.Date; import java.util.List; import javax.crypto.KeyAgreement; -public class BouncyCastleSecureAreaTest { +public class SoftwareSecureAreaTest { private static final String TAG = "BouncyCastleSATest"; // limit to <= 23 chars + PrivateKey mAttestationKey; + String mAttestationKeySignatureAlgorithm; + List mAttestationKeyCertification; + @Before public void setup() { Security.insertProviderAt(new BouncyCastleProvider(), 1); + + // Create an attestation key... + try { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider()); + kpg.initialize(new ECGenParameterSpec("secp256r1")); + KeyPair attestationKeyPair = kpg.generateKeyPair(); + mAttestationKey = attestationKeyPair.getPrivate(); + mAttestationKeySignatureAlgorithm = "SHA256withECDSA"; + + long nowMillis = System.currentTimeMillis(); + JcaX509v3CertificateBuilder certBuilder = + new JcaX509v3CertificateBuilder(new X500Name("CN=Test Attestation Key"), + BigInteger.ONE, + new Date(nowMillis), + new Date(nowMillis + 24*3600*1000), + new X500Name("CN=Test Attestation Key"), + attestationKeyPair.getPublic()); + ContentSigner signer; + signer = new JcaContentSignerBuilder("SHA256withECDSA") + .build(attestationKeyPair.getPrivate()); + byte[] encodedCert = certBuilder.build(signer).getEncoded(); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + ByteArrayInputStream certBais = new ByteArrayInputStream(encodedCert); + mAttestationKeyCertification = new ArrayList<>(); + mAttestationKeyCertification.add((X509Certificate) cf.generateCertificate(certBais)); + + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | IOException | + CertificateException | OperatorCreationException e) { + throw new AssertionError(e); + } } @Test public void testEcKeyDeletion() { EphemeralStorageEngine storage = new EphemeralStorageEngine(); - BouncyCastleSecureArea ks = new BouncyCastleSecureArea(storage); + SoftwareSecureArea ks = new SoftwareSecureArea(storage); // First create the key... - ks.createKey("testKey", new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); - BouncyCastleSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); + ks.createKey("testKey", new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build()); + SoftwareSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); List certChain = keyInfo.getAttestation(); Assert.assertTrue(certChain.size() >= 1); @@ -79,18 +129,17 @@ public void testEcKeyDeletion() { @Test public void testEcKeySigning() { EphemeralStorageEngine storage = new EphemeralStorageEngine(); - BouncyCastleSecureArea ks = new BouncyCastleSecureArea(storage); + SoftwareSecureArea ks = new SoftwareSecureArea(storage); - ks.createKey("testKey", new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); + ks.createKey("testKey", new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build()); - BouncyCastleSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); + SoftwareSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); Assert.assertNotNull(keyInfo); Assert.assertTrue(keyInfo.getAttestation().size() >= 1); Assert.assertEquals(SecureArea.KEY_PURPOSE_SIGN, keyInfo.getKeyPurposes()); Assert.assertEquals(SecureArea.EC_CURVE_P256, keyInfo.getEcCurve()); Assert.assertFalse(keyInfo.isHardwareBacked()); Assert.assertFalse(keyInfo.isPassphraseProtected()); - Assert.assertNull(keyInfo.getAttestationKeyAlias()); byte[] dataToSign = new byte[] {4, 5, 6}; byte[] derSignature; @@ -115,18 +164,17 @@ public void testEcKeySigning() { @Test public void testEcKeyCreate() { EphemeralStorageEngine storage = new EphemeralStorageEngine(); - BouncyCastleSecureArea ks = new BouncyCastleSecureArea(storage); + SoftwareSecureArea ks = new SoftwareSecureArea(storage); - ks.createKey("testKey", new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); + ks.createKey("testKey", new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build()); - BouncyCastleSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); + SoftwareSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); Assert.assertNotNull(keyInfo); Assert.assertTrue(keyInfo.getAttestation().size() >= 1); Assert.assertEquals(SecureArea.KEY_PURPOSE_SIGN, keyInfo.getKeyPurposes()); Assert.assertEquals(SecureArea.EC_CURVE_P256, keyInfo.getEcCurve()); Assert.assertFalse(keyInfo.isHardwareBacked()); Assert.assertFalse(keyInfo.isPassphraseProtected()); - Assert.assertNull(keyInfo.getAttestationKeyAlias()); // Check the leaf certificate is self-signed. try { @@ -140,34 +188,45 @@ public void testEcKeyCreate() { } } + public static @NonNull + byte[] getChallenge(@NonNull X509Certificate cert) { + byte[] octetString = cert.getExtensionValue(AttestationExtension.ATTESTATION_OID); + try { + ASN1InputStream asn1InputStream = new ASN1InputStream(octetString); + byte[] encodedCbor = ((ASN1OctetString) asn1InputStream.readObject()).getOctets(); + return AttestationExtension.decode(encodedCbor); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + @Test - public void testEcKeyCreateWithAttestationKey() { + public void testEcKeyCreateWithAttestationKey() throws SecureArea.KeyLockedException { EphemeralStorageEngine storage = new EphemeralStorageEngine(); - BouncyCastleSecureArea ks = new BouncyCastleSecureArea(storage); - - ks.createKey("attestationKey", - new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); - BouncyCastleSecureArea.KeyInfo aKeyInfo = ks.getKeyInfo("attestationKey"); - List attestationKeyCertChain = aKeyInfo.getAttestation(); - Assert.assertTrue(attestationKeyCertChain.size() >= 1); + SoftwareSecureArea ks = new SoftwareSecureArea(storage); + byte[] challenge = new byte[] {1, 2, 3}; ks.createKey("testKey", - new BouncyCastleSecureArea.CreateKeySettings.Builder() - .setAttestationKeyAlias("attestationKey") + new SoftwareSecureArea.CreateKeySettings.Builder(challenge) + .setAttestationKey(mAttestationKey, + mAttestationKeySignatureAlgorithm, + mAttestationKeyCertification) .build()); - BouncyCastleSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); + SoftwareSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); Assert.assertNotNull(keyInfo); - Assert.assertTrue(keyInfo.getAttestation().size() >= 1); + Assert.assertTrue(keyInfo.getAttestation().size() >= 2); Assert.assertEquals(SecureArea.KEY_PURPOSE_SIGN, keyInfo.getKeyPurposes()); Assert.assertEquals(SecureArea.EC_CURVE_P256, keyInfo.getEcCurve()); Assert.assertFalse(keyInfo.isHardwareBacked()); Assert.assertFalse(keyInfo.isPassphraseProtected()); - Assert.assertEquals("attestationKey", keyInfo.getAttestationKeyAlias()); - // Check the leaf certificate is signed by attestationKey. + // Check challenge. + Assert.assertArrayEquals(challenge, getChallenge(keyInfo.getAttestation().get(0))); + + // Check the leaf certificate is signed by mAttestationKey. try { - keyInfo.getAttestation().get(0).verify(attestationKeyCertChain.get(0).getPublicKey()); + keyInfo.getAttestation().get(0).verify(mAttestationKeyCertification.get(0).getPublicKey()); } catch (CertificateException | InvalidKeyException | NoSuchAlgorithmException @@ -180,16 +239,11 @@ public void testEcKeyCreateWithAttestationKey() { @Test public void testEcKeySigningWithKeyWithoutCorrectPurpose() { EphemeralStorageEngine storage = new EphemeralStorageEngine(); - BouncyCastleSecureArea ks = new BouncyCastleSecureArea(storage); - - ks.createKey("attestationKey", - new BouncyCastleSecureArea.CreateKeySettings.Builder() - .build()); + SoftwareSecureArea ks = new SoftwareSecureArea(storage); ks.createKey("testKey", - new BouncyCastleSecureArea.CreateKeySettings.Builder() + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]) .setKeyPurposes(SecureArea.KEY_PURPOSE_AGREE_KEY) - .setAttestationKeyAlias("attestationKey") .build()); byte[] dataToSign = new byte[] {4, 5, 6}; try { @@ -205,7 +259,7 @@ public void testEcKeySigningWithKeyWithoutCorrectPurpose() { @Test public void testEcdh() { EphemeralStorageEngine storage = new EphemeralStorageEngine(); - BouncyCastleSecureArea ks = new BouncyCastleSecureArea(storage); + SoftwareSecureArea ks = new SoftwareSecureArea(storage); KeyPair otherKeyPair; try { @@ -216,24 +270,18 @@ public void testEcdh() { throw new AssertionError("Unexpected exception", e); } - ks.createKey("attestationKey", - new BouncyCastleSecureArea.CreateKeySettings.Builder() - .build()); - ks.createKey("testKey", - new BouncyCastleSecureArea.CreateKeySettings.Builder() + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]) .setKeyPurposes(SecureArea.KEY_PURPOSE_AGREE_KEY) - .setAttestationKeyAlias("attestationKey") .build()); - BouncyCastleSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); + SoftwareSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); Assert.assertNotNull(keyInfo); Assert.assertTrue(keyInfo.getAttestation().size() >= 1); Assert.assertEquals(SecureArea.KEY_PURPOSE_AGREE_KEY, keyInfo.getKeyPurposes()); Assert.assertEquals(SecureArea.EC_CURVE_P256, keyInfo.getEcCurve()); Assert.assertFalse(keyInfo.isHardwareBacked()); Assert.assertFalse(keyInfo.isPassphraseProtected()); - Assert.assertEquals("attestationKey", keyInfo.getAttestationKeyAlias()); // First do the ECDH from the perspective of our side... byte[] ourSharedSecret; @@ -262,7 +310,7 @@ public void testEcdh() { @Test public void testEcdhAndSigning() { EphemeralStorageEngine storage = new EphemeralStorageEngine(); - BouncyCastleSecureArea ks = new BouncyCastleSecureArea(storage); + SoftwareSecureArea ks = new SoftwareSecureArea(storage); KeyPair otherKeyPair; try { @@ -274,12 +322,12 @@ public void testEcdhAndSigning() { } ks.createKey("testKey", - new BouncyCastleSecureArea.CreateKeySettings.Builder() + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]) .setKeyPurposes(SecureArea.KEY_PURPOSE_AGREE_KEY | SecureArea.KEY_PURPOSE_SIGN) .build()); - BouncyCastleSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); + SoftwareSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); Assert.assertNotNull(keyInfo); Assert.assertTrue(keyInfo.getAttestation().size() >= 1); Assert.assertEquals(SecureArea.KEY_PURPOSE_SIGN @@ -287,7 +335,6 @@ public void testEcdhAndSigning() { Assert.assertEquals(SecureArea.EC_CURVE_P256, keyInfo.getEcCurve()); Assert.assertFalse(keyInfo.isHardwareBacked()); Assert.assertFalse(keyInfo.isPassphraseProtected()); - Assert.assertNull(keyInfo.getAttestationKeyAlias()); // First do the ECDH from the perspective of our side... byte[] ourSharedSecret; @@ -335,7 +382,7 @@ public void testEcdhAndSigning() { @Test public void testEcdhWithoutCorrectPurpose() { EphemeralStorageEngine storage = new EphemeralStorageEngine(); - BouncyCastleSecureArea ks = new BouncyCastleSecureArea(storage); + SoftwareSecureArea ks = new SoftwareSecureArea(storage); KeyPair otherKeyPair; try { @@ -347,7 +394,7 @@ public void testEcdhWithoutCorrectPurpose() { } ks.createKey("testKey", - new BouncyCastleSecureArea.CreateKeySettings.Builder() + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]) //.setKeyPurpose(SecureArea.KEY_PURPOSE_AGREE_KEY) .build()); @@ -365,22 +412,21 @@ public void testEcdhWithoutCorrectPurpose() { @Test public void testEcKeySigningWithLockedKey() { EphemeralStorageEngine storage = new EphemeralStorageEngine(); - BouncyCastleSecureArea ks = new BouncyCastleSecureArea(storage); + SoftwareSecureArea ks = new SoftwareSecureArea(storage); String passphrase = "verySekrit"; ks.createKey("testKey", - new BouncyCastleSecureArea.CreateKeySettings.Builder() + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]) .setPassphraseRequired(true, passphrase) .build()); - BouncyCastleSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); + SoftwareSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); Assert.assertNotNull(keyInfo); Assert.assertTrue(keyInfo.getAttestation().size() >= 1); Assert.assertEquals(SecureArea.KEY_PURPOSE_SIGN, keyInfo.getKeyPurposes()); Assert.assertEquals(SecureArea.EC_CURVE_P256, keyInfo.getEcCurve()); Assert.assertFalse(keyInfo.isHardwareBacked()); Assert.assertTrue(keyInfo.isPassphraseProtected()); - Assert.assertNull(keyInfo.getAttestationKeyAlias()); byte[] dataToSign = new byte[] {4, 5, 6}; byte[] derSignature = new byte[0]; @@ -399,7 +445,7 @@ public void testEcKeySigningWithLockedKey() { derSignature = ks.sign("testKey", SecureArea.ALGORITHM_ES256, dataToSign, - new BouncyCastleSecureArea.KeyUnlockData("wrongPassphrase")); + new SoftwareSecureArea.KeyUnlockData("wrongPassphrase")); Assert.fail(); } catch (SecureArea.KeyLockedException e) { // This is the expected path. @@ -410,7 +456,7 @@ public void testEcKeySigningWithLockedKey() { derSignature = ks.sign("testKey", SecureArea.ALGORITHM_ES256, dataToSign, - new BouncyCastleSecureArea.KeyUnlockData(passphrase)); + new SoftwareSecureArea.KeyUnlockData(passphrase)); } catch (SecureArea.KeyLockedException e) { throw new AssertionError(e); } @@ -431,17 +477,17 @@ public void testEcKeySigningWithLockedKey() { @Test public void testEcKeyCreationOverridesExistingAlias() { EphemeralStorageEngine storage = new EphemeralStorageEngine(); - BouncyCastleSecureArea ks = new BouncyCastleSecureArea(storage); + SoftwareSecureArea ks = new SoftwareSecureArea(storage); ks.createKey("testKey", - new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); - BouncyCastleSecureArea.KeyInfo keyInfoOld = ks.getKeyInfo("testKey"); + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build()); + SoftwareSecureArea.KeyInfo keyInfoOld = ks.getKeyInfo("testKey"); List certChainOld = keyInfoOld.getAttestation(); Assert.assertTrue(certChainOld.size() >= 1); ks.createKey("testKey", - new BouncyCastleSecureArea.CreateKeySettings.Builder().build()); - BouncyCastleSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]).build()); + SoftwareSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); List certChain = keyInfo.getAttestation(); Assert.assertTrue(certChain.size() >= 1); byte[] dataToSign = new byte[] {4, 5, 6}; @@ -473,7 +519,7 @@ public void testEcKeyCreationOverridesExistingAlias() { @Test public void testEcKeySigningAllCurves() { EphemeralStorageEngine storage = new EphemeralStorageEngine(); - BouncyCastleSecureArea ks = new BouncyCastleSecureArea(storage); + SoftwareSecureArea ks = new SoftwareSecureArea(storage); int[] knownEcCurves = new int[] { SecureArea.EC_CURVE_P256, @@ -490,18 +536,17 @@ public void testEcKeySigningAllCurves() { for (@SecureArea.EcCurve int ecCurve : knownEcCurves) { ks.createKey("testKey", - new BouncyCastleSecureArea.CreateKeySettings.Builder() + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]) .setEcCurve(ecCurve) .build()); - BouncyCastleSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); + SoftwareSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); Assert.assertNotNull(keyInfo); Assert.assertTrue(keyInfo.getAttestation().size() >= 1); Assert.assertEquals(SecureArea.KEY_PURPOSE_SIGN, keyInfo.getKeyPurposes()); Assert.assertEquals(ecCurve, keyInfo.getEcCurve()); Assert.assertFalse(keyInfo.isHardwareBacked()); Assert.assertFalse(keyInfo.isPassphraseProtected()); - Assert.assertNull(keyInfo.getAttestationKeyAlias()); @SecureArea.Algorithm int[] signatureAlgorithms = new int[0]; switch (ecCurve) { @@ -578,13 +623,11 @@ public void testEcKeySigningAllCurves() { @Test public void testEcKeyEcdhAllCurves() { EphemeralStorageEngine storage = new EphemeralStorageEngine(); - BouncyCastleSecureArea ks = new BouncyCastleSecureArea(storage); + SoftwareSecureArea ks = new SoftwareSecureArea(storage); // Because we're using curves that cannot be used for signatures we need to - // create an key used to sign the key attestations - ks.createKey("attestationKey", - new BouncyCastleSecureArea.CreateKeySettings.Builder() - .build()); + // set an attestation key since self-signed certificates won't work. + // TODO: do this int[] knownEcCurves = new int[] { SecureArea.EC_CURVE_P256, @@ -641,20 +684,18 @@ public void testEcKeyEcdhAllCurves() { } ks.createKey("testKey", - new BouncyCastleSecureArea.CreateKeySettings.Builder() + new SoftwareSecureArea.CreateKeySettings.Builder(new byte[0]) .setKeyPurposes(SecureArea.KEY_PURPOSE_AGREE_KEY) .setEcCurve(ecCurve) - .setAttestationKeyAlias("attestationKey") .build()); - BouncyCastleSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); + SoftwareSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey"); Assert.assertNotNull(keyInfo); Assert.assertTrue(keyInfo.getAttestation().size() >= 1); Assert.assertEquals(SecureArea.KEY_PURPOSE_AGREE_KEY, keyInfo.getKeyPurposes()); Assert.assertEquals(ecCurve, keyInfo.getEcCurve()); Assert.assertFalse(keyInfo.isHardwareBacked()); Assert.assertFalse(keyInfo.isPassphraseProtected()); - Assert.assertEquals("attestationKey", keyInfo.getAttestationKeyAlias()); // First do the ECDH from the perspective of our side... byte[] ourSharedSecret;