Skip to content

Commit

Permalink
Fix serialization of byte arrays in SD-JWT
Browse files Browse the repository at this point in the history
  • Loading branch information
nodh committed Sep 19, 2024
1 parent 7225114 commit 7c41ed1
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Release NEXT:
- Remove `OidcSiopVerifier.newInstance()` methods and replace them with constructors
- Remove `Validator.newDefaultInstance()` methods and replace them with constructors
- Remove `WalletService.newDefaultInstance()` methods and replace them with constructors
- Fix serialization of byte arrays in SD-JWT disclosures and claims
* Add `TransactionDataEntry` class
* Add `DocumentDigestEntry` class
* Add `DocumentDigestEntryCSC` class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import at.asitplus.signum.indispensable.josef.JsonWebKeySet
import at.asitplus.signum.indispensable.josef.JsonWebToken
import at.asitplus.signum.indispensable.josef.JwsHeader
import at.asitplus.signum.indispensable.josef.toJwsAlgorithm
import at.asitplus.wallet.lib.agent.*
import at.asitplus.wallet.lib.agent.CryptoService
import at.asitplus.wallet.lib.agent.DefaultCryptoService
import at.asitplus.wallet.lib.agent.EphemeralKeyWithoutCert
import at.asitplus.wallet.lib.agent.KeyMaterial
import at.asitplus.wallet.lib.cbor.CoseService
import at.asitplus.wallet.lib.cbor.DefaultCoseService
import at.asitplus.wallet.lib.data.ConstantIndex
Expand Down Expand Up @@ -81,15 +84,15 @@ class WalletService(
) {

constructor(
clientId: String,
redirectUrl: String,
keyPairAdapter: KeyMaterial,
clientId: String = "https://wallet.a-sit.at/app",
redirectUrl: String = "$clientId/callback",
keyMaterial: KeyMaterial,
remoteResourceRetriever: RemoteResourceRetrieverFunction = { null },
stateToCodeStore: MapStore<String, String> = DefaultMapStore(),
) : this(
clientId = clientId,
redirectUrl = redirectUrl,
cryptoService = DefaultCryptoService(keyPairAdapter),
cryptoService = DefaultCryptoService(keyMaterial),
remoteResourceRetriever = remoteResourceRetriever,
stateToCodeStore = stateToCodeStore
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ import at.asitplus.wallet.mdl.MobileDrivingLicenceDataElements.FAMILY_NAME
import at.asitplus.wallet.mdl.MobileDrivingLicenceDataElements.GIVEN_NAME
import at.asitplus.wallet.mdl.MobileDrivingLicenceDataElements.ISSUE_DATE
import at.asitplus.wallet.mdl.MobileDrivingLicenceScheme
import io.matthewnelson.encoding.base64.Base64
import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArray
import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString
import kotlinx.datetime.Clock
import kotlinx.datetime.LocalDate
import kotlin.random.Random
import kotlin.time.Duration.Companion.minutes


class DummyOAuth2IssuerCredentialDataProvider(
private val userInfo: OidcUserInfoExtended,
private val clock: Clock = Clock.System,
Expand Down Expand Up @@ -58,16 +62,17 @@ class DummyOAuth2IssuerCredentialDataProvider(
val givenName = userInfo.userInfo.givenName
val subjectId = subjectPublicKey.didEncoded
val claims = listOfNotNull(
givenName?.let { optionalClaim(claimNames, "given_name", it) },
givenName?.let { optionalClaim(claimNames, CLAIM_GIVEN_NAME, it) },
familyName?.let { optionalClaim(claimNames, "family_name", it) },
userInfo.userInfo.birthDate?.let { optionalClaim(claimNames, "date_of_birth", it) },
userInfo.userInfo.picture?.let { optionalClaim(claimNames, CLAIM_PORTRAIT, it.decodeToByteArray(Base64())) },
)
return when (representation) {
ConstantIndex.CredentialRepresentation.SD_JWT ->
CredentialToBeIssued.VcSd(claims, expiration)

ConstantIndex.CredentialRepresentation.PLAIN_JWT -> CredentialToBeIssued.VcJwt(
AtomicAttribute2023(subjectId, "given_name", givenName ?: "no value"), expiration,
AtomicAttribute2023(subjectId, GIVEN_NAME, givenName ?: "no value"), expiration,
)

ConstantIndex.CredentialRepresentation.ISO_MDOC -> CredentialToBeIssued.Iso(
Expand Down Expand Up @@ -155,13 +160,17 @@ class DummyOAuth2IssuerCredentialDataProvider(
private fun optionalClaim(claimNames: Collection<String>?, name: String, value: Any) =
if (claimNames.isNullOrContains(name)) ClaimToBeIssued(name, value) else null


private fun issuerSignedItem(name: String, value: Any, digestId: UInt) = IssuerSignedItem(
digestId = digestId,
random = Random.nextBytes(16),
elementIdentifier = name,
elementValue = value
)

companion object {
const val CLAIM_GIVEN_NAME = "given_name"
const val CLAIM_PORTRAIT = "portrait"
}
}

object DummyOAuth2DataProvider : OAuth2DataProvider {
Expand All @@ -170,7 +179,8 @@ object DummyOAuth2DataProvider : OAuth2DataProvider {
OidcUserInfo(
subject = "subject",
givenName = "Erika",
familyName = "Musterfrau"
familyName = "Musterfrau",
picture = Random.nextBytes(64).encodeToString(Base64())
)
).getOrThrow()
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class OidvciProcessTest : FunSpec({
client = WalletService(
clientId = "https://wallet.a-sit.at/app",
redirectUrl = "https://wallet.a-sit.at/callback",
keyPairAdapter = EphemeralKeyWithSelfSignedCert()
keyMaterial = EphemeralKeyWithSelfSignedCert()
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package at.asitplus.wallet.lib.jws

import at.asitplus.signum.indispensable.io.Base64UrlStrict
import at.asitplus.wallet.lib.data.SelectiveDisclosureItem
import io.matthewnelson.encoding.base64.Base64
import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArray
import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString
import kotlinx.serialization.KSerializer
Expand Down Expand Up @@ -32,6 +33,7 @@ object SelectiveDisclosureItemSerializer : KSerializer<SelectiveDisclosureItem>
val valueElement = when (val value = value.claimValue) {
is Boolean -> JsonPrimitive(value)
is Number -> JsonPrimitive(value)
is ByteArray -> JsonPrimitive(value.encodeToString(Base64UrlStrict))
else -> JsonPrimitive(value.toString())
}
encoder.encodeSerializableValue(
Expand All @@ -47,15 +49,17 @@ object SelectiveDisclosureItemSerializer : KSerializer<SelectiveDisclosureItem>
override fun deserialize(decoder: Decoder): SelectiveDisclosureItem {
val items = decoder.decodeSerializableValue(listSerializer)
if (items.count() != 3) throw IllegalArgumentException()
val (firstElement, secondElement, thirdElement) = items
return SelectiveDisclosureItem(
salt = items[0].content.decodeToByteArray(Base64UrlStrict),
claimName = items[1].content,
claimValue = items[2].booleanOrNull
?: items[2].longOrNull
?: items[2].intOrNull
?: items[2].doubleOrNull
?: items[2].floatOrNull
?: items[2].content
salt = firstElement.content.decodeToByteArray(Base64UrlStrict),
claimName = secondElement.content,
claimValue = thirdElement.booleanOrNull
?: thirdElement.longOrNull
?: thirdElement.intOrNull
?: thirdElement.doubleOrNull
?: thirdElement.floatOrNull
?: runCatching { thirdElement.content.decodeToByteArray(Base64UrlStrict) }.getOrNull()
?: thirdElement.content
)
}

Expand Down

0 comments on commit 7c41ed1

Please sign in to comment.