Skip to content

Commit

Permalink
Add support for mDL issuance (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
dzarras authored Jan 18, 2024
1 parent 21c5d9f commit fa6a46e
Show file tree
Hide file tree
Showing 20 changed files with 1,459 additions and 38 deletions.
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@ the [EUDI Wallet Reference Implementation project description](https://github.co
An implementation of a credential issuing service, according to
[OpenId4VCI - draft12](https://openid.github.io/OpenID4VCI/openid-4-verifiable-credential-issuance-wg-draft.html)

The service provides generic support for `mso_mdoc` and `SD-JWT-VC` formats using PID as an example
The service provides generic support for `mso_mdoc` and `SD-JWT-VC` formats using PID and mDL as an example
and requires the use of a suitable OAUTH2 server.

| Credential/Attestation | Format |
|------------------------|-----------|
| PID | mso_mdoc |
| PID | SD-JWT-VC |
| mDL | mso_mdoc |

### OpenId4VCI coverage

| Feature | Coverage |
Expand All @@ -45,20 +51,21 @@ A Keycloak instance accessible via https://localhost/idp/ with the Realm *pid-is

The Realm *pid-issuer-realm*:

- has user self-registration active with a custom registration page accessible via https://localhost/idp/realms/pid-issuer-realm/account/#/
- has user self-registration active with a custom registration page accessible
via https://localhost/idp/realms/pid-issuer-realm/account/#/
- defines *eu.europa.ec.eudiw.pid_vc_sd_jwt* scope for requesting PID issuance in SD JWT VC format
- defines *eu.europa.ec.eudiw.pid_mso_mdoc* scope for requesting PID issuance in MSO MDOC format
- defines *wallet-dev* and *pid-issuer-srv* clients
- contains sample user with credentials: tneal / password

Administration console is accessible via https://localhost/idp/admin/ using the credentials admin / password

### PID Issuer
### PID mDL Issuer

A PID Issuer instance accessible via https://localhost/pid-issuer/
A PID mDL Issuer instance accessible via https://localhost/pid-issuer/

It uses the configured Keycloak instance as an Authorization Server, and PID issuance both *SD JWT VC* and *MSO MDOC*
formats is enabled. Additionally *deferred issuance* is enabled for *SD JWT VC* format.
It uses the configured Keycloak instance as an Authorization Server, and supports issuing of PID and mDL.
Additionally, *deferred issuance* is enabled for PID in *SD JWT VC* format.

The issuing country is set to GR (Greece).

Expand Down Expand Up @@ -147,7 +154,6 @@ curl http://localhost:8080/.well-known/openid-credential-issuer | jq .

### Credential Endpoint


### Credentials Offer

Generate sample offer
Expand Down
80 changes: 77 additions & 3 deletions docker-compose/keycloak/realms/pid-issuer-realm-realm.json
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,12 @@
"roles": [
"eid-holder-natural-person"
]
},
{
"clientScope": "org.iso.18013.5.1.mDL",
"roles": [
"eid-holder-natural-person"
]
}
],
"clientScopeMappings": {
Expand Down Expand Up @@ -709,7 +715,8 @@
],
"optionalClientScopes": [
"eu.europa.ec.eudiw.pid_vc_sd_jwt",
"eu.europa.ec.eudiw.pid_mso_mdoc"
"eu.europa.ec.eudiw.pid_mso_mdoc",
"org.iso.18013.5.1.mDL"
]
},
{
Expand Down Expand Up @@ -1012,7 +1019,8 @@
"optionalClientScopes": [
"roles",
"eu.europa.ec.eudiw.pid_vc_sd_jwt",
"eu.europa.ec.eudiw.pid_mso_mdoc"
"eu.europa.ec.eudiw.pid_mso_mdoc",
"org.iso.18013.5.1.mDL"
]
}
],
Expand Down Expand Up @@ -1364,6 +1372,71 @@
}
]
},
{
"id": "261a329e-327b-43fa-849b-5c3c8748c663",
"name": "org.iso.18013.5.1.mDL",
"description": "",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "true",
"display.on.consent.screen": "true",
"gui.order": "",
"consent.screen.text": "Do you consent to issue mDL?"
},
"protocolMappers": [
{
"id": "d06095b4-af59-40e1-ad1a-017c5c1f8473",
"name": "given name",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"aggregate.attrs": "false",
"userinfo.token.claim": "true",
"multivalued": "false",
"user.attribute": "firstName",
"id.token.claim": "false",
"access.token.claim": "false",
"claim.name": "given_name",
"jsonType.label": "String"
}
},
{
"id": "7b14d41e-74ec-4cf8-bc07-9afb932a797e",
"name": "family name",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"aggregate.attrs": "false",
"userinfo.token.claim": "true",
"multivalued": "false",
"user.attribute": "lastName",
"id.token.claim": "false",
"access.token.claim": "false",
"claim.name": "family_name",
"jsonType.label": "String"
}
},
{
"id": "1ab7730f-9a35-4587-86de-1fc2db219989",
"name": "email",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"aggregate.attrs": "false",
"userinfo.token.claim": "true",
"multivalued": "false",
"user.attribute": "email",
"id.token.claim": "false",
"access.token.claim": "false",
"claim.name": "email",
"jsonType.label": "String"
}
}
]
},
{
"id": "00bf2e53-5336-47ef-819f-3f1823a2cc81",
"name": "roles",
Expand Down Expand Up @@ -1419,7 +1492,8 @@
],
"defaultOptionalClientScopes": [
"eu.europa.ec.eudiw.pid_mso_mdoc",
"eu.europa.ec.eudiw.pid_vc_sd_jwt"
"eu.europa.ec.eudiw.pid_vc_sd_jwt",
"org.iso.18013.5.1.mDL"
],
"browserSecurityHeaders": {
"contentSecurityPolicyReportOnly": "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import eu.europa.ec.eudi.pidissuer.adapter.input.web.MetaDataApi
import eu.europa.ec.eudi.pidissuer.adapter.input.web.WalletApi
import eu.europa.ec.eudi.pidissuer.adapter.out.jose.DefaultExtractJwkFromCredentialKey
import eu.europa.ec.eudi.pidissuer.adapter.out.jose.EncryptCredentialResponseWithNimbus
import eu.europa.ec.eudi.pidissuer.adapter.out.mdl.EncodeMobileDrivingLicenceInCborWithMicroservice
import eu.europa.ec.eudi.pidissuer.adapter.out.mdl.GetMobileDrivingLicenceDataMock
import eu.europa.ec.eudi.pidissuer.adapter.out.mdl.IssueMobileDrivingLicence
import eu.europa.ec.eudi.pidissuer.adapter.out.persistence.InMemoryCNonceRepository
import eu.europa.ec.eudi.pidissuer.adapter.out.persistence.InMemoryDeferredCredentialRepository
import eu.europa.ec.eudi.pidissuer.adapter.out.pid.*
Expand Down Expand Up @@ -77,7 +80,7 @@ private val log = LoggerFactory.getLogger(PidIssuerApplication::class.java)
/**
* [WebClient] instances for usage within the application.
*/
private object WebClients {
internal object WebClients {

/**
* A [WebClient] with [Json] serialization enabled.
Expand Down Expand Up @@ -129,6 +132,18 @@ fun beans(clock: Clock) = beans {
ref(),
)
}
bean {
EncodePidInCborWithMicroService(env.readRequiredUrl("issuer.pid.mso_mdoc.encoderUrl"), ref())
}
bean {
GetMobileDrivingLicenceDataMock()
}
bean {
EncodeMobileDrivingLicenceInCborWithMicroservice(
ref(),
env.readRequiredUrl("issuer.mdl.mso_mdoc.encoderUrl"),
)
}
//
// Encryption of credential response
//
Expand Down Expand Up @@ -161,10 +176,6 @@ fun beans(clock: Clock) = beans {
bean {
val issuerPublicUrl = env.readRequiredUrl("issuer.publicUrl", removeTrailingSlash = true)

bean {
EncodePidInCborWithMicroService(env.readRequiredUrl("issuer.pid.mso_mdoc.encoderUrl"), ref())
}

CredentialIssuerMetaData(
id = issuerPublicUrl,
credentialEndPoint = issuerPublicUrl.appendPath(WalletApi.CREDENTIAL_ENDPOINT),
Expand Down Expand Up @@ -216,6 +227,12 @@ fun beans(clock: Clock) = beans {
else issueSdJwtVcPid,
)
}

val enableMobileDrivingLicence = env.getProperty("issuer.mdl.enabled", true)
if (enableMobileDrivingLicence) {
val mdlIssuer = IssueMobileDrivingLicence(issuerPublicUrl, ref(), ref())
add(mdlIssuer)
}
},
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2023 European Commission
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package eu.europa.ec.eudi.pidissuer.adapter.out.jose

import com.nimbusds.jose.jwk.ECKey
import org.bouncycastle.util.io.pem.PemObject
import org.bouncycastle.util.io.pem.PemWriter
import java.io.StringWriter
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi

internal fun ECKey.toPem(): String =
StringWriter().use { stringWriter ->
PemWriter(stringWriter).use { pemWriter ->
pemWriter.writeObject(PemObject("PUBLIC KEY", this.toECPublicKey().encoded))
}
stringWriter.toString()
}

@OptIn(ExperimentalEncodingApi::class)
internal fun ECKey.toBase64UrlSafeEncodedPem(): String = Base64.UrlSafe.encode(this.toPem().toByteArray())
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2023 European Commission
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package eu.europa.ec.eudi.pidissuer.adapter.out.mdl

import arrow.core.raise.Raise
import com.nimbusds.jose.jwk.ECKey
import eu.europa.ec.eudi.pidissuer.port.input.IssueCredentialError

/**
* Encodes a Mobile Driving Licence in CBOR format.
*/
fun interface EncodeMobileDrivingLicenceInCbor {

context(Raise<IssueCredentialError.Unexpected>)
suspend operator fun invoke(licence: MobileDrivingLicence, holderKey: ECKey): String
}
Loading

0 comments on commit fa6a46e

Please sign in to comment.