diff --git a/CHANGELOG.md b/CHANGELOG.md index dd8435e3af951f4cf4f64a94fb577ecb1a53127e..ddf8b885dc74a30bd11522587d5c994018071284 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 edc666d4fd9cdb8da87b935ef92d5736a62ce80f..4aec06670f8b4b13551281fa466e81458c2c7bbd 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 603e672a0860a7a896962900c23a63aff45e5487..01319540d9c14061cc89105d42605863b2f9784b 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 529d97213f7fd464f9aca9482adbbc2dc85c60b4..1613a6d13a7fd1104b02a01001403431f6007457 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 279c1fe95f0bb2d3b032beb3c7e1afde3ca8a2cc..d524ae231739420e8de0ba0b93344d9d575a5adc 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 7dc6fcce904899cf2f9873cc93304ba419e51dd9..040b304e171697ca5e27f18cb0bb2df19c95144f 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 061e6bb7f97d89e12c077cbffd39334be96a3c43..157c88e0a719f6d49f7aba727e129e694c512ccc 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 8ed4f1d1c76542f69fe00f930874451a95f68986..5dd33d2ca4041a174d70f4d02bdc5ebcd2a2716f 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 81817e8c29bec0b2cfe9ed5a76ffb77a635fdbf7..26d026c4ff4880f24d974efa36299785a8a01e02 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 b21d183efe8df3cef57c18bf5d4578af6dd687fd..04a0742ee818487d1f71d220faf548a493fc2fe8 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 ff0f67dfc9d1d2a6f0365e353ffdfb4ae7f1b637..ea5ed0f46611d1d5801eb791cec7587654b96177 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 abec236deacc509a42b3c81919a9520766f5be05..a055283222d9e69b7feef6064fa829b8eabdf741 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 1e3c9dcb1b4efd5452847a210d4f7533101b27fc..72063ba3e7a4e47e4c1cb2bba0909f4dbcbed94b 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 031dfbbed28f8881263a0ca8554970508072d654..45992ad7871a9f34db85c7321117efa9ead937f9 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 46531bef2a63c351c1d6fcf19a6c520ebd967e41..7c3f9e34b54658ed9318c04cf03cff13bad83fd2 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 c34f5f55e851493fac92f07a0551750a7eb511cd..59bc1111faf5198353eccd5babaf8467eb603bff 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 c73be6c18a15f013ed05bbd33b50020c5ee2dbe1..a68dcdee60ff16d8e14615e6d821302e1efe6674 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 e6c61aee55fd71557f0439d9f33c11afdd600434..d247bd32e9685dcd854ece834262a50f40541ed9 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 4b556bcac38bce829dd0aa6d4044db166d06f49b..7510394acfc9d9e432e9d74a57cfaf1f00431970 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 aaaddbf80ea8db568ad93b89d481452661a48d6c..bdfb19b385c4c2cd5dee9207a692c23023c604b6 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 b3a0e0bcef0c27e51b6ea4f401cce3c70886feb6..ea9ec61ad4f7fd110b4f49348d2e2d954aead5df 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 963ef50813737a85dfb1d180987b416c1ca44b7e..54b006761b3465dc8413a7a0026a5da1ba3202f5 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 b18e6a752d8e7fc1f8f0480d64d8cb1c23ba97ba..8d89787a1f6c3beb4fa7cb76573c5cafff731c0f 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 0f3d6b845038f3a0ac0f2261ed28bf9f48c6ffde..5793f52c8f78e5241746126bb2931b79b0e6801c 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