forked from wwWallet/wallet-ecosystem
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implemented pick up code for ehic issuer
- Loading branch information
Showing
9 changed files
with
252 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
wallet-enterprise-configurations/ehic-issuer/keys/issuer.private.ecdh.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"kty":"EC","x":"5Q_PzfqQf6MRGFjMZ1owZGtuzHCozN_KKC2RNgzacLs","y":"Coaxh8MYMw1WeQODdAmI5C0ZXwi7h0qB1914G341Kww","crv":"P-256","d":"TYSFecf3b2uK1ubH12ROTkNPgpbnOazJLIxmZEEW1mU"} |
1 change: 1 addition & 0 deletions
1
wallet-enterprise-configurations/ehic-issuer/keys/issuer.public.ecdh.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"kty":"EC","x":"5Q_PzfqQf6MRGFjMZ1owZGtuzHCozN_KKC2RNgzacLs","y":"Coaxh8MYMw1WeQODdAmI5C0ZXwi7h0qB1914G341Kww","crv":"P-256"} |
1 change: 1 addition & 0 deletions
1
wallet-enterprise-configurations/ehic-issuer/keys/vault.public.ecdh.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"kty":"EC","x":"PiXfYPrIGa3SE6bINX9X20EoKCtO_r2dDKhOzoDGnuU","y":"9gxiOT-vNDLe0HLNJA6HpH5vlCuCknnBmXUZLB05Vgk","crv":"P-256"} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
240 changes: 240 additions & 0 deletions
240
...configuration/SupportedCredentialsConfiguration/PickupCodeEHICSupportedCredentialSdJwt.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
import config from "../../../config"; | ||
import { VerifiableCredentialFormat, Display, CredentialSupportedJwtVcJson } from "../../types/oid4vci"; | ||
import { CredentialIssuer } from "../../lib/CredentialIssuerConfig/CredentialIssuer"; | ||
import { SupportedCredentialProtocol } from "../../lib/CredentialIssuerConfig/SupportedCredentialProtocol"; | ||
import { AuthorizationServerState } from "../../entities/AuthorizationServerState.entity"; | ||
import { CredentialView } from "../../authorization/types"; | ||
import crypto, { randomUUID } from 'crypto'; | ||
import fs from 'fs'; | ||
import path from 'path'; | ||
import axios from 'axios'; | ||
import { compactDecrypt, calculateJwkThumbprint, CompactEncrypt } from 'jose'; | ||
import { CredentialStatusList } from "../../lib/CredentialStatus"; | ||
const currentWorkingDirectory = __dirname + "/../../../../"; | ||
|
||
var publicKeyFilePath; | ||
var publicKeyContent; | ||
|
||
publicKeyFilePath = path.resolve(currentWorkingDirectory, 'keys', 'issuer.public.ecdh.json'); | ||
publicKeyContent = fs.readFileSync(publicKeyFilePath, 'utf8'); | ||
const credentialIssuerPublicKeyJWK = JSON.parse(publicKeyContent) as crypto.JsonWebKey; | ||
// const credentialIssuerPublicKey = crypto.createPublicKey({ key: credentialIssuerPublicKeyJWK, format: 'jwk' }); | ||
|
||
|
||
publicKeyFilePath = path.resolve(currentWorkingDirectory, 'keys', 'vault.public.ecdh.json'); | ||
publicKeyContent = fs.readFileSync(publicKeyFilePath, 'utf8'); | ||
const vaultPublicKeyJWK = JSON.parse(publicKeyContent) as crypto.JsonWebKey; | ||
const vaultPublicKey = crypto.createPublicKey({ key: vaultPublicKeyJWK, format: 'jwk' }); | ||
|
||
|
||
var privateKeyFilePath; | ||
var privateKeyContent; | ||
|
||
privateKeyFilePath = path.resolve(currentWorkingDirectory, 'keys', 'issuer.private.ecdh.json'); | ||
privateKeyContent = fs.readFileSync(privateKeyFilePath, 'utf8'); | ||
const credentialIssuerPrivateKeyJWK = JSON.parse(privateKeyContent) as crypto.JsonWebKey; | ||
const credentialIssuerPrivateKey = crypto.createPrivateKey({ key: credentialIssuerPrivateKeyJWK, format: 'jwk' }); | ||
|
||
|
||
|
||
const dataset = JSON.parse(fs.readFileSync('/datasets/dataset.json', 'utf-8').toString()) as any; | ||
|
||
|
||
export class PickupCodeEHICSupportedCredentialSdJwt implements SupportedCredentialProtocol { | ||
|
||
|
||
constructor(private credentialIssuerConfig: CredentialIssuer) { } | ||
|
||
getCredentialIssuerConfig(): CredentialIssuer { | ||
return this.credentialIssuerConfig; | ||
} | ||
getId(): string { | ||
return "urn:credential:ehic" | ||
} | ||
getFormat(): VerifiableCredentialFormat { | ||
return VerifiableCredentialFormat.VC_SD_JWT; | ||
} | ||
getTypes(): string[] { | ||
return ["VerifiableCredential", "VerifiableAttestation", "EuropeanHealthInsuranceCard", this.getId()]; | ||
} | ||
getDisplay(): Display { | ||
return { | ||
name: "EHIC Card", | ||
logo: { url: config.url + "/images/ehicCard.png" }, | ||
background_color: "#4CC3DD" | ||
} | ||
} | ||
|
||
|
||
async getProfile(_userSession: AuthorizationServerState): Promise<CredentialView | null> { | ||
return null; | ||
} | ||
|
||
async generateCredentialResponse(userSession: AuthorizationServerState, holderDID: string): Promise<{ format: VerifiableCredentialFormat; credential: any; }> { | ||
if (!userSession.issuer_state || userSession.issuer_state == "null") { | ||
throw new Error("issuer_state was not found user session"); | ||
} | ||
|
||
console.log('type of issuer state ', typeof userSession.issuer_state); | ||
if (!userSession.personalIdentifier) { | ||
throw new Error("Cannot generate credential: personalIdentifier is missing"); | ||
} | ||
|
||
const { issuer_state } = userSession; | ||
console.log("issuer state = ", userSession.issuer_state); | ||
let { plaintext } = await compactDecrypt(issuer_state, credentialIssuerPrivateKey); | ||
const { | ||
iss, | ||
exp, | ||
jti, // is the collection id | ||
aud, | ||
sub, // authorized identities to receive this specific credential, | ||
nonce, | ||
} = JSON.parse(new TextDecoder().decode(plaintext)) as { iss: string, exp: number, jti: string, aud: string, sub: string[], nonce: string }; | ||
|
||
|
||
console.log("Issuer state attributes: ", { | ||
iss, | ||
exp, | ||
jti, // is the collection id | ||
aud, | ||
sub, // authorized identities to receive this specific credential | ||
nonce, | ||
}) | ||
const expectedIssuer = await calculateJwkThumbprint(vaultPublicKeyJWK); | ||
if (!iss || iss !== expectedIssuer) { | ||
throw new Error(`'iss' is missing from issuer_state or expected value '${expectedIssuer}'`); | ||
} | ||
|
||
const expectedAudience = await calculateJwkThumbprint(credentialIssuerPublicKeyJWK); | ||
if (!aud || aud !== expectedAudience) { | ||
throw new Error(`'aud' is missing from issuer_state or expected value for '${expectedAudience}'`); | ||
} | ||
|
||
if (exp && Math.floor(Date.now() / 1000) > exp) { | ||
console.log("Exp cmp = ", Math.floor(Date.now() / 1000) > exp) | ||
throw new Error(`'exp' is missing from issuer_state or the issuer_state is expired`); | ||
} | ||
|
||
console.log("User session = ", userSession) | ||
if (!sub || !sub.includes(userSession.personalIdentifier)) { | ||
console.log(`Personal identifier ${userSession.personalIdentifier} is not authorized to receive this credential`); | ||
throw new Error(`Personal identifier ${userSession.personalIdentifier} is not authorized to receive this credential`); | ||
} | ||
|
||
const collection_id = jti; | ||
|
||
const jwePayload = { | ||
iss: await calculateJwkThumbprint(credentialIssuerPublicKeyJWK), | ||
exp: (Math.floor(Date.now() / 1000)) + 60*5, // expires in 5 minutes, | ||
jti: collection_id, | ||
aud: await calculateJwkThumbprint(vaultPublicKeyJWK), | ||
sub: userSession.personalIdentifier, | ||
nonce: nonce, | ||
}; | ||
|
||
const fetchRequestToken = await new CompactEncrypt(new TextEncoder().encode(JSON.stringify(jwePayload))) | ||
.setProtectedHeader({ | ||
alg: 'ECDH-ES+A256KW', // Elliptic Curve Diffie-Hellman Ephemeral Static with AES Key Wrap using 256-bit key | ||
enc: 'A256GCM', // AES GCM using 256-bit key | ||
epk: { | ||
kty: 'EC', // Elliptic Curve Key Type | ||
crv: 'P-256' // Curve name | ||
// you can add other parameters as needed, like 'x' and 'y' for specific key pairs | ||
} | ||
}) | ||
.encrypt(vaultPublicKey); | ||
|
||
let fetchResponse = null; | ||
try { | ||
fetchResponse = await axios.post('http://resources-vault:6555/fetch', { | ||
fetch_request_token: fetchRequestToken | ||
}); | ||
} | ||
catch(err) { | ||
console.log(err) | ||
console.error('Failed fetch request') | ||
throw new Error("Failed fetch request"); | ||
} | ||
if (fetchResponse == null || !fetchResponse.data.claims) { | ||
console.error("'claims' is missing from resources vault fetch response"); | ||
throw new Error("'claims' is missing from resources vault fetch response"); | ||
} | ||
|
||
const { claims } = fetchResponse.data; | ||
console.log("Claims = ", claims) | ||
|
||
|
||
// use the dataset to retrieve only the username based on personalIdentifier | ||
const username = dataset.users.filter((u: any) => u.authentication.personalIdentifier == userSession.personalIdentifier)[0].authentication.username; | ||
const payload = { | ||
"@context": ["https://www.w3.org/2018/credentials/v1"], | ||
"type": this.getTypes(), | ||
"id": `urn:ehic:${randomUUID()}`, | ||
"name": "EHIC ID Card", // https://www.w3.org/TR/vc-data-model-2.0/#names-and-descriptions | ||
"description": "This credential is issued by the National EHIC ID credential issuer and it can be used for authentication purposes", | ||
"credentialSubject": { | ||
...claims, | ||
"id": holderDID, | ||
}, | ||
"credentialStatus": { | ||
"id": `${config.crl.url}#${(await CredentialStatusList.insert(username, claims.personalIdentifier)).id}`, | ||
"type": "CertificateRevocationList" | ||
}, | ||
"credentialBranding": { | ||
"image": { | ||
"url": config.url + "/images/ehicCard.png" | ||
}, | ||
"backgroundColor": "#8ebeeb", | ||
"textColor": "#ffffff" | ||
}, | ||
}; | ||
|
||
console.log("payload = ", payload) | ||
const disclosureFrame = { | ||
vc: { | ||
credentialSubject: { | ||
// familyName: true, | ||
// firstName: true, | ||
// birthdate: true, | ||
personalIdentifier: true, | ||
socialSecurityIdentification: { | ||
ssn: true | ||
}, | ||
validityPeriod: { | ||
startingDate: true, | ||
endingDate: true | ||
}, | ||
documentId: true, | ||
competentInstitution: { | ||
competentInstitutionId: true, | ||
competentInstitutionName: true, | ||
competentInstitutionCountryCode: true | ||
}, | ||
} | ||
} | ||
} | ||
const { jws } = await this.getCredentialIssuerConfig().getCredentialSigner() | ||
.sign({ | ||
vc: payload | ||
}, {}, disclosureFrame); | ||
const response = { | ||
format: this.getFormat(), | ||
credential: jws | ||
}; | ||
|
||
return response; | ||
} | ||
|
||
exportCredentialSupportedObject(): CredentialSupportedJwtVcJson { | ||
return { | ||
id: this.getId(), | ||
format: this.getFormat(), | ||
display: [this.getDisplay()], | ||
types: this.getTypes(), | ||
cryptographic_binding_methods_supported: ["ES256"] | ||
} | ||
} | ||
|
||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters