From 1b47e1fd0b8a3a061a61b2d252fd0167dbe99edd Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sat, 17 Feb 2024 10:49:45 -0600 Subject: [PATCH] add sd-jwt --- dist/main.js | 19 +++++-- index.js | 12 +++-- src/exampleCose.js | 9 ++-- src/exampleJwt.js | 11 ++-- src/exampleSdJwt.js | 126 ++++++++++++++++++++++++++++++++++++++++++++ src/getHtml.js | 48 ++--------------- 6 files changed, 162 insertions(+), 63 deletions(-) create mode 100644 src/exampleSdJwt.js diff --git a/dist/main.js b/dist/main.js index dfcdd80..19cbaa4 100644 --- a/dist/main.js +++ b/dist/main.js @@ -16,7 +16,7 @@ /***/ ((__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 */ processExample: () => (/* binding */ processExample)\n/* harmony export */ });\n/* harmony import */ var _src_getHtml__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./src/getHtml */ \"./src/getHtml.js\");\n/* harmony import */ var _src_exampleKey__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./src/exampleKey */ \"./src/exampleKey.js\");\n/* harmony import */ var _src_exampleCose__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./src/exampleCose */ \"./src/exampleCose.js\");\n/* harmony import */ var _src_exampleJwt__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./src/exampleJwt */ \"./src/exampleJwt.js\");\n\n\n\n\n\n\n\nasync function processVcJoseCose() {\n // add styling for examples\n addVcJoseStyles();\n const examples = Array.from(document.querySelectorAll(\".vc-jose-cose\")).filter((e) => !!e.innerText)\n for (const index in examples) {\n const example = examples[index]\n const json = JSON.parse(example.innerText.replace(/\\/\\/ .*$/gm, ''))\n const processedData = await processExample(index, json);\n example.outerHTML = processedData.html\n }\n}\n\nfunction addVcJoseStyles() {\n const styles = document.createElement('style');\n\n styles.innerHTML += `\n .vc-jose-cose-tabbed {\n overflow-x: hidden;\n margin: 0 0;\n }\n \n .vc-jose-cose-tabbed [type=\"radio\"] {\n display: none;\n }\n \n .vc-jose-cose-tabs {\n display: flex;\n align-items: stretch;\n list-style: none;\n padding: 0;\n border-bottom: 1px solid #ccc;\n }\n \n li.vc-jose-cose-tab {\n margin: unset;\n margin-left: 8px;\n }\n \n .vc-jose-cose-tab>label {\n display: block;\n margin-bottom: -1px;\n padding: .4em .5em;\n border: 1px solid #ccc;\n border-top-right-radius: .4em;\n border-top-left-radius: .4em;\n background: #eee;\n color: #666;\n cursor: pointer;\n transition: all 0.3s;\n }\n \n .vc-jose-cose-tab:hover label {\n border-left-color: #333;\n border-top-color: #333;\n border-right-color: #333;\n color: #333;\n }\n \n .vc-jose-cose-tab-content {\n display: none;\n }\n \n .vc-jose-cose-tabbed [type=\"radio\"]:nth-of-type(1):checked~.vc-jose-cose-tabs .vc-jose-cose-tab:nth-of-type(1) label,\n .vc-jose-cose-tabbed [type=\"radio\"]:nth-of-type(2):checked~.vc-jose-cose-tabs .vc-jose-cose-tab:nth-of-type(2) label,\n .vc-jose-cose-tabbed [type=\"radio\"]:nth-of-type(3):checked~.vc-jose-cose-tabs .vc-jose-cose-tab:nth-of-type(3) label {\n border-bottom-color: #fff;\n background: #fff;\n color: #222;\n }\n \n .vc-jose-cose-tabbed [type=\"radio\"]:nth-of-type(1):checked~.vc-jose-cose-tab-content:nth-of-type(1),\n .vc-jose-cose-tabbed [type=\"radio\"]:nth-of-type(2):checked~.vc-jose-cose-tab-content:nth-of-type(2),\n .vc-jose-cose-tabbed [type=\"radio\"]:nth-of-type(3):checked~.vc-jose-cose-tab-content:nth-of-type(3) {\n display: block;\n }\n \n .sd-jwt-header {\n color: red\n }\n .sd-jwt-payload {\n color: green\n }\n \n .sd-jwt-payload-verified{\n color: purple\n }\n \n .sd-jwt-signature {\n color: blue\n }\n \n .sd-jwt-disclosure {\n color: purple\n }\n \n .sd-jwt-compact {\n background-color: rgba(0,0,0,.03);\n }`;\n\n document.head.appendChild(styles);\n}\n\nasync function processExample(index, json) {\n const privateKey = await (0,_src_exampleKey__WEBPACK_IMPORTED_MODULE_1__.getPrivateKey)()\n const coseExample = await (0,_src_exampleCose__WEBPACK_IMPORTED_MODULE_2__.getCoseExample)(privateKey, json)\n const jwtExample = await (0,_src_exampleJwt__WEBPACK_IMPORTED_MODULE_3__.getJwtExample)(privateKey, json)\n const html = (0,_src_getHtml__WEBPACK_IMPORTED_MODULE_0__.getHtml)({index, coseExample, jwtExample});\n return {html};\n}\n\nwindow.respecVcJoseCose = {\n processVcJoseCose\n}\n\n\n//# sourceURL=webpack://respec-vc-jose-cose/./index.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ processExample: () => (/* binding */ processExample)\n/* harmony export */ });\n/* harmony import */ var _src_getHtml__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./src/getHtml */ \"./src/getHtml.js\");\n/* harmony import */ var _src_exampleKey__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./src/exampleKey */ \"./src/exampleKey.js\");\n/* harmony import */ var _src_exampleCose__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./src/exampleCose */ \"./src/exampleCose.js\");\n/* harmony import */ var _src_exampleJwt__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./src/exampleJwt */ \"./src/exampleJwt.js\");\n/* harmony import */ var _src_exampleSdJwt__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./src/exampleSdJwt */ \"./src/exampleSdJwt.js\");\n\n\n\n\n\n\n\n\n\n\nasync function processVcJoseCose() {\n // add styling for examples\n addVcJoseStyles();\n const examples = Array.from(document.querySelectorAll(\".vc-jose-cose\")).filter((e) => !!e.innerText)\n for (const index in examples) {\n const example = examples[index]\n const json = JSON.parse(example.innerText.replace(/\\/\\/ .*$/gm, ''))\n const processedData = await processExample(index, json);\n example.outerHTML = processedData.html\n }\n}\n\nfunction addVcJoseStyles() {\n const styles = document.createElement('style');\n\n styles.innerHTML += `\n .vc-jose-cose-tabbed {\n overflow-x: hidden;\n margin: 0 0;\n }\n \n .vc-jose-cose-tabbed [type=\"radio\"] {\n display: none;\n }\n \n .vc-jose-cose-tabs {\n display: flex;\n align-items: stretch;\n list-style: none;\n padding: 0;\n border-bottom: 1px solid #ccc;\n }\n \n li.vc-jose-cose-tab {\n margin: unset;\n margin-left: 8px;\n }\n \n .vc-jose-cose-tab>label {\n display: block;\n margin-bottom: -1px;\n padding: .4em .5em;\n border: 1px solid #ccc;\n border-top-right-radius: .4em;\n border-top-left-radius: .4em;\n background: #eee;\n color: #666;\n cursor: pointer;\n transition: all 0.3s;\n }\n \n .vc-jose-cose-tab:hover label {\n border-left-color: #333;\n border-top-color: #333;\n border-right-color: #333;\n color: #333;\n }\n \n .vc-jose-cose-tab-content {\n display: none;\n }\n \n .vc-jose-cose-tabbed [type=\"radio\"]:nth-of-type(1):checked~.vc-jose-cose-tabs .vc-jose-cose-tab:nth-of-type(1) label,\n .vc-jose-cose-tabbed [type=\"radio\"]:nth-of-type(2):checked~.vc-jose-cose-tabs .vc-jose-cose-tab:nth-of-type(2) label,\n .vc-jose-cose-tabbed [type=\"radio\"]:nth-of-type(3):checked~.vc-jose-cose-tabs .vc-jose-cose-tab:nth-of-type(3) label {\n border-bottom-color: #fff;\n background: #fff;\n color: #222;\n }\n \n .vc-jose-cose-tabbed [type=\"radio\"]:nth-of-type(1):checked~.vc-jose-cose-tab-content:nth-of-type(1),\n .vc-jose-cose-tabbed [type=\"radio\"]:nth-of-type(2):checked~.vc-jose-cose-tab-content:nth-of-type(2),\n .vc-jose-cose-tabbed [type=\"radio\"]:nth-of-type(3):checked~.vc-jose-cose-tab-content:nth-of-type(3) {\n display: block;\n }\n \n .sd-jwt-header {\n color: red\n }\n .sd-jwt-payload {\n color: green\n }\n \n .sd-jwt-payload-verified{\n color: purple\n }\n \n .sd-jwt-signature {\n color: blue\n }\n \n .sd-jwt-disclosure {\n color: purple\n }\n \n .sd-jwt-compact {\n background-color: rgba(0,0,0,.03);\n }`;\n\n document.head.appendChild(styles);\n}\n\nasync function processExample(index, json) {\n const privateKey = await (0,_src_exampleKey__WEBPACK_IMPORTED_MODULE_1__.getPrivateKey)();\n const coseExample = await (0,_src_exampleCose__WEBPACK_IMPORTED_MODULE_2__.getCoseExample)(privateKey, json);\n const jwtExample = await (0,_src_exampleJwt__WEBPACK_IMPORTED_MODULE_3__.getJwtExample)(privateKey, json);\n const sdJwtExample = await (0,_src_exampleSdJwt__WEBPACK_IMPORTED_MODULE_4__.getSdJwtExample)(privateKey, json);\n const html = (0,_src_getHtml__WEBPACK_IMPORTED_MODULE_0__.getHtml)({index, coseExample, jwtExample, sdJwtExample});\n return {html};\n}\n\nwindow.respecVcJoseCose = {\n processVcJoseCose\n}\n\n\n//# sourceURL=webpack://respec-vc-jose-cose/./index.js?"); /***/ }), @@ -5934,7 +5934,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", ({\n value: true\n})); /***/ ((__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 */ getCoseExample: () => (/* binding */ getCoseExample)\n/* harmony export */ });\n/* harmony import */ var _transmute_cose__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @transmute/cose */ \"./node_modules/@transmute/cose/dist/index.js\");\n/* harmony import */ var _transmute_cose__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_transmute_cose__WEBPACK_IMPORTED_MODULE_0__);\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\n\n \n\nfunction buf2hex(buffer) { // buffer is an ArrayBuffer\n return [...new Uint8Array(buffer)]\n .map(x => x.toString(16).padStart(2, '0'))\n .join('');\n}\n\nconst getCredential = async (privateKey, byteSigner, messageType, messageJson) => {\n const message = 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(JSON.stringify(messageJson, null, 2))\n })\n return message;\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 const message = await (0,_transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1__.holder)({\n alg: privateKey.alg,\n type: messageType,\n }).issue({\n signer: byteSigner,\n presentation: messageJson,\n disclosures: disclosures\n })\n return message;\n}\n\nconst getBinaryMessage = async (privateKey, messageType, messageJson) =>{\n const signer = _transmute_cose__WEBPACK_IMPORTED_MODULE_0__.detached.signer({\n remote: _transmute_cose__WEBPACK_IMPORTED_MODULE_0__.crypto.signer({\n secretKeyJwk: privateKey\n })\n })\n const byteSigner = {\n sign: async (payload)=>{\n return signer.sign({\n protectedHeader: new Map([[1, -35]]),\n unprotectedHeader: new Map(),\n payload\n })\n }\n }\n switch(messageType){\n case 'application/vc+ld+json+cose': {\n return getCredential(privateKey, byteSigner, messageType, messageJson)\n }\n case 'application/vp+ld+json+cose': {\n return getPresentation(privateKey, byteSigner, messageType, messageJson)\n }\n default: {\n throw new Error('Unknown message type')\n }\n }\n}\n\nconst getCoseExample = async (privateKey, messageJson) => {\n const type = Array.isArray(messageJson.type) ? messageJson.type : [messageJson.type]\n const messageType = type.includes('VerifiableCredential') ? 'application/vc+ld+json+cose' : 'application/vp+ld+json+cose'\n const message = await getBinaryMessage(privateKey, messageType, messageJson)\n const messageHex = buf2hex(message)\n const messageDiag = await _transmute_cose__WEBPACK_IMPORTED_MODULE_0__.cbor.diagnose(message)\n return `\n// ${messageType.replace('+cose', '')}\n
\n${JSON.stringify(messageJson, null, 2)}\n
\n// ${messageType} (detached payload)\n
\n${messageHex}\n
\n// application/cbor-diagnostic\n
\n${messageDiag.trim()}\n
\n\n `.trim()\n}\n\n//# sourceURL=webpack://respec-vc-jose-cose/./src/exampleCose.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ getCoseExample: () => (/* binding */ getCoseExample)\n/* harmony export */ });\n/* harmony import */ var _transmute_cose__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @transmute/cose */ \"./node_modules/@transmute/cose/dist/index.js\");\n/* harmony import */ var _transmute_cose__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_transmute_cose__WEBPACK_IMPORTED_MODULE_0__);\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\n\n \n\nfunction buf2hex(buffer) { // buffer is an ArrayBuffer\n return [...new Uint8Array(buffer)]\n .map(x => x.toString(16).padStart(2, '0'))\n .join('');\n}\n\nconst getCredential = async (privateKey, byteSigner, messageType, messageJson) => {\n const message = 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(JSON.stringify(messageJson, null, 2))\n })\n return message;\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 const message = await (0,_transmute_verifiable_credentials__WEBPACK_IMPORTED_MODULE_1__.holder)({\n alg: privateKey.alg,\n type: messageType,\n }).issue({\n signer: byteSigner,\n presentation: messageJson,\n disclosures: disclosures\n })\n return message;\n}\n\nconst getBinaryMessage = async (privateKey, messageType, messageJson) =>{\n const signer = _transmute_cose__WEBPACK_IMPORTED_MODULE_0__.detached.signer({\n remote: _transmute_cose__WEBPACK_IMPORTED_MODULE_0__.crypto.signer({\n secretKeyJwk: privateKey\n })\n })\n const byteSigner = {\n sign: async (payload)=>{\n return signer.sign({\n protectedHeader: new Map([[1, -35]]),\n unprotectedHeader: new Map(),\n payload\n })\n }\n }\n switch(messageType){\n case 'application/vc+ld+json+cose': {\n return getCredential(privateKey, byteSigner, messageType, messageJson)\n }\n case 'application/vp+ld+json+cose': {\n return getPresentation(privateKey, byteSigner, messageType, messageJson)\n }\n default: {\n throw new Error('Unknown message type')\n }\n }\n}\n\nconst getCoseExample = async (privateKey, messageJson) => {\n const type = Array.isArray(messageJson.type) ? messageJson.type : [messageJson.type]\n const messageType = type.includes('VerifiableCredential') ? 'application/vc+ld+json+cose' : 'application/vp+ld+json+cose'\n const message = await getBinaryMessage(privateKey, messageType, messageJson)\n const messageHex = buf2hex(message)\n const messageDiag = await _transmute_cose__WEBPACK_IMPORTED_MODULE_0__.cbor.diagnose(message)\n return `\n// ${messageType.replace('+cose', '')}\n
\n${JSON.stringify(messageJson, null, 2)}\n
\n// application/cbor-diagnostic\n
\n${messageDiag.trim()}\n
\n// ${messageType} (detached payload)\n
\n${messageHex}\n
\n `.trim()\n}\n\n//# sourceURL=webpack://respec-vc-jose-cose/./src/exampleCose.js?"); /***/ }), @@ -5945,7 +5945,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\n\n\nconst getCredential = async (privateKey, byteSigner, messageType, messageJson) => {\n const message = 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 return message;\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 const message = 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 return message;\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: 'ES384' })\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// ${messageType.replace('+jwt', '')}\n
\n${JSON.stringify(messageJson, null, 2)}\n
\n// ${messageType}\n
\n${messageEncoded}\n
\n// Protected Header\n
\n${JSON.stringify(decodedHeader, null, 2)}\n
\n\n `.trim()\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\n\n\nconst getCredential = async (privateKey, byteSigner, messageType, messageJson) => {\n const message = 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 return message;\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 const message = 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 return message;\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// ${messageType.replace('+jwt', '')}\n
\n${JSON.stringify(messageJson, null, 2)}\n
\n// Protected Header\n
\n${JSON.stringify(decodedHeader, null, 2)}\n
\n// ${messageType}\n
\n${messageEncoded}\n
\n `.trim()\n}\n\n//# sourceURL=webpack://respec-vc-jose-cose/./src/exampleJwt.js?"); /***/ }), @@ -5960,6 +5960,17 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ }), +/***/ "./src/exampleSdJwt.js": +/*!*****************************!*\ + !*** ./src/exampleSdJwt.js ***! + \*****************************/ +/***/ ((__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// import moment from 'moment';\n \n\n\n\n\nconst generateIssuerClaims = (example)=> {\n const generatedClaimsYaml = yaml__WEBPACK_IMPORTED_MODULE_0__[\"default\"].stringify(example).replace(/id\\: /g, '!sd id: ').replace(/type\\:/g, '!sd type:')\n return generatedClaimsYaml\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 const edited3 = edited2.replace(/\\!sd id\\:/g, `id:`)\n return edited3\n}\n\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 getVerifiedHtml = (verified)=> {\nreturn `
\n
\n// disclosed protected header\n${JSON.stringify(verified.protectedHeader, null, 2).trim()}
\n
\n// disclosed protected claimset\n${JSON.stringify(verified.claimset, null, 2).trim()}
\n
\n`\n}\n\nconst getDisclosabilityHtml = (claims)=> {\n return `
\n${claims.trim().replace(/\\!sd/g, `!sd`)}\n  
`\n }\n\nconst getDisclosuresHtml = (disclosure)=> {\n return `
${disclosure.trim().replace(/False/g, `False`)}
`\n }\n\nconst getCredential = async (privateKey, byteSigner, messageType, messageJson) => {\n const message = 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 return message;\n}\n\nconst getPresentation = async (privateKey, byteSigner, messageType, messageJson) => {\n const credential = await getCredential(privateKey, byteSigner, 'application/vc+ld+json+sd-jwt', messageJson)\n // since examples are always enveloped, and truncated, we never actually process key binding or disclosures\n return credential\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 const decodedHeader = jose__WEBPACK_IMPORTED_MODULE_2__.decodeProtectedHeader(messageEncoded.split('~')[0])\n const issuerClaims = generateIssuerClaims(messageJson)\n const messageType2 = 'application/ld+yaml'\n return `\n// ${messageType2}\n
\n${issuerClaims}\n
\n// Protected Header\n
\n${JSON.stringify(decodedHeader, null, 2)}\n
\n// ${messageType}\n
\n${messageEncoded}\n
\n `.trim()\n}\n\n//# sourceURL=webpack://respec-vc-jose-cose/./src/exampleSdJwt.js?"); + +/***/ }), + /***/ "./src/getHtml.js": /*!************************!*\ !*** ./src/getHtml.js ***! @@ -5967,7 +5978,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 */ getHtml: () => (/* binding */ getHtml)\n/* harmony export */ });\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 getVerifiedHtml = (verified)=> {\nreturn `
\n
\n// disclosed protected header\n${JSON.stringify(verified.protectedHeader, null, 2).trim()}
\n
\n// disclosed protected claimset\n${JSON.stringify(verified.claimset, null, 2).trim()}
\n
\n`\n}\n\nconst getDisclosabilityHtml = (claims)=> {\n return `
\n${claims.trim().replace(/\\!sd/g, `!sd`)}\n  
`\n }\n\nconst getDisclosuresHtml = (disclosure)=> {\n return `
${disclosure.trim().replace(/False/g, `False`)}
`\n }\n\nconst getHtml = ({ index, coseExample, jwtExample })=>{\n // TODO: refactor\n // const tab1Content = getDisclosabilityHtml(claims);\n // const tab2Content = getSdHtml(vc);\n // const tab3Content = getDisclosuresHtml(disclosure);\n // const tab4Content = getSdHtml(vp);\n // const tab5Content = getVerifiedHtml(verified);\n\n // 3 tabs is the most that fits on 1 screen\n const tab1Content = coseExample;\n const tab2Content = jwtExample;\n const tab3Content = 'tab3Content';\n \n return `\n
\n \n \n \n \n
\n${tab1Content}\n
\n
\n${tab2Content}\n
\n
\n${tab3Content}\n
\n
`\n}\n\n//# sourceURL=webpack://respec-vc-jose-cose/./src/getHtml.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ getHtml: () => (/* binding */ getHtml)\n/* harmony export */ });\n\n\nconst getHtml = ({ index, coseExample, jwtExample, sdJwtExample })=>{\n const tab1Content = coseExample;\n const tab2Content = jwtExample;\n const tab3Content = sdJwtExample;\n return `\n
\n \n \n \n \n
\n${tab1Content}\n
\n
\n${tab2Content}\n
\n
\n${tab3Content}\n
\n
`\n}\n\n//# sourceURL=webpack://respec-vc-jose-cose/./src/getHtml.js?"); /***/ }), diff --git a/index.js b/index.js index 0a1f640..c42c476 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,9 @@ import {getHtml} from './src/getHtml'; import { getPrivateKey } from './src/exampleKey'; import { getCoseExample } from './src/exampleCose'; import { getJwtExample } from './src/exampleJwt'; +import { getSdJwtExample } from './src/exampleSdJwt'; + + async function processVcJoseCose() { // add styling for examples @@ -108,10 +111,11 @@ function addVcJoseStyles() { } export async function processExample(index, json) { - const privateKey = await getPrivateKey() - const coseExample = await getCoseExample(privateKey, json) - const jwtExample = await getJwtExample(privateKey, json) - const html = getHtml({index, coseExample, jwtExample}); + const privateKey = await getPrivateKey(); + const coseExample = await getCoseExample(privateKey, json); + const jwtExample = await getJwtExample(privateKey, json); + const sdJwtExample = await getSdJwtExample(privateKey, json); + const html = getHtml({index, coseExample, jwtExample, sdJwtExample}); return {html}; } diff --git a/src/exampleCose.js b/src/exampleCose.js index 58af127..b4493b8 100644 --- a/src/exampleCose.js +++ b/src/exampleCose.js @@ -79,14 +79,13 @@ export const getCoseExample = async (privateKey, messageJson) => {
 ${JSON.stringify(messageJson, null, 2)}
 
-// ${messageType} (detached payload) -
-${messageHex}
-
// application/cbor-diagnostic
 ${messageDiag.trim()}
 
- +// ${messageType} (detached payload) +
+${messageHex}
+
`.trim() } \ No newline at end of file diff --git a/src/exampleJwt.js b/src/exampleJwt.js index 0254b3f..637596a 100644 --- a/src/exampleJwt.js +++ b/src/exampleJwt.js @@ -44,7 +44,7 @@ const getBinaryMessage = async (privateKey, messageType, messageJson) =>{ const jws = await new jose.CompactSign( bytes ) - .setProtectedHeader({ kid: privateKey.kid, alg: 'ES384' }) + .setProtectedHeader({ kid: privateKey.kid, alg: privateKey.alg }) .sign(await key.importKeyLike({ type: 'application/jwk+json', content: new TextEncoder().encode(JSON.stringify(privateKey)) @@ -76,14 +76,13 @@ export const getJwtExample = async (privateKey, messageJson) => {
 ${JSON.stringify(messageJson, null, 2)}
 
-// ${messageType} -
-${messageEncoded}
-
// Protected Header
 ${JSON.stringify(decodedHeader, null, 2)}
 
- +// ${messageType} +
+${messageEncoded}
+
`.trim() } \ No newline at end of file diff --git a/src/exampleSdJwt.js b/src/exampleSdJwt.js new file mode 100644 index 0000000..54ad322 --- /dev/null +++ b/src/exampleSdJwt.js @@ -0,0 +1,126 @@ + + +import yaml from 'yaml' +// import moment from 'moment'; +import { issuer, holder, text, key } from "@transmute/verifiable-credentials"; + +import * as jose from 'jose' + + +export const generateIssuerClaims = (example)=> { + const generatedClaimsYaml = yaml.stringify(example).replace(/id\: /g, '!sd id: ').replace(/type\:/g, '!sd type:') + return generatedClaimsYaml +} + +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 + const edited3 = edited2.replace(/\!sd id\:/g, `id:`) + return edited3 +} + + + +const getSdHtml = (vc) =>{ + const [token, ...disclosure] = vc.split('~'); + const [header, payload, signature] = token.split('.'); + const disclosures = disclosure.map((d)=>{ + return `~${d}` + }).join('') + return ` +
${header}.${payload}.${signature}${disclosures}
` +} + +const getVerifiedHtml = (verified)=> { +return `
+
+// disclosed protected header
+${JSON.stringify(verified.protectedHeader, null, 2).trim()}
+
+// disclosed protected claimset
+${JSON.stringify(verified.claimset, null, 2).trim()}
+
+` +} + +const getDisclosabilityHtml = (claims)=> { + return `
+${claims.trim().replace(/\!sd/g, `!sd`)}
+  
` + } + +const getDisclosuresHtml = (disclosure)=> { + return `
${disclosure.trim().replace(/False/g, `False`)}
` + } + +const getCredential = async (privateKey, byteSigner, messageType, messageJson) => { + const message = await issuer({ + alg: privateKey.alg, + type: messageType, + signer: byteSigner + }).issue({ + claimset: new TextEncoder().encode(generateIssuerClaims(messageJson)) + }) + return message; +} + +const getPresentation = async (privateKey, byteSigner, messageType, messageJson) => { + const credential = await getCredential(privateKey, byteSigner, 'application/vc+ld+json+sd-jwt', messageJson) + // since examples are always enveloped, and truncated, we never actually process key binding or disclosures + return credential +} + +const getBinaryMessage = async (privateKey, messageType, messageJson) =>{ + const byteSigner = { + sign: async (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) + } + } + switch(messageType){ + case 'application/vc+ld+json+sd-jwt': { + return getCredential(privateKey, byteSigner, messageType, messageJson) + } + case 'application/vp+ld+json+sd-jwt': { + return getPresentation(privateKey, byteSigner, messageType, messageJson) + } + default: { + 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 decodedHeader = jose.decodeProtectedHeader(messageEncoded.split('~')[0]) + const issuerClaims = generateIssuerClaims(messageJson) + const messageType2 = 'application/ld+yaml' + return ` +// ${messageType2} +
+${issuerClaims}
+
+// Protected Header +
+${JSON.stringify(decodedHeader, null, 2)}
+
+// ${messageType} +
+${messageEncoded}
+
+ `.trim() +} \ No newline at end of file diff --git a/src/getHtml.js b/src/getHtml.js index 12d01c7..a21ce9b 100644 --- a/src/getHtml.js +++ b/src/getHtml.js @@ -1,54 +1,14 @@ -const getSdHtml = (vc) =>{ - const [token, ...disclosure] = vc.split('~'); - const [header, payload, signature] = token.split('.'); - const disclosures = disclosure.map((d)=>{ - return `~${d}` - }).join('') - return ` -
${header}.${payload}.${signature}${disclosures}
` -} - -const getVerifiedHtml = (verified)=> { -return `
-
-// disclosed protected header
-${JSON.stringify(verified.protectedHeader, null, 2).trim()}
-
-// disclosed protected claimset
-${JSON.stringify(verified.claimset, null, 2).trim()}
-
-` -} - -const getDisclosabilityHtml = (claims)=> { - return `
-${claims.trim().replace(/\!sd/g, `!sd`)}
-  
` - } - -const getDisclosuresHtml = (disclosure)=> { - return `
${disclosure.trim().replace(/False/g, `False`)}
` - } - -export const getHtml = ({ index, coseExample, jwtExample })=>{ - // TODO: refactor - // const tab1Content = getDisclosabilityHtml(claims); - // const tab2Content = getSdHtml(vc); - // const tab3Content = getDisclosuresHtml(disclosure); - // const tab4Content = getSdHtml(vp); - // const tab5Content = getVerifiedHtml(verified); - // 3 tabs is the most that fits on 1 screen +export const getHtml = ({ index, coseExample, jwtExample, sdJwtExample })=>{ const tab1Content = coseExample; const tab2Content = jwtExample; - const tab3Content = 'tab3Content'; - + const tab3Content = sdJwtExample; return `
- - + +