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

Wallet verification #4

Merged
merged 12 commits into from
Aug 16, 2024
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies {
implementation(libs.arrow.core)
implementation(libs.arrow.fx.coroutines)
implementation(libs.zkp)
implementation(libs.eudi.lib.jvm.sdjwt.kt)
testImplementation(kotlin("test"))
testImplementation(libs.kotlinx.coroutines.test)
testImplementation("org.springframework.boot:spring-boot-starter-test")
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ sonarqube = "5.0.0.4638"
dependencycheck = "9.1.0"
jacoco = "0.8.11"
zkp = "main-SNAPSHOT"
eudiLibJvmSdjwtKt = "0.5.1"


[libraries]
Expand All @@ -27,6 +28,7 @@ bouncy-castle = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bou
arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" }
arrow-fx-coroutines = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" }
zkp = { group = "com.github.TICESoftware", name = "ZKP", version.ref = "zkp" }
eudi-lib-jvm-sdjwt-kt = { module = "eu.europa.ec.eudi:eudi-lib-jvm-sdjwt-kt", version.ref = "eudiLibJvmSdjwtKt" }

[plugins]
foojay-resolver-convention = { id = "org.gradle.toolchains.foojay-resolver-convention", version.ref = "foojay" }
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ plugins {
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven {
url = uri("https://jitpack.io")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
* limitations under the License.
*/
package eu.europa.ec.eudi.verifier.endpoint

import arrow.core.NonEmptyList
import arrow.core.recover
import arrow.core.some
Expand Down Expand Up @@ -44,6 +43,7 @@ import eu.europa.ec.eudi.verifier.endpoint.port.out.cfg.CreateQueryWalletRespons
import eu.europa.ec.eudi.verifier.endpoint.port.out.cfg.GenerateResponseCode
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.boot.web.codec.CodecCustomizer
import org.springframework.context.support.beans
Expand All @@ -57,7 +57,9 @@ import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.config.web.server.invoke
import org.springframework.web.cors.CorsConfiguration
import org.springframework.web.cors.reactive.CorsConfigurationSource
import java.io.ByteArrayInputStream
import java.security.KeyStore
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.time.Clock
import java.time.Duration
Expand Down Expand Up @@ -119,11 +121,11 @@ internal fun beans(clock: Clock) = beans {
}

bean { GenerateResponseCode.Random }
bean { PostWalletResponseLive(ref(), ref(), ref(), clock, ref(), ref(), ref()) }
bean { PostWalletResponseLive(ref(), ref(), ref(), clock, ref(), ref(), ref(), ref()) }
bean { GenerateEphemeralEncryptionKeyPairNimbus }
bean { GetWalletResponseLive(ref()) }
bean { GetJarmJwksLive(ref()) }
bean { PostZkpJwkRequestLive(ref(), ref()) }
bean { PostZkpJwkRequestLive(ref(), ref(), ref()) }

//
// Scheduled
Expand All @@ -135,6 +137,12 @@ internal fun beans(clock: Clock) = beans {
//
bean { verifierConfig(env, clock) }

//
// Issuer Public Key
//

bean { getIssuerEcKey(env) }

//
// End points
//
Expand Down Expand Up @@ -275,6 +283,21 @@ private fun jarSigningConfig(environment: Environment, clock: Clock): SigningCon
return SigningConfig(key, algorithm)
}

fun getIssuerEcKey(environment: Environment): ECKey {
val issuerCert = environment.getRequiredProperty("verifier.issuer.cert")
val logger: Logger = LoggerFactory.getLogger(PostWalletResponseLive::class.java)
logger.info("ISSUERCERT: $issuerCert")
val pemKey = "-----BEGIN CERTIFICATE-----\n" +
"${issuerCert}\n" +
"-----END CERTIFICATE-----"
val certificateFactory: CertificateFactory =
CertificateFactory.getInstance("X.509")
val certificate =
certificateFactory.generateCertificate(ByteArrayInputStream(pemKey.toByteArray())) as X509Certificate
val ecKey = ECKey.parse(certificate)
return ecKey
}

private fun verifierConfig(environment: Environment, clock: Clock): VerifierConfig {
val clientIdScheme = run {
val clientId = environment.getProperty("verifier.clientId", "verifier")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@ class WalletApi(
*/
private suspend fun handleGetRequestObject(req: ServerRequest): ServerResponse {
suspend fun requestObjectFound(jwt: String) =
ok().contentType(MediaType.parseMediaType("application/oauth-authz-req+jwt"))
.bodyValueAndAwait(jwt)
ok().contentType(MediaType.parseMediaType("application/oauth-authz-req+jwt")).bodyValueAndAwait(jwt)

val requestId = req.requestId()
logger.info("Handling GetRequestObject for $requestId ...")
Expand Down Expand Up @@ -137,9 +136,7 @@ class WalletApi(
private suspend fun handleGetPublicJwkSet(): ServerResponse {
logger.info("Handling GetPublicJwkSet ...")
val publicJwkSet = JWKSet(signingKey).toJSONObject(true)
return ok()
.contentType(MediaType.parseMediaType(JWKSet.MIME_TYPE))
.bodyValueAndAwait(publicJwkSet)
return ok().contentType(MediaType.parseMediaType(JWKSet.MIME_TYPE)).bodyValueAndAwait(publicJwkSet)
}

/**
Expand All @@ -150,8 +147,7 @@ class WalletApi(
return when (val queryResponse = getJarmJwks(requestId)) {
is NotFound -> notFound().buildAndAwait()
is InvalidState -> badRequest().buildAndAwait()
is Found -> ok()
.contentType(MediaType.parseMediaType(JWKSet.MIME_TYPE))
is Found -> ok().contentType(MediaType.parseMediaType(JWKSet.MIME_TYPE))
.bodyValueAndAwait(queryResponse.value.toJSONObject(true))
}
}
Expand All @@ -163,6 +159,7 @@ class WalletApi(
private suspend fun handlePostZkpJwk(request: ServerRequest): ServerResponse = try {
logger.info("Handling PostZkpJwk ...")
val requestId = request.requestId()
logger.info("RequestID PostZkpJwk for $requestId ")
val outcome = either { postZkpJwkRequest(request, requestId) }
outcome.fold(
ifRight = { jwkSet: List<EphemeralKeyResponse> ->
Expand Down Expand Up @@ -246,29 +243,20 @@ class WalletApi(
urlBuilder(baseUrl = baseUrl, pathTemplate = PRESENTATION_DEFINITION_PATH)

fun publicJwkSet(baseUrl: String): EmbedOption.ByReference<Any> = EmbedOption.ByReference { _ ->
DefaultUriBuilderFactory(baseUrl)
.uriString(GET_PUBLIC_JWK_SET_PATH)
.build()
.toURL()
DefaultUriBuilderFactory(baseUrl).uriString(GET_PUBLIC_JWK_SET_PATH).build().toURL()
}

fun jarmJwksByReference(baseUrl: String): EmbedOption.ByReference<RequestId> =
urlBuilder(baseUrl, JARM_JWK_SET_PATH)

fun directPost(baseUrl: String): URL =
DefaultUriBuilderFactory(baseUrl)
.uriString(WALLET_RESPONSE_PATH)
.build()
.toURL()
DefaultUriBuilderFactory(baseUrl).uriString(WALLET_RESPONSE_PATH).build().toURL()

private fun urlBuilder(
baseUrl: String,
pathTemplate: String,
) = EmbedOption.byReference<RequestId> { requestId ->
DefaultUriBuilderFactory(baseUrl)
.uriString(pathTemplate)
.build(requestId.value)
.toURL()
DefaultUriBuilderFactory(baseUrl).uriString(pathTemplate).build(requestId.value).toURL()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ class PresentationInMemoryRepo(
is Presentation.Requested -> p.requestId
is Presentation.RequestObjectRetrieved -> p.requestId
is Presentation.Submitted -> p.requestId
is Presentation.ZkpState -> p.requestId
is Presentation.TimedOut -> null
}
LoadPresentationByRequestId { requestId ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import eu.europa.ec.eudi.prex.PresentationSubmission
import java.security.interfaces.ECPrivateKey
import java.time.Clock
import java.time.Instant
import java.util.concurrent.ConcurrentHashMap

@JvmInline
value class TransactionId(val value: String) {
Expand Down Expand Up @@ -157,7 +158,7 @@ sealed interface Presentation {
* as part of the initialization of the process (when using request JAR parameter)
* or later on (when using request_uri JAR parameter)
*/
class RequestObjectRetrieved private constructor(
data class RequestObjectRetrieved constructor(
override val id: TransactionId,
override val initiatedAt: Instant,
override val type: PresentationType,
Expand All @@ -167,6 +168,8 @@ sealed interface Presentation {
val ephemeralEcPrivateKey: EphemeralEncryptionKeyPairJWK?,
val responseMode: ResponseModeOption,
val getWalletResponseMethod: GetWalletResponseMethod,
val zkpKeys: ConcurrentHashMap<String, ECPrivateKey>?,

) : Presentation {
init {
require(initiatedAt.isBefore(requestObjectRetrievedAt) || initiatedAt == requestObjectRetrievedAt)
Expand All @@ -185,6 +188,7 @@ sealed interface Presentation {
requested.ephemeralEcPrivateKey,
requested.responseMode,
requested.getWalletResponseMethod,
null,
)
}
}
Expand Down Expand Up @@ -233,42 +237,6 @@ sealed interface Presentation {
}
}

class ZkpState private constructor(
override val id: TransactionId,
override val initiatedAt: Instant,
override val type: PresentationType,
val requestId: RequestId,
val requestObjectRetrievedAt: Instant,
val submittedAt: Instant,
val walletResponse: WalletResponse,
val nonce: Nonce,
val responseCode: ResponseCode?,
val privateKey: ECPrivateKey,
) : Presentation {

companion object {
fun zkpReady(
submitted: Submitted,
privateKey: ECPrivateKey,
): Result<ZkpState> = runCatching {
with(submitted) {
ZkpState(
id,
initiatedAt,
type,
requestId,
requestObjectRetrievedAt,
submittedAt,
walletResponse,
nonce,
responseCode,
privateKey,
)
}
}
}
}

class TimedOut private constructor(
override val id: TransactionId,
override val initiatedAt: Instant,
Expand Down Expand Up @@ -306,18 +274,6 @@ sealed interface Presentation {
at,
)
}

fun timeOut(presentation: ZkpState, at: Instant): Result<TimedOut> = runCatching {
require(presentation.initiatedAt.isBefore(at))
TimedOut(
presentation.id,
presentation.initiatedAt,
presentation.type,
presentation.requestObjectRetrievedAt,
presentation.submittedAt,
at,
)
}
}
}
}
Expand All @@ -329,7 +285,6 @@ fun Presentation.isExpired(at: Instant): Boolean {
is Presentation.RequestObjectRetrieved -> requestObjectRetrievedAt.isBeforeOrEqual(at)
is Presentation.TimedOut -> false
is Presentation.Submitted -> initiatedAt.isBeforeOrEqual(at)
is Presentation.ZkpState -> initiatedAt.isBeforeOrEqual(at)
}
}

Expand All @@ -351,6 +306,3 @@ fun Presentation.RequestObjectRetrieved.submit(

fun Presentation.Submitted.timedOut(clock: Clock): Result<Presentation.TimedOut> =
Presentation.TimedOut.timeOut(this, clock.instant())

fun Presentation.ZkpState.timedOut(clock: Clock): Result<Presentation.TimedOut> =
Presentation.TimedOut.timeOut(this, clock.instant())
Loading