From aecfdc6f68b3d4af9e5a135e03c7a04076ff7fdd Mon Sep 17 00:00:00 2001 From: gabe Date: Wed, 15 May 2024 20:47:32 -0700 Subject: [PATCH 1/9] update plugin to remove yaml, add disclosures, add headers --- plugin/attic/sd-jwt/index.js | 119 -------------------- plugin/dist/main.js | 4 +- plugin/index.js | 2 +- plugin/src/exampleJwt.js | 13 +-- plugin/src/exampleSdJwt.js | 205 ++++++++++++++++++++++++----------- 5 files changed, 150 insertions(+), 193 deletions(-) delete mode 100644 plugin/attic/sd-jwt/index.js diff --git a/plugin/attic/sd-jwt/index.js b/plugin/attic/sd-jwt/index.js deleted file mode 100644 index 888c1a1..0000000 --- a/plugin/attic/sd-jwt/index.js +++ /dev/null @@ -1,119 +0,0 @@ -import yaml from 'yaml' -import moment from 'moment'; -import SD from '@transmute/vc-jwt-sd' -import {base64url, calculateJwkThumbprint} from 'jose'; - -const digestName = 'sha-256' - -const isVC = (json) => json.type.includes('VerifiableCredential') -const isVP = (json) => json.type.includes('VerifiablePresentation') - -const salter = () => { - const array = new Uint8Array(16); - crypto.getRandomValues(array); - return base64url.encode(array) -} - -const digester = { - name: digestName, - digest: async (json) => { - const content = new TextEncoder().encode(json); - const digest = await crypto.subtle.digest(digestName.toUpperCase(), content); - return base64url.encode(new Uint8Array(digest)); - } -} - -export const generateIssuerClaims = (example)=> { - return yaml.stringify(example).replace(/id\: /g, '!sd id: ').replace(/type\:/g, '!sd type:') -} - -export const generateHolderDisclosure = (example) => { - const claims = generateIssuerClaims(example) - // redact nested ideas at depth 2 (spaces) - const edited1 = claims.replace(/ !sd id\:(.*?)\n/g, ` id: False\n`) - // disclose types - const edited2 = edited1.replace(/\!sd type\:/g, `type:`) - // redact remaining ids - return edited2.replace(/\!sd id\:/g, `id:`) -} - -export const getExampleMetadata = async ({ alg, json }) => { - console.log('getExampleMetadata', { alg, json }) - let iss = undefined - - if(json.issuer){ - iss = typeof json.issuer === 'string' ? json.issuer: json.issuer.id - } - - if (json.holder){ - iss = typeof json.holder === 'string' ? json.holder: json.holder.id - } - - const iat = moment().unix(); - const exp = moment().add(1, 'years').unix(); - const nonce = salter() - const aud = 'https://verifier.example' - const issuerKeyPair = await SD.JWK.generate(alg) - const holderKeyPair = await SD.JWK.generate(alg) - return { alg, json, iss, iat, exp, nonce, aud, issuerKeyPair, holderKeyPair } -} - -export const issueAndVerifyWithSdJWt = async ({ alg, json , iss, iat, exp, nonce, aud, issuerKeyPair, holderKeyPair, claims, disclosure }) =>{ - const kid = await calculateJwkThumbprint(issuerKeyPair.publicKeyJwk) - let typ = undefined - let cty = undefined - if (isVC(json)){ - typ = 'vc+ld+json+sd-jwt' - cty = 'vc+ld+json' - } - if (isVP(json)){ - typ = 'vp+ld+json+sd-jwt' - cty = 'vp+ld+json' - } - const issuer = new SD.Issuer({ - alg, - iss, - kid, - typ, - cty, - digester, - signer: await SD.JWS.signer(issuerKeyPair.secretKeyJwk), - salter - }) - const holder = new SD.Holder({ - alg, - digester, - signer: await SD.JWS.signer(holderKeyPair.secretKeyJwk) - }) - const verifier = new SD.Verifier({ - alg, - digester, - verifier: { - verify: async (token) => { - const parsed = SD.Parse.compact(token) - const verifier = await SD.JWS.verifier(issuerKeyPair.publicKeyJwk) - return verifier.verify(parsed.jwt) - } - } - }) - const vc = await issuer.issue({ - claims: claims, - iat: iat, - exp: exp, - holder: holderKeyPair.publicKeyJwk - }) - const vp = await holder.present({ - credential: vc, - disclosure: disclosure, - nonce, - aud - }) - const verified = await verifier.verify({ - presentation: vp, - nonce, - aud - }) - - return { verified, vc, vp } -} - diff --git a/plugin/dist/main.js b/plugin/dist/main.js index f276f02..2aafcd3 100644 --- a/plugin/dist/main.js +++ b/plugin/dist/main.js @@ -7862,7 +7862,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ getJwtExample: () => (/* binding */ getJwtExample)\n/* harmony export */ });\n/* harmony import */ var _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @transmute/verifiable-credentials */ \"./node_modules/@transmute/verifiable-credentials/dist/index.js\");\n/* harmony import */ var _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var jose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! jose */ \"./node_modules/jose/dist/browser/index.js\");\n\n\n\n\nconst getCredential = async (privateKey, byteSigner, messageType, messageJson) => {\n return await (0,_transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_0__.issuer)({\n alg: privateKey.alg,\n type: messageType,\n signer: byteSigner\n }).issue({\n claimset: new TextEncoder().encode(JSON.stringify(messageJson, null, 2))\n });\n}\n\nconst getPresentation = async (privateKey, byteSigner, messageType, messageJson) => {\n const disclosures = (messageJson.verifiableCredential || []).map((enveloped)=>{\n const { id } = enveloped\n const type = id.includes('base64url') ? id.split(';base64url,')[0].replace('data:', '') :id.split(';')[0].replace('data:', '')\n const content = id.includes('base64url') ? new TextEncoder().encode(id.split('base64url,').pop()) : new TextEncoder().encode(id.split(';').pop())\n return {\n type,\n credential: content\n }\n })\n return await (0,_transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_0__.holder)({\n alg: privateKey.alg,\n type: messageType,\n }).issue({\n signer: byteSigner,\n presentation: messageJson,\n disclosures: disclosures\n });\n}\n\n\nconst getJwtHtml = (token) =>{\n const [header, payload, signature] = token.split('.');\n return `\n
${header}.${payload}.${signature}
`\n}\n\nconst getBinaryMessage = async (privateKey, messageType, messageJson) =>{\n\n const byteSigner = {\n sign: async (bytes) => {\n\n const jws = await new jose__WEBPACK_IMPORTED_MODULE_1__.CompactSign(\n bytes\n )\n .setProtectedHeader({ kid: privateKey.kid, alg: privateKey.alg })\n .sign(await _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_0__.key.importKeyLike({\n type: 'application/jwk+json',\n content: new TextEncoder().encode(JSON.stringify(privateKey))\n }))\n return _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_0__.text.encoder.encode(jws)\n }\n }\n switch(messageType){\n case 'application/vc+ld+json+jwt': {\n return getCredential(privateKey, byteSigner, messageType, messageJson)\n }\n case 'application/vp+ld+json+jwt': {\n return getPresentation(privateKey, byteSigner, messageType, messageJson)\n }\n default: {\n throw new Error('Unknown message type')\n }\n }\n}\n\nconst getJwtExample = async (privateKey, messageJson) => {\n const type = Array.isArray(messageJson.type) ? messageJson.type : [messageJson.type]\n const messageType = type.includes('VerifiableCredential') ? 'application/vc+ld+json+jwt' : 'application/vp+ld+json+jwt'\n const message = await getBinaryMessage(privateKey, messageType, messageJson)\n const messageEncoded = new TextDecoder().decode(message)\n // const decodedHeader = jose.decodeProtectedHeader(messageEncoded)\n // Not displaying protected header to save space\n //

Protected

\n //
\n    // ${JSON.stringify(decodedHeader, null, 2)}\n    // 
\n return `\n

${messageType.replace('+jwt', '')}

\n
\n${JSON.stringify(messageJson, null, 2)}\n
\n

${messageType}

\n
\n${getJwtHtml(messageEncoded)}\n
\n `.trim()\n}\n\n\n//# sourceURL=webpack://respec-vc-jose-cose/./src/exampleJwt.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ getJwtExample: () => (/* binding */ getJwtExample)\n/* harmony export */ });\n/* harmony import */ var _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @transmute/verifiable-credentials */ \"./node_modules/@transmute/verifiable-credentials/dist/index.js\");\n/* harmony import */ var _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var jose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! jose */ \"./node_modules/jose/dist/browser/index.js\");\n\n\n\n\nconst getCredential = async (privateKey, byteSigner, messageType, messageJson) => {\n return await (0,_transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_0__.issuer)({\n alg: privateKey.alg,\n type: messageType,\n signer: byteSigner\n }).issue({\n claimset: new TextEncoder().encode(JSON.stringify(messageJson, null, 2))\n });\n}\n\nconst getPresentation = async (privateKey, byteSigner, messageType, messageJson) => {\n const disclosures = (messageJson.verifiableCredential || []).map((enveloped)=>{\n const { id } = enveloped\n const type = id.includes('base64url') ? id.split(';base64url,')[0].replace('data:', '') :id.split(';')[0].replace('data:', '')\n const content = id.includes('base64url') ? new TextEncoder().encode(id.split('base64url,').pop()) : new TextEncoder().encode(id.split(';').pop())\n return {\n type,\n credential: content\n }\n })\n return await (0,_transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_0__.holder)({\n alg: privateKey.alg,\n type: messageType,\n }).issue({\n signer: byteSigner,\n presentation: messageJson,\n disclosures: disclosures\n });\n}\n\n\nconst getJwtHtml = (token) =>{\n const [header, payload, signature] = token.split('.');\n return `\n
${header}.${payload}.${signature}
`\n}\n\nconst getBinaryMessage = async (privateKey, messageType, messageJson) =>{\n\n const byteSigner = {\n sign: async (bytes) => {\n\n const jws = await new jose__WEBPACK_IMPORTED_MODULE_1__.CompactSign(\n bytes\n )\n .setProtectedHeader({ kid: privateKey.kid, alg: privateKey.alg })\n .sign(await _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_0__.key.importKeyLike({\n type: 'application/jwk+json',\n content: new TextEncoder().encode(JSON.stringify(privateKey))\n }))\n return _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_0__.text.encoder.encode(jws)\n }\n }\n switch(messageType){\n case 'application/vc+ld+json+jwt': {\n return getCredential(privateKey, byteSigner, messageType, messageJson)\n }\n case 'application/vp+ld+json+jwt': {\n return getPresentation(privateKey, byteSigner, messageType, messageJson)\n }\n default: {\n throw new Error('Unknown message type')\n }\n }\n}\n\nconst getJwtExample = async (privateKey, messageJson) => {\n const type = Array.isArray(messageJson.type) ? messageJson.type : [messageJson.type]\n const messageType = type.includes('VerifiableCredential') ? 'application/vc+ld+json+jwt' : 'application/vp+ld+json+jwt'\n const message = await getBinaryMessage(privateKey, messageType, messageJson)\n const messageEncoded = new TextDecoder().decode(message)\n const decodedHeader = jose__WEBPACK_IMPORTED_MODULE_1__.decodeProtectedHeader(messageEncoded)\n return `\n

Protected Headers

\n
\n${JSON.stringify(decodedHeader, null, 2)}\n
\n

${messageType.replace('+jwt', '')}

\n
\n${JSON.stringify(messageJson, null, 2)}\n
\n

${messageType}

\n
\n${getJwtHtml(messageEncoded)}\n
\n `.trim()\n}\n\n\n//# sourceURL=webpack://respec-vc-jose-cose/./src/exampleJwt.js?"); /***/ }), @@ -7884,7 +7884,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ generateHolderDisclosure: () => (/* binding */ generateHolderDisclosure),\n/* harmony export */ generateIssuerClaims: () => (/* binding */ generateIssuerClaims),\n/* harmony export */ getSdJwtExample: () => (/* binding */ getSdJwtExample)\n/* harmony export */ });\n/* harmony import */ var yaml__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! yaml */ \"./node_modules/yaml/browser/index.js\");\n/* harmony import */ var _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @transmute/verifiable-credentials */ \"./node_modules/@transmute/verifiable-credentials/dist/index.js\");\n/* harmony import */ var _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var jose__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! jose */ \"./node_modules/jose/dist/browser/index.js\");\n\n\n\n\n\nconst generateIssuerClaims = (example)=> {\n return yaml__WEBPACK_IMPORTED_MODULE_0__[\"default\"].stringify(example).replace(/id\\: /g, '!sd id: ').replace(/type\\:/g, '!sd type:')\n}\n\nconst generateHolderDisclosure = (example) => {\n const claims = generateIssuerClaims(example)\n // redact nested ideas at depth 2 (spaces)\n const edited1 = claims.replace(/ !sd id\\:(.*?)\\n/g, ` id: False\\n`)\n // disclose types\n const edited2 = edited1.replace(/\\!sd type\\:/g, `type:`)\n // redact remaining ids\n return edited2.replace(/\\!sd id\\:/g, `id:`)\n}\n\nconst getSdHtml = (vc) =>{\n const [token, ...disclosure] = vc.split('~');\n const [header, payload, signature] = token.split('.');\n const disclosures = disclosure.map((d)=>{\n return `~${d}`\n }).join('')\n return `\n
${header}.${payload}.${signature}${disclosures}
`\n}\n\n\nconst getDisclosabilityHtml = (claims)=> {\n return `
\n${claims.trim().replace(/\\!sd/g, `!sd`)}\n  
`\n}\n\nconst getCredential = async (privateKey, byteSigner, messageType, messageJson) => {\n return await (0,_transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1__.issuer)({\n alg: privateKey.alg,\n type: messageType,\n signer: byteSigner\n }).issue({\n claimset: new TextEncoder().encode(generateIssuerClaims(messageJson))\n });\n}\n\nconst getPresentation = async (privateKey, byteSigner, messageType, messageJson) => {\n // since examples are always enveloped, and truncated, we never actually process key binding or disclosures\n return await getCredential(privateKey, byteSigner, 'application/vc+ld+json+sd-jwt', messageJson)\n}\n\nconst getBinaryMessage = async (privateKey, messageType, messageJson) =>{\n const byteSigner = {\n sign: async (bytes) => {\n const jws = await new jose__WEBPACK_IMPORTED_MODULE_2__.CompactSign(\n bytes\n )\n .setProtectedHeader({ kid: privateKey.kid, alg: privateKey.alg })\n .sign(await _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1__.key.importKeyLike({\n type: 'application/jwk+json',\n content: new TextEncoder().encode(JSON.stringify(privateKey))\n }))\n return _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1__.text.encoder.encode(jws)\n }\n }\n switch(messageType){\n case 'application/vc+ld+json+sd-jwt': {\n return getCredential(privateKey, byteSigner, messageType, messageJson)\n }\n case 'application/vp+ld+json+sd-jwt': {\n return getPresentation(privateKey, byteSigner, messageType, messageJson)\n }\n default: {\n throw new Error('Unknown message type')\n }\n }\n}\n\nconst getSdJwtExample = async (privateKey, messageJson) => {\n const type = Array.isArray(messageJson.type) ? messageJson.type : [messageJson.type]\n const messageType = type.includes('VerifiableCredential') ? 'application/vc+ld+json+sd-jwt' : 'application/vp+ld+json+sd-jwt'\n const message = await getBinaryMessage(privateKey, messageType, messageJson)\n const messageEncoded = new TextDecoder().decode(message)\n\n const issuerClaims = generateIssuerClaims(messageJson)\n const messageType2 = 'application/ld+yaml'\n\n// const decodedHeader = jose.decodeProtectedHeader(messageEncoded.split('~')[0])\n// Not displaying protected header to save space\n//

Protected

\n//
\n// ${JSON.stringify(decodedHeader, null, 2)}\n// 
\n return `\n\n

${messageType2}

\n
\n${getDisclosabilityHtml(issuerClaims)}\n
\n\n

${messageType}

\n
\n${getSdHtml(messageEncoded)}\n
\n `.trim()\n}\n\n\n//# sourceURL=webpack://respec-vc-jose-cose/./src/exampleSdJwt.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ generateIssuerClaims: () => (/* binding */ generateIssuerClaims),\n/* harmony export */ getSdJwtExample: () => (/* binding */ getSdJwtExample)\n/* harmony export */ });\n/* harmony import */ var yaml__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! yaml */ \"./node_modules/yaml/browser/index.js\");\n/* harmony import */ var _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @transmute/verifiable-credentials */ \"./node_modules/@transmute/verifiable-credentials/dist/index.js\");\n/* harmony import */ var _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var jose__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! jose */ \"./node_modules/jose/dist/browser/index.js\");\n/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! crypto */ \"./node_modules/crypto-browserify/index.js\");\n\n\n\n // Ensure you have this dependency\n\nconst calculateHash = (value) => {\n return _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1__.base64url.encode(crypto__WEBPACK_IMPORTED_MODULE_3__.createHash('sha256').update(value).digest());\n};\n\n// Custom JSON.stringify with prettier formatting\nconst customJSONStringify = (obj) => {\n return JSON.stringify(obj, null, 2).replace(/\\n/g, '
').replace(/\\s/g, ' ');\n};\n\nconst generateDisclosureHtml = (claimName, hash, disclosure, contents) => {\n return `\n
\n

Claim: ${claimName}

\n

SHA-256 Hash: ${hash}

\n

Disclosure(s): ${disclosure}

\n

Contents: ${customJSONStringify(JSON.parse(contents))}

\n
\n`;\n};\n\nconst getDisclosuresFromPayload = (payload) => {\n const decodedPayload = new TextDecoder().decode(_transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1__.base64url.decode(payload));\n const claims = JSON.parse(decodedPayload);\n const disclosures = [];\n\n for (const claimName in claims) {\n const claim = claims[claimName];\n const hash = calculateHash(JSON.stringify(claim));\n const disclosure = _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1__.base64url.encode(JSON.stringify(claim));\n const contents = JSON.stringify(claim, null, 2); // Prettified formatting\n\n disclosures.push({ claimName, hash, disclosure, contents });\n }\n\n return disclosures;\n};\n\nconst getSdHtml = (vc) => {\n const [token, ...disclosure] = vc.split('~');\n const [header, payload, signature] = token.split('.');\n const disclosures = disclosure.map((d) => {\n return `~${d}`;\n }).join('');\n return `\n
${header}.${payload}.${signature}${disclosures}
`;\n};\n\nconst getHeadersHtml = (vc) => {\n const [token] = vc.split('~');\n const [header] = token.split('.');\n const headerJson = JSON.parse(new TextDecoder().decode(_transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1__.base64url.decode(header)));\n return `
${customJSONStringify(headerJson)}
`;\n};\n\nconst getDisclosabilityHtml = async (vc) => {\n const [token] = vc.split('~');\n const [, payload] = token.split('.');\n\n const disclosures = getDisclosuresFromPayload(payload);\n const disclosureHtml = disclosures.map(({ claimName, hash, disclosure, contents }) =>\n generateDisclosureHtml(claimName, hash, disclosure, contents)\n );\n\n return `\n\n
\n ${disclosureHtml.join('\\n')}\n
\n`;\n};\n\nconst generateIssuerClaims = (example) => {\n return yaml__WEBPACK_IMPORTED_MODULE_0__[\"default\"].stringify(example).replace(/id: /g, '!sd id: ').replace(/type:/g, '!sd type:');\n};\n\nconst getCredential = async (privateKey, byteSigner, messageType, messageJson) => {\n return await (0,_transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1__.issuer)({\n alg: privateKey.alg,\n type: messageType,\n signer: byteSigner\n }).issue({\n claimset: new TextEncoder().encode(generateIssuerClaims(messageJson))\n });\n};\n\nconst getPresentation = async (privateKey, byteSigner, messageType, messageJson) => {\n return await getCredential(privateKey, byteSigner, 'application/vc+ld+json+sd-jwt', messageJson);\n};\n\nconst getBinaryMessage = async (privateKey, messageType, messageJson) => {\n const byteSigner = {\n sign: async (bytes) => {\n const jws = await new jose__WEBPACK_IMPORTED_MODULE_2__.CompactSign(bytes)\n .setProtectedHeader({ kid: privateKey.kid, alg: privateKey.alg })\n .sign(await _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1__.key.importKeyLike({\n type: 'application/jwk+json',\n content: new TextEncoder().encode(JSON.stringify(privateKey))\n }));\n return _transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1__.text.encoder.encode(jws);\n }\n };\n switch (messageType) {\n case 'application/vc+ld+json+sd-jwt': {\n return getCredential(privateKey, byteSigner, messageType, messageJson);\n }\n case 'application/vp+ld+json+sd-jwt': {\n return getPresentation(privateKey, byteSigner, messageType, messageJson);\n }\n default: {\n throw new Error('Unknown message type');\n }\n }\n};\n\nconst getSdJwtExample = async (privateKey, messageJson) => {\n const type = Array.isArray(messageJson.type) ? messageJson.type : [messageJson.type];\n const messageType = type.includes('VerifiableCredential') ? 'application/vc+ld+json+sd-jwt' : 'application/vp+ld+json+sd-jwt';\n const message = await getBinaryMessage(privateKey, messageType, messageJson);\n const messageEncoded = new TextDecoder().decode(message);\n\n return `\n

Protected Headers

\n
\n${getHeadersHtml(messageEncoded)}\n
\n\n

Disclosures

\n
\n${await getDisclosabilityHtml(messageEncoded)}\n
\n\n

${messageType}

\n
\n${getSdHtml(messageEncoded)}\n
\n `.trim();\n};\n\n\n//# sourceURL=webpack://respec-vc-jose-cose/./src/exampleSdJwt.js?"); /***/ }), diff --git a/plugin/index.js b/plugin/index.js index e06b1c1..0d1ba19 100644 --- a/plugin/index.js +++ b/plugin/index.js @@ -1,4 +1,4 @@ -import {getCombinedHtml, getCoseHtml, getHtml, getJwtHtml, getSdJwtHtml} from './src/getHtml'; +import { getCombinedHtml, getCoseHtml, getJwtHtml, getSdJwtHtml } from './src/getHtml'; import { getPrivateKey } from './src/exampleKey'; import { getCoseExample } from './src/exampleCose'; import { getJwtExample } from './src/exampleJwt'; diff --git a/plugin/src/exampleJwt.js b/plugin/src/exampleJwt.js index 91bff82..58282c0 100644 --- a/plugin/src/exampleJwt.js +++ b/plugin/src/exampleJwt.js @@ -1,4 +1,4 @@ -import {holder, issuer, key, text} from "@transmute/verifiable-credentials"; +import {base64url, holder, issuer, key, text} from "@transmute/verifiable-credentials"; import * as jose from 'jose' @@ -73,13 +73,12 @@ export const getJwtExample = async (privateKey, messageJson) => { const messageType = type.includes('VerifiableCredential') ? 'application/vc+ld+json+jwt' : 'application/vp+ld+json+jwt' const message = await getBinaryMessage(privateKey, messageType, messageJson) const messageEncoded = new TextDecoder().decode(message) - // const decodedHeader = jose.decodeProtectedHeader(messageEncoded) - // Not displaying protected header to save space - //

Protected

- //
-    // ${JSON.stringify(decodedHeader, null, 2)}
-    // 
+ const decodedHeader = jose.decodeProtectedHeader(messageEncoded) return ` +

Protected Headers

+
+${JSON.stringify(decodedHeader, null, 2)}
+

${messageType.replace('+jwt', '')}

 ${JSON.stringify(messageJson, null, 2)}
diff --git a/plugin/src/exampleSdJwt.js b/plugin/src/exampleSdJwt.js
index 9998933..dc89ac9 100644
--- a/plugin/src/exampleSdJwt.js
+++ b/plugin/src/exampleSdJwt.js
@@ -1,38 +1,123 @@
-import yaml from 'yaml'
-import {issuer, key, text} from "@transmute/verifiable-credentials";
-
-import * as jose from 'jose'
-
-export const generateIssuerClaims = (example)=> {
-    return yaml.stringify(example).replace(/id\: /g, '!sd id: ').replace(/type\:/g, '!sd type:')
-}
-
-export const generateHolderDisclosure = (example) => {
-    const claims = generateIssuerClaims(example)
-    // redact nested ideas at depth 2 (spaces)
-    const edited1 = claims.replace(/  !sd id\:(.*?)\n/g, `  id: False\n`)
-    // disclose types
-    const edited2 = edited1.replace(/\!sd type\:/g, `type:`)
-    // redact remaining ids
-    return edited2.replace(/\!sd id\:/g, `id:`)
-}
-
-const getSdHtml = (vc) =>{
+import yaml from 'yaml';
+import { base64url, issuer, key, text } from "@transmute/verifiable-credentials";
+import * as jose from 'jose';
+import crypto from 'crypto'; // Ensure you have this dependency
+
+const calculateHash = (value) => {
+    return base64url.encode(crypto.createHash('sha256').update(value).digest());
+};
+
+// Custom JSON.stringify with prettier formatting
+const customJSONStringify = (obj) => {
+    return JSON.stringify(obj, null, 2).replace(/\n/g, '
').replace(/\s/g, ' '); +}; + +const generateDisclosureHtml = (claimName, hash, disclosure, contents) => { + return ` +
+

Claim: ${claimName}

+

SHA-256 Hash: ${hash}

+

Disclosure(s): ${disclosure}

+

Contents: ${customJSONStringify(JSON.parse(contents))}

+
+`; +}; + +const getDisclosuresFromPayload = (payload) => { + const decodedPayload = new TextDecoder().decode(base64url.decode(payload)); + const claims = JSON.parse(decodedPayload); + const disclosures = []; + + for (const claimName in claims) { + const claim = claims[claimName]; + const hash = calculateHash(JSON.stringify(claim)); + const disclosure = base64url.encode(JSON.stringify(claim)); + const contents = JSON.stringify(claim, null, 2); // Prettified formatting + + disclosures.push({ claimName, hash, disclosure, contents }); + } + + return disclosures; +}; + +const getSdHtml = (vc) => { const [token, ...disclosure] = vc.split('~'); const [header, payload, signature] = token.split('.'); - const disclosures = disclosure.map((d)=>{ - return `~${d}` - }).join('') + const disclosures = disclosure.map((d) => { + return `~${d}`; + }).join(''); return ` -
${header}.${payload}.${signature}${disclosures}
` -} +
${header}.${payload}.${signature}${disclosures}
`; +}; +const getHeadersHtml = (vc) => { + const [token] = vc.split('~'); + const [header] = token.split('.'); + const headerJson = JSON.parse(new TextDecoder().decode(base64url.decode(header))); + return `
${customJSONStringify(headerJson)}
`; +}; -const getDisclosabilityHtml = (claims)=> { - return `
-${claims.trim().replace(/\!sd/g, `!sd`)}
-  
` -} +const getDisclosabilityHtml = async (vc) => { + const [token] = vc.split('~'); + const [, payload] = token.split('.'); + + const disclosures = getDisclosuresFromPayload(payload); + const disclosureHtml = disclosures.map(({ claimName, hash, disclosure, contents }) => + generateDisclosureHtml(claimName, hash, disclosure, contents) + ); + + return ` + +
+ ${disclosureHtml.join('\n')} +
+`; +}; + +export const generateIssuerClaims = (example) => { + return yaml.stringify(example).replace(/id: /g, '!sd id: ').replace(/type:/g, '!sd type:'); +}; const getCredential = async (privateKey, byteSigner, messageType, messageJson) => { return await issuer({ @@ -42,65 +127,57 @@ const getCredential = async (privateKey, byteSigner, messageType, messageJson) = }).issue({ claimset: new TextEncoder().encode(generateIssuerClaims(messageJson)) }); -} +}; const getPresentation = async (privateKey, byteSigner, messageType, messageJson) => { - // since examples are always enveloped, and truncated, we never actually process key binding or disclosures - return await getCredential(privateKey, byteSigner, 'application/vc+ld+json+sd-jwt', messageJson) -} + return await getCredential(privateKey, byteSigner, 'application/vc+ld+json+sd-jwt', messageJson); +}; -const getBinaryMessage = async (privateKey, messageType, messageJson) =>{ +const getBinaryMessage = async (privateKey, messageType, messageJson) => { const byteSigner = { sign: async (bytes) => { - const jws = await new jose.CompactSign( - bytes - ) + const jws = await new jose.CompactSign(bytes) .setProtectedHeader({ kid: privateKey.kid, alg: privateKey.alg }) .sign(await key.importKeyLike({ type: 'application/jwk+json', content: new TextEncoder().encode(JSON.stringify(privateKey)) - })) - return text.encoder.encode(jws) + })); + return text.encoder.encode(jws); } - } - switch(messageType){ + }; + switch (messageType) { case 'application/vc+ld+json+sd-jwt': { - return getCredential(privateKey, byteSigner, messageType, messageJson) + return getCredential(privateKey, byteSigner, messageType, messageJson); } case 'application/vp+ld+json+sd-jwt': { - return getPresentation(privateKey, byteSigner, messageType, messageJson) + return getPresentation(privateKey, byteSigner, messageType, messageJson); } default: { - throw new Error('Unknown message type') + throw new Error('Unknown message type'); } } -} +}; export const getSdJwtExample = async (privateKey, messageJson) => { - const type = Array.isArray(messageJson.type) ? messageJson.type : [messageJson.type] - const messageType = type.includes('VerifiableCredential') ? 'application/vc+ld+json+sd-jwt' : 'application/vp+ld+json+sd-jwt' - const message = await getBinaryMessage(privateKey, messageType, messageJson) - const messageEncoded = new TextDecoder().decode(message) - - const issuerClaims = generateIssuerClaims(messageJson) - const messageType2 = 'application/ld+yaml' - -// const decodedHeader = jose.decodeProtectedHeader(messageEncoded.split('~')[0]) -// Not displaying protected header to save space -//

Protected

-//
-// ${JSON.stringify(decodedHeader, null, 2)}
-// 
+ const type = Array.isArray(messageJson.type) ? messageJson.type : [messageJson.type]; + const messageType = type.includes('VerifiableCredential') ? 'application/vc+ld+json+sd-jwt' : 'application/vp+ld+json+sd-jwt'; + const message = await getBinaryMessage(privateKey, messageType, messageJson); + const messageEncoded = new TextDecoder().decode(message); + return ` +

Protected Headers

+
+${getHeadersHtml(messageEncoded)} +
-

${messageType2}

+

Disclosures

-${getDisclosabilityHtml(issuerClaims)} +${await getDisclosabilityHtml(messageEncoded)}

${messageType}

${getSdHtml(messageEncoded)}
- `.trim() -} + `.trim(); +}; From 974f350231a1b396dfccee0038f471d0868c0de5 Mon Sep 17 00:00:00 2001 From: gabe Date: Wed, 15 May 2024 20:48:12 -0700 Subject: [PATCH 2/9] update yaml --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index f225186..a25758a 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ Securing Verifiable Credentials using JOSE and COSE - + - + - + - +