Skip to content

Commit

Permalink
Merge remote-tracking branch 'funke2/funke' into sign-extension-arkg
Browse files Browse the repository at this point in the history
  • Loading branch information
emlun committed Nov 14, 2024
2 parents dfa7bcb + 0f358f4 commit fc90187
Show file tree
Hide file tree
Showing 16 changed files with 585 additions and 38 deletions.
4 changes: 3 additions & 1 deletion .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ REACT_APP_VERSION=$npm_package_version
GENERATE_SOURCEMAP=false
REACT_APP_DISPLAY_CONSOLE=true
REACT_APP_WEBAUTHN_RPID=localhost
REACT_APP_OPENID4VCI_REDIRECT_URI=http://localhost:3000/
REACT_APP_OPENID4VCI_REDIRECT_URI=https://secure.wwwallet.local:8443/
REACT_APP_OPENID4VP_SAN_DNS_CHECK=false
REACT_APP_OPENID4VP_SAN_DNS_CHECK_SSL_CERTS=false
REACT_APP_VALIDATE_CREDENTIALS_WITH_TRUST_ANCHORS=true
REACT_APP_OPENID4VCI_EID_CLIENT_URL=http://127.0.0.1:24727/eID-Client
REACT_APP_PID_CREDENTIAL_ISSUER_IDENTIFIER=http://
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.2.2",
"private": true,
"dependencies": {
"@auth0/mdl": "^1.5.1",
"@cef-ebsi/key-did-resolver": "^1.1.0",
"@sd-jwt/core": "^0.2.1",
"@testing-library/jest-dom": "^5.16.5",
Expand All @@ -12,6 +13,8 @@
"autoprefixer": "^10.4.14",
"axios": "^1.4.0",
"cbor-web": "^9.0.2",
"cbor-x": "^1.6.0",
"cose-kit": "^1.7.1",
"did-resolver": "^4.1.0",
"firebase": "^10.1.0",
"i18next": "^23.2.11",
Expand Down
22 changes: 11 additions & 11 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#003476" />
<meta name="description" content="wwWallet" />
<meta name="description" content="Funke Wallet" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/wallet_192.png">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

<title>wwWallet</title>
<meta name="description" content="wwWallet is a secure web wallet for storing and managing verifiable credentials.">
<title>Funke Wallet</title>
<meta name="description" content="Funke Wallet is a secure web wallet for storing and managing verifiable credentials.">
<meta name="keywords"
content="wwWallet, web wallet, wallet, secure storage, verifiable credentials, digital credentials, credentials management">
<meta property="og:title" content="wwWallet: Secure Storage and Management of Verifiable Credentials">
content="Funke Wallet, web wallet, wallet, secure storage, verifiable credentials, digital credentials, credentials management">
<meta property="og:title" content="Funke Wallet: Secure Storage and Management of Verifiable Credentials">
<meta property="og:description"
content="wwWallet is a secure web wallet for storing and managing verifiable credentials.">
<meta property="og:image" content="https://demo.wwwallet.org/wallet_512.png">
<meta property="og:url" content="https://demo.wwwallet.org">
content="Funke Wallet is a secure web wallet for storing and managing verifiable credentials.">
<meta property="og:image" content="https://funke.wwwallet.org/wallet_512.png">
<meta property="og:url" content="https://funke.wwwallet.org">
<meta property="og:type" content="website">
<meta name="twitter:title" content="wwWallet: Secure Storage and Management of Verifiable Credentials">
<meta name="twitter:title" content="Funke Wallet: Secure Storage and Management of Verifiable Credentials">
<meta name="twitter:description"
content="wwWallet is a secure web wallet for storing and managing verifiable credentials.">
<meta name="twitter:image" content="https://demo.wwwallet.org/wallet_512.png">
content="Funke Wallet is a secure web wallet for storing and managing verifiable credentials.">
<meta name="twitter:image" content="https://funke.wwwallet.org/wallet_512.png">
<meta name="twitter:card" content="summary_large_image">
</head>

Expand Down
6 changes: 3 additions & 3 deletions public/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"short_name": "wwWallet",
"name": "wwWallet",
"short_name": "Funke Wallet",
"name": "Funke Wallet",
"icons": [
{
"src": "wallet_16.png",
Expand Down Expand Up @@ -32,7 +32,7 @@
"display": "standalone",
"orientation": "any",
"theme_color": "#003476",
"description": "wwWallet enables secure storage and management of verifiable credentials.",
"description": "Funke Wallet enables secure storage and management of verifiable credentials.",
"background_color": "#ffffff",
"scope": "/",
"dir": "ltr",
Expand Down
2 changes: 1 addition & 1 deletion public/robots.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ Disallow: /send
Allow: /login

Allow: /
Sitemap: https://demo.wwwallet.org/sitemap.xml
Sitemap: https://funke.wwwallet.org/sitemap.xml
6 changes: 3 additions & 3 deletions public/sitemap.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://demo.wwwallet.org/login</loc>
<lastmod>2024-04-25</lastmod>
<loc>https://funke.wwwallet.org/login</loc>
<lastmod>2024-10-04</lastmod>
<priority>1.0</priority>
<changefreq>monthly</changefreq>
<changefreq>weekly</changefreq>
</url>
</urlset>
4 changes: 3 additions & 1 deletion src/components/Credentials/CredentialInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ const CredentialInfo = ({ credential, mainClassName = "text-sm lg:text-base w-fu
<tbody className="divide-y-4 divide-transparent">
{parsedCredential && (
<>
{renderRow('expdate', 'Expiration', formatDate(new Date(parsedCredential?.exp * 1000).toISOString()), screenType)}
{renderRow('familyName', 'Family Name', parsedCredential?.family_name, screenType)}
{renderRow('firstName', 'Given Name', parsedCredential?.given_name, screenType)}
{renderRow('id', 'Personal ID', parsedCredential?.personal_identifier, screenType)}
Expand All @@ -86,6 +85,9 @@ const CredentialInfo = ({ credential, mainClassName = "text-sm lg:text-base w-fu
{renderRow('grade', 'Grade', parsedCredential?.grade, screenType)}
{renderRow('id', 'Social Security Number', parsedCredential?.ssn, screenType)}
{renderRow('id', 'Document Number', parsedCredential?.document_number, screenType)}

{renderRow('expdate', 'Expiration', parsedCredential?.exp ? formatDate(new Date(parsedCredential?.exp * 1000).toISOString()) : undefined, screenType)}

</>
)}
</tbody>
Expand Down
35 changes: 34 additions & 1 deletion src/context/ContainerContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import defaultCredentialImage from "../assets/images/cred.png";
import renderSvgTemplate from "../components/Credentials/RenderSvgTemplate";
import renderCustomSvgTemplate from "../components/Credentials/RenderCustomSvgTemplate";
import StatusContext from "./StatusContext";
import { MDoc } from "@auth0/mdl";
import { deviceResponseParser, mdocPIDParser } from "../lib/utils/mdocPIDParser";

export type ContainerContextValue = {
httpProxy: IHttpProxy,
Expand Down Expand Up @@ -92,6 +94,8 @@ export const ContainerContextProvider = ({ children }) => {
cont.register<IOpenID4VCIHelper>('OpenID4VCIHelper', OpenID4VCIHelper, cont.resolve<IHttpProxy>('HttpProxy'));
const credentialParserRegistry = cont.resolve<ICredentialParserRegistry>('CredentialParserRegistry');

credentialParserRegistry.addParser(deviceResponseParser);
credentialParserRegistry.addParser(mdocPIDParser);
credentialParserRegistry.addParser({
async parse(rawCredential) {

Expand Down Expand Up @@ -208,7 +212,11 @@ export const ContainerContextProvider = ({ children }) => {
audience,
issuanceDate: new Date().toISOString(),
});
}
},

async function generateDeviceResponse(mdocCredential: MDoc, presentationDefinition: any, mdocGeneratedNonce: string, verifierGeneratedNonce: string, clientId: string, responseUri: string) {
return keystore.generateDeviceResponse(mdocCredential, presentationDefinition, mdocGeneratedNonce, verifierGeneratedNonce, clientId, responseUri);
},
);

cont.register<OpenID4VCIClientFactory>('OpenID4VCIClientFactory', OpenID4VCIClientFactory,
Expand All @@ -225,6 +233,31 @@ export const ContainerContextProvider = ({ children }) => {
...c
});
},

async function authorizationRequestModifier(credentialIssuerIdentifier: string, url: string, request_uri?: string, client_id?: string) {
if (!credentialIssuerIdentifier.startsWith(process.env.REACT_APP_PID_CREDENTIAL_ISSUER_IDENTIFIER)) {
return { url };
}
const isMobile = window.innerWidth <= 480;
const eIDClientURL = isMobile ? process.env.REACT_APP_OPENID4VCI_EID_CLIENT_URL.replace('http', 'eid') : process.env.REACT_APP_OPENID4VCI_EID_CLIENT_URL;
console.log("Eid client url = ", eIDClientURL)
const urlObj = new URL(url);
// Construct the base URL
const baseUrl = `${urlObj.protocol}//${urlObj.hostname}${urlObj.pathname}`;

// Parameters
// Encode parameters
const encodedClientId = encodeURIComponent(client_id);
const encodedRequestUri = encodeURIComponent(request_uri);
const tcTokenURL = `${baseUrl}?client_id=${encodedClientId}&request_uri=${encodedRequestUri}`;

const newLoc = `${eIDClientURL}?tcTokenURL=${encodeURIComponent(tcTokenURL)}`

console.log("new loc = ", newLoc)
return {
url: newLoc
};
},
);

const httpProxy = cont.resolve<IHttpProxy>('HttpProxy');
Expand Down
3 changes: 3 additions & 0 deletions src/lib/services/OpenID4VCIClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ export class OpenID4VCIClient implements IOpenID4VCIClient {

const formData = new URLSearchParams();

console.log({ code_challenge, code_verifier })
console.log("Meta = ", this.config.credentialIssuerMetadata)
console.log("Conf id = ", credentialConfigurationId)
const selectedCredentialConfigurationSupported = this.config.credentialIssuerMetadata.credential_configurations_supported[credentialConfigurationId];
formData.append("scope", selectedCredentialConfigurationSupported.scope);

Expand Down
91 changes: 86 additions & 5 deletions src/lib/services/OpenID4VPRelyingParty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import { Verify } from "../utils/Verify";
import { HasherAlgorithm, HasherAndAlgorithm, SdJwt } from "@sd-jwt/core";
import { VerifiableCredentialFormat } from "../schemas/vc";
import { generateRandomIdentifier } from "../utils/generateRandomIdentifier";
import { base64url, EncryptJWT, importJWK, importX509, jwtVerify } from "jose";
import { base64url, CompactEncrypt, importJWK, importX509, jwtVerify } from "jose";
import { OpenID4VPRelyingPartyState, ResponseMode, ResponseModeSchema } from "../types/OpenID4VPRelyingPartyState";
import { OpenID4VPRelyingPartyStateRepository } from "./OpenID4VPRelyingPartyStateRepository";
import { IHttpProxy } from "../interfaces/IHttpProxy";
import { ICredentialParserRegistry } from "../interfaces/ICredentialParser";
import { extractSAN, getPublicKeyFromB64Cert } from "../utils/pki";
import axios from "axios";
import { BACKEND_URL, OPENID4VP_SAN_DNS_CHECK_SSL_CERTS, OPENID4VP_SAN_DNS_CHECK } from "../../config";
import { MDoc } from "@auth0/mdl";
import { parse } from '@auth0/mdl';
import { JSONPath } from "jsonpath-plus";
import { cborDecode, cborEncode } from "../utils/cbor";

export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {

Expand All @@ -22,7 +26,8 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
private credentialParserRegistry: ICredentialParserRegistry,
private getAllStoredVerifiableCredentials: () => Promise<{ verifiableCredentials: StorableCredential[] }>,
private signJwtPresentationKeystoreFn: (nonce: string, audience: string, verifiableCredentials: any[]) => Promise<{ vpjwt: string }>,
private storeVerifiablePresentation: (presentation: string, format: string, identifiersOfIncludedCredentials: string[], presentationSubmission: any, audience: string) => Promise<void>
private storeVerifiablePresentation: (presentation: string, format: string, identifiersOfIncludedCredentials: string[], presentationSubmission: any, audience: string) => Promise<void>,
private generateDeviceResponseFn: (mdocCredential: MDoc, presentationDefinition: any, mdocGeneratedNonce: string, verifierGeneratedNonce: string, clientId: string, responseUri: string) => Promise<{ deviceResponseMDoc: MDoc }>,
) { }


Expand Down Expand Up @@ -145,7 +150,7 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
for (const vc of vcList) {
try {

if (vc.format === VerifiableCredentialFormat.SD_JWT_VC && (descriptor.format === undefined || VerifiableCredentialFormat.SD_JWT_VC in descriptor.format)) {
if (vc.format === VerifiableCredentialFormat.SD_JWT_VC && (VerifiableCredentialFormat.SD_JWT_VC in descriptor.format)) {
const result = await this.credentialParserRegistry.parse(vc.credential);
if ('error' in result) {
throw new Error('Could not parse credential');
Expand All @@ -155,6 +160,40 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
continue;
}
}

if (vc.format == VerifiableCredentialFormat.MSO_MDOC && (VerifiableCredentialFormat.MSO_MDOC in descriptor.format)) {
const credentialBytes = base64url.decode(vc.credential);
const issuerSigned = cborDecode(credentialBytes);
// According to ISO 23220-4: The value of input descriptor id should be the doctype
const m = {
version: '1.0',
documents: [new Map([
['docType', descriptor.id],
['issuerSigned', issuerSigned]
])],
status: 0
};
const encoded = cborEncode(m);
const mdoc = parse(encoded);
const [document] = mdoc.documents;
const ns = document.getIssuerNameSpace(document.issuerSignedNameSpaces[0]);
const json = {};
json[descriptor.id] = ns;

const fieldsWithValue = descriptor.constraints.fields.map((field) => {
const values = field.path.map((possiblePath) => JSONPath({ path: possiblePath, json: json })[0]);
const val = values.filter((v) => v != undefined || v != null)[0]; // get first value that is not undefined
return { field, val };
});
console.log("Fields with value = ", fieldsWithValue)

if (fieldsWithValue.map((fwv) => fwv.val).includes(undefined)) {
continue; // there is at least one field missing from the requirements
}

conformingVcList.push(vc.credentialIdentifier);
continue;
}
}
catch (err) {
console.error("Failed to match a descriptor")
Expand Down Expand Up @@ -269,6 +308,9 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
const client_id = S.client_id;
const nonce = S.nonce;

let apu = undefined;
let apv = undefined;

let { verifiableCredentials } = await this.getAllStoredVerifiableCredentials();
const allSelectedCredentialIdentifiers = Array.from(selectionMap.values());
const filteredVCEntities = verifiableCredentials
Expand Down Expand Up @@ -302,6 +344,44 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
});
originalVCs.push(vcEntity);
}
else if (vcEntity.format === VerifiableCredentialFormat.MSO_MDOC) {
console.log("Response uri = ", response_uri);
const descriptor = presentationDefinition.input_descriptors.filter((desc) => desc.id === descriptor_id)[0];
const credentialBytes = base64url.decode(vcEntity.credential);
const issuerSigned = cborDecode(credentialBytes);

// According to ISO 23220-4: The value of input descriptor id should be the doctype
const m = {
version: '1.0',
documents: [new Map([
['docType', descriptor.id],
['issuerSigned', issuerSigned]
])],
status: 0
};
const encoded = cborEncode(m);
const mdoc = parse(encoded);

const mdocGeneratedNonce = generateRandomIdentifier(8); // mdoc generated nonce
apu = mdocGeneratedNonce; // no need to base64url encode. jose library handles it
apv = nonce; // no need to base64url encode. jose library handles it

const { deviceResponseMDoc } = await this.generateDeviceResponseFn(mdoc, presentationDefinition, mdocGeneratedNonce, nonce, client_id, response_uri);
function uint8ArrayToHexString(uint8Array) {
// @ts-ignore
return Array.from(uint8Array, byte => byte.toString(16).padStart(2, '0')).join('');
}
console.log("Device response in hex format = ", uint8ArrayToHexString(deviceResponseMDoc.encode()));
const encodedDeviceResponse = base64url.encode(deviceResponseMDoc.encode());
selectedVCs.push(encodedDeviceResponse);
generatedVPs.push(encodedDeviceResponse);
descriptorMap.push({
id: descriptor_id,
format: VerifiableCredentialFormat.MSO_MDOC,
path: `$`
});
originalVCs.push(vcEntity);
}
}

const presentationSubmission = {
Expand All @@ -315,11 +395,12 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
if (S.response_mode === ResponseMode.DIRECT_POST_JWT && S.client_metadata.authorization_encrypted_response_alg && S.client_metadata.jwks.keys.length > 0) {
const rp_eph_pub_jwk = S.client_metadata.jwks.keys[0];
const rp_eph_pub = await importJWK(rp_eph_pub_jwk, S.client_metadata.authorization_encrypted_response_alg);
const jwe = await new EncryptJWT({
const jwe = await new CompactEncrypt(new TextEncoder().encode(JSON.stringify({
vp_token: generatedVPs[0],
presentation_submission: presentationSubmission,
state: S.state ?? undefined
})
})))
.setKeyManagementParameters({ apu: new TextEncoder().encode(apu), apv: new TextEncoder().encode(apv) })
.setProtectedHeader({ alg: S.client_metadata.authorization_encrypted_response_alg, enc: S.client_metadata.authorization_encrypted_response_enc, kid: rp_eph_pub_jwk.kid })
.encrypt(rp_eph_pub);

Expand Down
27 changes: 27 additions & 0 deletions src/lib/utils/cbor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Encoder, Options } from "cbor-x";

const encoderDefaults: Options = {
tagUint8Array: false,
useRecords: false,
mapsAsObjects: false,
// @ts-ignore
useTag259ForMaps: false,
};

export const cborDecode = (
input: Buffer | Uint8Array,
options: Options = encoderDefaults,
): any => {
const params = { ...encoderDefaults, ...options };
const enc = new Encoder(params);
return enc.decode(input);
};

export const cborEncode = (
obj: unknown,
options: Options = encoderDefaults,
): Buffer => {
const params = { ...encoderDefaults, ...options };
const enc = new Encoder(params);
return enc.encode(obj);
};
Loading

0 comments on commit fc90187

Please sign in to comment.