Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework BouncyCastleSecureArea. #392

Merged
merged 1 commit into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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<X509Certificate>

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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
}

Expand Down Expand Up @@ -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")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {

Expand All @@ -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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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("<h6>DeviceKey</h6>")
sb.append("${getFormattedCheck(true)}Curve: <b>${curveNameFor(Util.getCurve(doc.deviceKey))}</b><br>")
val deviceKeySha1 = FormatUtil.encodeToString(
MessageDigest.getInstance("SHA-1").digest(doc.deviceKey.encoded)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<X509Certificate> issuerCert = keyMaterial.issuerCertificate();

Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2023 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 com.android.identity.securearea
davidz25 marked this conversation as resolved.
Show resolved Hide resolved

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<DataItem> = 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
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ public interface SecureArea {
/**
* Class with information about a key.
*
* <p>Concrete {@link SecureArea} implementations may subclass this to provide
* <p>Concrete {@link SecureArea} implementations may subclass this to provide additional
* implementation-specific information about the key.
*/
class KeyInfo {
Expand All @@ -247,7 +247,7 @@ protected KeyInfo(@NonNull List<X509Certificate> attestation,
}

/**
* Gets the attestation for a key.
* Gets the attestation for the key.
*
* @return A list of certificates representing a certificate chain.
*/
Expand Down
Loading
Loading