Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Refactorings #18

Merged
merged 5 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package at.asitplus.wallet.lib.msg

import at.asitplus.wallet.lib.aries.jsonSerializer
import at.asitplus.wallet.lib.data.Base64Strict
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
import kotlinx.serialization.encodeToString

/**
Expand All @@ -33,7 +30,7 @@ data class JwmAttachment(

fun decodeString(): String? {
if (data.base64 != null)
return data.base64.decodeToByteArrayOrNull(Base64())?.decodeToString()
return data.base64.decodeToByteArrayOrNull(Base64Strict)?.decodeToString()
if (data.jws != null)
return data.jws
return null
Expand All @@ -42,7 +39,7 @@ data class JwmAttachment(

fun decodeBinary(): ByteArray? {
if (data.base64 != null)
return data.base64.decodeToByteArrayOrNull(Base64())
return data.base64.decodeToByteArrayOrNull(Base64Strict)
return null
.also { Napier.w("Could not binary decode JWM attachment") }
}
Expand All @@ -60,15 +57,15 @@ data class JwmAttachment(
id = uuid4().toString(),
mediaType = "application/base64",
data = JwmAttachmentData(
base64 = data.encodeToByteArray().encodeToString(Base64())
base64 = data.encodeToByteArray().encodeToString(Base64Strict)
)
)

fun encodeBase64(data: ByteArray) = JwmAttachment(
id = uuid4().toString(),
mediaType = "application/base64",
data = JwmAttachmentData(
base64 = data.encodeToString(Base64())
base64 = data.encodeToString(Base64Strict)
)
)

Expand All @@ -78,7 +75,7 @@ data class JwmAttachment(
filename = filename,
parent = parent,
data = JwmAttachmentData(
base64 = data.encodeToString(Base64())
base64 = data.encodeToString(Base64Strict)
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import at.asitplus.wallet.lib.agent.IssuerCredentialDataProvider
import at.asitplus.wallet.lib.cbor.CoseKey
import at.asitplus.wallet.lib.data.AtomicAttribute2023
import at.asitplus.wallet.lib.data.ConstantIndex
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.random.Random
import kotlin.time.Duration.Companion.minutes
Expand Down Expand Up @@ -60,7 +61,7 @@ class DummyCredentialDataProvider(
)
}

private fun randomValue() = Random.nextBytes(32).encodeBase16()
private fun randomValue() = Random.nextBytes(32).encodeToString(Base16(strict = true))

}

Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import io.github.aakira.napier.Napier
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
Expand Down Expand Up @@ -292,7 +291,7 @@ class OidcSiopWallet(
AuthenticationResponseParameters(
idToken = signedIdToken,
state = params.state,
vpToken = vp.document.serialize().encodeToString(Base16()),
vpToken = vp.document.serialize().encodeToString(Base16(strict = true)),
presentationSubmission = presentationSubmission,
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import at.asitplus.wallet.lib.agent.Issuer
import at.asitplus.wallet.lib.cbor.CoseEllipticCurve
import at.asitplus.wallet.lib.cbor.CoseKey
import at.asitplus.wallet.lib.cbor.CoseKeyType
import at.asitplus.wallet.lib.data.Base64UrlStrict
import at.asitplus.wallet.lib.data.ConstantIndex
import at.asitplus.wallet.lib.data.VcDataModelConstants.VERIFIABLE_CREDENTIAL
import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DOC_TYPE_MDL
Expand All @@ -22,8 +23,7 @@ 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.*
import io.matthewnelson.encoding.base64.Base64
import io.ktor.http.URLBuilder
import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString
import kotlin.coroutines.cancellation.CancellationException

Expand Down Expand Up @@ -167,8 +167,7 @@ class IssuerService(
return when (val issuedCredential = issuedCredentialResult.successful.first()) {
is Issuer.IssuedCredential.Iso -> CredentialResponseParameters(
format = CredentialFormatEnum.MSO_MDOC,
credential = issuedCredential.issuerSigned.serialize()
.encodeToString(Base64 { encodeToUrlSafe = true; padEncoded = false })
credential = issuedCredential.issuerSigned.serialize().encodeToString(Base64UrlStrict)
)

is Issuer.IssuedCredential.Vc -> CredentialResponseParameters(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import at.asitplus.wallet.lib.iso.DrivingPrivilege
import at.asitplus.wallet.lib.iso.ElementValue
import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DataElements
import at.asitplus.wallet.lib.iso.IssuerSignedItem
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 kotlinx.datetime.LocalDate
import kotlin.random.Random
Expand Down Expand Up @@ -91,7 +92,7 @@ class DummyCredentialDataProvider(
return KmmResult.success(listOfAttributes)
}

private fun randomValue() = Random.nextBytes(32).encodeBase16()
private fun randomValue() = Random.nextBytes(32).encodeToString(Base16(strict = true))

fun buildIssuerSignedItem(elementIdentifier: String, elementValue: String, digestId: UInt) = IssuerSignedItem(
digestId = digestId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package at.asitplus.wallet.lib.agent
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
Expand Down Expand Up @@ -47,7 +46,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().encodeToString(Base16()),
vcId = issuerSignedItemList.toString().encodeToByteArray().sha256().encodeToString(Base16(strict = true)),
statusListIndex = newIndex,
revoked = false,
expirationDate = expirationDate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import at.asitplus.wallet.lib.cbor.CoseHeader
import at.asitplus.wallet.lib.cbor.CoseKey
import at.asitplus.wallet.lib.cbor.CoseService
import at.asitplus.wallet.lib.cbor.DefaultCoseService
import at.asitplus.wallet.lib.data.Base64Strict
import at.asitplus.wallet.lib.data.CredentialStatus
import at.asitplus.wallet.lib.data.RevocationListSubject
import at.asitplus.wallet.lib.data.VcDataModelConstants.REVOCATION_LIST_MIN_SIZE
Expand All @@ -28,12 +29,9 @@ import at.asitplus.wallet.lib.jws.JwsContentTypeConstants
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
import kotlin.time.Duration
import kotlin.time.Duration.Companion.hours

Expand Down Expand Up @@ -221,7 +219,7 @@ class IssuerAgent(
issuerCredentialStore.getRevokedStatusListIndexList(timePeriod)
.forEach { bitset[it] = true }
val input = bitset.toByteArray()
return zlibService.compress(input)?.encodeToString(Base64())
return zlibService.compress(input)?.encodeToString(Base64Strict)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import at.asitplus.wallet.lib.ZlibService
import at.asitplus.wallet.lib.cbor.CoseKey
import at.asitplus.wallet.lib.cbor.DefaultVerifierCoseService
import at.asitplus.wallet.lib.cbor.VerifierCoseService
import at.asitplus.wallet.lib.data.Base64Strict
import at.asitplus.wallet.lib.data.IsoDocumentParsed
import at.asitplus.wallet.lib.data.RevocationListSubject
import at.asitplus.wallet.lib.data.VerifiableCredentialJws
Expand All @@ -24,10 +25,7 @@ import at.asitplus.wallet.lib.jws.JwsSigned
import at.asitplus.wallet.lib.jws.VerifierJwsService
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
Expand Down Expand Up @@ -90,7 +88,7 @@ class Validator(
return false
.also { Napier.d("credentialSubject invalid") }
val encodedList = parsedVc.jws.vc.credentialSubject.encodedList
this.revocationList = encodedList.decodeToByteArrayOrNull(Base64())?.let {
this.revocationList = encodedList.decodeToByteArrayOrNull(Base64Strict)?.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")
Expand Down Expand Up @@ -190,56 +188,80 @@ class Validator(
*/
fun verifyDocument(doc: Document, challenge: String): Verifier.VerifyPresentationResult {
if (doc.docType != DOC_TYPE_MDL)
return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16()))
return Verifier.VerifyPresentationResult.InvalidStructure(
doc.serialize().encodeToString(Base16(strict = true))
)
.also { Napier.w("Invalid docType: ${doc.docType}") }
if (doc.errors != null) {
return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16()))
return Verifier.VerifyPresentationResult.InvalidStructure(
doc.serialize().encodeToString(Base16(strict = true))
)
.also { Napier.w("Document has errors: ${doc.errors}") }
}
val issuerSigned = doc.issuerSigned
val issuerAuth = issuerSigned.issuerAuth

