Skip to content

Commit 131258f

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents 0c6ec35 + aaaf1e9 commit 131258f

File tree

21 files changed

+252
-68
lines changed

21 files changed

+252
-68
lines changed

waltid-applications/waltid-web-wallet/apps/waltid-dev-wallet/src/pages/login.vue

+3-2
Original file line numberDiff line numberDiff line change
@@ -431,11 +431,12 @@ const MMSDK = new MetaMaskSDK({
431431
injectProvider: true
432432
});
433433
434-
await MMSDK.connect();
435-
const ethereum = MMSDK.getProvider();
434+
436435
437436
438437
async function openWeb3() {
438+
await MMSDK.connect();
439+
const ethereum = MMSDK.getProvider();
439440
const response = await fetch("/wallet-api/auth/account/web3/nonce", { method: "GET" });
440441
const challenge = await response.text();
441442
console.log("====Frontend DEBUG LOGS====");

waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/interfaces/ISessionCache.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ interface ISessionCache<T> {
88

99
/**
1010
* Puts the specified [session] with the specified [id] in the session cache.
11+
* @param id The ID to store the session under
12+
* @param session The session to store
13+
* @param ttl Optional custom time-to-live duration for this session.
14+
* If null, the default expiration is used.
1115
* @return the previous value associated with the [id], or `null` if the key was not present in the cache.
1216
*/
13-
fun putSession(id: String, session: T)
17+
fun putSession(id: String, session: T, ttl: kotlin.time.Duration? = null)
1418

1519
fun getSessionByAuthServerState(authServerState: String): T?
1620
/**

waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDCredentialVerifier.kt

+15-4
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ abstract class OpenIDCredentialVerifier(val config: CredentialVerifierConfig) :
4646
clientIdScheme: ClientIdScheme = config.defaultClientIdScheme,
4747
openId4VPProfile: OpenId4VPProfile = OpenId4VPProfile.DEFAULT,
4848
walletInitiatedAuthState: String? = null,
49-
trustedRootCAs: List<String>? = null
49+
trustedRootCAs: List<String>? = null,
50+
sessionTtl: Duration? = null // Custom TTL duration for the session
5051
): PresentationSession {
5152
val session = PresentationSession(
5253
id = sessionId ?: ShortIdUtils.randomSessionId(),
@@ -58,7 +59,7 @@ abstract class OpenIDCredentialVerifier(val config: CredentialVerifierConfig) :
5859
openId4VPProfile = openId4VPProfile,
5960
trustedRootCAs = trustedRootCAs
6061
).also {
61-
putSession(it.id, it)
62+
putSession(it.id, it, sessionTtl ?: expiresIn)
6263
}
6364
val presentationDefinitionUri = when(openId4VPProfile) {
6465
OpenId4VPProfile.ISO_18013_7_MDOC, OpenId4VPProfile.HAIP -> null
@@ -108,18 +109,28 @@ abstract class OpenIDCredentialVerifier(val config: CredentialVerifierConfig) :
108109
nonce = Uuid.random().toString()
109110
)
110111
return session.copy(authorizationRequest = authReq).also {
111-
putSession(session.id, it)
112+
putSession(session.id, it, sessionTtl ?: expiresIn)
112113
}
113114
}
114115

115116
open fun verify(tokenResponse: TokenResponse, session: PresentationSession): PresentationSession {
116117
// https://json-schema.org/specification
117118
// https://github.com/OptimumCode/json-schema-validator
119+
// Calculate the remaining time to live based on the session's expiration timestamp
120+
val remainingTtl = session.expirationTimestamp?.let {
121+
val now = Clock.System.now()
122+
if (it > now) {
123+
it - now // Calculate duration between now and expiration
124+
} else {
125+
null // Already expired
126+
}
127+
}
128+
118129
return session.copy(
119130
tokenResponse = tokenResponse,
120131
verificationResult = doVerify(tokenResponse, session)
121132
).also {
122-
putSession(it.id, it)
133+
putSession(it.id, it, remainingTtl)
123134
}
124135
}
125136

waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/EBSITestWallet.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import io.ktor.serialization.kotlinx.json.*
4242
import kotlinx.coroutines.runBlocking
4343
import kotlinx.datetime.Instant
4444
import kotlinx.serialization.json.*
45+
import kotlin.time.Duration
4546
import kotlin.uuid.ExperimentalUuidApi
4647
import kotlin.uuid.Uuid
4748

@@ -225,7 +226,7 @@ class EBSITestWallet(
225226
)
226227
}
227228

228-
override fun putSession(id: String, session: SIOPSession) {
229+
override fun putSession(id: String, session: SIOPSession, ttl: Duration?) {
229230
sessionCache[id] = session
230231
}
231232

waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/TestCredentialWallet.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import io.ktor.util.*
5252
import kotlinx.coroutines.runBlocking
5353
import kotlinx.datetime.Instant
5454
import kotlinx.serialization.json.*
55+
import kotlin.time.Duration
5556

5657
const val WALLET_PORT = 8001
5758
const val WALLET_BASE_URL = "http://localhost:${WALLET_PORT}"
@@ -241,7 +242,7 @@ class TestCredentialWallet(
241242
TODO("Not yet implemented")
242243
}
243244

244-
override fun putSession(id: String, session: SIOPSession) {
245+
override fun putSession(id: String, session: SIOPSession, ttl: Duration?) {
245246
sessionCache[id] = session
246247
}
247248

waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/VPTestVerifier.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import kotlinx.coroutines.runBlocking
2222
import kotlinx.serialization.json.Json
2323
import kotlinx.serialization.json.jsonObject
2424
import kotlinx.serialization.json.jsonPrimitive
25+
import kotlin.time.Duration
2526

2627
const val VP_VERIFIER_PORT = 8002
2728
const val VP_VERIFIER_BASE_URL = "http://localhost:$VP_VERIFIER_PORT"
@@ -34,7 +35,7 @@ class VPTestVerifier : OpenIDCredentialVerifier(
3435
private val presentationDefinitionCache = mutableMapOf<String, PresentationDefinition>()
3536
override fun getSession(id: String): PresentationSession? = sessionCache[id]
3637

37-
override fun putSession(id: String, session: PresentationSession) {
38+
override fun putSession(id: String, session: PresentationSession, ttl: Duration?) {
3839
sessionCache[id] = session
3940
}
4041

waltid-libraries/waltid-core-wallet/src/jvmMain/java/id/walt/wallet/core/service/oidc4vc/TestCredentialWallet.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ class TestCredentialWallet(
383383
TODO("Not yet implemented")
384384
}
385385

386-
override fun putSession(id: String, session: VPresentationSession) {
386+
override fun putSession(id: String, session: VPresentationSession, ttl: Duration?) {
387387
sessionCache[id] = session
388388
}
389389

waltid-services/waltid-e2e-tests/config/credential-issuer-metadata.conf

+7
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ supportedCredentialTypes = {
3636
doctype = "org.iso.18013.5.1.mDL"
3737
}
3838

39+
"org.iso.18013.5.1.mDL-JWT-Proof" = {
40+
format = mso_mdoc
41+
cryptographic_binding_methods_supported = ["cose_key"]
42+
credential_signing_alg_values_supported = ["ES256"]
43+
doctype = "org.iso.18013.5.1.mDL"
44+
}
45+
3946
"urn:eu.europa.ec.eudi:pid:1" = {
4047
format = "vc+sd-jwt"
4148
cryptographic_binding_methods_supported = ["jwk"]

waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialWallet.kt

+5-4
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ class LspPotentialWallet(val client: HttpClient, val walletId: String) {
5353
}
5454
}
5555

56-
suspend fun testMDocIssuance() = test("test mdoc issuance") {
56+
suspend fun testMDocIssuance(issuanceRequestData: String, useForPresentation: Boolean) = test("test mdoc issuance") {
5757
// === get credential offer from test issuer API ===
58-
val issuanceReq = Json.decodeFromString<IssuanceRequest>(IssuanceExamples.mDLCredentialIssuanceData).copy(
58+
val issuanceReq = Json.decodeFromString<IssuanceRequest>(issuanceRequestData).copy(
5959
authenticationMethod = AuthenticationMethod.PRE_AUTHORIZED
6060
)
6161
val offerResp = client.post("/openid4vc/mdoc/issue") {
@@ -77,7 +77,7 @@ class LspPotentialWallet(val client: HttpClient, val walletId: String) {
7777
}.expectSuccess().body<CredentialOffer.Draft13>()
7878

7979
assertEquals(1, resolvedOffer.credentialConfigurationIds.size)
80-
assertEquals("org.iso.18013.5.1.mDL", resolvedOffer.credentialConfigurationIds.first())
80+
assertEquals(issuanceReq.credentialConfigurationId, resolvedOffer.credentialConfigurationIds.first())
8181

8282
// === resolve issuer metadata ===
8383
val issuerMetadata =
@@ -100,7 +100,8 @@ class LspPotentialWallet(val client: HttpClient, val walletId: String) {
100100
val fetchedCredential = client.get("/wallet-api/wallet/$walletId/credentials/${issuedCred.id}")
101101
.expectSuccess().body<WalletCredential>()
102102
assertEquals(issuedCred.format, fetchedCredential.format)
103-
runBlocking { issuedMdocId = fetchedCredential.id }
103+
if(useForPresentation)
104+
runBlocking { issuedMdocId = fetchedCredential.id }
104105
}
105106

106107
suspend fun testMdocPresentation() = test("test mdoc presentation") {

waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import id.walt.commons.web.plugins.httpJson
99
import id.walt.credentials.schemes.JwsSignatureScheme
1010
import id.walt.crypto.keys.KeyGenerationRequest
1111
import id.walt.crypto.keys.KeyType
12+
import id.walt.issuer.issuance.IssuanceExamples
1213
import id.walt.issuer.issuance.IssuanceRequest
1314
import id.walt.issuer.issuerModule
1415
import id.walt.issuer.lspPotential.lspPotentialIssuanceTestApi
@@ -323,7 +324,8 @@ class WaltidServicesE2ETests {
323324
lspPotentialVerification.testPotentialInteropTrack3()
324325
lspPotentialVerification.testPotentialInteropTrack4()
325326
val lspPotentialWallet = setupTestWallet()
326-
lspPotentialWallet.testMDocIssuance()
327+
lspPotentialWallet.testMDocIssuance(IssuanceExamples.mDLCredentialIssuanceData, true)
328+
lspPotentialWallet.testMDocIssuance(IssuanceExamples.mDLCredentialIssuanceDataJwtProof, false)
327329
lspPotentialWallet.testMdocPresentation()
328330
lspPotentialWallet.testSDJwtVCIssuance()
329331
lspPotentialWallet.testSDJwtPresentation(OpenId4VPProfile.HAIP)

waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt

+42-13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package id.walt.issuer.issuance
55
import org.cose.java.AlgorithmID
66
import org.cose.java.OneKey
77
import cbor.Cbor
8+
import com.nimbusds.jose.jwk.ECKey
89
import com.nimbusds.jose.jwk.JWK
910
import com.nimbusds.jose.util.X509CertUtils
1011
import id.walt.commons.config.ConfigManager
@@ -153,9 +154,9 @@ open class CIProvider(
153154
}
154155

155156

156-
fun putSession(id: String, session: IssuanceSession) {
157+
fun putSession(id: String, session: IssuanceSession, ttl: Duration? = null) {
157158
log.debug { "SETTING CI AUTH SESSION: $id = $session" }
158-
authSessions[id] = session
159+
authSessions.set(id, session, ttl)
159160
}
160161

161162
fun removeSession(id: String) {
@@ -266,18 +267,36 @@ open class CIProvider(
266267
}))
267268
}
268269

270+
private suspend fun extractHolderKey(proof: ProofOfPossession): COSECryptoProviderKeyInfo? {
271+
when(proof.proofType) {
272+
ProofType.cwt -> {
273+
return proof.cwt?.base64UrlDecode()?.let {
274+
COSESign1Utils.extractHolderKey(Cbor.decodeFromByteArray<COSESign1>(it))
275+
}
276+
}
277+
else -> {
278+
return proof.jwt?.let { JwtUtils.parseJWTHeader(it) }?.get(JWTClaims.Header.jwk)?.jsonObject?.let {
279+
JWKKey.importJWK(it.toString()).getOrNull()?.let { key ->
280+
COSECryptoProviderKeyInfo(
281+
key.getKeyId(),
282+
AlgorithmID.ECDSA_256,
283+
ECKey.parse(key.exportJWK()).toECPublicKey(),
284+
null
285+
)
286+
}
287+
}
288+
}
289+
}
290+
}
291+
269292
@OptIn(ExperimentalSerializationApi::class)
270293
private suspend fun doGenerateMDoc(
271294
credentialRequest: CredentialRequest,
272295
issuanceSession: IssuanceSession
273296
): CredentialResult {
274-
val coseSign1 = Cbor.decodeFromByteArray<COSESign1>(
275-
credentialRequest.proof?.cwt?.base64UrlDecode() ?: throw CredentialError(
276-
credentialRequest,
277-
CredentialErrorCode.invalid_or_missing_proof, message = "No CWT proof found on credential request"
278-
)
279-
)
280-
val holderKey = COSESign1Utils.extractHolderKey(coseSign1)
297+
val proof = credentialRequest.proof ?: throw CredentialError(credentialRequest, CredentialErrorCode.invalid_or_missing_proof, message = "No proof found on credential request")
298+
val holderKey = extractHolderKey(proof) ?: throw CredentialError(credentialRequest, CredentialErrorCode.invalid_or_missing_proof, message = "No holder key could be extracted from proof")
299+
281300
val nonce = OpenID4VCI.getNonceFromProof(credentialRequest.proof!!) ?: throw CredentialError(
282301
credentialRequest,
283302
CredentialErrorCode.invalid_or_missing_proof,
@@ -425,10 +444,20 @@ open class CIProvider(
425444
}
426445

427446
private fun generateProofOfPossessionNonceFor(session: IssuanceSession): IssuanceSession {
447+
// Calculate remaining TTL based on session expiration
448+
val remainingTtl = session.expirationTimestamp?.let {
449+
val now = Clock.System.now()
450+
if (it > now) {
451+
it - now // Calculate duration between now and expiration
452+
} else {
453+
null // Already expired
454+
}
455+
}
456+
428457
return session.copy(
429458
cNonce = randomUUID()
430459
).also {
431-
putSession(it.id, it)
460+
putSession(it.id, it, remainingTtl)
432461
}
433462
}
434463

@@ -477,7 +506,7 @@ open class CIProvider(
477506
val updatedSession = IssuanceSession(
478507
id = it.id,
479508
authorizationRequest = authorizationRequest,
480-
expirationTimestamp = Clock.System.now().plus(5.minutes),
509+
expirationTimestamp = Clock.System.now().plus(expiresIn),
481510
issuanceRequests = it.issuanceRequests,
482511
authServerState = authServerState,
483512
txCode = it.txCode,
@@ -487,7 +516,7 @@ open class CIProvider(
487516
callbackUrl = it.callbackUrl,
488517
customParameters = it.customParameters
489518
)
490-
putSession(it.id, updatedSession)
519+
putSession(it.id, updatedSession, expiresIn)
491520
}
492521
}
493522

@@ -525,7 +554,7 @@ open class CIProvider(
525554
credentialOffer = credentialOfferBuilder.build(),
526555
callbackUrl = callbackUrl
527556
).also {
528-
putSession(it.id, it)
557+
putSession(it.id, it, expiresIn)
529558
}
530559
}
531560

waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuanceExamples.kt

+23
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,29 @@ object IssuanceExamples {
458458

459459
val mDLCredentialIssuanceExample = typedValueExampleDescriptorDsl<IssuanceRequest>(mDLCredentialIssuanceData)
460460

461+
val mDLCredentialIssuanceDataJwtProof = """
462+
{
463+
"issuerKey": {
464+
"type": "jwk",
465+
"jwk": ${Json.parseToJsonElement(LspPotentialIssuanceInterop.POTENTIAL_ISSUER_JWK_KEY.jwk!!)}
466+
},
467+
"issuerDid":"",
468+
"credentialConfigurationId":"org.iso.18013.5.1.mDL-JWT-Proof",
469+
"credentialData":null,
470+
"mdocData": {
471+
"org.iso.18013.5.1": {
472+
"family_name": "Doe",
473+
"given_name": "John",
474+
"birth_date": "1980-01-02"
475+
}
476+
},
477+
"x5Chain": ${buildJsonArray { add(LspPotentialInterop.POTENTIAL_ISSUER_CERT) }},
478+
"trustedRootCAs": ${buildJsonArray { add(LspPotentialInterop.POTENTIAL_ROOT_CA_CERT) }}
479+
}
480+
""".trimIndent()
481+
482+
val mDLCredentialIssuanceJwtProofExample = typedValueExampleDescriptorDsl<IssuanceRequest>(mDLCredentialIssuanceDataJwtProof)
483+
461484
// language=JSON
462485
val batchExampleJwt = typedValueExampleDescriptorDsl<List<IssuanceRequest>>(
463486
"""

0 commit comments

Comments
 (0)