Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
21 changes: 21 additions & 0 deletions http/src/main/kotlin/at/asitplus/wallet/backend/Extensions.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
package at.asitplus.wallet.backend

import at.asitplus.catchingUnwrapped
import at.asitplus.signum.indispensable.josef.JsonWebToken
import at.asitplus.signum.indispensable.josef.JwsCompactTyped
import at.asitplus.wallet.lib.ktor.openid.DPoP
import at.asitplus.wallet.lib.ktor.openid.OAuthClientAttestation
import at.asitplus.wallet.lib.ktor.openid.OAuthClientAttestationPop
import at.asitplus.wallet.lib.oauth2.RequestInfo
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import jakarta.servlet.http.HttpServletRequest
import org.springframework.web.util.UriComponentsBuilder
import java.net.URL
import java.security.MessageDigest
Expand All @@ -12,5 +22,16 @@ object Extensions {

fun ByteArray.sha256(): ByteArray = MessageDigest.getInstance("SHA-256").digest(this)

fun HttpServletRequest.toRequestInfo() = RequestInfo(
url = requestURL.toString(),
method = HttpMethod.parse(method),
dpop = headerToJws(HttpHeaders.DPoP),
clientAttestation = headerToJws(HttpHeaders.OAuthClientAttestation),
clientAttestationPop = headerToJws(HttpHeaders.OAuthClientAttestationPop),
)

fun HttpServletRequest.headerToJws(headerName: String) =
catchingUnwrapped { JwsCompactTyped<JsonWebToken>(getHeader(headerName)) }.getOrNull()

}

Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ class BackendConfiguration {
applicationEventPublisher: ApplicationEventPublisher,
identityColumnResynchronizer: IdentityColumnResynchronizer,
): RevocationService = DefaultRevocationService(
preparedCredentialRepo = preparedCredentialRepo,
issuedCredentialRepo = credentialRepo,
revokedCredentialRepo = revokedCredentialRepo,
applicationEventPublisher = applicationEventPublisher,
Expand Down Expand Up @@ -204,30 +203,31 @@ class BackendConfiguration {
@Bean
fun issuerAgent(
issuerCredentialStore: IssuerCredentialStore,
statusListIssuer: StatusListAgent,
@Qualifier("issuerKeyMaterial") issuerKeyMaterial: KeyMaterial,
@Qualifier("isoMdocIssuerKeyMaterial") isoMdocIssuerKeyMaterial: KeyMaterial,
): Issuer = IsoMdocRoutingIssuer(
defaultIssuer = buildIssuerAgent(issuerCredentialStore, issuerKeyMaterial),
isoMdocIssuer = buildIssuerAgent(issuerCredentialStore, isoMdocIssuerKeyMaterial),
defaultIssuer = buildIssuerAgent(issuerCredentialStore, statusListIssuer, issuerKeyMaterial),
isoMdocIssuer = buildIssuerAgent(issuerCredentialStore, statusListIssuer, isoMdocIssuerKeyMaterial),
)

private fun buildIssuerAgent(
issuerCredentialStore: IssuerCredentialStore,
statusListIssuer: StatusListAgent,
keyMaterial: KeyMaterial,
): IssuerAgent = IssuerAgent(
keyMaterial = keyMaterial,
issuerCredentialStore = issuerCredentialStore,
statusListBaseUrl = configuration.publicContext.appendPath(Paths.Credentials.StatusUrl),
cryptoAlgorithms = setOf(keyMaterial.signatureAlgorithm),
timePeriodProvider = timePeriodProvider(),
identifier = UniformResourceIdentifier(configuration.publicContext.toString())
identifier = UniformResourceIdentifier(configuration.publicContext.toString()),
statusListAgent = statusListIssuer
)

@Bean
fun statusListIssuer(
referencedTokenStore: ReferencedTokenStore,
@Qualifier("issuerKeyMaterial") issuerKeyMaterial: KeyMaterial,
): StatusListIssuer = StatusListAgent(
): StatusListAgent = StatusListAgent(
keyMaterial = issuerKeyMaterial,
issuerCredentialStore = referencedTokenStore,
statusListBaseUrl = configuration.publicContext.appendPath(Paths.Credentials.StatusUrl),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,10 @@ import at.asitplus.openid.PushedAuthenticationResponseParameters
import at.asitplus.openid.RequestParameters
import at.asitplus.openid.TokenRequestParameters
import at.asitplus.openid.TokenResponseParameters
import at.asitplus.wallet.backend.Extensions.toRequestInfo
import at.asitplus.wallet.backend.Paths
import at.asitplus.wallet.backend.auth.SpringSecurityAuthenticationSupplier
import at.asitplus.wallet.lib.ktor.openid.DPoP
import at.asitplus.wallet.lib.ktor.openid.DPoPNonce
import at.asitplus.wallet.lib.ktor.openid.OAuthClientAttestation
import at.asitplus.wallet.lib.ktor.openid.OAuthClientAttestationPop
import at.asitplus.wallet.lib.oauth2.RequestInfo
import at.asitplus.wallet.lib.oauth2.SimpleAuthorizationService
import at.asitplus.wallet.lib.oidvci.OAuth2Exception
import at.asitplus.wallet.lib.oidvci.decodeFromPostBody
Expand All @@ -25,7 +22,6 @@ import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType.APPLICATION_JSON_VALUE
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.security.core.Authentication
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.ui.ModelMap
Expand All @@ -35,6 +31,7 @@ import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.ModelAndView

Expand Down Expand Up @@ -160,14 +157,4 @@ class OAuth2Controller(
return result.response
}

private fun HttpServletRequest.toRequestInfo() = RequestInfo(
url = requestURL.toString(),
method = HttpMethod.parse(method),
dpop = getHeader(HttpHeaders.DPoP),
clientAttestation = getHeader(HttpHeaders.OAuthClientAttestation),
clientAttestationPop = getHeader(HttpHeaders.OAuthClientAttestationPop),
)

}


Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,19 @@ import at.asitplus.openid.DisplayLogoProperties
import at.asitplus.openid.DisplayProperties
import at.asitplus.openid.OpenIdConstants
import at.asitplus.signum.indispensable.josef.io.joseCompliantSerializer
import at.asitplus.wallet.backend.Extensions.toRequestInfo
import at.asitplus.wallet.backend.Paths
import at.asitplus.wallet.backend.config.BackendConfigurationProperties
import at.asitplus.wallet.backend.config.MetadataConfiguration
import at.asitplus.wallet.backend.data.OidcIssuerCredentialDataProvider
import at.asitplus.wallet.lib.data.MediaTypes
import at.asitplus.wallet.lib.ktor.openid.DPoP
import at.asitplus.wallet.lib.ktor.openid.DPoPNonce
import at.asitplus.wallet.lib.ktor.openid.OAuthClientAttestation
import at.asitplus.wallet.lib.ktor.openid.OAuthClientAttestationPop
import at.asitplus.wallet.lib.oauth2.RequestInfo
import at.asitplus.wallet.lib.oidvci.CredentialIssuer
import at.asitplus.wallet.lib.oidvci.WalletService
import io.github.aakira.napier.Napier
import io.ktor.client.utils.CacheControl
import io.ktor.http.*
import jakarta.servlet.http.HttpServletRequest
import kotlinx.serialization.encodeToString
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.MediaType.APPLICATION_JSON_VALUE
Expand Down Expand Up @@ -82,14 +78,6 @@ class OpenId4VciController(
.body(result.response)
}

private fun HttpServletRequest.toRequestInfo() = RequestInfo(
url = requestURL.toString(),
method = HttpMethod.parse(method),
dpop = getHeader(HttpHeaders.DPoP),
clientAttestation = getHeader(HttpHeaders.OAuthClientAttestation),
clientAttestationPop = getHeader(HttpHeaders.OAuthClientAttestationPop),
)

/**
* Issues the credential, when the token sent by the Wallet is valid,
* see [CredentialIssuer.credential].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ package at.asitplus.wallet.backend.controller
import at.asitplus.openid.JarRequestParameters
import at.asitplus.openid.JwtVcIssuerMetadata
import at.asitplus.openid.OpenIdConstants
import at.asitplus.openid.dcql.DCQLClaimsPathPointer
import at.asitplus.signum.indispensable.josef.JsonWebKey
import at.asitplus.signum.indispensable.josef.JwsCompactTyped
import at.asitplus.signum.indispensable.josef.io.joseCompliantSerializer
import at.asitplus.wallet.backend.Extensions.appendPath
import at.asitplus.wallet.backend.Paths
import at.asitplus.wallet.backend.config.BackendConfigurationProperties
import at.asitplus.wallet.eupidsdjwt.EuPidSdJwtDataElements
import at.asitplus.wallet.eupidsdjwt.EuPidSdJwtDataElements.BIRTH_DATE
import at.asitplus.wallet.eupidsdjwt.EuPidSdJwtDataElements.FAMILY_NAME
import at.asitplus.wallet.eupidsdjwt.EuPidSdJwtDataElements.GIVEN_NAME
import at.asitplus.wallet.lib.RequestOptionsCredential
import at.asitplus.wallet.lib.agent.KeyMaterial
import at.asitplus.wallet.lib.agent.Validator
Expand Down Expand Up @@ -263,11 +266,7 @@ class PublicController(
credentialScheme = AttributeIndex.resolveSdJwtAttributeType("urn:eudi:pid:1")
?: error("EU PID SD-JWT scheme not resolved"),
representation = ConstantIndex.CredentialRepresentation.SD_JWT,
requestedAttributes = setOf(
EuPidSdJwtDataElements.FAMILY_NAME,
EuPidSdJwtDataElements.GIVEN_NAME,
EuPidSdJwtDataElements.BIRTH_DATE
),
attributePaths = setOf(FAMILY_NAME, GIVEN_NAME, BIRTH_DATE).map { DCQLClaimsPathPointer(it) }.toSet(),
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ import java.time.Instant
class IssuedCredential() {

constructor(
vcId: String,
subjectId: String,
userInfoSubject: String,
validUntil: Instant,
timePeriod: Int,
attributeName: String,
revocationListIndex: Long,
) : this() {
this.vcId = vcId
this.subjectId = subjectId
this.userInfoSubject = userInfoSubject
this.attributeName = attributeName
Expand All @@ -37,9 +35,6 @@ class IssuedCredential() {
@CreationTimestamp
lateinit var createdOn: Instant

@Column
lateinit var vcId: String

@Column
lateinit var subjectId: String

Expand All @@ -64,12 +59,12 @@ class IssuedCredential() {
override fun toString(): String {
return "IssuedCredential(id=$id, " +
"createdOn=$createdOn, " +
"vcId='$vcId', " +
"subjectId='$subjectId', " +
"attributeName='$attributeName', " +
"validUntil=$validUntil, " +
"userInfoSubject=$userInfoSubject, " +
"timePeriod=$timePeriod, " +
"revocationListIndex=$revocationListIndex)"
"revocationListIndex=$revocationListIndex, " +
"revoked=$revoked)"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import java.util.*
@Repository
interface IssuedCredentialRepository : JpaRepository<IssuedCredential, Long> {

fun findBytimePeriodAndVcId(timePeriod: Int, vcId: String): IssuedCredential?

fun findBytimePeriodAndRevocationListIndex(timePeriod: Int, revocationListIndex: Long): IssuedCredential?

fun findAllByValidUntilAfter(validUntil: Instant): Collection<IssuedCredential>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import at.asitplus.wallet.backend.service.RevocationService
import at.asitplus.wallet.lib.agent.CredentialToBeIssued
import at.asitplus.wallet.lib.agent.Issuer
import at.asitplus.wallet.lib.agent.IssuerCredentialStore
import at.asitplus.wallet.lib.data.rfc.tokenStatusList.RevocationListInfo
import at.asitplus.wallet.lib.data.rfc.tokenStatusList.StatusListView
import at.asitplus.wallet.lib.data.rfc.tokenStatusList.agents.ReferencedTokenStore
import at.asitplus.wallet.lib.data.rfc.tokenStatusList.iso18013.Identifier
Expand Down Expand Up @@ -35,31 +36,54 @@ class IssuerCredentialStoreAdapter(
override fun revokeIdentifier(timePeriod: Int, identifier: ByteArray): Boolean =
revocationService.revokeIdentifier(timePeriod, identifier)

override suspend fun storeReferencedToken(
credential: CredentialToBeIssued,
timePeriod: Int,
): KmmResult<ReferencedTokenStore.StoredCredentialReference> =
revocationService.storeReferencedToken(credential, timePeriod).onSuccess {
// need to do this immediately, so clients receiving our credential can check the status list right away
revocationListWriter.writeRevocationList(it.timePeriod)
}

override fun getStatusListView(timePeriod: Int): StatusListView =
revocationService.getStatusListView(timePeriod)

override fun getRawIdentifierList(timePeriod: Int): Map<Identifier, IdentifierInfo> =
revocationService.getRawIdentifierList(timePeriod)

@Deprecated("Use method from `ReferencedTokenStore` instead")
@Suppress("DEPRECATION")
override suspend fun createStoredCredentialReference(
credential: CredentialToBeIssued,
timePeriod: Int,
): KmmResult<IssuerCredentialStore.StoredCredentialReference> =
revocationService.createStoredCredentialReference(credential, timePeriod)
revocationService.storeReferencedToken(credential, timePeriod).map {
IssuerCredentialStore.StoredCredentialReference(
id = it.id,
timePeriod = it.timePeriod,
statusListIndex = it.statusListIndex
)
}

/**
* Called by an [Issuer] when the credential has been signed and delivered to the holder.
*/
@Suppress("DEPRECATION")
@Deprecated("Issuer will call onCredentialStored instead")
override suspend fun updateStoredCredential(
reference: IssuerCredentialStore.StoredCredentialReference,
credential: Issuer.IssuedCredential,
): KmmResult<IssuerCredentialStore.StoredCredentialReference> {
val result = revocationService.updateStoredCredential(
): KmmResult<IssuerCredentialStore.StoredCredentialReference> =
revocationService.updateStoredCredential(
reference = reference,
credential = credential
)
// need to do this immediately, so clients receiving our credential can check the status list right away
revocationListWriter.writeRevocationList(reference.timePeriod)
return result

/**
* Called by an [Issuer] when the credential has been signed and delivered to the holder.
*/
override suspend fun onCredentialIssued(
credential: Issuer.IssuedCredential,
) {
revocationService.onCredentialIssued(
credential = credential,
)
}
}
Loading
Loading