diff --git a/appholder/src/main/java/com/android/identity/wallet/settings/SettingsScreenState.kt b/appholder/src/main/java/com/android/identity/wallet/settings/SettingsScreenState.kt index f2ad268e9..cab668afe 100644 --- a/appholder/src/main/java/com/android/identity/wallet/settings/SettingsScreenState.kt +++ b/appholder/src/main/java/com/android/identity/wallet/settings/SettingsScreenState.kt @@ -4,7 +4,6 @@ import android.os.Parcelable import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import com.android.identity.securearea.EcCurve -import com.android.identity.securearea.SecureArea import kotlinx.parcelize.Parcelize @Stable @@ -22,10 +21,7 @@ data class SettingsScreenState( val debugEnabled: Boolean = true ) { - fun isBleEnabled(): Boolean { - return isBleDataRetrievalEnabled - || isBlePeripheralModeEnabled - } + fun isBleEnabled(): Boolean = isBleDataRetrievalEnabled || isBlePeripheralModeEnabled fun canToggleBleDataRetrievalMode(newBleCentralMode: Boolean): Boolean { val updatedState = copy(isBleDataRetrievalEnabled = newBleCentralMode) @@ -47,12 +43,11 @@ data class SettingsScreenState( return updatedState.hasDataRetrieval() } - private fun hasDataRetrieval(): Boolean { - return isBleDataRetrievalEnabled + private fun hasDataRetrieval(): Boolean = + isBleDataRetrievalEnabled || isBlePeripheralModeEnabled || wifiAwareEnabled || nfcEnabled - } @Parcelize enum class SessionEncryptionCurveOption : Parcelable { @@ -66,9 +61,8 @@ data class SettingsScreenState( X25519, X448; - fun toEcCurve(): EcCurve { - - return when (this) { + fun toEcCurve(): EcCurve = + when (this) { P256 -> EcCurve.P256 P384 -> EcCurve.P384 P521 -> EcCurve.P521 @@ -79,11 +73,10 @@ data class SettingsScreenState( X25519 -> EcCurve.X25519 X448 -> EcCurve.X448 } - } companion object { - fun fromEcCurve(curve: EcCurve): SessionEncryptionCurveOption { - return when (curve) { + fun fromEcCurve(curve: EcCurve): SessionEncryptionCurveOption = + when (curve) { EcCurve.P256 -> P256 EcCurve.P384 -> P384 EcCurve.P521 -> P521 @@ -95,7 +88,6 @@ data class SettingsScreenState( EcCurve.X448 -> X448 else -> throw IllegalStateException("Unknown EcCurve") } - } } } } \ No newline at end of file diff --git a/appholder/src/main/java/com/android/identity/wallet/support/MdocAuthStateExtensions.kt b/appholder/src/main/java/com/android/identity/wallet/support/MdocAuthStateExtensions.kt index ebc468771..14088a783 100644 --- a/appholder/src/main/java/com/android/identity/wallet/support/MdocAuthStateExtensions.kt +++ b/appholder/src/main/java/com/android/identity/wallet/support/MdocAuthStateExtensions.kt @@ -1,13 +1,11 @@ package com.android.identity.wallet.support import com.android.identity.securearea.KeyPurpose -import com.android.identity.securearea.SecureArea import com.android.identity.wallet.composables.state.MdocAuthStateOption -fun MdocAuthStateOption.toKeyPurpose(): KeyPurpose { - return if (this == MdocAuthStateOption.ECDSA) { +fun MdocAuthStateOption.toKeyPurpose(): KeyPurpose = + if (this == MdocAuthStateOption.ECDSA) { KeyPurpose.SIGN } else { KeyPurpose.AGREE_KEY - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/appholder/src/main/java/com/android/identity/wallet/support/SecureAreaSupportNull.kt b/appholder/src/main/java/com/android/identity/wallet/support/SecureAreaSupportNull.kt index 0d4729412..0369c1259 100644 --- a/appholder/src/main/java/com/android/identity/wallet/support/SecureAreaSupportNull.kt +++ b/appholder/src/main/java/com/android/identity/wallet/support/SecureAreaSupportNull.kt @@ -47,22 +47,15 @@ class SecureAreaSupportNull : SecureAreaSupport { throw IllegalStateException("No implementation") } - override fun getSecureAreaSupportState(): SecureAreaSupportState { - return state - } - - override fun createAuthKeySettingsConfiguration(secureAreaSupportState: SecureAreaSupportState): ByteArray { - return ByteArray(0) - } + override fun getSecureAreaSupportState(): SecureAreaSupportState = state + override fun createAuthKeySettingsConfiguration(secureAreaSupportState: SecureAreaSupportState): ByteArray = + ByteArray(0) override fun createAuthKeySettingsFromConfiguration( encodedConfiguration: ByteArray, challenge: ByteArray, validFrom: Timestamp, validUntil: Timestamp - ): CreateKeySettings { - return CreateKeySettings(challenge) - } - + ): CreateKeySettings = CreateKeySettings(challenge) } diff --git a/appholder/src/main/java/com/android/identity/wallet/support/androidkeystore/AndroidAuthKeyCurveOption.kt b/appholder/src/main/java/com/android/identity/wallet/support/androidkeystore/AndroidAuthKeyCurveOption.kt index 57456f8fd..664883398 100644 --- a/appholder/src/main/java/com/android/identity/wallet/support/androidkeystore/AndroidAuthKeyCurveOption.kt +++ b/appholder/src/main/java/com/android/identity/wallet/support/androidkeystore/AndroidAuthKeyCurveOption.kt @@ -9,11 +9,10 @@ import kotlinx.parcelize.Parcelize enum class AndroidAuthKeyCurveOption : Parcelable { P_256, Ed25519, X25519; - fun toEcCurve(): EcCurve { - return when (this) { + fun toEcCurve(): EcCurve = + when (this) { P_256 -> EcCurve.P256 Ed25519 -> EcCurve.ED25519 X25519 -> EcCurve.X25519 } - } } \ No newline at end of file diff --git a/appholder/src/main/java/com/android/identity/wallet/support/softwarekeystore/SoftwareAuthKeyCurveOption.kt b/appholder/src/main/java/com/android/identity/wallet/support/softwarekeystore/SoftwareAuthKeyCurveOption.kt index e3cc5d453..86056f668 100644 --- a/appholder/src/main/java/com/android/identity/wallet/support/softwarekeystore/SoftwareAuthKeyCurveOption.kt +++ b/appholder/src/main/java/com/android/identity/wallet/support/softwarekeystore/SoftwareAuthKeyCurveOption.kt @@ -19,8 +19,8 @@ enum class SoftwareAuthKeyCurveOption : Parcelable { X25519, X448; - fun toEcCurve(): EcCurve { - return when (this) { + fun toEcCurve(): EcCurve = + when (this) { P256 -> EcCurve.P256 P384 -> EcCurve.P384 P521 -> EcCurve.P521 @@ -33,5 +33,4 @@ enum class SoftwareAuthKeyCurveOption : Parcelable { X25519 -> EcCurve.X25519 X448 -> EcCurve.X448 } - } } \ No newline at end of file diff --git a/appholder/src/main/java/com/android/identity/wallet/util/PreferencesHelper.kt b/appholder/src/main/java/com/android/identity/wallet/util/PreferencesHelper.kt index 66e072ddc..9e606eacc 100644 --- a/appholder/src/main/java/com/android/identity/wallet/util/PreferencesHelper.kt +++ b/appholder/src/main/java/com/android/identity/wallet/util/PreferencesHelper.kt @@ -5,7 +5,6 @@ import android.content.SharedPreferences import androidx.core.content.edit import androidx.preference.PreferenceManager import com.android.identity.securearea.EcCurve -import com.android.identity.securearea.SecureArea import com.android.identity.util.Logger import java.io.File @@ -38,84 +37,70 @@ object PreferencesHelper { return storageDir; } - fun isBleDataRetrievalEnabled(): Boolean { - return sharedPreferences.getBoolean(BLE_DATA_RETRIEVAL, true) - } + fun isBleDataRetrievalEnabled(): Boolean = + sharedPreferences.getBoolean(BLE_DATA_RETRIEVAL, true) - fun setBleDataRetrievalEnabled(enabled: Boolean) { + fun setBleDataRetrievalEnabled(enabled: Boolean) = sharedPreferences.edit { putBoolean(BLE_DATA_RETRIEVAL, enabled) } - } - fun isBleDataRetrievalPeripheralModeEnabled(): Boolean { - return sharedPreferences.getBoolean(BLE_DATA_RETRIEVAL_PERIPHERAL_MODE, false) - } + fun isBleDataRetrievalPeripheralModeEnabled(): Boolean = + sharedPreferences.getBoolean(BLE_DATA_RETRIEVAL_PERIPHERAL_MODE, false) - fun setBlePeripheralDataRetrievalMode(enabled: Boolean) { + fun setBlePeripheralDataRetrievalMode(enabled: Boolean) = sharedPreferences.edit { putBoolean(BLE_DATA_RETRIEVAL_PERIPHERAL_MODE, enabled) } - } - fun isBleL2capEnabled(): Boolean { - return sharedPreferences.getBoolean(BLE_DATA_L2CAP, false) - } + fun isBleL2capEnabled(): Boolean = + sharedPreferences.getBoolean(BLE_DATA_L2CAP, false) - fun setBleL2CAPEnabled(enabled: Boolean) { + fun setBleL2CAPEnabled(enabled: Boolean) = sharedPreferences.edit { putBoolean(BLE_DATA_L2CAP, enabled) } - } - fun isBleClearCacheEnabled(): Boolean { - return sharedPreferences.getBoolean(BLE_CLEAR_CACHE, false) - } + fun isBleClearCacheEnabled(): Boolean = + sharedPreferences.getBoolean(BLE_CLEAR_CACHE, false) - fun setBleClearCacheEnabled(enabled: Boolean) { + fun setBleClearCacheEnabled(enabled: Boolean) = sharedPreferences.edit { putBoolean(BLE_CLEAR_CACHE, enabled) } - } - fun isWifiDataRetrievalEnabled(): Boolean { - return sharedPreferences.getBoolean(WIFI_DATA_RETRIEVAL, false) - } + fun isWifiDataRetrievalEnabled(): Boolean = + sharedPreferences.getBoolean(WIFI_DATA_RETRIEVAL, false) - fun setWifiDataRetrievalEnabled(enabled: Boolean) { + fun setWifiDataRetrievalEnabled(enabled: Boolean) = sharedPreferences.edit { putBoolean(WIFI_DATA_RETRIEVAL, enabled) } - } - fun isNfcDataRetrievalEnabled(): Boolean { - return sharedPreferences.getBoolean(NFC_DATA_RETRIEVAL, false) - } + fun isNfcDataRetrievalEnabled(): Boolean = + sharedPreferences.getBoolean(NFC_DATA_RETRIEVAL, false) - fun setNfcDataRetrievalEnabled(enabled: Boolean) { + fun setNfcDataRetrievalEnabled(enabled: Boolean) = sharedPreferences.edit { putBoolean(NFC_DATA_RETRIEVAL, enabled) } - } - fun isConnectionAutoCloseEnabled(): Boolean { - return sharedPreferences.getBoolean(CONNECTION_AUTO_CLOSE, true) - } + fun isConnectionAutoCloseEnabled(): Boolean = + sharedPreferences.getBoolean(CONNECTION_AUTO_CLOSE, true) - fun setConnectionAutoCloseEnabled(enabled: Boolean) { + fun setConnectionAutoCloseEnabled(enabled: Boolean) = sharedPreferences.edit { putBoolean(CONNECTION_AUTO_CLOSE, enabled) } - } - fun shouldUseStaticHandover(): Boolean { - return sharedPreferences.getBoolean(STATIC_HANDOVER, false) - } + fun shouldUseStaticHandover(): Boolean = + sharedPreferences.getBoolean(STATIC_HANDOVER, false) - fun setUseStaticHandover(enabled: Boolean) { + fun setUseStaticHandover(enabled: Boolean) = sharedPreferences.edit { putBoolean(STATIC_HANDOVER, enabled) } - } - fun isDebugLoggingEnabled(): Boolean { - return sharedPreferences.getBoolean(DEBUG_LOG, true) - } + fun isDebugLoggingEnabled(): Boolean = + sharedPreferences.getBoolean(DEBUG_LOG, true) - fun setDebugLoggingEnabled(enabled: Boolean) { - sharedPreferences.edit { putBoolean(DEBUG_LOG, enabled) } - Logger.isDebugEnabled = enabled - } + fun setDebugLoggingEnabled(enabled: Boolean) = + sharedPreferences + .edit { putBoolean(DEBUG_LOG, enabled) } + .also { Logger.isDebugEnabled = enabled } - fun getEphemeralKeyCurveOption(): EcCurve { - return EcCurve.fromInt(sharedPreferences.getInt(EPHEMERAL_KEY_CURVE_OPTION, EcCurve.P256.coseCurveIdentifier)) - } + fun getEphemeralKeyCurveOption(): EcCurve = + EcCurve.fromInt( + sharedPreferences.getInt( + EPHEMERAL_KEY_CURVE_OPTION, + EcCurve.P256.coseCurveIdentifier + ) + ) - fun setEphemeralKeyCurveOption(newValue: EcCurve) { + fun setEphemeralKeyCurveOption(newValue: EcCurve) = sharedPreferences.edit { putInt(EPHEMERAL_KEY_CURVE_OPTION, newValue.coseCurveIdentifier) } - } } \ No newline at end of file diff --git a/appverifier/build.gradle b/appverifier/build.gradle index bd9883789..1c25ed6bd 100644 --- a/appverifier/build.gradle +++ b/appverifier/build.gradle @@ -73,6 +73,7 @@ dependencies { implementation libs.bundles.compose implementation libs.cbor implementation libs.code.scanner + implementation libs.identity.credential androidTestImplementation libs.bundles.ui.testing diff --git a/appverifier/src/main/java/com/android/mdl/appreader/transfer/TransferManager.kt b/appverifier/src/main/java/com/android/mdl/appreader/transfer/TransferManager.kt index a166d6fa6..66bb5d483 100644 --- a/appverifier/src/main/java/com/android/mdl/appreader/transfer/TransferManager.kt +++ b/appverifier/src/main/java/com/android/mdl/appreader/transfer/TransferManager.kt @@ -69,9 +69,11 @@ class TransferManager private constructor(private val context: Context) { fun getTransferStatus(): LiveData = transferStatusLd fun initVerificationHelper() { - val builder = VerificationHelper.Builder(context, + val builder = VerificationHelper.Builder( + context, responseListener, - context.mainExecutor()) + context.mainExecutor() + ) val options = DataTransportOptions.Builder() .setBleUseL2CAP(userPreferences.isBleL2capEnabled()) .setBleClearCache(userPreferences.isBleClearCacheEnabled()) @@ -84,10 +86,11 @@ class TransferManager private constructor(private val context: Context) { val bleUuid = UUID.randomUUID() negotiatedHandoverConnectionMethods.add( ConnectionMethodBle( - false, - true, - null, - bleUuid) + false, + true, + null, + bleUuid + ) ) builder.setNegotiatedHandoverConnectionMethods(negotiatedHandoverConnectionMethods) @@ -97,9 +100,11 @@ class TransferManager private constructor(private val context: Context) { } fun initVerificationHelperReverseEngagement() { - val builder = VerificationHelper.Builder(context, + val builder = VerificationHelper.Builder( + context, responseListener, - context.mainExecutor()) + context.mainExecutor() + ) val options = DataTransportOptions.Builder() .setBleUseL2CAP(userPreferences.isBleL2capEnabled()) .setBleClearCache(userPreferences.isBleClearCacheEnabled()) @@ -116,17 +121,16 @@ class TransferManager private constructor(private val context: Context) { usingReverseEngagement = true } - fun setQrDeviceEngagement(qrDeviceEngagement: String) { + fun setQrDeviceEngagement(qrDeviceEngagement: String) = verification?.setDeviceEngagementFromQrCode(qrDeviceEngagement) - } - fun setNdefDeviceEngagement(adapter: NfcAdapter, activity: Activity) { + fun setNdefDeviceEngagement(adapter: NfcAdapter, activity: Activity) = adapter.enableReaderMode( activity, readerModeListener, NfcAdapter.FLAG_READER_NFC_A + NfcAdapter.FLAG_READER_NFC_B + NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK + NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS, - null) - } + null + ) private val readerModeListener = NfcAdapter.ReaderCallback { tag -> verification?.nfcProcessOnTagDiscovered(tag) @@ -175,7 +179,7 @@ class TransferManager private constructor(private val context: Context) { disconnect() } - fun disconnect(){ + fun disconnect() { try { verification?.disconnect() } catch (e: RuntimeException) { @@ -281,6 +285,7 @@ class TransferManager private constructor(private val context: Context) { ) readerKeyCertificateChain = listOf(readerCertificate) } + SECP384R1.name, BRAINPOOLP384R1.name -> { val keyPair = ReaderCertificateGenerator.generateECDSAKeyPair(curveName) @@ -296,6 +301,7 @@ class TransferManager private constructor(private val context: Context) { ) readerKeyCertificateChain = listOf(readerCertificate) } + SECP521R1.name, BRAINPOOLP512R1.name -> { val keyPair = ReaderCertificateGenerator.generateECDSAKeyPair(curveName) @@ -311,6 +317,7 @@ class TransferManager private constructor(private val context: Context) { ) readerKeyCertificateChain = listOf(readerCertificate) } + ED25519.name, ED448.name -> { val keyPair = ReaderCertificateGenerator.generateECDSAKeyPair(curveName) @@ -375,34 +382,24 @@ class TransferManager private constructor(private val context: Context) { } ?: throw IllegalStateException("Response not received") } - fun getMdocSessionEncryptionCurve(): EcCurve { - return verification!!.eReaderKeyCurve - } + fun getMdocSessionEncryptionCurve(): EcCurve = verification!!.eReaderKeyCurve - fun getTapToEngagementDurationMillis(): Long { - return verification?.tapToEngagementDurationMillis ?: 0 - } + fun getTapToEngagementDurationMillis(): Long = verification?.tapToEngagementDurationMillis ?: 0 - fun getBleScanningMillis(): Long { - return verification!!.scanningTimeMillis - } + fun getBleScanningMillis(): Long = verification!!.scanningTimeMillis - fun getEngagementToRequestDurationMillis(): Long { - return verification?.engagementToRequestDurationMillis ?: 0 - } + fun getEngagementToRequestDurationMillis(): Long = + verification?.engagementToRequestDurationMillis ?: 0 - fun getRequestToResponseDurationMillis(): Long { - return verification?.requestToResponseDurationMillis ?: 0 - } + fun getRequestToResponseDurationMillis(): Long = + verification?.requestToResponseDurationMillis ?: 0 - fun getEngagementMethod(): String { + fun getEngagementMethod(): String = when (verification?.engagementMethod) { - VerificationHelper.ENGAGEMENT_METHOD_QR_CODE -> return "QR Code" - VerificationHelper.ENGAGEMENT_METHOD_NFC_STATIC_HANDOVER -> return "NFC Static Handover" - VerificationHelper.ENGAGEMENT_METHOD_NFC_NEGOTIATED_HANDOVER -> return "NFC Negotiated Handover" - VerificationHelper.ENGAGEMENT_METHOD_REVERSE -> return "Reverse" + VerificationHelper.ENGAGEMENT_METHOD_QR_CODE -> "QR Code" + VerificationHelper.ENGAGEMENT_METHOD_NFC_STATIC_HANDOVER -> "NFC Static Handover" + VerificationHelper.ENGAGEMENT_METHOD_NFC_NEGOTIATED_HANDOVER -> "NFC Negotiated Handover" + VerificationHelper.ENGAGEMENT_METHOD_REVERSE -> "Reverse" + else -> "N/A" } - return "N/A" - } - } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d513de0a8..b5f7ee92b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -105,6 +105,7 @@ org-jmrtd-jmrtd = { group = "org.jmrtd", name ="jmrtd", version.ref="jmrtd-version" } com-google-mlkit-text-recognition = { group = "com.google.mlkit", name = "text-recognition", version.ref="mlkit-version" } junit = { group = "junit", name = "junit", version.ref = "junit" } +identity-credential = { group = "com.android.identity", name = "identity-credential", version = "20231002" } [bundles] androidx-core = ["androidx-core-ktx", "androidx-appcompat", "androidx-material", "androidx-contraint-layout", "androidx-fragment-ktx", "androidx-legacy-v4", "androidx-preference-ktx", "androidx-work"] diff --git a/identity-mdoc/src/main/java/com/android/identity/mdoc/util/MdocUtil.java b/identity-mdoc/src/main/java/com/android/identity/mdoc/util/MdocUtil.java index 9b813a25f..4ec5f4cbc 100644 --- a/identity-mdoc/src/main/java/com/android/identity/mdoc/util/MdocUtil.java +++ b/identity-mdoc/src/main/java/com/android/identity/mdoc/util/MdocUtil.java @@ -362,7 +362,7 @@ private static boolean hasElementValue(byte[] encodedIssuerSignedItem) { for (String namespaceName : documentRequest.getNamespaces()) { for (String dataElementName : documentRequest.getEntryNames(namespaceName)) { boolean intentToRetain = documentRequest.getIntentToRetain(namespaceName, dataElementName); - elements.add(new CredentialRequest.DataElement(namespaceName, dataElementName, intentToRetain)); + elements.add(new CredentialRequest.DataElement(namespaceName, dataElementName, intentToRetain, false)); } } return new CredentialRequest(elements); diff --git a/identity/src/main/java/com/android/identity/credential/AuthenticationKey.kt b/identity/src/main/java/com/android/identity/credential/AuthenticationKey.kt index ac2f447f8..a2af3e7ca 100644 --- a/identity/src/main/java/com/android/identity/credential/AuthenticationKey.kt +++ b/identity/src/main/java/com/android/identity/credential/AuthenticationKey.kt @@ -62,6 +62,7 @@ class AuthenticationKey { private set private lateinit var privateApplicationData: SimpleApplicationData + /** * Application specific data. * @@ -135,7 +136,7 @@ class AuthenticationKey { if (replacementAlias != null) { mapBuilder.put("replacementAlias", replacementAlias) } - return builder.build()[0] + return builder.build().first() } /** @@ -143,19 +144,16 @@ class AuthenticationKey { * key is designated to replace this key. */ val replacement: PendingAuthenticationKey? - get() { - if (replacementAlias == null) { - return null - } - for (pendingAuthKey in credential.pendingAuthenticationKeys) { - if (pendingAuthKey.alias == replacementAlias) { - return pendingAuthKey + get() = credential.pendingAuthenticationKeys.firstOrNull { it.alias == replacementAlias } + .also { + if (it == null && replacementAlias != null) { + Logger.w( + TAG, "Pending key with alias $replacementAlias which " + + "is intended to replace this key does not exist" + ) } } - Logger.w(TAG, "Pending key with alias $replacementAlias which " + - "is intended to replace this key does not exist") - return null - } + fun setReplacementAlias(alias: String) { replacementAlias = alias @@ -186,40 +184,36 @@ class AuthenticationKey { fun fromCbor( dataItem: DataItem, credential: Credential - ): AuthenticationKey { - val ret = AuthenticationKey() - ret.alias = Util.cborMapExtractString(dataItem, "alias") - if (Util.cborMapHasKey(dataItem, "domain")) { - ret.domain = Util.cborMapExtractString(dataItem, "domain") + ) = AuthenticationKey().apply { + alias = Util.cborMapExtractString(dataItem, "alias") + domain = if (Util.cborMapHasKey(dataItem, "domain")) { + Util.cborMapExtractString(dataItem, "domain") } else { - ret.domain = "" + "" } val secureAreaIdentifier = Util.cborMapExtractString(dataItem, "secureAreaIdentifier") - ret.secureArea = - credential.secureAreaRepository.getImplementation(secureAreaIdentifier)!! - requireNotNull(ret.secureArea) { "Unknown Secure Area $secureAreaIdentifier" } - ret.usageCount = Util.cborMapExtractNumber(dataItem, "usageCount").toInt() - ret.issuerProvidedData = Util.cborMapExtractByteString(dataItem, "data") - ret.validFrom = - Timestamp.ofEpochMilli(Util.cborMapExtractNumber(dataItem, "validFrom")) - ret.validUntil = - Timestamp.ofEpochMilli(Util.cborMapExtractNumber(dataItem, "validUntil")) + secureArea = credential.secureAreaRepository.getImplementation(secureAreaIdentifier)!! + requireNotNull(secureArea) { "Unknown Secure Area $secureAreaIdentifier" } + usageCount = Util.cborMapExtractNumber(dataItem, "usageCount").toInt() + issuerProvidedData = Util.cborMapExtractByteString(dataItem, "data") + validFrom = Timestamp.ofEpochMilli(Util.cborMapExtractNumber(dataItem, "validFrom")) + validUntil = Timestamp.ofEpochMilli(Util.cborMapExtractNumber(dataItem, "validUntil")) if (Util.cborMapHasKey(dataItem, "replacementAlias")) { - ret.replacementAlias = Util.cborMapExtractString(dataItem, "replacementAlias") + replacementAlias = Util.cborMapExtractString(dataItem, "replacementAlias") } val applicationDataDataItem: DataItem = Util.cborMapExtract(dataItem, "applicationData") check(applicationDataDataItem is ByteString) { "applicationData not found or not byte[]" } - ret.credential = credential - ret.privateApplicationData = SimpleApplicationData.decodeFromCbor( - applicationDataDataItem.bytes - ) { ret.credential.saveCredential() } + this.credential = credential + privateApplicationData = SimpleApplicationData + .decodeFromCbor(applicationDataDataItem.bytes) { + credential.saveCredential() + } if (Util.cborMapHasKey(dataItem, "authenticationKeyCounter")) { - ret.authenticationKeyCounter = + authenticationKeyCounter = Util.cborMapExtractNumber(dataItem, "authenticationKeyCounter") } - return ret } } } \ No newline at end of file diff --git a/identity/src/main/java/com/android/identity/credential/Credential.kt b/identity/src/main/java/com/android/identity/credential/Credential.kt index 5522d5ba6..929b13667 100644 --- a/identity/src/main/java/com/android/identity/credential/Credential.kt +++ b/identity/src/main/java/com/android/identity/credential/Credential.kt @@ -90,7 +90,7 @@ class Credential private constructor( private val storageEngine: StorageEngine, internal val secureAreaRepository: SecureAreaRepository ) { - private var privateApplicationData = SimpleApplicationData { saveCredential() } + /** * Application specific data. * @@ -98,25 +98,27 @@ class Credential private constructor( * with the authentication key. Setters and associated getters are * enumerated in the [ApplicationData] interface. */ + private var _applicationData = SimpleApplicationData { saveCredential() } val applicationData: ApplicationData - get() = privateApplicationData + get() = _applicationData + - private var privatePendingAuthenticationKeys: MutableList = ArrayList() /** * Pending authentication keys. */ + private var _pendingAuthenticationKeys = mutableListOf() val pendingAuthenticationKeys: List // Return shallow copy b/c backing field may get modified if certify() or delete() is called. - get() = ArrayList(privatePendingAuthenticationKeys) + get() = _pendingAuthenticationKeys.toList() - private var privateAuthenticationKeys: MutableList = ArrayList() /** * Certified authentication keys. */ + private var _authenticationKeys = mutableListOf() val authenticationKeys: List // Return shallow copy b/c backing field may get modified if certify() or delete() is called. - get() = ArrayList(privateAuthenticationKeys) + get() = _authenticationKeys.toList() /** * Authentication key counter. @@ -131,15 +133,15 @@ class Credential private constructor( val t0 = Timestamp.now() val builder = CborBuilder() val map: MapBuilder = builder.addMap() - map.put("applicationData", privateApplicationData.encodeAsCbor()) + map.put("applicationData", _applicationData.encodeAsCbor()) val pendingAuthenticationKeysArrayBuilder: ArrayBuilder> = map.putArray("pendingAuthenticationKeys") - for (pendingAuthenticationKey in privatePendingAuthenticationKeys) { + for (pendingAuthenticationKey in _pendingAuthenticationKeys) { pendingAuthenticationKeysArrayBuilder.add(pendingAuthenticationKey.toCbor()) } val authenticationKeysArrayBuilder: ArrayBuilder> = map.putArray("authenticationKeys") - for (authenticationKey in privateAuthenticationKeys) { + for (authenticationKey in _authenticationKeys) { authenticationKeysArrayBuilder.add(authenticationKey.toCbor()) } map.put("authenticationKeyCounter", authenticationKeyCounter) @@ -165,39 +167,35 @@ class Credential private constructor( check(dataItems.size == 1) { "Expected 1 item, found " + dataItems.size } check(dataItems[0] is Map) { "Item is not a map" } - val map = dataItems[0] as Map + val map = dataItems.first() as Map val applicationDataDataItem: DataItem = map[UnicodeString("applicationData")] check(applicationDataDataItem is ByteString) { "applicationData not found or not byte[]" } - privateApplicationData = SimpleApplicationData.decodeFromCbor( - applicationDataDataItem.bytes - ) { saveCredential() } + _applicationData = SimpleApplicationData + .decodeFromCbor(applicationDataDataItem.bytes) { + saveCredential() + } - privatePendingAuthenticationKeys = ArrayList() + _pendingAuthenticationKeys = ArrayList() val pendingAuthenticationKeysDataItem: DataItem = map[UnicodeString("pendingAuthenticationKeys")] check(pendingAuthenticationKeysDataItem is Array) { "pendingAuthenticationKeys not found or not array" } for (item in pendingAuthenticationKeysDataItem.dataItems) { - privatePendingAuthenticationKeys.add(PendingAuthenticationKey.fromCbor(item, this)) + _pendingAuthenticationKeys.add(PendingAuthenticationKey.fromCbor(item, this)) } - privateAuthenticationKeys = ArrayList() + _authenticationKeys = ArrayList() val authenticationKeysDataItem: DataItem = map[UnicodeString("authenticationKeys")] check(authenticationKeysDataItem is Array) { "authenticationKeys not found or not array" } for (item in authenticationKeysDataItem.dataItems) { - privateAuthenticationKeys.add(AuthenticationKey.fromCbor(item, this)) + _authenticationKeys.add(AuthenticationKey.fromCbor(item, this)) } authenticationKeyCounter = Util.cborMapExtractNumber(map, "authenticationKeyCounter") return true } fun deleteCredential() { - // Need to use shallow copies because delete() modifies the list. - for (key in ArrayList(privatePendingAuthenticationKeys)) { - key.delete() - } - for (key in ArrayList(privateAuthenticationKeys)) { - key.delete() - } + _pendingAuthenticationKeys.clear() + _authenticationKeys.clear() storageEngine.delete(CREDENTIAL_PREFIX + name) } @@ -213,30 +211,24 @@ class Credential private constructor( domain: String, now: Timestamp? ): AuthenticationKey? { + var candidate: AuthenticationKey? = null - for (authenticationKey in privateAuthenticationKeys) { - if (authenticationKey.domain != domain) { - continue - } - // If current time is passed... - if (now != null) { - // ... ignore slots that aren't yet valid - if (now.toEpochMilli() < authenticationKey.validFrom.toEpochMilli()) { - continue - } - // .. ignore slots that aren't valid anymore - if (now.toEpochMilli() > authenticationKey.validUntil.toEpochMilli()) { - continue - } - } + _authenticationKeys.filter { + it.domain == domain && ( + now != null + && (now.toEpochMilli() >= it.validFrom.toEpochMilli()) + && (now.toEpochMilli() <= it.validUntil.toEpochMilli()) + ) + }.forEach { authenticationKey -> // If we already have a candidate, prefer this one if its usage count is lower - if (candidate != null) { - if (authenticationKey.usageCount < candidate.usageCount) { + candidate?.let { candidateAuthKey -> + if (authenticationKey.usageCount < candidateAuthKey.usageCount) { candidate = authenticationKey } - } else { + } ?: run { candidate = authenticationKey } + } return candidate } @@ -282,16 +274,16 @@ class Credential private constructor( asReplacementFor, this ) - privatePendingAuthenticationKeys.add(pendingAuthenticationKey) + _pendingAuthenticationKeys.add(pendingAuthenticationKey) asReplacementFor?.setReplacementAlias(pendingAuthenticationKey.alias) saveCredential() return pendingAuthenticationKey } fun removePendingAuthenticationKey(pendingAuthenticationKey: PendingAuthenticationKey) { - check(privatePendingAuthenticationKeys.remove(pendingAuthenticationKey)) { "Error removing pending authentication key" } + check(_pendingAuthenticationKeys.remove(pendingAuthenticationKey)) { "Error removing pending authentication key" } if (pendingAuthenticationKey.replacementForAlias != null) { - for (authKey in privateAuthenticationKeys) { + for (authKey in _authenticationKeys) { if (authKey.alias == pendingAuthenticationKey.replacementForAlias) { authKey.replacementAlias = null break @@ -302,9 +294,9 @@ class Credential private constructor( } fun removeAuthenticationKey(authenticationKey: AuthenticationKey) { - check(privateAuthenticationKeys.remove(authenticationKey)) { "Error removing authentication key" } + check(_authenticationKeys.remove(authenticationKey)) { "Error removing authentication key" } if (authenticationKey.replacementAlias != null) { - for (pendingAuthKey in privatePendingAuthenticationKeys) { + for (pendingAuthKey in _pendingAuthenticationKeys) { if (pendingAuthKey.alias == authenticationKey.replacementAlias) { pendingAuthKey.replacementForAlias = null break @@ -320,7 +312,7 @@ class Credential private constructor( validFrom: Timestamp, validUntil: Timestamp ): AuthenticationKey { - check(privatePendingAuthenticationKeys.remove(pendingAuthenticationKey)) { "Error removing pending authentication key" } + check(_pendingAuthenticationKeys.remove(pendingAuthenticationKey)) { "Error removing pending authentication key" } val authenticationKey = AuthenticationKey.create( pendingAuthenticationKey, issuerProvidedAuthenticationData, @@ -328,7 +320,7 @@ class Credential private constructor( validUntil, this ) - privateAuthenticationKeys.add(authenticationKey) + _authenticationKeys.add(authenticationKey) val authKeyToDelete = pendingAuthenticationKey.replacementFor authKeyToDelete?.delete() saveCredential() diff --git a/identity/src/main/java/com/android/identity/credential/CredentialStore.kt b/identity/src/main/java/com/android/identity/credential/CredentialStore.kt index c0d03e06f..9b092306d 100644 --- a/identity/src/main/java/com/android/identity/credential/CredentialStore.kt +++ b/identity/src/main/java/com/android/identity/credential/CredentialStore.kt @@ -87,16 +87,14 @@ class CredentialStore( * * @return list of all credential names in the store. */ - fun listCredentials(): List { - val ret = mutableListOf() - for (name in storageEngine.enumerate()) { - if (name.startsWith(Credential.CREDENTIAL_PREFIX)) { - ret.add(name.substring(Credential.CREDENTIAL_PREFIX.length)) - } - } - return ret + fun listCredentials(): List = mutableListOf().apply { + storageEngine.enumerate() + .filter { name -> name.startsWith(Credential.CREDENTIAL_PREFIX) } + .map { name -> name.substring(Credential.CREDENTIAL_PREFIX.length) } + .forEach { name -> add(name) } } + /** * Deletes a credential. * diff --git a/identity/src/main/java/com/android/identity/internal/Util.kt b/identity/src/main/java/com/android/identity/internal/Util.kt index cfd09565a..7e3cf27cd 100644 --- a/identity/src/main/java/com/android/identity/internal/Util.kt +++ b/identity/src/main/java/com/android/identity/internal/Util.kt @@ -47,7 +47,6 @@ import org.bouncycastle.asn1.ASN1Encodable import org.bouncycastle.asn1.ASN1InputStream import org.bouncycastle.asn1.ASN1Integer import org.bouncycastle.asn1.ASN1OctetString -import org.bouncycastle.asn1.ASN1Primitive import org.bouncycastle.asn1.ASN1Sequence import org.bouncycastle.asn1.DERSequenceGenerator import org.bouncycastle.jcajce.provider.asymmetric.edec.BCEdDSAPublicKey @@ -63,7 +62,6 @@ import java.math.BigInteger import java.nio.ByteBuffer import java.nio.ByteOrder import java.security.AlgorithmParameters -import java.security.InvalidAlgorithmParameterException import java.security.InvalidKeyException import java.security.KeyFactory import java.security.KeyPair @@ -105,10 +103,12 @@ import javax.crypto.Mac import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec + /** * Utility functions. */ object Util { + private const val TAG = "Util" private const val COSE_LABEL_ALG: Long = 1 private const val COSE_LABEL_X5CHAIN: Long = 33 // temporary identifier @@ -147,9 +147,8 @@ object Util { } @JvmStatic - fun toHex(bytes: ByteArray): String { - return toHex(bytes, 0, bytes.size) - } + fun toHex(bytes: ByteArray): String = + toHex(bytes, 0, bytes.size) @JvmStatic fun toHex(bytes: ByteArray, from: Int, to: Int): String { @@ -163,9 +162,8 @@ object Util { } @JvmStatic - fun base16(bytes: ByteArray): String { - return toHex(bytes).uppercase() - } + fun base16(bytes: ByteArray): String = + toHex(bytes).uppercase() @JvmStatic fun cborEncode(dataItem: DataItem): ByteArray { @@ -181,29 +179,24 @@ object Util { } @JvmStatic - fun cborEncodeBoolean(value: Boolean): ByteArray { - return cborEncode(CborBuilder().add(value).build().get(0)) - } + fun cborEncodeBoolean(value: Boolean): ByteArray = + cborEncode(CborBuilder().add(value).build().first()) @JvmStatic - fun cborEncodeString(value: String): ByteArray { - return cborEncode(CborBuilder().add(value).build().get(0)) - } + fun cborEncodeString(value: String): ByteArray = + cborEncode(CborBuilder().add(value).build().first()) @JvmStatic - fun cborEncodeNumber(value: Long): ByteArray { - return cborEncode(CborBuilder().add(value).build().get(0)) - } + fun cborEncodeNumber(value: Long): ByteArray = + cborEncode(CborBuilder().add(value).build().first()) @JvmStatic - fun cborEncodeBytestring(value: ByteArray): ByteArray { - return cborEncode(CborBuilder().add(value).build().get(0)) - } + fun cborEncodeBytestring(value: ByteArray): ByteArray = + cborEncode(CborBuilder().add(value).build().first()) @JvmStatic - fun cborEncodeDateTime(timestamp: Timestamp): ByteArray { - return cborEncode(cborBuildDateTime(timestamp)) - } + fun cborEncodeDateTime(timestamp: Timestamp): ByteArray = + cborEncode(cborBuildDateTime(timestamp)) /** * Returns #6.0(tstr) where tstr is the ISO 8601 encoding of the given point in time. @@ -230,13 +223,12 @@ object Util { require(dataItems.size == 1) { "Unexpected number of items, expected 1 got ${dataItems.size}" } - return dataItems.get(0) + return dataItems.first() } @JvmStatic fun cborDecodeBoolean(data: ByteArray): Boolean { - val simple: SimpleValue - simple = try { + val simple: SimpleValue = try { cborDecode(data) as SimpleValue } catch (e: ClassCastException) { throw IllegalArgumentException("Data given cannot be cast into a boolean.", e) @@ -252,7 +244,7 @@ object Util { */ @JvmStatic fun checkedLongValue(item: DataItem): Long { - val bigNum = castTo(Number::class.java, item).value + val bigNum = item.castToNumber().value val result = bigNum.toLong() if (bigNum != BigInteger.valueOf(result)) { throw ArithmeticException("Expected long value, got '$bigNum'") @@ -261,9 +253,8 @@ object Util { } @JvmStatic - fun cborDecodeString(data: ByteArray): String { - return checkedStringValue(cborDecode(data)) - } + fun cborDecodeString(data: ByteArray): String = + checkedStringValue(cborDecode(data)) /** * Accepts a `DataItem`, attempts to cast it to a `UnicodeString`, then returns the @@ -271,27 +262,19 @@ object Util { * `UnicodeString`. */ @JvmStatic - fun checkedStringValue(item: DataItem): String { - return castTo(UnicodeString::class.java, item).string - } + fun checkedStringValue(item: DataItem): String = item.castToUnicodeString().string @JvmStatic - fun cborDecodeLong(data: ByteArray): Long { - return checkedLongValue(cborDecode(data)) - } + fun cborDecodeLong(data: ByteArray): Long = + checkedLongValue(cborDecode(data)) @JvmStatic - fun cborDecodeByteString(data: ByteArray): ByteArray { - val dataItem = cborDecode(data) - return castTo( - ByteString::class.java, dataItem - ).bytes - } + fun cborDecodeByteString(data: ByteArray): ByteArray = + cborDecode(data).castToByteString().bytes @JvmStatic - fun cborDecodeDateTime(data: ByteArray): Timestamp { - return cborDecodeDateTime(cborDecode(data)) - } + fun cborDecodeDateTime(data: ByteArray): Timestamp = + cborDecodeDateTime(cborDecode(data)) @JvmStatic fun cborDecodeDateTime(di: DataItem): Timestamp { @@ -323,13 +306,12 @@ object Util { * * Also throws `IllegalArgumentException` if `value == null`. * */ - fun castTo(clazz: Class, value: V?): T { - return if (value == null || !clazz.isAssignableFrom(value.javaClass)) { + fun castTo(clazz: Class, value: V?): T = + if (value == null || !clazz.isAssignableFrom(value.javaClass)) { throw IllegalArgumentException("Expected type $clazz") } else { value as T } - } /** * Helper function to check if a given certificate chain is valid. @@ -353,16 +335,14 @@ object Util { // Check the previous certificate was signed by this one. try { prevCertificate.verify(certificate.publicKey) - } catch (e: CertificateException) { - return false - } catch (e: InvalidKeyException) { - return false - } catch (e: NoSuchAlgorithmException) { - return false - } catch (e: NoSuchProviderException) { - return false - } catch (e: SignatureException) { - return false + } catch (e: Exception) { + when (e) { + is CertificateException, + is InvalidKeyException, + is NoSuchAlgorithmException, + is NoSuchProviderException, + is SignatureException -> return false + } } } else { // we're the leaf certificate so we're not signing anything nor @@ -457,13 +437,13 @@ object Util { // Next field is the payload, independently of how it's transported (RFC // 8152 section 4.4). Since our API specifies only one of |data| and // |detachedContent| can be non-empty, it's simply just the non-empty one. - if (payload != null && payload.size > 0) { + if (payload != null && payload.isNotEmpty()) { array.add(payload) } else { array.add(detachedContent) } array.end() - return cborEncode(sigStructure.build().get(0)) + return cborEncode(sigStructure.build().first()) } /* @@ -484,16 +464,12 @@ object Util { } catch (e: IOException) { throw IllegalArgumentException("Error decoding DER signature", e) } - val asn1Encodables: Array = castTo( - ASN1Sequence::class.java, asn1 - ).toArray() + val asn1Encodables: Array = castTo(ASN1Sequence::class.java, asn1).toArray() require(asn1Encodables.size == 2) { "Expected two items in sequence" } - val r: BigInteger = castTo( - ASN1Integer::class.java, asn1Encodables[0].toASN1Primitive() - ).value - val s: BigInteger = castTo( - ASN1Integer::class.java, asn1Encodables[1].toASN1Primitive() - ).value + val r: BigInteger = + castTo(ASN1Integer::class.java, asn1Encodables[0].toASN1Primitive()).value + val s: BigInteger = + castTo(ASN1Integer::class.java, asn1Encodables[1].toASN1Primitive()).value val rBytes = stripLeadingZeroes(r.toByteArray()) val sBytes = stripLeadingZeroes(s.toByteArray()) val baos = ByteArrayOutputStream() @@ -517,14 +493,10 @@ object Util { // r and s are always positive and may use all bits so use the constructor which // parses them as unsigned. val r = BigInteger( - 1, Arrays.copyOfRange( - signature, 0, signature.size / 2 - ) + 1, signature.copyOfRange(0, signature.size / 2) ) val s = BigInteger( - 1, Arrays.copyOfRange( - signature, signature.size / 2, signature.size - ) + 1, signature.copyOfRange(signature.size / 2, signature.size) ) val baos = ByteArrayOutputStream() try { @@ -567,7 +539,7 @@ object Util { val protectedHeaders = CborBuilder() val protectedHeadersMap: MapBuilder = protectedHeaders.addMap() protectedHeadersMap.put(COSE_LABEL_ALG, alg) - val protectedHeadersBytes = cborEncode(protectedHeaders.build().get(0)) + val protectedHeadersBytes = cborEncode(protectedHeaders.build().first()) val toBeSigned = coseBuildToBeSigned(protectedHeadersBytes, data, detachedContent) val coseSignature = try { s.update(toBeSigned) @@ -581,7 +553,7 @@ object Util { array.add(protectedHeadersBytes) val unprotectedHeaders: MapBuilder> = array.addMap() try { - if (certificateChain != null && certificateChain.size > 0) { + if (!certificateChain.isNullOrEmpty()) { if (certificateChain.size == 1) { val cert = certificateChain.iterator().next() unprotectedHeaders.put(COSE_LABEL_X5CHAIN, cert.encoded) @@ -598,13 +570,13 @@ object Util { } catch (e: CertificateEncodingException) { throw IllegalStateException("Error encoding certificate", e) } - if (data == null || data.size == 0) { + if (data == null || data.isEmpty()) { array.add(SimpleValue(SimpleValueType.NULL)) } else { array.add(data) } array.add(coseSignature) - return builder.build().get(0) + return builder.build().first() } @JvmStatic @@ -623,8 +595,11 @@ object Util { require(!(dataLen > 0 && detachedContentLen > 0)) { "data and detachedContent cannot both be non-empty" } val protectedHeaders = CborBuilder() val protectedHeadersMap: MapBuilder = protectedHeaders.addMap() - protectedHeadersMap.put(COSE_LABEL_ALG, signatureAlgorithm.coseAlgorithmIdentifier.toLong()) - val protectedHeadersBytes = cborEncode(protectedHeaders.build().get(0)) + protectedHeadersMap.put( + COSE_LABEL_ALG, + signatureAlgorithm.coseAlgorithmIdentifier.toLong() + ) + val protectedHeadersBytes = cborEncode(protectedHeaders.build().first()) val toBeSigned = coseBuildToBeSigned(protectedHeadersBytes, data, detachedContent) val derSignature: ByteArray = secureArea.sign(alias, signatureAlgorithm, toBeSigned, keyUnlockData) @@ -634,7 +609,7 @@ object Util { array.add(protectedHeadersBytes) val unprotectedHeaders: MapBuilder> = array.addMap() try { - if (certificateChain != null && certificateChain.size > 0) { + if (!certificateChain.isNullOrEmpty()) { if (certificateChain.size == 1) { val cert = certificateChain.iterator().next() unprotectedHeaders.put(COSE_LABEL_X5CHAIN, cert.encoded) @@ -651,13 +626,13 @@ object Util { } catch (e: CertificateEncodingException) { throw IllegalStateException("Error encoding certificate", e) } - if (data == null || data.size == 0) { + if (data == null || data.isEmpty()) { array.add(SimpleValue(SimpleValueType.NULL)) } else { array.add(data) } array.add(coseSignature) - return builder.build().get(0) + return builder.build().first() } /** @@ -681,9 +656,8 @@ object Util { val s = Signature.getInstance(algorithm) s.initSign(key) coseSign1Sign(s, data, additionalData, certificateChain) - } catch (e: NoSuchAlgorithmException) { - throw IllegalStateException("Caught exception", e) - } catch (e: InvalidKeyException) { + } catch (e: Exception) { + // can be either NoSuchAlgorithmException, InvalidKeyException or for any exception throw IllegalStateException("Caught exception", e) } } @@ -744,11 +718,8 @@ object Util { verifier.initVerify(publicKey) verifier.update(toBeSigned) verifier.verify(derSignature) - } catch (e: SignatureException) { - throw IllegalStateException("Error verifying signature", e) - } catch (e: NoSuchAlgorithmException) { - throw IllegalStateException("Error verifying signature", e) - } catch (e: InvalidKeyException) { + } catch (e: Exception) { + // on any exception, such as SignatureException, NoSuchAlgorithmException, InvalidKeyException throw IllegalStateException("Error verifying signature", e) } } @@ -771,12 +742,12 @@ object Util { // Next field is the payload, independently of how it's transported (RFC // 8152 section 4.4). Since our API specifies only one of |data| and // |detachedContent| can be non-empty, it's simply just the non-empty one. - if (payload.size > 0) { + if (payload.isNotEmpty()) { array.add(payload) } else { array.add(detachedContent) } - return cborEncode(macStructure.build().get(0)) + return cborEncode(macStructure.build().first()) } @JvmStatic @@ -791,7 +762,7 @@ object Util { val protectedHeaders = CborBuilder() val protectedHeadersMap: MapBuilder = protectedHeaders.addMap() protectedHeadersMap.put(COSE_LABEL_ALG, COSE_ALG_HMAC_256_256) - val protectedHeadersBytes = cborEncode(protectedHeaders.build().get(0)) + val protectedHeadersBytes = cborEncode(protectedHeaders.build().first()) val toBeMACed = coseBuildToBeMACed(protectedHeadersBytes, data!!, detachedContent!!) val mac: ByteArray mac = try { @@ -814,19 +785,15 @@ object Util { array.add(data) } array.add(mac) - return builder.build().get(0) + return builder.build().first() } @JvmStatic fun coseMac0GetTag(coseMac0: DataItem): ByteArray { - val items = castTo( - co.nstant.`in`.cbor.model.Array::class.java, coseMac0 - ).dataItems + val items = coseMac0.castToArray().dataItems require(items.size >= 4) { "coseMac0 have less than 4 elements" } val tagItem = items[3] - return castTo( - ByteString::class.java, tagItem - ).bytes + return tagItem.castToByteString().bytes } /** @@ -882,9 +849,7 @@ object Util { @JvmStatic fun cborExtractTaggedCbor(encodedTaggedBytestring: ByteArray): ByteArray { val item = cborDecode(encodedTaggedBytestring) - val itemByteString = castTo( - ByteString::class.java, item - ) + val itemByteString = item.castToByteString() require(!(!item.hasTag() || item.tag.value != CBOR_SEMANTIC_TAG_ENCODED_CBOR)) { "ByteString is not tagged with tag 24" } return itemByteString.bytes } @@ -895,10 +860,7 @@ object Util { */ @JvmStatic fun cborExtractTaggedAndEncodedCbor(item: DataItem): DataItem { - val itemByteString = - castTo( - ByteString::class.java, item - ) + val itemByteString = item.castToByteString() require( !(!item.hasTag() || item.tag .value != CBOR_SEMANTIC_TAG_ENCODED_CBOR) @@ -913,21 +875,15 @@ object Util { @JvmStatic fun coseSign1GetData(coseSign1: DataItem): ByteArray { require(coseSign1.majorType == MajorType.ARRAY) { "Data item is not an array" } - val items = castTo( - co.nstant.`in`.cbor.model.Array::class.java, coseSign1 - ).dataItems + val items = coseSign1.castToArray().dataItems require(items.size >= 4) { "Expected at least four items in COSE_Sign1 array" } var payload = ByteArray(0) if (items[2].majorType == MajorType.SPECIAL) { require((items[2] as Special).specialType == SpecialType.SIMPLE_VALUE) { "Item 2 (payload) is a special but not a simple value" } - val simple: SimpleValue = castTo( - SimpleValue::class.java, items[2] - ) + val simple: SimpleValue = items[2].castToSimpleValue() require(simple.simpleValueType == SimpleValueType.NULL) { "Item 2 (payload) is a simple but not the value null" } } else if (items[2].majorType == MajorType.BYTE_STRING) { - payload = castTo( - ByteString::class.java, items[2] - ).bytes + payload = items[2].castToByteString().bytes } else { throw IllegalArgumentException("Item 2 (payload) is not nil or byte-string") } @@ -943,11 +899,9 @@ object Util { fun coseSign1GetX5Chain( coseSign1: DataItem ): List { - val ret = ArrayList() + val ret = mutableListOf() require(coseSign1.majorType == MajorType.ARRAY) { "Data item is not an array" } - val items = castTo( - co.nstant.`in`.cbor.model.Array::class.java, coseSign1 - ).dataItems + val items = coseSign1.castToArray().dataItems require(items.size >= 4) { "Expected at least four items in COSE_Sign1 array" } require(items[1].majorType == MajorType.MAP) { "Item 1 (unprotected headers) is not a map" } val map = items[1] as Map @@ -956,21 +910,11 @@ object Util { try { val factory = CertificateFactory.getInstance("X.509") if (x5chainItem is ByteString) { - val certBais = ByteArrayInputStream( - castTo( - ByteString::class.java, x5chainItem - ).bytes - ) + val certBais = ByteArrayInputStream(x5chainItem.castToByteString().bytes) ret.add(factory.generateCertificate(certBais) as X509Certificate) } else if (x5chainItem is co.nstant.`in`.cbor.model.Array) { - for (certItem in castTo( - co.nstant.`in`.cbor.model.Array::class.java, x5chainItem - ).dataItems) { - val certBais = ByteArrayInputStream( - castTo( - ByteString::class.java, certItem - ).bytes - ) + for (certItem in x5chainItem.castToArray().dataItems) { + val certBais = ByteArrayInputStream(certItem.castToByteString().bytes) ret.add(factory.generateCertificate(certBais) as X509Certificate) } } else { @@ -1010,7 +954,7 @@ object Util { .put(COSE_KEY_PARAM_CRV, curve.coseCurveIdentifier.toLong()) .put(COSE_KEY_PARAM_X, x) .end() - .build().get(0) + .build().first() } EcCurve.X25519, EcCurve.X448 -> { @@ -1021,7 +965,7 @@ object Util { .put(COSE_KEY_PARAM_CRV, curve.coseCurveIdentifier.toLong()) .put(COSE_KEY_PARAM_X, x) .end() - .build().get(0) + .build().first() } else -> { @@ -1037,7 +981,7 @@ object Util { .put(COSE_KEY_PARAM_X, x) .put(COSE_KEY_PARAM_Y, y) .end() - .build().get(0) + .build().first() } } return item @@ -1061,193 +1005,102 @@ object Util { .put(COSE_KEY_PARAM_X, x) .put(COSE_KEY_PARAM_Y, y) .end() - .build().get(0) + .build().first() } @JvmStatic - fun cborMapHasKey(map: DataItem, key: String): Boolean { - val item = castTo( - Map::class.java, map - )[UnicodeString(key)] - return item != null - } + fun cborMapHasKey(map: DataItem, key: String): Boolean = + map.castToMap()[key.toUnicodeString()] != null @JvmStatic - fun cborMapHasKey(map: DataItem, key: Long): Boolean { - val keyDataItem: DataItem = if (key >= 0) UnsignedInteger(key) else NegativeInteger(key) - val item = castTo( - Map::class.java, map - )[keyDataItem] - return item != null - } + fun cborMapHasKey(map: DataItem, key: Long): Boolean = + map.castToMap()[key.toIntegerNumber()] != null + @JvmStatic - fun cborMapExtractNumber(map: DataItem, key: Long): Long { - val keyDataItem: DataItem = if (key >= 0) UnsignedInteger(key) else NegativeInteger(key) - val item = castTo( - Map::class.java, map - )[keyDataItem] - return checkedLongValue(item) - } + fun cborMapExtractNumber(map: DataItem, key: Long): Long = + checkedLongValue(map.castToMap()[key.toIntegerNumber()]) + @JvmStatic - fun cborMapExtractNumber(map: DataItem, key: String): Long { - val item = castTo( - Map::class.java, map - )[UnicodeString(key)] - return checkedLongValue(item) - } + fun cborMapExtractNumber(map: DataItem, key: String): Long = + checkedLongValue(map.castToMap()[key.toUnicodeString()]) @JvmStatic - fun cborMapExtractString( - map: DataItem, - key: String - ): String { - val item = castTo( - Map::class.java, map - )[UnicodeString(key)] - return checkedStringValue(item) - } + fun cborMapExtractString(map: DataItem, key: String): String = + checkedStringValue(map.castToMap()[key.toUnicodeString()]) @JvmStatic - fun cborMapExtractString(map: DataItem, key: Long): String { - val keyDataItem: DataItem = if (key >= 0) UnsignedInteger(key) else NegativeInteger(key) - val item = castTo( - Map::class.java, map - )[keyDataItem] - return checkedStringValue(item) - } + fun cborMapExtractString(map: DataItem, key: Long): String = + checkedStringValue(map.castToMap()[key.toIntegerNumber()]) @JvmStatic - fun cborMapExtractArray( - map: DataItem, - key: String - ): List { - val item = castTo( - Map::class.java, map - )[UnicodeString(key)] - return castTo( - co.nstant.`in`.cbor.model.Array::class.java, item - ).dataItems + fun cborMapExtractArray(map: DataItem, key: String): List { + val item = map.castToMap()[key.toUnicodeString()] + return item.castToArray().dataItems } @JvmStatic fun cborMapExtractArray(map: DataItem, key: Long): List { - val keyDataItem: DataItem = if (key >= 0) UnsignedInteger(key) else NegativeInteger(key) - val item = castTo( - Map::class.java, map - )[keyDataItem] - return castTo( - co.nstant.`in`.cbor.model.Array::class.java, item - ).dataItems + val item = map.castToMap()[key.toIntegerNumber()] + return item.castToArray().dataItems } @JvmStatic - fun cborMapExtractMap( - map: DataItem, - key: String - ): DataItem { - val item = castTo( - Map::class.java, map - )[UnicodeString(key)] - return castTo(Map::class.java, item) + fun cborMapExtractMap(map: DataItem, key: String): DataItem { + val item = map.castToMap()[key.toUnicodeString()] + return item.castToMap() } @JvmStatic - fun cborMapExtractMapStringKeys(map: DataItem): Collection { - val ret: MutableList = ArrayList() - for (item in castTo( - Map::class.java, map - ).keys) { - ret.add(checkedStringValue(item)) - } - return ret - } + fun cborMapExtractMapStringKeys(map: DataItem): Collection = + map.castToMap().keys.map { checkedStringValue(it) } @JvmStatic - fun cborMapExtractMapNumberKeys(map: DataItem): Collection { - val ret: MutableList = ArrayList() - for (item in castTo( - Map::class.java, map - ).keys) { - ret.add(checkedLongValue(item)) - } - return ret - } + fun cborMapExtractMapNumberKeys(map: DataItem): Collection = + map.castToMap().keys.map { checkedLongValue(it) } @JvmStatic - fun cborMapExtractByteString( - map: DataItem, - key: Long - ): ByteArray { - val keyDataItem: DataItem = if (key >= 0) UnsignedInteger(key) else NegativeInteger(key) - val item = castTo( - Map::class.java, map - )[keyDataItem] - return castTo( - ByteString::class.java, item - ).bytes + fun cborMapExtractByteString(map: DataItem, key: Long): ByteArray { + val item = map.castToMap()[key.toIntegerNumber()] + return item.castToByteString().bytes } @JvmStatic - fun cborMapExtractByteString( - map: DataItem, - key: String - ): ByteArray { - val item = castTo( - Map::class.java, map - )[UnicodeString(key)] - return castTo( - ByteString::class.java, item - ).bytes + fun cborMapExtractByteString(map: DataItem, key: String): ByteArray { + val item = map.castToMap()[key.toUnicodeString()] + return item.castToByteString().bytes } @JvmStatic fun cborMapExtractBoolean(map: DataItem, key: String): Boolean { - val item = castTo( - Map::class.java, map - )[UnicodeString(key)] - return castTo( - SimpleValue::class.java, - item - ).simpleValueType == SimpleValueType.TRUE + val item = map.castToMap()[key.toUnicodeString()] + return item.castToSimpleValue().simpleValueType == SimpleValueType.TRUE } @JvmStatic fun cborMapExtractBoolean(map: DataItem, key: Long): Boolean { - val keyDataItem: DataItem = if (key >= 0) UnsignedInteger(key) else NegativeInteger(key) - val item = castTo( - Map::class.java, map - )[keyDataItem] - return castTo( - SimpleValue::class.java, - item - ).simpleValueType == SimpleValueType.TRUE - } - - @JvmStatic - fun cborMapExtractDateTime(map: DataItem, key: String?): Timestamp { - val item = castTo( - Map::class.java, map - )[UnicodeString(key)] - val unicodeString: UnicodeString = castTo( - UnicodeString::class.java, item - ) - return cborDecodeDateTime(unicodeString) + val keyDataItem: DataItem = key.toIntegerNumber() + val item = map.castToMap()[keyDataItem] + return item.castToSimpleValue().simpleValueType == SimpleValueType.TRUE } @JvmStatic - fun cborMapExtract(map: DataItem, key: String): DataItem { - return castTo( - Map::class.java, map - )[UnicodeString(key)] - ?: throw IllegalArgumentException("Expected item") + fun cborMapExtractDateTime(map: DataItem, key: String): Timestamp { + val item = map.castToMap()[key.toUnicodeString()] + val unicodeString: UnicodeString = item.castToUnicodeString() + return cborDecodeDateTime(unicodeString) } @JvmStatic - fun coseKeyGetCurve(coseKey: DataItem): EcCurve { - return EcCurve.fromInt(cborMapExtractNumber(coseKey, COSE_KEY_PARAM_CRV).toInt()) - } + fun cborMapExtract(map: DataItem, key: String): DataItem = + map.castToMap()[key.toUnicodeString()] + ?: throw IllegalArgumentException("Expected item for key $key") + + + @JvmStatic + fun coseKeyGetCurve(coseKey: DataItem): EcCurve = + EcCurve.fromInt(cborMapExtractNumber(coseKey, COSE_KEY_PARAM_CRV).toInt()) private fun coseKeyDecodeEc2(coseKey: DataItem): PublicKey { val crv: EcCurve = @@ -1290,13 +1143,8 @@ object Util { ECPublicKeySpec(ecPoint, ecParameters) val kf = KeyFactory.getInstance("EC") kf.generatePublic(keySpec) as ECPublicKey - } catch (e: NoSuchAlgorithmException) { - throw IllegalStateException("Unexpected error", e) - } catch (e: InvalidParameterSpecException) { - throw IllegalStateException("Unexpected error", e) - } catch (e: InvalidKeySpecException) { - throw IllegalStateException("Unexpected error", e) - } catch (e: NoSuchProviderException) { + } catch (e: Exception) { + // any exception. such as NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException, NoSuchProviderException throw IllegalStateException("Unexpected error", e) } } @@ -1346,13 +1194,8 @@ object Util { baos.write(prefix) baos.write(encodedX) kf.generatePublic(X509EncodedKeySpec(baos.toByteArray())) - } catch (e: NoSuchAlgorithmException) { - throw IllegalStateException("Unexpected error", e) - } catch (e: InvalidKeySpecException) { - throw IllegalStateException("Unexpected error", e) - } catch (e: IOException) { - throw IllegalStateException("Unexpected error", e) - } catch (e: NoSuchProviderException) { + } catch (e: Exception) { + // any exception, such as NoSuchAlgorithmException, InvalidKeySpecException, IOException, NoSuchProviderException throw IllegalStateException("Unexpected error", e) } } @@ -1369,12 +1212,10 @@ object Util { @JvmStatic fun coseKeyDecode(coseKey: DataItem): PublicKey { val kty = cborMapExtractNumber(coseKey, COSE_KEY_KTY) - return if (kty == COSE_KEY_TYPE_EC2) { - coseKeyDecodeEc2(coseKey) - } else if (kty == COSE_KEY_TYPE_OKP) { - coseKeyDecodeOkp(coseKey) - } else { - throw IllegalArgumentException("Expected COSE_KEY_TYPE_EC2 or COSE_KEY_TYPE_OKP, got $kty") + return when (kty) { + COSE_KEY_TYPE_EC2 -> coseKeyDecodeEc2(coseKey) + COSE_KEY_TYPE_OKP -> coseKeyDecodeOkp(coseKey) + else -> throw IllegalArgumentException("Expected COSE_KEY_TYPE_EC2 or COSE_KEY_TYPE_OKP, got $kty") } } @@ -1397,15 +1238,22 @@ object Util { ) val salt = MessageDigest.getInstance("SHA-256").digest(sessionTranscriptBytes) - val info = byteArrayOf( - 'E'.code.toByte(), - 'M'.code.toByte(), - 'a'.code.toByte(), - 'c'.code.toByte(), - 'K'.code.toByte(), - 'e'.code.toByte(), - 'y'.code.toByte() - ) + + val infoChars = "EMacKey" + val info = infoChars.map { ch -> + ch.code.toByte() + }.toByteArray() + + //TODO dx cleanup +// val info = byteArrayOf( +// 'E'.code.toByte(), +// 'M'.code.toByte(), +// 'a'.code.toByte(), +// 'c'.code.toByte(), +// 'K'.code.toByte(), +// 'e'.code.toByte(), +// 'y'.code.toByte() +// ) val derivedKey = computeHkdf( "HmacSha256", sharedSecret, @@ -1414,9 +1262,8 @@ object Util { 32 ) SecretKeySpec(derivedKey, "") - } catch (e: InvalidKeyException) { - throw IllegalStateException("Error performing key agreement", e) - } catch (e: NoSuchAlgorithmException) { + } catch (e: Exception) { + // including InvalidKeyException, NoSuchAlgorithmException throw IllegalStateException("Error performing key agreement", e) } } @@ -1532,11 +1379,10 @@ object Util { sb.append("]") } else { sb.append("[\n$indentString") - var count = 0 - for (item in items) { + for ((count, item) in items.withIndex()) { sb.append(" ") cborPrettyPrintDataItem(sb, indent + 2, item) - if (++count < items.size) { + if (count + 1 < items.size) { sb.append(",") } sb.append("\n" + indentString) @@ -1549,18 +1395,17 @@ object Util { // Major type 5: a map of pairs of data items. val keys = (dataItem as Map).keys - if (keys.size == 0) { + if (keys.isEmpty()) { sb.append("{}") } else { sb.append("{\n$indentString") - var count = 0 - for (key in keys) { + for ((count, key) in keys.withIndex()) { sb.append(" ") val value = dataItem[key] cborPrettyPrintDataItem(sb, indent + 2, key) sb.append(" : ") cborPrettyPrintDataItem(sb, indent + 2, value) - if (++count < keys.size) { + if (count + 1 < keys.size) { sb.append(",") } sb.append("\n" + indentString) @@ -1580,6 +1425,7 @@ object Util { SimpleValueType.UNDEFINED -> sb.append("undefined") SimpleValueType.RESERVED -> sb.append("reserved") SimpleValueType.UNALLOCATED -> sb.append("unallocated") + null -> {} // Java case } } else if (dataItem is DoublePrecisionFloat) { val df = DecimalFormat( @@ -1598,13 +1444,14 @@ object Util { } else { sb.append("break") } + + // Java case: "Enum argument can be null in Java, but exhaustive when contains no null branch" + null -> {} } } @JvmStatic - fun canonicalizeCbor(encodedCbor: ByteArray): ByteArray { - return cborEncode(cborDecode(encodedCbor)) - } + fun canonicalizeCbor(encodedCbor: ByteArray): ByteArray = cborEncode(cborDecode(encodedCbor)) @JvmStatic fun replaceLine( @@ -1695,7 +1542,7 @@ object Util { entryNameMapBuilder.put(entryName, false) } } - return cborEncode(builder.build().get(0)) + return cborEncode(builder.build().first()) } @JvmStatic @@ -1707,16 +1554,12 @@ object Util { val bais = ByteArrayInputStream(cborBytes) val dataItems: List = CborDecoder(bais).decode() require(dataItems.size == 1) { "Expected 1 item, found ${dataItems.size}" } - val array = castTo( - co.nstant.`in`.cbor.model.Array::class.java, dataItems[0] - ) + val array = dataItems[0].castToArray() val items = array.dataItems require(items.size >= 2) { "Expected at least 2 array items, found ${items.size}" } val id = checkedStringValue(items[0]) require(id == "ProofOfBinding") { "Expected ProofOfBinding, got $id" } - val popSha256 = castTo( - ByteString::class.java, items[1] - ).bytes + val popSha256 = items[1].castToByteString().bytes require(popSha256.size == 32) { "Expected bstr to be 32 bytes, it is ${popSha256.size}" } popSha256 } catch (e: IOException) { @@ -1753,9 +1596,7 @@ object Util { encodedElementValue: ByteArray ): ByteArray { val issuerSignedItemElem = cborDecode(encodedIssuerSignedItem) - val issuerSignedItem = castTo( - Map::class.java, issuerSignedItemElem - ) + val issuerSignedItem = issuerSignedItemElem.castToMap() val elementValue = cborDecode(encodedElementValue) issuerSignedItem.put(UnicodeString("elementValue"), elementValue) @@ -1774,11 +1615,8 @@ object Util { val privateKeySpec = ECPrivateKeySpec(s, ecParameters) val keyFactory = KeyFactory.getInstance("EC") keyFactory.generatePrivate(privateKeySpec) - } catch (e: NoSuchAlgorithmException) { - throw IllegalStateException(e) - } catch (e: InvalidParameterSpecException) { - throw IllegalStateException(e) - } catch (e: InvalidKeySpecException) { + } catch (e: Exception) { + // including NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException throw IllegalStateException(e) } } @@ -1801,11 +1639,8 @@ object Util { ECPublicKeySpec(ecPoint, ecParameters) val kf = KeyFactory.getInstance("EC") kf.generatePublic(keySpec) as ECPublicKey - } catch (e: NoSuchAlgorithmException) { - throw IllegalStateException("Unexpected error", e) - } catch (e: InvalidParameterSpecException) { - throw IllegalStateException("Unexpected error", e) - } catch (e: InvalidKeySpecException) { + } catch (e: Exception) { + // including NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException throw IllegalStateException("Unexpected error", e) } } @@ -1836,7 +1671,7 @@ object Util { fun extractDeviceRetrievalMethods( encodedDeviceEngagement: ByteArray ): List { - val ret: MutableList = ArrayList() + val ret = mutableListOf() val deviceEngagement = cborDecode(encodedDeviceEngagement) val methods = cborMapExtractArray(deviceEngagement, 2) for (method in methods) { @@ -1953,19 +1788,8 @@ object Util { val bais = ByteArrayInputStream(resultingCertBytes) cf.generateCertificate(bais) as X509Certificate - } catch (e: IOException) { - throw IllegalStateException("Error signing key with private key", e) - } catch (e: InvalidKeyException) { - throw IllegalStateException("Error signing key with private key", e) - } catch (e: KeyStoreException) { - throw IllegalStateException("Error signing key with private key", e) - } catch (e: NoSuchAlgorithmException) { - throw IllegalStateException("Error signing key with private key", e) - } catch (e: SignatureException) { - throw IllegalStateException("Error signing key with private key", e) - } catch (e: UnrecoverableEntryException) { - throw IllegalStateException("Error signing key with private key", e) - } catch (e: CertificateException) { + } catch (e: Exception) { + // including IOException, InvalidKeyException, KeyStoreException, NoSuchAlgorithmException, SignatureException, UnrecoverableEntryException, CertificateException throw IllegalStateException("Error signing key with private key", e) } } @@ -2004,10 +1828,11 @@ object Util { .addArray() .add(blobWithCoords) .end() - .build().get(0) + .build().first() ) ) - val encodedEReaderKeyItem = cborBuildTaggedByteString(cborEncodeString("doesn't matter")) + val encodedEReaderKeyItem = + cborBuildTaggedByteString(cborEncodeString("doesn't matter")) baos = ByteArrayOutputStream() try { val handoverSelectBytes = byteArrayOf(0x01, 0x02, 0x03) @@ -2016,7 +1841,7 @@ object Util { .add(handoverSelectBytes) .add(SimpleValue.NULL) .end() - .build().get(0) + .build().first() CborEncoder(baos).encode( CborBuilder() .addArray() @@ -2052,9 +1877,7 @@ object Util { } catch (e: CborException) { return -1 } - return if (dataItem == null) { - -1 - } else cborEncode(dataItem).size + return if (dataItem == null) { -1 } else cborEncode(dataItem).size } /** @@ -2198,12 +2021,26 @@ object Util { kpg.initialize(ECGenParameterSpec(stdName)) } kpg.generateKeyPair() - } catch (e: NoSuchAlgorithmException) { - throw IllegalStateException("Error generating ephemeral key-pair", e) - } catch (e: InvalidAlgorithmParameterException) { - throw IllegalStateException("Error generating ephemeral key-pair", e) - } catch (e: NoSuchProviderException) { + } catch (e: Exception) { + // any exception, such as NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException throw IllegalStateException("Error generating ephemeral key-pair", e) } } + + /* + Extension functions that are used inside this Util object + */ + + // DataItem casting helpers, "castTo_" + private fun DataItem.castToMap() = castTo(Map::class.java, this) + private fun DataItem.castToByteString() = castTo(ByteString::class.java, this) + private fun DataItem.castToSimpleValue() = castTo(SimpleValue::class.java, this) + private fun DataItem.castToNumber() = castTo(Number::class.java, this) + private fun DataItem.castToUnicodeString() = castTo(UnicodeString::class.java, this) + private fun DataItem.castToArray() = castTo(co.nstant.`in`.cbor.model.Array::class.java, this) + + // type converters "to_" + private fun String.toUnicodeString() = UnicodeString(this) + private fun Long.toIntegerNumber() = + if (this > 0) UnsignedInteger(this) else NegativeInteger(this) } \ No newline at end of file diff --git a/identity/src/main/java/com/android/identity/securearea/EcCurve.kt b/identity/src/main/java/com/android/identity/securearea/EcCurve.kt index 44c240530..cd74f2d63 100644 --- a/identity/src/main/java/com/android/identity/securearea/EcCurve.kt +++ b/identity/src/main/java/com/android/identity/securearea/EcCurve.kt @@ -41,10 +41,9 @@ enum class EcCurve(val coseCurveIdentifier: Int) { X448(5); companion object { - fun fromInt(coseCurveIdentifier: Int): EcCurve { - return EcCurve.values().find { it.coseCurveIdentifier == coseCurveIdentifier } + fun fromInt(coseCurveIdentifier: Int): EcCurve = + EcCurve.values().find { it.coseCurveIdentifier == coseCurveIdentifier } ?: throw IllegalArgumentException("No curve with COSE identifier $coseCurveIdentifier") - } } /** @@ -82,5 +81,4 @@ enum class EcCurve(val coseCurveIdentifier: Int) { X448 -> "x448" ED448 -> "ed448" } - } \ No newline at end of file diff --git a/identity/src/main/java/com/android/identity/securearea/SecureAreaRepository.kt b/identity/src/main/java/com/android/identity/securearea/SecureAreaRepository.kt index 2e0834c2e..bec82665a 100644 --- a/identity/src/main/java/com/android/identity/securearea/SecureAreaRepository.kt +++ b/identity/src/main/java/com/android/identity/securearea/SecureAreaRepository.kt @@ -40,21 +40,13 @@ class SecureAreaRepository { * @param identifier the identifier for the Secure Area. * @return the implementation or `null` if no implementation has been registered. */ - fun getImplementation(identifier: String): SecureArea? { - for (implementation in privateImplementations) { - if (implementation.identifier == identifier) { - return implementation - } - } - return null - } + fun getImplementation(identifier: String): SecureArea? = + privateImplementations.firstOrNull { it.identifier == identifier } /** * Adds a [SecureArea] to the repository. * * @param secureArea an instance of a type implementing the [SecureArea] interface. */ - fun addImplementation(secureArea: SecureArea) { - privateImplementations.add(secureArea) - } + fun addImplementation(secureArea: SecureArea) = privateImplementations.add(secureArea) } \ No newline at end of file diff --git a/identity/src/main/java/com/android/identity/securearea/software/SoftwareCreateKeySettings.kt b/identity/src/main/java/com/android/identity/securearea/software/SoftwareCreateKeySettings.kt index a19ab1b09..8e4551ac7 100644 --- a/identity/src/main/java/com/android/identity/securearea/software/SoftwareCreateKeySettings.kt +++ b/identity/src/main/java/com/android/identity/securearea/software/SoftwareCreateKeySettings.kt @@ -31,17 +31,22 @@ class SoftwareCreateKeySettings private constructor( * * @param attestationChallenge challenge to include in attestation for the key. */ - class Builder(private val attestationChallenge: ByteArray) { - private var keyPurposes = setOf(KeyPurpose.SIGN) - private var ecCurve = EcCurve.P256 - private var passphraseRequired = false - private var passphrase: String? = "" - private var subject: String? = null - private var validFrom: Timestamp? = null - private var validUntil: Timestamp? = null - private var attestationKey: PrivateKey? = null - private var attestationKeySignatureAlgorithm: String? = null + class Builder( + private val attestationChallenge: ByteArray, + private var keyPurposes: Set = setOf(KeyPurpose.SIGN), + private var ecCurve: EcCurve = EcCurve.P256, + private var passphraseRequired: Boolean = false, + private var passphrase: String? = "", + private var subject: String? = null, + private var validFrom: Timestamp? = null, + private var validUntil: Timestamp? = null, + private var attestationKey: PrivateKey? = null, + private var attestationKeySignatureAlgorithm: String? = null, private var attestationKeyCertification: List? = null + ) { + constructor(challenge: ByteArray) : this(challenge, setOf(KeyPurpose.SIGN)) { + + } /** * Sets the attestation key to use for attesting to the key. @@ -57,11 +62,10 @@ class SoftwareCreateKeySettings private constructor( attestationKey: PrivateKey, attestationKeySignatureAlgorithm: String, attestationKeyCertification: List - ): Builder { + ) = apply { this.attestationKey = attestationKey this.attestationKeySignatureAlgorithm = attestationKeySignatureAlgorithm this.attestationKeyCertification = attestationKeyCertification - return this } /** @@ -73,10 +77,9 @@ class SoftwareCreateKeySettings private constructor( * @return the builder. * @throws IllegalArgumentException if no purpose is set. */ - fun setKeyPurposes(keyPurposes: Set): Builder { + fun setKeyPurposes(keyPurposes: Set) = apply { require(!keyPurposes.isEmpty()) { "Purposes cannot be empty" } this.keyPurposes = keyPurposes - return this } /** @@ -87,9 +90,8 @@ class SoftwareCreateKeySettings private constructor( * @param curve the curve to use. * @return the builder. */ - fun setEcCurve(curve: EcCurve): Builder { + fun setEcCurve(curve: EcCurve) = apply { ecCurve = curve - return this } /** @@ -99,11 +101,10 @@ class SoftwareCreateKeySettings private constructor( * @param passphrase the passphrase to use, must not be `null` if `required` is `true`. * @return the builder. */ - fun setPassphraseRequired(required: Boolean, passphrase: String?): Builder { + fun setPassphraseRequired(required: Boolean, passphrase: String?) = apply { check(!(passphraseRequired && passphrase == null)) { "Passphrase cannot be null if it's required" } passphraseRequired = required this.passphrase = passphrase - return this } /** @@ -112,9 +113,8 @@ class SoftwareCreateKeySettings private constructor( * @param subject subject field * @return the builder. */ - fun setSubject(subject: String?): Builder { + fun setSubject(subject: String?) = apply { this.subject = subject - return this } /** @@ -126,13 +126,9 @@ class SoftwareCreateKeySettings private constructor( * @param validUntil the point in time after which the key is not valid. * @return the builder. */ - fun setValidityPeriod( - validFrom: Timestamp, - validUntil: Timestamp - ): Builder { + fun setValidityPeriod(validFrom: Timestamp, validUntil: Timestamp) = apply { this.validFrom = validFrom this.validUntil = validUntil - return this } /** @@ -140,14 +136,13 @@ class SoftwareCreateKeySettings private constructor( * * @return a new [SoftwareCreateKeySettings]. */ - fun build(): SoftwareCreateKeySettings { - return SoftwareCreateKeySettings( + fun build(): SoftwareCreateKeySettings = + SoftwareCreateKeySettings( passphraseRequired, passphrase!!, ecCurve, keyPurposes, attestationChallenge, subject, validFrom, validUntil, attestationKey, attestationKeySignatureAlgorithm, attestationKeyCertification ) - } } } \ No newline at end of file diff --git a/identity/src/main/java/com/android/identity/securearea/software/SoftwareSecureArea.kt b/identity/src/main/java/com/android/identity/securearea/software/SoftwareSecureArea.kt index be7182479..a55f83226 100644 --- a/identity/src/main/java/com/android/identity/securearea/software/SoftwareSecureArea.kt +++ b/identity/src/main/java/com/android/identity/securearea/software/SoftwareSecureArea.kt @@ -200,13 +200,8 @@ class SoftwareSecureArea(private val storageEngine: StorageEngine) : SecureArea val encryptedPrivateKey = baos.toByteArray() map.put("publicKey", keyPair.public.encoded) map.put("encryptedPrivateKey", encryptedPrivateKey) - } catch (e: NoSuchPaddingException) { - throw IllegalStateException("Error encrypting private key", e) - } catch (e: IllegalBlockSizeException) { - throw IllegalStateException("Error encrypting private key", e) - } catch (e: BadPaddingException) { - throw IllegalStateException("Error encrypting private key", e) - } catch (e: InvalidKeyException) { + } catch (e: Exception) { + // such as NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException throw IllegalStateException("Error encrypting private key", e) } } @@ -268,17 +263,8 @@ class SoftwareSecureArea(private val storageEngine: StorageEngine) : SecureArea } attestationBuilder.end() storageEngine.put(PREFIX + alias, cborEncode(builder.build().get(0))) - } catch (e: NoSuchAlgorithmException) { - throw IllegalStateException("Unexpected exception", e) - } catch (e: CertificateException) { - throw IllegalStateException("Unexpected exception", e) - } catch (e: InvalidAlgorithmParameterException) { - throw IllegalStateException("Unexpected exception", e) - } catch (e: OperatorCreationException) { - throw IllegalStateException("Unexpected exception", e) - } catch (e: IOException) { - throw IllegalStateException("Unexpected exception", e) - } catch (e: NoSuchProviderException) { + } catch (e: Exception) { + // such as NoSuchAlgorithmException, CertificateException, InvalidAlgorithmParameterException, OperatorCreationException, IOException, NoSuchProviderException throw IllegalStateException("Unexpected exception", e) } } @@ -350,17 +336,8 @@ class SoftwareSecureArea(private val storageEngine: StorageEngine) : SecureArea byteBuffer[cipherText] cipher.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(128, iv)) cipher.doFinal(cipherText) - } catch (e: NoSuchAlgorithmException) { - throw KeyLockedException("Error decrypting private key", e) - } catch (e: NoSuchPaddingException) { - throw KeyLockedException("Error decrypting private key", e) - } catch (e: IllegalBlockSizeException) { - throw KeyLockedException("Error decrypting private key", e) - } catch (e: BadPaddingException) { - throw KeyLockedException("Error decrypting private key", e) - } catch (e: InvalidKeyException) { - throw KeyLockedException("Error decrypting private key", e) - } catch (e: InvalidAlgorithmParameterException) { + } catch (e: Exception) { + // such as NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException throw KeyLockedException("Error decrypting private key", e) } } else { @@ -370,11 +347,8 @@ class SoftwareSecureArea(private val storageEngine: StorageEngine) : SecureArea val privateKey = try { val ecKeyFac = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME) ecKeyFac.generatePrivate(encodedKeySpec) - } catch (e: NoSuchAlgorithmException) { - throw IllegalStateException("Error loading private key", e) - } catch (e: InvalidKeySpecException) { - throw IllegalStateException("Error loading private key", e) - } catch (e: NoSuchProviderException) { + } catch (e: Exception) { + // such as NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException throw IllegalStateException("Error loading private key", e) } return KeyData(curve, keyPurposes, privateKey) @@ -429,13 +403,8 @@ class SoftwareSecureArea(private val storageEngine: StorageEngine) : SecureArea s.initSign(keyData.privateKey) s.update(dataToSign) s.sign() - } catch (e: NoSuchAlgorithmException) { - throw IllegalStateException("Unexpected Exception", e) - } catch (e: SignatureException) { - throw IllegalStateException("Unexpected Exception", e) - } catch (e: InvalidKeyException) { - throw IllegalStateException("Unexpected Exception", e) - } catch (e: NoSuchProviderException) { + } catch (e: Exception) { + // such as NoSuchAlgorithmException, SignatureException, InvalidKeyException, NoSuchProviderException throw IllegalStateException("Unexpected Exception", e) } } @@ -453,9 +422,8 @@ class SoftwareSecureArea(private val storageEngine: StorageEngine) : SecureArea ka.init(keyData.privateKey) ka.doPhase(otherKey, true) ka.generateSecret() - } catch (e: NoSuchAlgorithmException) { - throw IllegalStateException("Unexpected Exception", e) - } catch (e: InvalidKeyException) { + } catch (e: Exception) { + // such as NoSuchAlgorithmException, InvalidKeyException throw IllegalStateException("Unexpected Exception", e) } } diff --git a/identity/src/main/java/com/android/identity/storage/EphemeralStorageEngine.kt b/identity/src/main/java/com/android/identity/storage/EphemeralStorageEngine.kt index 7f6d438d8..0bf22e050 100644 --- a/identity/src/main/java/com/android/identity/storage/EphemeralStorageEngine.kt +++ b/identity/src/main/java/com/android/identity/storage/EphemeralStorageEngine.kt @@ -23,9 +23,7 @@ package com.android.identity.storage class EphemeralStorageEngine : StorageEngine { private val data: MutableMap = LinkedHashMap() - override fun get(key: String): ByteArray? { - return data[key] - } + override fun get(key: String): ByteArray? = data[key] override fun put(key: String, data: ByteArray) { this.data[key] = data @@ -39,7 +37,5 @@ class EphemeralStorageEngine : StorageEngine { data.clear() } - override fun enumerate(): Collection { - return data.keys - } + override fun enumerate(): Collection =data.keys } \ No newline at end of file diff --git a/identity/src/main/java/com/android/identity/storage/GenericStorageEngine.kt b/identity/src/main/java/com/android/identity/storage/GenericStorageEngine.kt index 0fe9dbf71..b3515dec8 100644 --- a/identity/src/main/java/com/android/identity/storage/GenericStorageEngine.kt +++ b/identity/src/main/java/com/android/identity/storage/GenericStorageEngine.kt @@ -38,29 +38,27 @@ class GenericStorageEngine(private val storageDirectory: File) : StorageEngine { private const val PREFIX = "IC_GenericStorageEngine_" } - private fun getTargetFile(name: String): File { - return try { + private fun getTargetFile(name: String): File = + try { val fileName = PREFIX + URLEncoder.encode(name, "UTF-8") File(storageDirectory, fileName) } catch (e: UnsupportedEncodingException) { throw IllegalStateException("Unexpected UnsupportedEncodingException", e) } - } - override fun get(key: String): ByteArray? { - val file = getTargetFile(key) - return try { + override fun get(key: String): ByteArray? = + try { + val file = getTargetFile(key) if (!Files.exists(file.toPath())) { null } else Files.readAllBytes(file.toPath()) } catch (e: IOException) { throw IllegalStateException("Unexpected exception", e) } - } override fun put(key: String, data: ByteArray) { - val file = getTargetFile(key) try { + val file = getTargetFile(key) // TODO: do this atomically Files.deleteIfExists(file.toPath()) Files.write(file.toPath(), data, StandardOpenOption.CREATE_NEW) @@ -69,8 +67,8 @@ class GenericStorageEngine(private val storageDirectory: File) : StorageEngine { } } - override fun delete(name: String) { - val file = getTargetFile(name) + override fun delete(key: String) { + val file = getTargetFile(key) try { Files.deleteIfExists(file.toPath()) } catch (e: IOException) { diff --git a/identity/src/main/java/com/android/identity/util/SimpleApplicationData.kt b/identity/src/main/java/com/android/identity/util/SimpleApplicationData.kt index a40656678..9e55e01da 100644 --- a/identity/src/main/java/com/android/identity/util/SimpleApplicationData.kt +++ b/identity/src/main/java/com/android/identity/util/SimpleApplicationData.kt @@ -25,6 +25,13 @@ import co.nstant.`in`.cbor.model.UnicodeString import com.android.identity.credential.NameSpacedData import com.android.identity.credential.NameSpacedData.Companion.fromEncodedCbor import com.android.identity.internal.Util +import com.android.identity.internal.Util.cborDecodeBoolean +import com.android.identity.internal.Util.cborDecodeLong +import com.android.identity.internal.Util.cborDecodeString +import com.android.identity.internal.Util.cborEncode +import com.android.identity.internal.Util.cborEncodeBoolean +import com.android.identity.internal.Util.cborEncodeNumber +import com.android.identity.internal.Util.cborEncodeString import java.io.ByteArrayInputStream /** @@ -35,10 +42,9 @@ import java.io.ByteArrayInputStream * @param onDataChanged callback invoked whenever changes are made to a key that is * if it's added, changed, or removed. */ -class SimpleApplicationData( - private val onDataChanged: (key: String) -> Unit -) : ApplicationData { - private val data: MutableMap = LinkedHashMap() +class SimpleApplicationData(private val onDataChanged: (key: String) -> Unit) : ApplicationData { + + private val data = mutableMapOf() override fun setData(key: String, value: ByteArray?): ApplicationData { if (value == null) { @@ -50,50 +56,30 @@ class SimpleApplicationData( return this } - override fun setString(key: String, value: String): ApplicationData { - return setData(key, Util.cborEncodeString(value)) - } + override fun setString(key: String, value: String): ApplicationData = + setData(key, cborEncodeString(value)) - override fun setNumber(key: String, value: Long): ApplicationData { - return setData(key, Util.cborEncodeNumber(value)) - } + override fun setNumber(key: String, value: Long): ApplicationData = + setData(key, cborEncodeNumber(value)) - override fun setBoolean(key: String, value: Boolean): ApplicationData { - return setData(key, Util.cborEncodeBoolean(value)) - } + override fun setBoolean(key: String, value: Boolean): ApplicationData = + setData(key, cborEncodeBoolean(value)) - override fun setNameSpacedData(key: String, value: NameSpacedData): ApplicationData { - return setData(key, value.encodeAsCbor()) - } + override fun setNameSpacedData(key: String, value: NameSpacedData): ApplicationData = + setData(key, value.encodeAsCbor()) - override fun keyExists(key: String): Boolean { - return data[key] != null - } + override fun keyExists(key: String): Boolean = data[key] != null - override fun getData(key: String): ByteArray { - return data[key] - ?: throw IllegalArgumentException("Key '$key' is not present") - } + override fun getData(key: String): ByteArray = + data[key] ?: throw IllegalArgumentException("Key '$key' is not present") - override fun getString(key: String): String { - val value = getData(key) - return Util.cborDecodeString(value) - } + override fun getString(key: String): String = cborDecodeString(getData(key)) - override fun getNumber(key: String): Long { - val value = getData(key) - return Util.cborDecodeLong(value) - } + override fun getNumber(key: String): Long = cborDecodeLong(getData(key)) - override fun getBoolean(key: String): Boolean { - val value = getData(key) - return Util.cborDecodeBoolean(value) - } + override fun getBoolean(key: String): Boolean = cborDecodeBoolean(getData(key)) - override fun getNameSpacedData(key: String): NameSpacedData { - val value = getData(key) - return fromEncodedCbor(value) - } + override fun getNameSpacedData(key: String): NameSpacedData = fromEncodedCbor(getData(key)) /** * Encode the [ApplicationData] as a byte[] using [CBOR](http://cbor.io/). @@ -107,7 +93,7 @@ class SimpleApplicationData( appDataMapBuilder.put(key, data[key]) } appDataMapBuilder.end() - return Util.cborEncode(appDataBuilder.build().get(0)) + return cborEncode(appDataBuilder.build().get(0)) } companion object { @@ -129,8 +115,7 @@ class SimpleApplicationData( onDataChanged: (key: String) -> Unit ): SimpleApplicationData { val bais = ByteArrayInputStream(encodedApplicationData) - val dataItems: List - dataItems = try { + val dataItems: List = try { CborDecoder(bais).decode() } catch (e: CborException) { throw IllegalStateException("Error decoding CBOR", e) diff --git a/identity/src/main/java/com/android/identity/util/Timestamp.kt b/identity/src/main/java/com/android/identity/util/Timestamp.kt index 31a43d2b0..9fdc7b361 100644 --- a/identity/src/main/java/com/android/identity/util/Timestamp.kt +++ b/identity/src/main/java/com/android/identity/util/Timestamp.kt @@ -25,30 +25,21 @@ class Timestamp private constructor(private val epochMillis: Long) { /** * @return this represented as the number of milliseconds since midnight, January 1, 1970 UTC. */ - fun toEpochMilli(): Long { - return epochMillis - } + fun toEpochMilli(): Long = epochMillis - override fun toString(): String { - return "Timestamp{epochMillis=$epochMillis}" - } + override fun toString(): String = "Timestamp{epochMillis=$epochMillis}" - override fun equals(other: Any?): Boolean { - return other is Timestamp && other.epochMillis == epochMillis - } + override fun equals(other: Any?): Boolean = + other is Timestamp && other.epochMillis == epochMillis - override fun hashCode(): Int { - return java.lang.Long.hashCode(epochMillis) - } + override fun hashCode(): Int = java.lang.Long.hashCode(epochMillis) companion object { /** * @return a `Timestamp` representing the current time */ @JvmStatic - fun now(): Timestamp { - return Timestamp(System.currentTimeMillis()) - } + fun now(): Timestamp = Timestamp(System.currentTimeMillis()) /** * @param epochMillis A time represented as the number of milliseconds since midnight, @@ -56,8 +47,6 @@ class Timestamp private constructor(private val epochMillis: Long) { * @return a `Timestamp` representing the given time */ @JvmStatic - fun ofEpochMilli(epochMillis: Long): Timestamp { - return Timestamp(epochMillis) - } + fun ofEpochMilli(epochMillis: Long): Timestamp = Timestamp(epochMillis) } } \ No newline at end of file diff --git a/samples/age-verifier-mdl/build.gradle b/samples/age-verifier-mdl/build.gradle index b10d8b30e..dd7aa8f74 100644 --- a/samples/age-verifier-mdl/build.gradle +++ b/samples/age-verifier-mdl/build.gradle @@ -61,6 +61,7 @@ dependencies { implementation libs.compose.material implementation libs.bundles.bouncy.castle implementation libs.androidx.navigation.compose + implementation libs.identity.credential debugImplementation libs.compose.icons androidTestImplementation libs.androidx.test.ext.junit diff --git a/samples/age-verifier-mdl/src/main/java/com/android/identity/age_verifier_mdl/MainActivity.kt b/samples/age-verifier-mdl/src/main/java/com/android/identity/age_verifier_mdl/MainActivity.kt index bce9099a2..4aa34050a 100644 --- a/samples/age-verifier-mdl/src/main/java/com/android/identity/age_verifier_mdl/MainActivity.kt +++ b/samples/age-verifier-mdl/src/main/java/com/android/identity/age_verifier_mdl/MainActivity.kt @@ -310,7 +310,7 @@ class MainActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background, ) { - Column() { + Column { val scrollState = rememberScrollState() Column( modifier = Modifier @@ -319,7 +319,7 @@ class MainActivity : ComponentActivity() { verticalArrangement = Arrangement.spacedBy(16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - Column() { + Column { Text( modifier = Modifier.fillMaxWidth(), text = "This app is used to engage with an mDL via NFC and request " + @@ -489,7 +489,7 @@ class MainActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background, ) { - Column() { + Column { val scrollState = rememberScrollState() Column( modifier = Modifier @@ -502,7 +502,7 @@ class MainActivity : ComponentActivity() { Column { Text(text = "Error: ${transferHelper.error}") } } else { if (resultPortrait != null) { - Column() { + Column { Image( bitmap = resultPortrait!!.asImageBitmap(), contentDescription = null, diff --git a/samples/preconsent-mdl/src/main/java/com/android/identity/preconsent_mdl/MainActivity.kt b/samples/preconsent-mdl/src/main/java/com/android/identity/preconsent_mdl/MainActivity.kt index 03bc9facf..50abab096 100644 --- a/samples/preconsent-mdl/src/main/java/com/android/identity/preconsent_mdl/MainActivity.kt +++ b/samples/preconsent-mdl/src/main/java/com/android/identity/preconsent_mdl/MainActivity.kt @@ -60,7 +60,6 @@ import com.android.identity.mdoc.util.MdocUtil import com.android.identity.preconsent_mdl.ui.theme.IdentityCredentialTheme import com.android.identity.securearea.CreateKeySettings import com.android.identity.securearea.EcCurve -import com.android.identity.securearea.SecureArea import com.android.identity.util.Logger import com.android.identity.util.Timestamp import org.bouncycastle.asn1.x500.X500Name @@ -83,17 +82,16 @@ class MainActivity : ComponentActivity() { companion object { private val TAG = "MainActivity" - val CREDENTIAL_ID = "mDL_Erika" - val AUTH_KEY_DOMAIN = "mdoc" + const val CREDENTIAL_ID = "mDL_Erika" + const val AUTH_KEY_DOMAIN = "mdoc" - val MDL_DOCTYPE = "org.iso.18013.5.1.mDL" - val MDL_NAMESPACE = "org.iso.18013.5.1" - val AAMVA_NAMESPACE = "org.iso.18013.5.1.aamva" + const val MDL_DOCTYPE = "org.iso.18013.5.1.mDL" + const val MDL_NAMESPACE = "org.iso.18013.5.1" + const val AAMVA_NAMESPACE = "org.iso.18013.5.1.aamva" } private lateinit var transferHelper: TransferHelper - private fun provisionCredentials() { if (transferHelper.credentialStore.lookupCredential(CREDENTIAL_ID) == null) { provisionCredential() @@ -106,22 +104,19 @@ class MainActivity : ComponentActivity() { val credential = transferHelper.credentialStore.createCredential(CREDENTIAL_ID) val baos = ByteArrayOutputStream() - BitmapFactory.decodeResource( - applicationContext.resources, - R.drawable.img_erika_portrait - ).compress(Bitmap.CompressFormat.JPEG, 50, baos) + BitmapFactory.decodeResource(applicationContext.resources, R.drawable.img_erika_portrait) + .compress(Bitmap.CompressFormat.JPEG, 50, baos) val portrait: ByteArray = baos.toByteArray() val now = Timestamp.now() - val issueDate = now - val expiryDate = Timestamp.ofEpochMilli(issueDate.toEpochMilli() + 5*365*24*3600*1000L) + val expiryDate = Timestamp.ofEpochMilli(now.toEpochMilli() + 5 * 365 * 24 * 3600 * 1000L) val credentialData = NameSpacedData.Builder() .putEntryString(MDL_NAMESPACE, "given_name", "Erika") .putEntryString(MDL_NAMESPACE, "family_name", "Mustermann") .putEntryByteString(MDL_NAMESPACE, "portrait", portrait) .putEntryNumber(MDL_NAMESPACE, "sex", 2) - .putEntry(MDL_NAMESPACE, "issue_date", Util.cborEncodeDateTime(issueDate)) + .putEntry(MDL_NAMESPACE, "issue_date", Util.cborEncodeDateTime(now)) .putEntry(MDL_NAMESPACE, "expiry_date", Util.cborEncodeDateTime(expiryDate)) .putEntryString(MDL_NAMESPACE, "document_number", "1234567890") .putEntryString(MDL_NAMESPACE, "issuing_authority", "State of Utopia") @@ -136,7 +131,7 @@ class MainActivity : ComponentActivity() { // Create AuthKeys and MSOs, make sure they're valid for a long time val timeSigned = now val validFrom = now - val validUntil = Timestamp.ofEpochMilli(validFrom.toEpochMilli() + 365*24*3600*1000L) + val validUntil = Timestamp.ofEpochMilli(validFrom.toEpochMilli() + 365 * 24 * 3600 * 1000L) // Create three authentication keys and certify them for (n in 0..2) { @@ -288,16 +283,25 @@ class MainActivity : ComponentActivity() { @Composable private fun MainScreen(context: Context) { val transferHelper = remember { TransferHelper.getInstance(context) } - val nfcStaticHandoverEnabled = remember { mutableStateOf(transferHelper.getNfcStaticHandoverEnabled()) } - val nfcNegotiatedHandoverEnabled = remember { mutableStateOf(transferHelper.getNfcNegotiatedHandoverEnabled()) } - val bleCentralClientDataTransferEnabled = remember { mutableStateOf(transferHelper.getBleCentralClientDataTransferEnabled()) } - val blePeripheralServerDataTransferEnabled = remember { mutableStateOf(transferHelper.getBlePeripheralServerDataTransferEnabled()) } - val wifiAwareDataTransferEnabled = remember { mutableStateOf(transferHelper.getWifiAwareDataTransferEnabled()) } - val nfcDataTransferEnabled = remember { mutableStateOf(transferHelper.getNfcDataTransferEnabled()) } - val tcpDataTransferEnabled = remember { mutableStateOf(transferHelper.getTcpDataTransferEnabled()) } - val udpDataTransferEnabled = remember { mutableStateOf(transferHelper.getUdpDataTransferEnabled()) } + val nfcStaticHandoverEnabled = + remember { mutableStateOf(transferHelper.getNfcStaticHandoverEnabled()) } + val nfcNegotiatedHandoverEnabled = + remember { mutableStateOf(transferHelper.getNfcNegotiatedHandoverEnabled()) } + val bleCentralClientDataTransferEnabled = + remember { mutableStateOf(transferHelper.getBleCentralClientDataTransferEnabled()) } + val blePeripheralServerDataTransferEnabled = + remember { mutableStateOf(transferHelper.getBlePeripheralServerDataTransferEnabled()) } + val wifiAwareDataTransferEnabled = + remember { mutableStateOf(transferHelper.getWifiAwareDataTransferEnabled()) } + val nfcDataTransferEnabled = + remember { mutableStateOf(transferHelper.getNfcDataTransferEnabled()) } + val tcpDataTransferEnabled = + remember { mutableStateOf(transferHelper.getTcpDataTransferEnabled()) } + val udpDataTransferEnabled = + remember { mutableStateOf(transferHelper.getUdpDataTransferEnabled()) } val l2capEnabled = remember { mutableStateOf(transferHelper.getL2CapEnabled()) } - val experimentalPsmEnabled = remember { mutableStateOf(transferHelper.getExperimentalPsmEnabled()) } + val experimentalPsmEnabled = + remember { mutableStateOf(transferHelper.getExperimentalPsmEnabled()) } val debugEnabled = remember { mutableStateOf(transferHelper.getDebugEnabled()) } val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) @@ -327,7 +331,7 @@ private fun MainScreen(context: Context) { .padding(innerPadding), color = MaterialTheme.colorScheme.background ) { - Column() { + Column { val scrollState = rememberScrollState() Column( modifier = Modifier @@ -335,7 +339,7 @@ private fun MainScreen(context: Context) { .verticalScroll(scrollState), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Column() { + Column { Text( modifier = Modifier.fillMaxWidth(), text = "This app contains an mDL with Preconsent which " + diff --git a/secure-area-test-app/src/main/java/com/android/identity/secure_area_test_app/MainActivity.kt b/secure-area-test-app/src/main/java/com/android/identity/secure_area_test_app/MainActivity.kt index 4f2a22f38..5a5d98907 100644 --- a/secure-area-test-app/src/main/java/com/android/identity/secure_area_test_app/MainActivity.kt +++ b/secure-area-test-app/src/main/java/com/android/identity/secure_area_test_app/MainActivity.kt @@ -507,17 +507,16 @@ class MainActivity : FragmentActivity() { // Unfortunately this is API is only available to system apps so we // have to use reflection to use it. - private fun getFirstApiLevel(): Int { + private fun getFirstApiLevel(): Int = try { val c = Class.forName("android.os.SystemProperties") val get = c.getMethod("get", String::class.java) val firstApiLevelString = get.invoke(c, "ro.product.first_api_level") as String - return firstApiLevelString.toInt() + firstApiLevelString.toInt() } catch (e: java.lang.Exception) { Logger.w(TAG, "Error getting ro.product.first_api_level", e) - return 0 + 0 } - } private fun getNameForApiLevel(apiLevel: Int): String { val fields = Build.VERSION_CODES::class.java.fields @@ -878,7 +877,7 @@ class MainActivity : FragmentActivity() { ) val keyInfo = androidKeystoreSecureArea.getKeyInfo("testKey") - val publicKey = keyInfo.attestation.get(0).publicKey + val publicKey = keyInfo.attestation.first().publicKey if (keyPurpose == KeyPurpose.SIGN) { val signingAlgorithm = getNaturalAlgorithmForCurve(curve) @@ -1035,7 +1034,7 @@ class MainActivity : FragmentActivity() { softwareSecureArea.createKey("testKey", builder.build()) val keyInfo = softwareSecureArea.getKeyInfo("testKey") - val publicKey = keyInfo.attestation.get(0).publicKey + val publicKey = keyInfo.attestation.first().publicKey var unlockData: KeyUnlockData? = null if (passphraseEnteredByUser != null) {