Skip to content

Commit

Permalink
Merge pull request #10 from a-sit-plus/feature/mdl-cbor-serializer-pu…
Browse files Browse the repository at this point in the history
…blickeys

Introduce generic structure for public keys
  • Loading branch information
nodh authored Aug 22, 2023
2 parents 0cea934 + 801b04e commit 9031314
Show file tree
Hide file tree
Showing 26 changed files with 278 additions and 199 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ class IssueCredentialProtocolTest : FreeSpec({
oobInvitation.shouldBeInstanceOf<InternalNextMessage.SendAndWrap>()
val invitationMessage = oobInvitation.message

val parsedInvitation = holderProtocol.parseMessage(invitationMessage, issuerCryptoService.toJsonWebKey())
val parsedInvitation = holderProtocol.parseMessage(invitationMessage, issuerCryptoService.toPublicKey().toJsonWebKey())
parsedInvitation.shouldBeInstanceOf<InternalNextMessage.SendAndWrap>()
val requestCredential = parsedInvitation.message

val parsedRequestCredential = issuerProtocol.parseMessage(requestCredential, holderCryptoService.toJsonWebKey())
val parsedRequestCredential = issuerProtocol.parseMessage(requestCredential, holderCryptoService.toPublicKey().toJsonWebKey())
parsedRequestCredential.shouldBeInstanceOf<InternalNextMessage.SendAndWrap>()
val issueCredential = parsedRequestCredential.message

val parsedIssueCredential = holderProtocol.parseMessage(issueCredential, issuerCryptoService.toJsonWebKey())
val parsedIssueCredential = holderProtocol.parseMessage(issueCredential, issuerCryptoService.toPublicKey().toJsonWebKey())
parsedIssueCredential.shouldBeInstanceOf<InternalNextMessage.Finished>()

val issuedCredential = parsedIssueCredential.lastMessage
Expand All @@ -70,11 +70,11 @@ class IssueCredentialProtocolTest : FreeSpec({
requestCredential.shouldBeInstanceOf<InternalNextMessage.SendAndWrap>()

val parsedRequestCredential =
issuerProtocol.parseMessage(requestCredential.message, holderCryptoService.toJsonWebKey())
issuerProtocol.parseMessage(requestCredential.message, holderCryptoService.toPublicKey().toJsonWebKey())
parsedRequestCredential.shouldBeInstanceOf<InternalNextMessage.SendAndWrap>()
val issueCredential = parsedRequestCredential.message

val parsedIssueCredential = holderProtocol.parseMessage(issueCredential, issuerCryptoService.toJsonWebKey())
val parsedIssueCredential = holderProtocol.parseMessage(issueCredential, issuerCryptoService.toPublicKey().toJsonWebKey())
parsedIssueCredential.shouldBeInstanceOf<InternalNextMessage.Finished>()

val issuedCredential = parsedIssueCredential.lastMessage
Expand All @@ -88,7 +88,7 @@ class IssueCredentialProtocolTest : FreeSpec({
threadId = uuid4().toString(),
attachment = JwmAttachment(id = uuid4().toString(), "mimeType", JwmAttachmentData())
),
issuerCryptoService.toJsonWebKey()
issuerCryptoService.toPublicKey().toJsonWebKey()
)
parsed.shouldBeInstanceOf<InternalNextMessage.IncorrectState>()
}
Expand All @@ -98,7 +98,7 @@ class IssueCredentialProtocolTest : FreeSpec({
oobInvitation.shouldBeInstanceOf<InternalNextMessage.SendAndWrap>()
val invitationMessage = oobInvitation.message

val parsedInvitation = holderProtocol.parseMessage(invitationMessage, issuerCryptoService.toJsonWebKey())
val parsedInvitation = holderProtocol.parseMessage(invitationMessage, issuerCryptoService.toPublicKey().toJsonWebKey())
parsedInvitation.shouldBeInstanceOf<InternalNextMessage.SendAndWrap>()
val requestCredential = parsedInvitation.message

Expand All @@ -116,7 +116,7 @@ class IssueCredentialProtocolTest : FreeSpec({
)
)
val parsedRequestCredential =
issuerProtocol.parseMessage(wrongRequestCredential, holderCryptoService.toJsonWebKey())
issuerProtocol.parseMessage(wrongRequestCredential, holderCryptoService.toPublicKey().toJsonWebKey())
parsedRequestCredential.shouldBeInstanceOf<InternalNextMessage.SendProblemReport>()
val problemReport = parsedRequestCredential.message

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,16 @@ class PresentProofProtocolTest : FreeSpec({
oobInvitation.shouldBeInstanceOf<InternalNextMessage.SendAndWrap>()
val invitationMessage = oobInvitation.message

val parsedInvitation = verifierProtocol.parseMessage(invitationMessage, holderCryptoService.toJsonWebKey())
val parsedInvitation = verifierProtocol.parseMessage(invitationMessage, holderCryptoService.toPublicKey().toJsonWebKey())
parsedInvitation.shouldBeInstanceOf<InternalNextMessage.SendAndWrap>()
val requestPresentation = parsedInvitation.message

val parsedRequestPresentation =
holderProtocol.parseMessage(requestPresentation, verifierCryptoService.toJsonWebKey())
holderProtocol.parseMessage(requestPresentation, verifierCryptoService.toPublicKey().toJsonWebKey())
parsedRequestPresentation.shouldBeInstanceOf<InternalNextMessage.SendAndWrap>()
val presentation = parsedRequestPresentation.message

val parsedPresentation = verifierProtocol.parseMessage(presentation, holderCryptoService.toJsonWebKey())
val parsedPresentation = verifierProtocol.parseMessage(presentation, holderCryptoService.toPublicKey().toJsonWebKey())
parsedPresentation.shouldBeInstanceOf<InternalNextMessage.Finished>()

val receivedPresentation = parsedPresentation.lastMessage
Expand All @@ -89,11 +89,11 @@ class PresentProofProtocolTest : FreeSpec({
requestPresentation.shouldBeInstanceOf<InternalNextMessage.SendAndWrap>()

val parsedRequestPresentation =
holderProtocol.parseMessage(requestPresentation.message, verifierCryptoService.toJsonWebKey())
holderProtocol.parseMessage(requestPresentation.message, verifierCryptoService.toPublicKey().toJsonWebKey())
parsedRequestPresentation.shouldBeInstanceOf<InternalNextMessage.SendAndWrap>()
val presentation = parsedRequestPresentation.message

val parsedPresentation = verifierProtocol.parseMessage(presentation, holderCryptoService.toJsonWebKey())
val parsedPresentation = verifierProtocol.parseMessage(presentation, holderCryptoService.toPublicKey().toJsonWebKey())
parsedPresentation.shouldBeInstanceOf<InternalNextMessage.Finished>()

val receivedPresentation = parsedPresentation.lastMessage
Expand All @@ -107,7 +107,7 @@ class PresentProofProtocolTest : FreeSpec({
parentThreadId = uuid4().toString(),
attachment = JwmAttachment(id = uuid4().toString(), "mimeType", JwmAttachmentData())
),
holderCryptoService.toJsonWebKey()
holderCryptoService.toPublicKey().toJsonWebKey()
)
parsed.shouldBeInstanceOf<InternalNextMessage.IncorrectState>()
}
Expand All @@ -117,12 +117,12 @@ class PresentProofProtocolTest : FreeSpec({
oobInvitation.shouldBeInstanceOf<InternalNextMessage.SendAndWrap>()
val invitationMessage = oobInvitation.message

val parsedInvitation = verifierProtocol.parseMessage(invitationMessage, holderCryptoService.toJsonWebKey())
val parsedInvitation = verifierProtocol.parseMessage(invitationMessage, holderCryptoService.toPublicKey().toJsonWebKey())
parsedInvitation.shouldBeInstanceOf<InternalNextMessage.SendAndWrap>()
val requestPresentation = parsedInvitation.message

val parsedRequestPresentation =
holderProtocol.parseMessage(requestPresentation, verifierCryptoService.toJsonWebKey())
holderProtocol.parseMessage(requestPresentation, verifierCryptoService.toPublicKey().toJsonWebKey())
parsedRequestPresentation.shouldBeInstanceOf<InternalNextMessage.SendProblemReport>()
val problemReport = parsedRequestPresentation.message

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package at.asitplus.wallet.lib.oidc

import at.asitplus.wallet.lib.CryptoPublicKey
import at.asitplus.wallet.lib.agent.CryptoService
import at.asitplus.wallet.lib.agent.DefaultVerifierCryptoService
import at.asitplus.wallet.lib.agent.Verifier
Expand All @@ -18,7 +19,6 @@ import at.asitplus.wallet.lib.data.dif.SchemaReference
import at.asitplus.wallet.lib.iso.IsoDataModelConstants.NAMESPACE_MDL
import at.asitplus.wallet.lib.jws.DefaultJwsService
import at.asitplus.wallet.lib.jws.DefaultVerifierJwsService
import at.asitplus.wallet.lib.jws.JsonWebKey
import at.asitplus.wallet.lib.jws.JwsAlgorithm
import at.asitplus.wallet.lib.jws.JwsHeader
import at.asitplus.wallet.lib.jws.JwsService
Expand Down Expand Up @@ -54,7 +54,7 @@ import kotlin.time.toDuration
class OidcSiopVerifier(
private val verifier: Verifier,
private val relyingPartyUrl: String,
private val agentPublicKey: JsonWebKey,
private val agentPublicKey: CryptoPublicKey,
private val jwsService: JwsService,
private val verifierJwsService: VerifierJwsService,
timeLeewaySeconds: Long = 300L,
Expand All @@ -80,7 +80,7 @@ class OidcSiopVerifier(
) = OidcSiopVerifier(
verifier = verifier,
relyingPartyUrl = relyingPartyUrl,
agentPublicKey = cryptoService.toJsonWebKey(),
agentPublicKey = cryptoService.toPublicKey(),
jwsService = jwsService,
verifierJwsService = verifierJwsService,
timeLeewaySeconds = timeLeewaySeconds,
Expand Down Expand Up @@ -175,7 +175,7 @@ class OidcSiopVerifier(
)
val metadata = RelyingPartyMetadata(
redirectUris = arrayOf(relyingPartyUrl),
jsonWebKeySet = JsonWebKeySet(arrayOf(agentPublicKey)),
jsonWebKeySet = JsonWebKeySet(arrayOf(agentPublicKey.toJsonWebKey())),
subjectSyntaxTypesSupported = arrayOf(URN_TYPE_JWK_THUMBPRINT, PREFIX_DID_KEY),
vpFormats = vpFormats,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package at.asitplus.wallet.lib.oidc

import at.asitplus.KmmResult
import at.asitplus.wallet.lib.CryptoPublicKey
import at.asitplus.wallet.lib.agent.CryptoService
import at.asitplus.wallet.lib.agent.Holder
import at.asitplus.wallet.lib.data.dif.ClaimFormatEnum
import at.asitplus.wallet.lib.data.dif.PresentationSubmission
import at.asitplus.wallet.lib.data.dif.PresentationSubmissionDescriptor
import at.asitplus.wallet.lib.jws.DefaultJwsService
import at.asitplus.wallet.lib.jws.DefaultVerifierJwsService
import at.asitplus.wallet.lib.jws.JsonWebKey
import at.asitplus.wallet.lib.jws.JwsAlgorithm
import at.asitplus.wallet.lib.jws.JwsHeader
import at.asitplus.wallet.lib.jws.JwsService
Expand Down Expand Up @@ -48,7 +48,7 @@ import kotlin.time.Duration.Companion.seconds
*/
class OidcSiopWallet(
private val holder: Holder,
private val agentPublicKey: JsonWebKey,
private val agentPublicKey: CryptoPublicKey,
private val jwsService: JwsService,
private val verifierJwsService: VerifierJwsService = DefaultVerifierJwsService(),
private val clock: Clock = Clock.System,
Expand All @@ -65,7 +65,7 @@ class OidcSiopWallet(
clientId: String = "https://wallet.a-sit.at/"
) = OidcSiopWallet(
holder = holder,
agentPublicKey = cryptoService.toJsonWebKey(),
agentPublicKey = cryptoService.toPublicKey(),
jwsService = jwsService,
verifierJwsService = verifierJwsService,
clock = clock,
Expand Down Expand Up @@ -218,9 +218,9 @@ class OidcSiopWallet(
val now = clock.now()
// we'll assume jwk-thumbprint
val idToken = IdToken(
issuer = agentPublicKey.jwkThumbprint,
subject = agentPublicKey.jwkThumbprint,
subjectJwk = agentPublicKey,
issuer = agentPublicKey.toJsonWebKey().jwkThumbprint,
subject = agentPublicKey.toJsonWebKey().jwkThumbprint,
subjectJwk = agentPublicKey.toJsonWebKey(),
audience = params.redirectUrl,
issuedAt = now,
expiration = now + 60.seconds,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import at.asitplus.wallet.lib.cbor.CoseKey
import at.asitplus.wallet.lib.data.AtomicAttribute2023
import at.asitplus.wallet.lib.data.ConstantIndex
import at.asitplus.wallet.lib.iso.DrivingPrivilege
import at.asitplus.wallet.lib.iso.DrivingPrivilegeCode
import at.asitplus.wallet.lib.iso.ElementValue
import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DataElements
import at.asitplus.wallet.lib.iso.IssuerSignedItem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class OidcSiopIsoProtocolTest : FreeSpec({
dataProvider = DummyCredentialDataProvider(),
).issueCredentialWithTypes(
holderAgent.identifier,
subjectPublicKey = holderCryptoService.toCoseKey(),
subjectPublicKey = holderCryptoService.toPublicKey().toCoseKey(),
attributeTypes = listOf(ConstantIndex.MobileDrivingLicence2023.vcType)
).toStoreCredentialInput()
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,15 @@
package at.asitplus.wallet.lib.oidvci

import at.asitplus.KmmResult
import at.asitplus.wallet.lib.agent.CredentialToBeIssued
import at.asitplus.wallet.lib.agent.DefaultCryptoService
import at.asitplus.wallet.lib.agent.IssuerAgent
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 at.asitplus.wallet.lib.iso.DeviceKeyInfo
import at.asitplus.wallet.lib.iso.DrivingPrivilege
import at.asitplus.wallet.lib.iso.DrivingPrivilegeCode
import at.asitplus.wallet.lib.iso.ElementValue
import at.asitplus.wallet.lib.iso.IsoDataModelConstants
import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DataElements
import at.asitplus.wallet.lib.iso.IssuerSignedItem
import at.asitplus.wallet.lib.iso.MobileSecurityObject
import at.asitplus.wallet.lib.iso.ValidityInfo
import at.asitplus.wallet.lib.iso.ValueDigest
import at.asitplus.wallet.lib.iso.ValueDigestList
import at.asitplus.wallet.lib.oidc.DummyCredentialDataProvider
import at.asitplus.wallet.lib.oidc.OpenIdConstants
import at.asitplus.wallet.lib.oidc.OpenIdConstants.GRANT_TYPE_CODE
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.ktor.http.Url
import kotlinx.datetime.Clock
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.LocalDate
import kotlinx.datetime.plus
import kotlin.random.Random

class OidvciProcessTest : FunSpec({

Expand Down
122 changes: 122 additions & 0 deletions vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/CryptoPublicKey.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package at.asitplus.wallet.lib

import at.asitplus.KmmResult
import at.asitplus.wallet.lib.cbor.CoseAlgorithm
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.jws.EcCurve
import at.asitplus.wallet.lib.jws.JsonWebKey
import at.asitplus.wallet.lib.jws.JwkType
import at.asitplus.wallet.lib.jws.MultibaseHelper


sealed class CryptoPublicKey {

abstract fun toCoseKey(): CoseKey
abstract fun toJsonWebKey(): JsonWebKey

data class Ec(
val curve: EcCurve,
val keyId: String,
val x: ByteArray,
val y: ByteArray,
) : CryptoPublicKey() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as Ec

if (curve != other.curve) return false
if (keyId != other.keyId) return false
if (!x.contentEquals(other.x)) return false
if (!y.contentEquals(other.y)) return false

return true
}

override fun hashCode(): Int {
var result = curve.hashCode()
result = 31 * result + keyId.hashCode()
result = 31 * result + x.contentHashCode()
result = 31 * result + y.contentHashCode()
return result
}

companion object {

fun fromKeyId(it: String): CryptoPublicKey? {
val (xCoordinate, yCoordinate) = MultibaseHelper.calcPublicKey(it)
?: return null
return CryptoPublicKey.Ec(
curve = EcCurve.SECP_256_R_1,
keyId = it,
x = xCoordinate,
y = yCoordinate
)
}

fun fromAnsiX963Bytes(curve: EcCurve, it: ByteArray): CryptoPublicKey? {
if (curve != EcCurve.SECP_256_R_1) {
return null
}
if (it.size != 1 + 32 + 32 || it[0] != 0x04.toByte()) {
return null
}
val xCoordinate = it.sliceArray(1 until 33)
val yCoordinate = it.sliceArray(33 until 65)
val keyId = MultibaseHelper.calcKeyId(curve, xCoordinate, yCoordinate)
?: return null
return CryptoPublicKey.Ec(
curve = curve,
keyId = keyId,
x = xCoordinate,
y = yCoordinate
)
}

fun fromCoordinates(curve: EcCurve, x: ByteArray, y: ByteArray): CryptoPublicKey? {
if (curve != EcCurve.SECP_256_R_1) {
return null
}
val keyId = MultibaseHelper.calcKeyId(curve, x, y)
?: return null
return CryptoPublicKey.Ec(
curve = curve,
keyId = keyId,
x = x,
y = y
)
}
}

fun toAnsiX963ByteArray(): KmmResult<ByteArray> {
return KmmResult.success(byteArrayOf(0x04.toByte()) + x + y);
}

override fun toCoseKey() = CoseKey(
type = CoseKeyType.EC2,
curve = curve.toCoseCurve(),
keyId = keyId.encodeToByteArray(),
algorithm = CoseAlgorithm.ES256,
x = x,
y = y
)

override fun toJsonWebKey() = JsonWebKey(
curve = curve,
type = JwkType.EC,
keyId = keyId,
x = x,
y = y
)
}

}

private fun EcCurve.toCoseCurve(): CoseEllipticCurve = when (this) {
EcCurve.SECP_256_R_1 -> CoseEllipticCurve.P256
EcCurve.SECP_384_R_1 -> CoseEllipticCurve.P384
EcCurve.SECP_521_R_1 -> CoseEllipticCurve.P521
}
Loading

0 comments on commit 9031314

Please sign in to comment.