From b227522be5fcb376c0c8086bdd4a573ce09aa172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= <bernd.pruenster@a-sit.at> Date: Tue, 29 Aug 2023 09:29:13 +0200 Subject: [PATCH] Release 3.0.1 (#14) - Dependency Updates - OKIO 3.5.0 - UUID 0.8.1 - Encodings 1.2.3 - JOSE+JWT 9.31 - JSON 20230618 --- CHANGELOG.md | 8 ++++ conventions-vclib/gradle-conventions-plugin | 2 +- .../src/main/kotlin/VcLibVersions.kt | 10 ++--- gradle.properties | 2 +- .../asitplus/wallet/lib/msg/JwmAttachment.kt | 13 ++++--- .../wallet/lib/oidc/OidcSiopWallet.kt | 4 +- .../wallet/lib/oidvci/IssuerService.kt | 9 +++-- .../agent/InMemoryIssuerCredentialStore.kt | 4 +- .../asitplus/wallet/lib/agent/IssuerAgent.kt | 4 +- .../at/asitplus/wallet/lib/agent/Validator.kt | 38 ++++++++++--------- .../wallet/lib/agent/VerifierAgent.kt | 4 +- .../at/asitplus/wallet/lib/cbor/CoseHeader.kt | 10 +++-- .../at/asitplus/wallet/lib/cbor/CoseKey.kt | 12 +++--- .../at/asitplus/wallet/lib/cbor/CoseSigned.kt | 20 +++++----- .../asitplus/wallet/lib/iso/DeviceRequest.kt | 6 ++- .../wallet/lib/iso/MobileDrivingLicence.kt | 6 ++- .../wallet/lib/iso/MobileSecurityObject.kt | 4 +- .../lib/jws/ByteArrayBase64Serializer.kt | 7 +++- .../lib/jws/ByteArrayBase64UrlSerializer.kt | 9 +++-- .../at/asitplus/wallet/lib/jws/JsonWebKey.kt | 7 +++- .../asitplus/wallet/lib/jws/JweEncrypted.kt | 26 ++++++------- .../at/asitplus/wallet/lib/jws/JwsService.kt | 13 ++++--- .../at/asitplus/wallet/lib/jws/JwsSigned.kt | 19 ++++++---- .../wallet/lib/jws/MultibaseHelper.kt | 7 +++- 24 files changed, 146 insertions(+), 98 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd8435e3..ddf8b885 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +Release 3.0.1 + - Dependency Updates + - OKIO 3.5.0 + - UUID 0.8.1 + - Encodings 1.2.3 + - JOSE+JWT 9.31 + - JSON 20230618 + Release 3.0.0: - Creating, issuing, managing and verifying ISO/IEC 18013-5:2021 credentials - Kotlin 1.9.10 diff --git a/conventions-vclib/gradle-conventions-plugin b/conventions-vclib/gradle-conventions-plugin index edc666d4..4aec0667 160000 --- a/conventions-vclib/gradle-conventions-plugin +++ b/conventions-vclib/gradle-conventions-plugin @@ -1 +1 @@ -Subproject commit edc666d4fd9cdb8da87b935ef92d5736a62ce80f +Subproject commit 4aec06670f8b4b13551281fa466e81458c2c7bbd diff --git a/conventions-vclib/src/main/kotlin/VcLibVersions.kt b/conventions-vclib/src/main/kotlin/VcLibVersions.kt index 603e672a..01319540 100644 --- a/conventions-vclib/src/main/kotlin/VcLibVersions.kt +++ b/conventions-vclib/src/main/kotlin/VcLibVersions.kt @@ -1,11 +1,11 @@ object VcLibVersions { - const val uuid = "0.5.0" + const val uuid = "0.8.1" const val resultlib = "1.5.3" - const val encoding = "1.1.3" - const val okio = "3.2.0" + const val encoding = "1.2.3" + const val okio = "3.5.0" object Jvm { - const val `jose-jwt` = "9.25.6" - const val json = "20210307" + const val `jose-jwt` = "9.31" + const val json = "20230618" } } diff --git a/gradle.properties b/gradle.properties index 529d9721..1613a6d1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,4 +18,4 @@ kotlin.experimental.tryK2=false # workaround dokka bug (need to wait for next snapshot build) org.jetbrains.dokka.classpath.excludePlatformDependencyFiles=true -artifactVersion = 3.0.0 +artifactVersion = 3.0.1 diff --git a/vclib-aries/src/commonMain/kotlin/at/asitplus/wallet/lib/msg/JwmAttachment.kt b/vclib-aries/src/commonMain/kotlin/at/asitplus/wallet/lib/msg/JwmAttachment.kt index 279c1fe9..d524ae23 100644 --- a/vclib-aries/src/commonMain/kotlin/at/asitplus/wallet/lib/msg/JwmAttachment.kt +++ b/vclib-aries/src/commonMain/kotlin/at/asitplus/wallet/lib/msg/JwmAttachment.kt @@ -5,6 +5,9 @@ import com.benasher44.uuid.uuid4 import io.github.aakira.napier.Napier import io.matthewnelson.component.base64.decodeBase64ToArray import io.matthewnelson.component.base64.encodeBase64 +import io.matthewnelson.encoding.base64.Base64 +import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArrayOrNull +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString @@ -30,7 +33,7 @@ data class JwmAttachment( fun decodeString(): String? { if (data.base64 != null) - return data.base64.decodeBase64ToArray()?.decodeToString() + return data.base64.decodeToByteArrayOrNull(Base64())?.decodeToString() if (data.jws != null) return data.jws return null @@ -39,7 +42,7 @@ data class JwmAttachment( fun decodeBinary(): ByteArray? { if (data.base64 != null) - return data.base64.decodeBase64ToArray() + return data.base64.decodeToByteArrayOrNull(Base64()) return null .also { Napier.w("Could not binary decode JWM attachment") } } @@ -57,7 +60,7 @@ data class JwmAttachment( id = uuid4().toString(), mediaType = "application/base64", data = JwmAttachmentData( - base64 = data.encodeToByteArray().encodeBase64() + base64 = data.encodeToByteArray().encodeToString(Base64()) ) ) @@ -65,7 +68,7 @@ data class JwmAttachment( id = uuid4().toString(), mediaType = "application/base64", data = JwmAttachmentData( - base64 = data.encodeBase64() + base64 = data.encodeToString(Base64()) ) ) @@ -75,7 +78,7 @@ data class JwmAttachment( filename = filename, parent = parent, data = JwmAttachmentData( - base64 = data.encodeBase64() + base64 = data.encodeToString(Base64()) ) ) diff --git a/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt index 7dc6fcce..040b304e 100644 --- a/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt +++ b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt @@ -35,6 +35,8 @@ import io.ktor.http.URLBuilder import io.ktor.http.Url import io.ktor.util.flattenEntries import io.matthewnelson.component.encoding.base16.encodeBase16 +import io.matthewnelson.encoding.base16.Base16 +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlinx.datetime.Clock import kotlin.time.Duration.Companion.seconds @@ -290,7 +292,7 @@ class OidcSiopWallet( AuthenticationResponseParameters( idToken = signedIdToken, state = params.state, - vpToken = vp.document.serialize().encodeBase16(), + vpToken = vp.document.serialize().encodeToString(Base16()), presentationSubmission = presentationSubmission, ) ) diff --git a/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/IssuerService.kt b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/IssuerService.kt index 061e6bb7..157c88e0 100644 --- a/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/IssuerService.kt +++ b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/IssuerService.kt @@ -22,9 +22,9 @@ import at.asitplus.wallet.lib.oidc.OpenIdConstants.TOKEN_PREFIX_BEARER import at.asitplus.wallet.lib.oidc.OpenIdConstants.TOKEN_TYPE_BEARER import at.asitplus.wallet.lib.oidc.OpenIdConstants.URN_TYPE_JWK_THUMBPRINT import at.asitplus.wallet.lib.oidvci.mdl.RequestedCredentialClaimSpecification -import io.ktor.http.URLBuilder -import io.matthewnelson.component.base64.Base64.UrlSafe -import io.matthewnelson.component.base64.encodeBase64 +import io.ktor.http.* +import io.matthewnelson.encoding.base64.Base64 +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlin.coroutines.cancellation.CancellationException /** @@ -167,7 +167,8 @@ class IssuerService( return when (val issuedCredential = issuedCredentialResult.successful.first()) { is Issuer.IssuedCredential.Iso -> CredentialResponseParameters( format = CredentialFormatEnum.MSO_MDOC, - credential = issuedCredential.issuerSigned.serialize().encodeBase64(UrlSafe()) + credential = issuedCredential.issuerSigned.serialize() + .encodeToString(Base64 { encodeToUrlSafe = true; padEncoded = false }) ) is Issuer.IssuedCredential.Vc -> CredentialResponseParameters( diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/InMemoryIssuerCredentialStore.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/InMemoryIssuerCredentialStore.kt index 8ed4f1d1..5dd33d2c 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/InMemoryIssuerCredentialStore.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/InMemoryIssuerCredentialStore.kt @@ -4,6 +4,8 @@ import at.asitplus.wallet.lib.data.CredentialSubject import at.asitplus.wallet.lib.iso.IssuerSignedItem import at.asitplus.wallet.lib.iso.sha256 import io.matthewnelson.component.encoding.base16.encodeBase16 +import io.matthewnelson.encoding.base16.Base16 +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlinx.datetime.Instant @@ -45,7 +47,7 @@ class InMemoryIssuerCredentialStore : IssuerCredentialStore { val list = map.getOrPut(timePeriod) { mutableListOf() } val newIndex = (list.maxOfOrNull { it.statusListIndex } ?: 0) + 1 list += Credential( - vcId = issuerSignedItemList.toString().encodeToByteArray().sha256().encodeBase16(), + vcId = issuerSignedItemList.toString().encodeToByteArray().sha256().encodeToString(Base16()), statusListIndex = newIndex, revoked = false, expirationDate = expirationDate diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/IssuerAgent.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/IssuerAgent.kt index 81817e8c..26d026c4 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/IssuerAgent.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/IssuerAgent.kt @@ -29,6 +29,8 @@ import at.asitplus.wallet.lib.jws.JwsService import com.benasher44.uuid.uuid4 import io.github.aakira.napier.Napier import io.matthewnelson.component.base64.encodeBase64 +import io.matthewnelson.encoding.base64.Base64 +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlinx.datetime.Clock import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.plus @@ -219,7 +221,7 @@ class IssuerAgent( issuerCredentialStore.getRevokedStatusListIndexList(timePeriod) .forEach { bitset[it] = true } val input = bitset.toByteArray() - return zlibService.compress(input)?.encodeBase64() + return zlibService.compress(input)?.encodeToString(Base64()) } /** diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Validator.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Validator.kt index b21d183e..04a0742e 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Validator.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Validator.kt @@ -26,6 +26,10 @@ import at.asitplus.wallet.lib.toBitSet import io.github.aakira.napier.Napier import io.matthewnelson.component.base64.decodeBase64ToArray import io.matthewnelson.component.encoding.base16.encodeBase16 +import io.matthewnelson.encoding.base16.Base16 +import io.matthewnelson.encoding.base64.Base64 +import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArrayOrNull +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlinx.serialization.cbor.ByteStringWrapper @@ -86,7 +90,7 @@ class Validator( return false .also { Napier.d("credentialSubject invalid") } val encodedList = parsedVc.jws.vc.credentialSubject.encodedList - this.revocationList = encodedList.decodeBase64ToArray()?.let { + this.revocationList = encodedList.decodeToByteArrayOrNull(Base64())?.let { zlibService.decompress(it)?.toBitSet() ?: return false.also { Napier.d("Invalid ZLIB") } } ?: return false.also { Napier.d("Invalid Base64") } Napier.d("Revocation list is valid") @@ -186,10 +190,10 @@ class Validator( */ fun verifyDocument(doc: Document, challenge: String): Verifier.VerifyPresentationResult { if (doc.docType != DOC_TYPE_MDL) - return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeBase16()) + return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16())) .also { Napier.w("Invalid docType: ${doc.docType}") } if (doc.errors != null) { - return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeBase16()) + return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16())) .also { Napier.w("Document has errors: ${doc.errors}") } } val issuerSigned = doc.issuerSigned @@ -197,45 +201,45 @@ class Validator( val issuerKey = issuerAuth.unprotectedHeader?.certificateChain?.let { CryptoUtils.extractPublicKeyFromX509Cert(it)?.toCoseKey() - } ?: return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeBase16()) + } ?: return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16())) .also { Napier.w("Got no issuer key in $issuerAuth") } if (verifierCoseService.verifyCose(issuerAuth, issuerKey).getOrNull() != true) { - return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeBase16()) + return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16())) .also { Napier.w("IssuerAuth not verified: $issuerAuth") } } val mso = issuerSigned.getIssuerAuthPayloadAsMso() - ?: return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeBase16()) - .also { Napier.w("MSO is null: ${issuerAuth.payload?.encodeBase16()}") } + ?: return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16())) + .also { Napier.w("MSO is null: ${issuerAuth.payload?.encodeToString(Base16())}") } if (mso.docType != DOC_TYPE_MDL) { - return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeBase16()) + return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16())) .also { Napier.w("Invalid docType in MSO: ${mso.docType}") } } val mdlItems = mso.valueDigests[NAMESPACE_MDL] - ?: return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeBase16()) + ?: return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16())) .also { Napier.w("mdlItems are null in MSO: ${mso.valueDigests}") } val walletKey = mso.deviceKeyInfo.deviceKey val deviceSignature = doc.deviceSigned.deviceAuth.deviceSignature - ?: return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeBase16()) + ?: return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16())) .also { Napier.w("DeviceSignature is null: ${doc.deviceSigned.deviceAuth}") } if (verifierCoseService.verifyCose(deviceSignature, walletKey).getOrNull() != true) { - return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeBase16()) + return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16())) .also { Napier.w("DeviceSignature not verified") } } val deviceSignaturePayload = deviceSignature.payload - ?: return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeBase16()) + ?: return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16())) .also { Napier.w("DeviceSignature does not contain challenge") } if (!deviceSignaturePayload.contentEquals(challenge.encodeToByteArray())) { - return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeBase16()) + return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16())) .also { Napier.w("DeviceSignature does not contain correct challenge") } } val issuerSignedItems = issuerSigned.namespaces?.get(NAMESPACE_MDL) - ?: return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeBase16()) + ?: return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16())) .also { Napier.w("No issuer signed items in ${issuerSigned.namespaces}") } val validatedItems = issuerSignedItems.entries.associateWith { it.verify(mdlItems) } @@ -251,7 +255,7 @@ class Validator( val issuerHash = mdlItems.entries.first { it.key == value.digestId } // TODO analyze usages of tag wrapping val verifierHash = serialized.wrapInCborTag(24).sha256() - if (!verifierHash.encodeBase16().contentEquals(issuerHash.value.encodeBase16())) { + if (!verifierHash.encodeToString(Base16()).contentEquals(issuerHash.value.encodeToString(Base16()))) { Napier.w("Could not verify hash of value for ${value.elementIdentifier}") return false } @@ -303,12 +307,12 @@ class Validator( Napier.d("Verifying ISO Cred $it") if (issuerKey == null) { Napier.w("ISO: No issuer key") - return Verifier.VerifyCredentialResult.InvalidStructure(it.serialize().encodeBase16()) + return Verifier.VerifyCredentialResult.InvalidStructure(it.serialize().encodeToString(Base16())) } val result = verifierCoseService.verifyCose(it.issuerAuth, issuerKey) if (result.getOrNull() != true) { Napier.w("ISO: Could not verify credential", result.exceptionOrNull()) - return Verifier.VerifyCredentialResult.InvalidStructure(it.serialize().encodeBase16()) + return Verifier.VerifyCredentialResult.InvalidStructure(it.serialize().encodeToString(Base16())) } return Verifier.VerifyCredentialResult.SuccessIso(it) } diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/VerifierAgent.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/VerifierAgent.kt index ff0f67df..ea5ed0f4 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/VerifierAgent.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/VerifierAgent.kt @@ -6,6 +6,8 @@ import at.asitplus.wallet.lib.iso.Document import at.asitplus.wallet.lib.jws.JwsSigned import io.github.aakira.napier.Napier import io.matthewnelson.component.encoding.base16.decodeBase16ToArray +import io.matthewnelson.encoding.base16.Base16 +import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArrayOrNull /** @@ -56,7 +58,7 @@ class VerifierAgent private constructor( return validator.verifyVpJws(it, challenge, identifier) } val document = - runCatching { it.decodeBase16ToArray()?.let { bytes -> Document.deserialize(bytes) } }.getOrNull() + runCatching { it.decodeToByteArrayOrNull(Base16())?.let { bytes -> Document.deserialize(bytes) } }.getOrNull() if (document != null) { return validator.verifyDocument(document, challenge) } diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseHeader.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseHeader.kt index abec236d..a0552832 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseHeader.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseHeader.kt @@ -4,6 +4,8 @@ import at.asitplus.wallet.lib.iso.cborSerializer import io.github.aakira.napier.Napier import io.ktor.http.content.ByteArrayContent import io.matthewnelson.component.encoding.base16.encodeBase16 +import io.matthewnelson.encoding.base16.Base16 +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -93,10 +95,10 @@ data class CoseHeader( return "CoseHeader(algorithm=$algorithm," + " criticalHeaders=$criticalHeaders," + " contentType=$contentType," + - " kid=${kid?.encodeBase16()}," + - " iv=${iv?.encodeBase16()}," + - " partialIv=${partialIv?.encodeBase16()}," + - " certificateChain=${certificateChain?.encodeBase16()})" + " kid=${kid?.encodeToString(Base16())}," + + " iv=${iv?.encodeToString(Base16())}," + + " partialIv=${partialIv?.encodeToString(Base16())}," + + " certificateChain=${certificateChain?.encodeToString(Base16())})" } companion object { diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseKey.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseKey.kt index 1e3c9dcb..72063ba3 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseKey.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseKey.kt @@ -10,6 +10,8 @@ import at.asitplus.wallet.lib.jws.MultibaseHelper import io.github.aakira.napier.Napier import io.matthewnelson.component.base64.encodeBase64 import io.matthewnelson.component.encoding.base16.encodeBase16 +import io.matthewnelson.encoding.base16.Base16 +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -113,14 +115,14 @@ data class CoseKey( override fun toString(): String { return "CoseKey(type=$type," + - " keyId=${keyId?.encodeBase16()}," + + " keyId=${keyId?.encodeToString(Base16())}," + " algorithm=$algorithm," + " operations=${operations?.contentToString()}," + - " baseIv=${baseIv?.encodeBase16()}," + + " baseIv=${baseIv?.encodeToString(Base16())}," + " curve=$curve," + - " x=${x?.encodeBase16()}," + - " y=${y?.encodeBase16()}," + - " d=${d?.encodeBase16()})" + " x=${x?.encodeToString(Base16())}," + + " y=${y?.encodeToString(Base16())}," + + " d=${d?.encodeToString(Base16())})" } override fun equals(other: Any?): Boolean { diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseSigned.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseSigned.kt index 031dfbbe..45992ad7 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseSigned.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseSigned.kt @@ -1,21 +1,19 @@ +@file:OptIn(ExperimentalUnsignedTypes::class) + package at.asitplus.wallet.lib.cbor import at.asitplus.wallet.lib.iso.cborSerializer import io.github.aakira.napier.Napier -import io.matthewnelson.component.encoding.base16.encodeBase16 -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable +import io.matthewnelson.encoding.base16.Base16 +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString +import kotlinx.serialization.* import kotlinx.serialization.builtins.ByteArraySerializer import kotlinx.serialization.cbor.ByteString import kotlinx.serialization.cbor.ByteStringWrapper -import kotlinx.serialization.cbor.Cbor import kotlinx.serialization.cbor.CborArray -import kotlinx.serialization.decodeFromByteArray import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encodeToByteArray import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder @@ -64,8 +62,8 @@ data class CoseSigned( override fun toString(): String { return "CoseSigned(protectedHeader=${protectedHeader.value}," + " unprotectedHeader=$unprotectedHeader," + - " payload=${payload?.encodeBase16()}," + - " signature=${signature.encodeBase16()})" + " payload=${payload?.encodeToString(Base16())}," + + " signature=${signature.encodeToString(Base16())})" } companion object { @@ -121,8 +119,8 @@ data class CoseSignatureInput( override fun toString(): String { return "CoseSignatureInput(contextString='$contextString'," + " protectedHeader=${protectedHeader.value}," + - " externalAad=${externalAad.encodeBase16()}," + - " payload=${payload?.encodeBase16()})" + " externalAad=${externalAad.encodeToString(Base16())}," + + " payload=${payload?.encodeToString(Base16())})" } diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/DeviceRequest.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/DeviceRequest.kt index 46531bef..7c3f9e34 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/DeviceRequest.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/DeviceRequest.kt @@ -6,6 +6,8 @@ import at.asitplus.wallet.lib.cbor.CoseSigned import at.asitplus.wallet.lib.iso.IsoDataModelConstants.NAMESPACE_MDL import io.github.aakira.napier.Napier import io.matthewnelson.component.encoding.base16.encodeBase16 +import io.matthewnelson.encoding.base16.Base16 +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlinx.datetime.LocalDate import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.InternalSerializationApi @@ -407,7 +409,7 @@ data class IssuerSignedItem( override fun toString(): String { return "IssuerSignedItem(digestId=$digestId," + - " random=${random.encodeBase16()}," + + " random=${random.encodeToString(Base16())}," + " elementIdentifier='$elementIdentifier'," + " elementValue=$elementValue)" } @@ -437,7 +439,7 @@ data class ElementValue( fun serialize() = cborSerializer.encodeToByteArray(this) override fun toString(): String { - return "ElementValue(bytes=${bytes?.encodeBase16()}," + + return "ElementValue(bytes=${bytes?.encodeToString(Base16())}," + " date=${date}," + " string=$string," + " drivingPrivilege=$drivingPrivilege)" diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/MobileDrivingLicence.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/MobileDrivingLicence.kt index c34f5f55..59bc1111 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/MobileDrivingLicence.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/MobileDrivingLicence.kt @@ -37,6 +37,8 @@ import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DataElements.WEIGHT import at.asitplus.wallet.lib.jws.ByteArrayBase64UrlSerializer import io.github.aakira.napier.Napier import io.matthewnelson.component.encoding.base16.encodeBase16 +import io.matthewnelson.encoding.base16.Base16 +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlinx.datetime.LocalDate import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName @@ -211,7 +213,7 @@ data class MobileDrivingLicence( " issuingCountry='$issuingCountry'," + " issuingAuthority='$issuingAuthority'," + " licenceNumber='$licenceNumber'," + - " portrait=${portrait.encodeBase16()}," + + " portrait=${portrait.encodeToString(Base16())}," + " drivingPrivileges=${drivingPrivileges}," + " unDistinguishingSign='$unDistinguishingSign'," + " administrativeNumber=$administrativeNumber," + @@ -234,7 +236,7 @@ data class MobileDrivingLicence( " residentCountry=$residentCountry," + " familyNameNationalCharacters=$familyNameNationalCharacters," + " givenNameNationalCharacters=$givenNameNationalCharacters," + - " signatureOrUsualMark=${signatureOrUsualMark?.encodeBase16()})" + " signatureOrUsualMark=${signatureOrUsualMark?.encodeToString(Base16())})" } companion object { diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/MobileSecurityObject.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/MobileSecurityObject.kt index c73be6c1..a68dcdee 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/MobileSecurityObject.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/MobileSecurityObject.kt @@ -5,6 +5,8 @@ package at.asitplus.wallet.lib.iso import at.asitplus.wallet.lib.cbor.CoseKey import io.github.aakira.napier.Napier import io.matthewnelson.component.encoding.base16.encodeBase16 +import io.matthewnelson.encoding.base16.Base16 +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlinx.datetime.Instant import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer @@ -104,7 +106,7 @@ data class ValueDigest( } override fun toString(): String { - return "MobileSecurityObjectNamespaceEntry(key=$key, value=${value.encodeBase16()})" + return "MobileSecurityObjectNamespaceEntry(key=$key, value=${value.encodeToString(Base16())})" } companion object { diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/ByteArrayBase64Serializer.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/ByteArrayBase64Serializer.kt index e6c61aee..d247bd32 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/ByteArrayBase64Serializer.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/ByteArrayBase64Serializer.kt @@ -2,6 +2,9 @@ package at.asitplus.wallet.lib.jws import io.matthewnelson.component.base64.decodeBase64ToArray import io.matthewnelson.component.base64.encodeBase64 +import io.matthewnelson.encoding.base64.Base64 +import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArrayOrNull +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor @@ -15,11 +18,11 @@ object ByteArrayBase64Serializer : KSerializer<ByteArray> { PrimitiveSerialDescriptor("ByteArrayBase64Serializer", PrimitiveKind.STRING) override fun serialize(encoder: Encoder, value: ByteArray) { - encoder.encodeString(value.encodeBase64()) + encoder.encodeString(value.encodeToString(Base64())) } override fun deserialize(decoder: Decoder): ByteArray { - return decoder.decodeString().decodeBase64ToArray() ?: byteArrayOf() + return decoder.decodeString().decodeToByteArrayOrNull(Base64()) ?: byteArrayOf() } } \ No newline at end of file diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/ByteArrayBase64UrlSerializer.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/ByteArrayBase64UrlSerializer.kt index 4b556bca..7510394a 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/ByteArrayBase64UrlSerializer.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/ByteArrayBase64UrlSerializer.kt @@ -1,8 +1,9 @@ package at.asitplus.wallet.lib.jws -import io.matthewnelson.component.base64.Base64 import io.matthewnelson.component.base64.decodeBase64ToArray -import io.matthewnelson.component.base64.encodeBase64 +import io.matthewnelson.encoding.base64.Base64 +import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArrayOrNull +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor @@ -16,11 +17,11 @@ object ByteArrayBase64UrlSerializer : KSerializer<ByteArray> { PrimitiveSerialDescriptor("ByteArrayBase64UrlSerializer", PrimitiveKind.STRING) override fun serialize(encoder: Encoder, value: ByteArray) { - encoder.encodeString(value.encodeBase64(Base64.UrlSafe(pad = false))) + encoder.encodeString(value.encodeToString(Base64 { encodeToUrlSafe = true; padEncoded = false })) } override fun deserialize(decoder: Decoder): ByteArray { - return decoder.decodeString().decodeBase64ToArray() ?: byteArrayOf() + return decoder.decodeString().decodeToByteArrayOrNull(Base64()) ?: byteArrayOf() } } \ No newline at end of file diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/JsonWebKey.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/JsonWebKey.kt index aaaddbf8..bdfb19b3 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/JsonWebKey.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/JsonWebKey.kt @@ -4,7 +4,8 @@ import at.asitplus.KmmResult import at.asitplus.wallet.lib.CryptoPublicKey import at.asitplus.wallet.lib.data.jsonSerializer import io.github.aakira.napier.Napier -import io.matthewnelson.component.base64.encodeBase64 +import io.matthewnelson.encoding.base64.Base64 +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString @@ -135,7 +136,9 @@ data class JsonWebKey( } override fun toString(): String { - return "JsonWebKey(type=$type, curve=$curve, keyId=$keyId, x=${x?.encodeBase64()}, y=${y?.encodeBase64()})" + return "JsonWebKey(type=$type, curve=$curve, keyId=$keyId, x=${x?.encodeToString(Base64())}, y=${ + y?.encodeToString(Base64()) + })" } fun toCryptoPublicKey(): CryptoPublicKey? { diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/JweEncrypted.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/JweEncrypted.kt index b3a0e0bc..ea9ec61a 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/JweEncrypted.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/JweEncrypted.kt @@ -1,9 +1,9 @@ package at.asitplus.wallet.lib.jws import io.github.aakira.napier.Napier -import io.matthewnelson.component.base64.Base64 -import io.matthewnelson.component.base64.decodeBase64ToArray -import io.matthewnelson.component.base64.encodeBase64 +import io.matthewnelson.encoding.base64.Base64 +import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArrayOrNull +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString /** * Representation of an encrypted JSON Web Encryption object, consisting of its 5 parts: Header, encrypted key, @@ -22,11 +22,11 @@ data class JweEncrypted( get() = JweHeader.deserialize(headerAsParsed.decodeToString()) fun serialize(): String { - return headerAsParsed.encodeBase64(Base64.UrlSafe(pad = false)) + - ".${encryptedKey?.encodeBase64(Base64.UrlSafe(pad = false)) ?: ""}" + - ".${iv.encodeBase64(Base64.UrlSafe(pad = false))}" + - ".${ciphertext.encodeBase64(Base64.UrlSafe(pad = false))}" + - ".${authTag.encodeBase64(Base64.UrlSafe(pad = false))}" + return headerAsParsed.encodeToString(Base64 { encodeToUrlSafe = true; padEncoded = false }) + + ".${encryptedKey?.encodeToString(Base64 { encodeToUrlSafe = true; padEncoded = false }) ?: ""}" + + ".${iv.encodeToString(Base64 { encodeToUrlSafe = true; padEncoded = false })}" + + ".${ciphertext.encodeToString(Base64 { encodeToUrlSafe = true; padEncoded = false })}" + + ".${authTag.encodeToString(Base64 { encodeToUrlSafe = true; padEncoded = false })}" } override fun equals(other: Any?): Boolean { @@ -61,14 +61,14 @@ data class JweEncrypted( fun parse(it: String): JweEncrypted? { val stringList = it.replace("[^A-Za-z0-9-_.]".toRegex(), "").split(".") if (stringList.size != 5) return null.also { Napier.w("Could not parse JWE: $it") } - val headerAsParsed = stringList[0].decodeBase64ToArray() + val headerAsParsed = stringList[0].decodeToByteArrayOrNull(Base64()) ?: return null.also { Napier.w("Could not parse JWE: $it") } - val encryptedKey = stringList[1].decodeBase64ToArray() - val iv = stringList[2].decodeBase64ToArray() + val encryptedKey = stringList[1].decodeToByteArrayOrNull(Base64()) + val iv = stringList[2].decodeToByteArrayOrNull(Base64()) ?: return null.also { Napier.w("Could not parse JWE: $it") } - val ciphertext = stringList[3].decodeBase64ToArray() + val ciphertext = stringList[3].decodeToByteArrayOrNull(Base64()) ?: return null.also { Napier.w("Could not parse JWE: $it") } - val authTag = stringList[4].decodeBase64ToArray() + val authTag = stringList[4].decodeToByteArrayOrNull(Base64()) ?: return null.also { Napier.w("Could not parse JWE: $it") } return JweEncrypted(headerAsParsed, encryptedKey, iv, ciphertext, authTag) } diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/JwsService.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/JwsService.kt index 963ef508..54b00676 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/JwsService.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/JwsService.kt @@ -8,9 +8,9 @@ import at.asitplus.wallet.lib.jws.JwsExtensions.encodeToByteArray import at.asitplus.wallet.lib.jws.JwsExtensions.encodeWithLength import at.asitplus.wallet.lib.jws.JwsExtensions.extractSignatureValues import io.github.aakira.napier.Napier -import io.matthewnelson.component.base64.Base64 -import io.matthewnelson.component.base64.encodeBase64 -import io.matthewnelson.component.base64.encodeBase64ToByteArray +import io.matthewnelson.encoding.base64.Base64 +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToByteArray +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlin.random.Random /** @@ -82,7 +82,8 @@ class DefaultJwsService(private val cryptoService: CryptoService) : JwsService { return null.also { Napier.w("Algorithm or keyId not matching to cryptoService") } } val signatureInput = header.serialize().encodeToByteArray() - .encodeBase64(Base64.UrlSafe(pad = false)) + "." + payload.encodeBase64(Base64.UrlSafe(pad = false)) + .encodeToString(Base64 { encodeToUrlSafe = true; padEncoded = false }) + + "." + payload.encodeToString(Base64 { encodeToUrlSafe = true; padEncoded = false }) val signatureInputBytes = signatureInput.encodeToByteArray() val signature = cryptoService.sign(signatureInputBytes).getOrElse { Napier.w("No signature from native code", it) @@ -130,7 +131,7 @@ class DefaultJwsService(private val cryptoService: CryptoService) : JwsService { return null } val iv = jweObject.iv - val aad = jweObject.headerAsParsed.encodeBase64ToByteArray(Base64.UrlSafe(pad = false)) + val aad = jweObject.headerAsParsed.encodeToByteArray(Base64 { encodeToUrlSafe = true; padEncoded = false }) val ciphertext = jweObject.ciphertext val authTag = jweObject.authTag val plaintext = @@ -177,7 +178,7 @@ class DefaultJwsService(private val cryptoService: CryptoService) : JwsService { val iv = Random.Default.nextBytes(jweEncryption.ivLengthBits / 8) val headerSerialized = jweHeader.serialize() val aad = headerSerialized.encodeToByteArray() - val aadForCipher = aad.encodeBase64ToByteArray(Base64.UrlSafe(pad = false)) + val aadForCipher = aad.encodeToByteArray(Base64 { encodeToUrlSafe = true; padEncoded = false }) val ciphertext = cryptoService.encrypt(key, iv, aadForCipher, payload, jweEncryption).getOrElse { Napier.w("No ciphertext from native code", it) diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/JwsSigned.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/JwsSigned.kt index b18e6a75..8d89787a 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/JwsSigned.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/JwsSigned.kt @@ -1,9 +1,10 @@ package at.asitplus.wallet.lib.jws -import io.matthewnelson.component.base64.Base64 -import io.matthewnelson.component.base64.decodeBase64ToArray -import io.matthewnelson.component.base64.encodeBase64 import io.github.aakira.napier.Napier +import io.matthewnelson.component.base64.decodeBase64ToArray +import io.matthewnelson.encoding.base64.Base64 +import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArrayOrNull +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString /** * Representation of a signed JSON Web Signature object, i.e. consisting of header, payload and signature. @@ -15,7 +16,11 @@ data class JwsSigned( val plainSignatureInput: String, ) { fun serialize(): String { - return "${plainSignatureInput}.${signature.encodeBase64(Base64.UrlSafe(pad = false))}" + return "${plainSignatureInput}.${ + signature.encodeToString(Base64 { + encodeToUrlSafe = true; padEncoded = false + }) + }" } override fun equals(other: Any?): Boolean { @@ -42,13 +47,13 @@ data class JwsSigned( fun parse(it: String): JwsSigned? { val stringList = it.replace("[^A-Za-z0-9-_.]".toRegex(), "").split(".") if (stringList.size != 3) return null.also { Napier.w("Could not parse JWS: $it") } - val headerInput = stringList[0].decodeBase64ToArray() + val headerInput = stringList[0].decodeToByteArrayOrNull(Base64()) ?: return null.also { Napier.w("Could not parse JWS: $it") } val header = JwsHeader.deserialize(headerInput.decodeToString()) ?: return null.also { Napier.w("Could not parse JWS: $it") } - val payload = stringList[1].decodeBase64ToArray() + val payload = stringList[1].decodeToByteArrayOrNull(Base64()) ?: return null.also { Napier.w("Could not parse JWS: $it") } - val signature = stringList[2].decodeBase64ToArray() + val signature = stringList[2].decodeToByteArrayOrNull(Base64()) ?: return null.also { Napier.w("Could not parse JWS: $it") } return JwsSigned(header, payload, signature, "${stringList[0]}.${stringList[1]}") } diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/MultibaseHelper.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/MultibaseHelper.kt index 0f3d6b84..5793f52c 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/MultibaseHelper.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/jws/MultibaseHelper.kt @@ -3,6 +3,9 @@ package at.asitplus.wallet.lib.jws import at.asitplus.wallet.lib.cbor.CoseEllipticCurve import io.matthewnelson.component.base64.decodeBase64ToArray import io.matthewnelson.component.base64.encodeBase64 +import io.matthewnelson.encoding.base64.Base64 +import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArrayOrNull +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString object MultibaseHelper { @@ -41,11 +44,11 @@ object MultibaseHelper { return decodeP256Key(multicodecDecode(multibaseDecode(stripped))) } - private fun multibaseWrapBase64(it: ByteArray) = "m${it.encodeBase64()}" + private fun multibaseWrapBase64(it: ByteArray) = "m${it.encodeToString(Base64())}" private fun multibaseDecode(it: String?) = if (it != null && it.startsWith("m")) { - it.removePrefix("m").decodeBase64ToArray() + it.removePrefix("m").decodeToByteArrayOrNull(Base64()) } else null // 0x1200 would be with compression, so we'll use 0x1290 -- GitLab