val issuerKey = issuerAuth.unprotectedHeader?.certificateChain?.let {
CryptoUtils.extractPublicKeyFromX509Cert(it)?.toCoseKey()
} ?: return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16()))
} ?: return Verifier.VerifyPresentationResult.InvalidStructure(
doc.serialize().encodeToString(Base16(strict = true))
)
.also { Napier.w("Got no issuer key in $issuerAuth") }

if (verifierCoseService.verifyCose(issuerAuth, issuerKey).getOrNull() != true) {
return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16()))
return Verifier.VerifyPresentationResult.InvalidStructure(
doc.serialize().encodeToString(Base16(strict = true))
)
.also { Napier.w("IssuerAuth not verified: $issuerAuth") }
}

val mso = issuerSigned.getIssuerAuthPayloadAsMso()
?: return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16()))
.also { Napier.w("MSO is null: ${issuerAuth.payload?.encodeToString(Base16())}") }
?: return Verifier.VerifyPresentationResult.InvalidStructure(
doc.serialize().encodeToString(Base16(strict = true))
)
.also { Napier.w("MSO is null: ${issuerAuth.payload?.encodeToString(Base16(strict = true))}") }
if (mso.docType != DOC_TYPE_MDL) {
return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16()))
return Verifier.VerifyPresentationResult.InvalidStructure(
doc.serialize().encodeToString(Base16(strict = true))
)
.also { Napier.w("Invalid docType in MSO: ${mso.docType}") }
}
val mdlItems = mso.valueDigests[NAMESPACE_MDL]
?: return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16()))
?: return Verifier.VerifyPresentationResult.InvalidStructure(
doc.serialize().encodeToString(Base16(strict = true))
)
.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().encodeToString(Base16()))
?: return Verifier.VerifyPresentationResult.InvalidStructure(
doc.serialize().encodeToString(Base16(strict = true))
)
.also { Napier.w("DeviceSignature is null: ${doc.deviceSigned.deviceAuth}") }

