From 894f0b9b1b33dbfc2e016e9112e3cdf6952a5ce1 Mon Sep 17 00:00:00 2001 From: Neal Date: Mon, 12 Feb 2024 12:54:26 -0600 Subject: [PATCH] updates to latest did and crypto package --- package-lock.json | 63 +--------- packages/credentials/package.json | 6 +- packages/credentials/src/jwt.ts | 57 +++------ packages/credentials/tests/jwt.spec.ts | 49 +++++--- .../tests/verifiable-credential.spec.ts | 113 +++++++++++++----- 5 files changed, 140 insertions(+), 148 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4c145ca12..3c4d6eb52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15296,9 +15296,9 @@ "license": "Apache-2.0", "dependencies": { "@sphereon/pex": "2.1.0", - "@web5/common": "0.2.2", - "@web5/crypto": "0.2.4", - "@web5/dids": "0.3.0" + "@web5/common": "0.2.3", + "@web5/crypto": "0.4.0", + "@web5/dids": "0.4.0" }, "devDependencies": { "@playwright/test": "1.40.1", @@ -15326,36 +15326,6 @@ "node": ">=18.0.0" } }, - "packages/credentials/node_modules/@noble/ciphers": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.4.0.tgz", - "integrity": "sha512-xaUaUUDWbHIFSxaQ/pIe+33VG2mfJp6N/KxKLmZr5biWdNznCAmfu24QRhX10BbVAuqOahAoyp0S4M9md6GPDw==", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "packages/credentials/node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", - "dependencies": { - "@noble/hashes": "1.3.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "packages/credentials/node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "packages/credentials/node_modules/@typescript-eslint/parser": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.0.tgz", @@ -15424,33 +15394,6 @@ } } }, - "packages/credentials/node_modules/@web5/common": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@web5/common/-/common-0.2.2.tgz", - "integrity": "sha512-dRn6SmALExeTLMTK/W5ozGarfaddK+Lraf5OjuIGLAaLfcX1RWx3oDMoY5Hr9LjfxHJC8mGXB8DnKflbeYJRgA==", - "dependencies": { - "level": "8.0.0", - "multiformats": "11.0.2", - "readable-stream": "4.4.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/credentials/node_modules/@web5/crypto": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@web5/crypto/-/crypto-0.2.4.tgz", - "integrity": "sha512-heRUuV10mZ04dWp1C2mNF/EEPw8nnRe+yAXvmclJ+4XUHL6+mY7j+hjYOTKUAQzd4ouvbHrpJM0uYcUntA3AeA==", - "dependencies": { - "@noble/ciphers": "0.4.0", - "@noble/curves": "1.2.0", - "@noble/hashes": "1.3.2", - "@web5/common": "0.2.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "packages/credentials/node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", diff --git a/packages/credentials/package.json b/packages/credentials/package.json index f2e7927ad..668ec55cd 100644 --- a/packages/credentials/package.json +++ b/packages/credentials/package.json @@ -75,9 +75,9 @@ }, "dependencies": { "@sphereon/pex": "2.1.0", - "@web5/common": "0.2.2", - "@web5/crypto": "0.2.4", - "@web5/dids": "0.3.0" + "@web5/common": "0.2.3", + "@web5/crypto": "0.4.0", + "@web5/dids": "0.4.0" }, "devDependencies": { "@playwright/test": "1.40.1", diff --git a/packages/credentials/src/jwt.ts b/packages/credentials/src/jwt.ts index 996a7559d..b67538f30 100644 --- a/packages/credentials/src/jwt.ts +++ b/packages/credentials/src/jwt.ts @@ -1,11 +1,10 @@ -import type { BearerDid } from '@web5/dids'; +import { BearerDid } from '@web5/dids'; import type { JwtPayload, - Web5Crypto, - CryptoAlgorithm, JwtHeaderParams, JwkParamsEcPublic, JwkParamsOkpPublic, + CryptoAlgorithm, } from '@web5/crypto'; import { Convert } from '@web5/common'; @@ -62,25 +61,23 @@ export type VerifyJwtOptions = { * Represents a signer with a specific cryptographic algorithm and options. * @template T - The type of cryptographic options. */ -type Signer = { - signer: CryptoAlgorithm, +type Signer = { + signer: EcdsaAlgorithm | EdDsaAlgorithm, options?: T | undefined alg: string crv: string } -const secp256k1Signer: Signer = { - signer : new EcdsaAlgorithm(), - options : { name: 'ES256K'}, - alg : 'ES256K', - crv : 'secp256k1' +const secp256k1Signer: Signer = { + signer : new EcdsaAlgorithm(), + alg : 'ES256K', + crv : 'secp256k1' }; -const ed25519Signer: Signer = { - signer : new EdDsaAlgorithm(), - options : { name: 'EdDSA' }, - alg : 'EdDSA', - crv : 'Ed25519' +const ed25519Signer: Signer = { + signer : new EdDsaAlgorithm(), + alg : 'EdDSA', + crv : 'Ed25519' }; /** @@ -90,7 +87,7 @@ const ed25519Signer: Signer = { */ export class Jwt { /** supported cryptographic algorithms. keys are `${alg}:${crv}`. */ - static algorithms: { [alg: string]: Signer } = { + static algorithms: { [alg: string]: Signer } = { 'ES256K:' : secp256k1Signer, 'ES256K:secp256k1' : secp256k1Signer, ':secp256k1' : secp256k1Signer, @@ -115,31 +112,16 @@ export class Jwt { */ static async sign(options: SignJwtOptions): Promise { const { signerDid, payload } = options; + const signer = await signerDid.getSigner(); - let vmId = signerDid.didDocument.verificationMethod![0].id!; + let vmId = signer.keyId; if (vmId.charAt(0) === '#') { vmId = `${signerDid.uri}${vmId}`; } - // TODO: Change once signer has a method to get the alg and kid - // const header = { - // typ : 'JWT', - // alg: signer.alg, - // kid: signer.kid - // } - - let alg; - if(signerDid.didDocument.verificationMethod![0].publicKeyJwk?.crv === 'Ed25519') { - alg = 'EdDSA'; - } else if(signerDid.didDocument.verificationMethod![0].publicKeyJwk?.crv === 'secp256k1'){ - alg = 'ES256K'; - } else { - throw new Error(`Signing failed: alg not supported`); - } - const header: JwtHeaderParams = { typ : 'JWT', - alg : alg!, + alg : signer.algorithm, kid : vmId, }; @@ -149,8 +131,6 @@ export class Jwt { const toSign = `${base64UrlEncodedHeader}.${base64UrlEncodedPayload}`; const toSignBytes = Convert.string(toSign).toUint8Array(); - const signer = await signerDid.getSigner(); - const signatureBytes = await signer.sign({data: toSignBytes}); const base64UrlEncodedSignature = Convert.uint8Array(signatureBytes).toBase64Url(); @@ -203,13 +183,12 @@ export class Jwt { throw new Error(`Verification failed: ${algorithmId} not supported`); } - const { signer, options: signatureAlgorithm } = Jwt.algorithms[algorithmId]; + const { signer } = Jwt.algorithms[algorithmId]; const isSignatureValid = await signer.verify({ - algorithm : signatureAlgorithm!, key : publicKeyJwk, + signature : signatureBytes, data : signedDataBytes, - signature : signatureBytes }); if (!isSignatureValid) { diff --git a/packages/credentials/tests/jwt.spec.ts b/packages/credentials/tests/jwt.spec.ts index ca3990734..c1e26f9a6 100644 --- a/packages/credentials/tests/jwt.spec.ts +++ b/packages/credentials/tests/jwt.spec.ts @@ -71,7 +71,7 @@ describe('Jwt', () => { describe('verify()', () => { it('throws error if JWT is expired', async () => { const did = await DidKey.create({ options: { algorithm: 'secp256k1'} }); - const header: JwtHeaderParams = { typ: 'JWT', alg: 'ES256K', kid: did.didDocument.verificationMethod![0].id }; + const header: JwtHeaderParams = { typ: 'JWT', alg: 'ES256K', kid: did.document.verificationMethod![0].id }; const base64UrlEncodedHeader = Convert.object(header).toBase64Url(); const payload: JwtPayload = { exp: Math.floor(Date.now() / 1000 - 1) }; @@ -102,7 +102,7 @@ describe('Jwt', () => { it('throws error if alg is not supported', async () => { const did = await DidKey.create({ options: { algorithm: 'secp256k1'} }); - const header: JwtHeaderParams = { typ: 'JWT', alg: 'RS256', kid: did.didDocument.verificationMethod![0].id }; + const header: JwtHeaderParams = { typ: 'JWT', alg: 'RS256', kid: did.document.verificationMethod![0].id }; const base64UrlEncodedHeader = Convert.object(header).toBase64Url(); const payload: JwtPayload = { iat: Math.floor(Date.now() / 1000) }; @@ -117,27 +117,42 @@ describe('Jwt', () => { }); it('returns signer DID if verification succeeds', async () => { - const portabldDid: PortableDid = { - uri : 'did:key:z6MkkGkByH7rSY3uxDEPTk1CZzPG5hvf564ABFLQzCFwyYNN', - verificationMethods : [{ - publicKeyJwk: { - kty : 'OKP', - crv : 'Ed25519', - x : 'VnSOQ-n7kRcYd0XGW2MNCv7DDY5py5XhNcjM7-Y1HVM' - }, - privateKeyJwk: { + const portableDid: PortableDid = { + uri : 'did:key:z6MkkGkByH7rSY3uxDEPTk1CZzPG5hvf564ABFLQzCFwyYNN', + document : { + '@context' : 'https://www.w3.org/ns/did/v1', + id : 'did:key:z6MkkGkByH7rSY3uxDEPTk1CZzPG5hvf564ABFLQzCFwyYNN', + verificationMethod : [ + { + id : 'did:key:z6MkkGkByH7rSY3uxDEPTk1CZzPG5hvf564ABFLQzCFwyYNN#z6MkkGkByH7rSY3uxDEPTk1CZzPG5hvf564ABFLQzCFwyYNN', // You may need to adjust the ID based on your requirements + type : 'JsonWebKey2020', // Adjust the type according to your needs, assuming JsonWebKey2020 + controller : 'did:key:z6MkkGkByH7rSY3uxDEPTk1CZzPG5hvf564ABFLQzCFwyYNN', + publicKeyJwk : { + kty : 'OKP', + crv : 'Ed25519', + x : 'VnSOQ-n7kRcYd0XGW2MNCv7DDY5py5XhNcjM7-Y1HVM', + }, + }, + ], + authentication: [ + 'did:key:z6MkkGkByH7rSY3uxDEPTk1CZzPG5hvf564ABFLQzCFwyYNN#z6MkkGkByH7rSY3uxDEPTk1CZzPG5hvf564ABFLQzCFwyYNN', + ], + // Add other fields like assertionMethod, capabilityInvocation, etc., as needed + }, + metadata : {}, // Populate according to DidMetadata interface + privateKeys : [ + { kty : 'OKP', crv : 'Ed25519', x : 'VnSOQ-n7kRcYd0XGW2MNCv7DDY5py5XhNcjM7-Y1HVM', - d : 'iTD5DIOKZNkwgzsND-I8CLIXmgTxfQ1HUzl9fpMktAo' + d : 'iTD5DIOKZNkwgzsND-I8CLIXmgTxfQ1HUzl9fpMktAo', }, - purposes: ['authentication'] - }] + ], }; - const did = await DidKey.fromKeys(portabldDid); + const did = await DidKey.import({ portableDid }); - const header: JwtHeaderParams = { typ: 'JWT', alg: 'EdDSA', kid: did.didDocument.verificationMethod![0].id }; + const header: JwtHeaderParams = { typ: 'JWT', alg: 'EdDSA', kid: did.document.verificationMethod![0].id }; const base64UrlEncodedHeader = Convert.object(header).toBase64Url(); const payload: JwtPayload = { iat: Math.floor(Date.now() / 1000) }; @@ -146,7 +161,7 @@ describe('Jwt', () => { const toSign = `${base64UrlEncodedHeader}.${base64UrlEncodedPayload}`; const toSignBytes = Convert.string(toSign).toUint8Array(); - const privateKeyJwk = portabldDid.verificationMethods![0].privateKeyJwk; + const privateKeyJwk = portableDid.privateKeys![0]; const signatureBytes = await Ed25519.sign({ key: privateKeyJwk as PrivateKeyJwk, data: toSignBytes }); const base64UrlEncodedSignature = Convert.uint8Array(signatureBytes).toBase64Url(); diff --git a/packages/credentials/tests/verifiable-credential.spec.ts b/packages/credentials/tests/verifiable-credential.spec.ts index 7431a6fcb..00beb160b 100644 --- a/packages/credentials/tests/verifiable-credential.spec.ts +++ b/packages/credentials/tests/verifiable-credential.spec.ts @@ -2,7 +2,7 @@ import type { BearerDid, PortableDid } from '@web5/dids'; import sinon from 'sinon'; import { expect } from 'chai'; -import { DidDht, DidKey, DidIon } from '@web5/dids'; +import { DidDht, DidKey, DidIon, DidJwk } from '@web5/dids'; import { Jwt } from '../src/jwt.js'; import { VerifiableCredential } from '../src/verifiable-credential.js'; @@ -65,6 +65,31 @@ describe('Verifiable Credential Tests', async() => { } }); + it('create and sign vc with did:jwk', async () => { + const did = await DidJwk.create(); + + const vc = await VerifiableCredential.create({ + type : 'TBDeveloperCredential', + subject : did.uri, + issuer : did.uri, + data : { + username: 'nitro' + } + }); + + const vcJwt = await vc.sign({ did }); + + await VerifiableCredential.verify({ vcJwt }); + + for( const currentVc of [vc, VerifiableCredential.parseJwt({ vcJwt })]){ + expect(currentVc.issuer).to.equal(did.uri); + expect(currentVc.subject).to.equal(did.uri); + expect(currentVc.type).to.equal('TBDeveloperCredential'); + expect(currentVc.vcDataModel.issuanceDate).to.not.be.undefined; + expect(currentVc.vcDataModel.credentialSubject).to.deep.equal({ id: did.uri, username: 'nitro'}); + } + }); + it('create and sign vc with did:ion', async () => { const did = await DidIon.create(); @@ -90,6 +115,31 @@ describe('Verifiable Credential Tests', async() => { } }); + it('create and sign vc with did:dht', async () => { + const did = await DidDht.create(); + + const vc = await VerifiableCredential.create({ + type : 'TBDeveloperCredential', + subject : did.uri, + issuer : did.uri, + data : { + username: 'nitro' + } + }); + + const vcJwt = await vc.sign({ did }); + + await VerifiableCredential.verify({ vcJwt }); + + for (const currentVc of [vc, VerifiableCredential.parseJwt({ vcJwt })]){ + expect(currentVc.issuer).to.equal(did.uri); + expect(currentVc.subject).to.equal(did.uri); + expect(currentVc.type).to.equal('TBDeveloperCredential'); + expect(currentVc.vcDataModel.issuanceDate).to.not.be.undefined; + expect(currentVc.vcDataModel.credentialSubject).to.deep.equal({ id: did.uri, username: 'nitro'}); + } + }); + it('should throw an error if data is not parseable into a JSON object', async () => { const issuerDid = 'did:example:issuer'; const subjectDid = 'did:example:subject'; @@ -284,39 +334,44 @@ describe('Verifiable Credential Tests', async() => { }); it('verify does not throw an exception with vaild vc signed by did:dht', async () => { - const portabldDid: PortableDid = { - uri : 'did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo', - verificationMethods : [ - { - id : 'did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo#0', - type : 'JsonWebKey', - controller : 'did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo', - publicKeyJwk : { - crv : 'Ed25519', - kty : 'OKP', - x : 'VYKm2SCIV9Vz3BRy-v5R9GHz3EOJCPvZ1_gP1e3XiB0', - kid : 'cyvOypa6k-4ffsRWcza37s5XVOh1kO9ICUeo1ZxHVM8', - alg : 'EdDSA', - }, - privateKeyJwk: { - crv : 'Ed25519', - d : 'hdSIwbQwVD-fNOVEgt-k3mMl44Ip1iPi58Ex6VDGxqY', - kty : 'OKP', - x : 'VYKm2SCIV9Vz3BRy-v5R9GHz3EOJCPvZ1_gP1e3XiB0', - kid : 'cyvOypa6k-4ffsRWcza37s5XVOh1kO9ICUeo1ZxHVM8', - alg : 'EdDSA', + const portableDid: PortableDid = { + uri : 'did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo', + document : { + '@context' : 'https://www.w3.org/ns/did/v1', + id : 'did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo', + verificationMethod : [ + { + id : 'did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo#0', + type : 'JsonWebKey', + controller : 'did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo', + publicKeyJwk : { + crv : 'Ed25519', + kty : 'OKP', + x : 'VYKm2SCIV9Vz3BRy-v5R9GHz3EOJCPvZ1_gP1e3XiB0', + kid : 'cyvOypa6k-4ffsRWcza37s5XVOh1kO9ICUeo1ZxHVM8', + alg : 'EdDSA', + }, }, - purposes: [ - 'authentication', - 'assertionMethod', - 'capabilityDelegation', - 'capabilityInvocation', - ], + ], + authentication : ['did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo#0'], + assertionMethod : ['did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo#0'], + capabilityDelegation : ['did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo#0'], + capabilityInvocation : ['did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo#0'], + }, + metadata : {}, + privateKeys : [ + { + crv : 'Ed25519', + d : 'hdSIwbQwVD-fNOVEgt-k3mMl44Ip1iPi58Ex6VDGxqY', + kty : 'OKP', + x : 'VYKm2SCIV9Vz3BRy-v5R9GHz3EOJCPvZ1_gP1e3XiB0', + kid : 'cyvOypa6k-4ffsRWcza37s5XVOh1kO9ICUeo1ZxHVM8', + alg : 'EdDSA', }, ], }; - const bearerDid = await DidDht.fromKeys(portabldDid); + const bearerDid = await DidDht.import({ portableDid }); const didDhtCreateStub = sinon.stub(DidDht, 'create').resolves(bearerDid);