if (verifierCoseService.verifyCose(deviceSignature, walletKey).getOrNull() != true) {
return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16()))
return Verifier.VerifyPresentationResult.InvalidStructure(
doc.serialize().encodeToString(Base16(strict = true))
)
.also { Napier.w("DeviceSignature not verified") }
}

val deviceSignaturePayload = deviceSignature.payload
?: return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16()))
?: return Verifier.VerifyPresentationResult.InvalidStructure(
doc.serialize().encodeToString(Base16(strict = true))
)
.also { Napier.w("DeviceSignature does not contain challenge") }
if (!deviceSignaturePayload.contentEquals(challenge.encodeToByteArray())) {
return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16()))
return Verifier.VerifyPresentationResult.InvalidStructure(
doc.serialize().encodeToString(Base16(strict = true))
)
.also { Napier.w("DeviceSignature does not contain correct challenge") }
}

val issuerSignedItems = issuerSigned.namespaces?.get(NAMESPACE_MDL)
?: return Verifier.VerifyPresentationResult.InvalidStructure(doc.serialize().encodeToString(Base16()))
?: return Verifier.VerifyPresentationResult.InvalidStructure(
doc.serialize().encodeToString(Base16(strict = true))
)
.also { Napier.w("No issuer signed items in ${issuerSigned.namespaces}") }

val validatedItems = issuerSignedItems.entries.associateWith { it.verify(mdlItems) }
Expand All @@ -255,7 +277,9 @@ 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.encodeToString(Base16()).contentEquals(issuerHash.value.encodeToString(Base16()))) {
if (!verifierHash.encodeToString(Base16(strict = true))
.contentEquals(issuerHash.value.encodeToString(Base16(strict = true)))
) {
Napier.w("Could not verify hash of value for ${value.elementIdentifier}")
return false
}
Expand Down Expand Up @@ -307,12 +331,16 @@ class Validator(
Napier.d("Verifying ISO Cred $it")
if (issuerKey == null) {
Napier.w("ISO: No issuer key")
return Verifier.VerifyCredentialResult.InvalidStructure(it.serialize().encodeToString(Base16()))
return Verifier.VerifyCredentialResult.InvalidStructure(
it.serialize().encodeToString(Base16(strict = true))
)
}
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().encodeToString(Base16()))
return Verifier.VerifyCredentialResult.InvalidStructure(
it.serialize().encodeToString(Base16(strict = true))
)
}
return Verifier.VerifyCredentialResult.SuccessIso(it)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import at.asitplus.wallet.lib.data.VerifiablePresentationParsed
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

Expand Down Expand Up @@ -58,7 +57,7 @@ class VerifierAgent private constructor(
return validator.verifyVpJws(it, challenge, identifier)
}
val document =
runCatching { it.decodeToByteArrayOrNull(Base16())?.let { bytes -> Document.deserialize(bytes) } }.getOrNull()
runCatching { it.decodeToByteArrayOrNull(Base16(strict = true))?.let { bytes -> Document.deserialize(bytes) } }.getOrNull()
if (document != null) {
return validator.verifyDocument(document, challenge)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package at.asitplus.wallet.lib.cbor

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
Expand Down Expand Up @@ -95,10 +93,10 @@ data class CoseHeader(
return "CoseHeader(algorithm=$algorithm," +
" criticalHeaders=$criticalHeaders," +
" contentType=$contentType," +
" kid=${kid?.encodeToString(Base16())}," +
" iv=${iv?.encodeToString(Base16())}," +
" partialIv=${partialIv?.encodeToString(Base16())}," +
" certificateChain=${certificateChain?.encodeToString(Base16())})"
" kid=${kid?.encodeToString(Base16(strict = true))}," +
" iv=${iv?.encodeToString(Base16(strict = true))}," +
" partialIv=${partialIv?.encodeToString(Base16(strict = true))}," +
" certificateChain=${certificateChain?.encodeToString(Base16(strict = true))})"
}

companion object {
Expand Down
Loading
Loading