From 0c1337ad8eeebbe1109d032cbdfdd0430804d4b6 Mon Sep 17 00:00:00 2001 From: Neal Date: Tue, 6 Feb 2024 13:27:35 -0600 Subject: [PATCH] updates --- packages/credentials/src/jwt.ts | 104 +++---- .../credentials/src/verifiable-credential.ts | 4 +- .../tests/presentation-exchange.spec.ts | 7 +- .../tests/verifiable-credential.spec.ts | 279 +++++++----------- 4 files changed, 145 insertions(+), 249 deletions(-) diff --git a/packages/credentials/src/jwt.ts b/packages/credentials/src/jwt.ts index 2752c2341..46464a2d0 100644 --- a/packages/credentials/src/jwt.ts +++ b/packages/credentials/src/jwt.ts @@ -4,8 +4,6 @@ import type { Web5Crypto, CryptoAlgorithm, JwtHeaderParams, - JwkParamsEcPrivate, - JwkParamsOkpPrivate, JwkParamsEcPublic, JwkParamsOkpPublic, } from '@web5/crypto'; @@ -49,7 +47,7 @@ export type ParseJwtOptions = { * Parameters for signing a JWT. */ export type SignJwtOptions = { - signerDid: PortableDid | BearerDid + signerDid: BearerDid payload: JwtPayload } @@ -116,80 +114,48 @@ export class Jwt { * @returns The compact JWT as a string. */ static async sign(options: SignJwtOptions): Promise { - let { signerDid, payload } = options; + const { signerDid, payload } = options; - if (isPortableDid(signerDid)) { - signerDid = signerDid as PortableDid; - - const privateKeyJwk = signerDid.verificationMethods![0].privateKeyJwk! as JwkParamsEcPrivate | JwkParamsOkpPrivate; - - let vmId = signerDid.verificationMethods![0].id!; - if (vmId.charAt(0) === '#') { - vmId = `${signerDid.uri}${vmId}`; - } - - const header: JwtHeaderParams = { - typ : 'JWT', - alg : privateKeyJwk.alg!, - kid : vmId - }; - - const base64UrlEncodedHeader = Convert.object(header).toBase64Url(); - const base64UrlEncodedPayload = Convert.object(payload).toBase64Url(); - - const toSign = `${base64UrlEncodedHeader}.${base64UrlEncodedPayload}`; - const toSignBytes = Convert.string(toSign).toUint8Array(); - - const algorithmId = `${header.alg}:${privateKeyJwk['crv'] || ''}`; - if (!(algorithmId in Jwt.algorithms)) { - throw new Error(`Signing failed: ${algorithmId} not supported`); - } - - const { signer, options: signatureAlgorithm } = Jwt.algorithms[algorithmId]; - - const signatureBytes = await signer.sign({ key: privateKeyJwk, data: toSignBytes, algorithm: signatureAlgorithm! }); - const base64UrlEncodedSignature = Convert.uint8Array(signatureBytes).toBase64Url(); + let vmId = signerDid.didDocument.verificationMethod![0].id!; + if (vmId.charAt(0) === '#') { + vmId = `${signerDid.uri}${vmId}`; + } - return `${toSign}.${base64UrlEncodedSignature}`; + // 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 { - signerDid = signerDid as BearerDid; - - let vmId = signerDid.didDocument.verificationMethod![0].id!; - if (vmId.charAt(0) === '#') { - vmId = `${signerDid.uri}${vmId}`; - } - - // TODO: Is this correct? - 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!, - kid : vmId, - }; + throw new Error(`Signing failed: alg not supported`); + } - const base64UrlEncodedHeader = Convert.object(header).toBase64Url(); - const base64UrlEncodedPayload = Convert.object(payload).toBase64Url(); + const header: JwtHeaderParams = { + typ : 'JWT', + alg : alg!, + kid : vmId, + }; - const toSign = `${base64UrlEncodedHeader}.${base64UrlEncodedPayload}`; - const toSignBytes = Convert.string(toSign).toUint8Array(); + const base64UrlEncodedHeader = Convert.object(header).toBase64Url(); + const base64UrlEncodedPayload = Convert.object(payload).toBase64Url(); - const signer = await signerDid.getSigner(); + const toSign = `${base64UrlEncodedHeader}.${base64UrlEncodedPayload}`; + const toSignBytes = Convert.string(toSign).toUint8Array(); - const signatureBytes = await signer.sign({data: toSignBytes}); + const signer = await signerDid.getSigner(); - const base64UrlEncodedSignature = Convert.uint8Array(signatureBytes).toBase64Url(); + const signatureBytes = await signer.sign({data: toSignBytes}); - return `${toSign}.${base64UrlEncodedSignature}`; - } + const base64UrlEncodedSignature = Convert.uint8Array(signatureBytes).toBase64Url(); + return `${toSign}.${base64UrlEncodedSignature}`; } /** @@ -307,8 +273,4 @@ export class Jwt { } }; } -} - -function isPortableDid(did: PortableDid | BearerDid): did is PortableDid { - return (did as PortableDid).verificationMethods !== undefined; } \ No newline at end of file diff --git a/packages/credentials/src/verifiable-credential.ts b/packages/credentials/src/verifiable-credential.ts index 4f2d55d26..d96690792 100644 --- a/packages/credentials/src/verifiable-credential.ts +++ b/packages/credentials/src/verifiable-credential.ts @@ -1,4 +1,4 @@ -import type { PortableDid, BearerDid } from '@web5/dids'; +import type { BearerDid } from '@web5/dids'; import type { ICredential, ICredentialSubject} from '@sphereon/ssi-types'; import { utils as cryptoUtils } from '@web5/crypto'; @@ -41,7 +41,7 @@ export type VerifiableCredentialCreateOptions = { * @param did - The issuer DID of the credential, represented as a PortableDid. */ export type VerifiableCredentialSignOptions = { - did: PortableDid | BearerDid; + did: BearerDid; }; type CredentialSubject = ICredentialSubject; diff --git a/packages/credentials/tests/presentation-exchange.spec.ts b/packages/credentials/tests/presentation-exchange.spec.ts index 66c87f8fa..997888a41 100644 --- a/packages/credentials/tests/presentation-exchange.spec.ts +++ b/packages/credentials/tests/presentation-exchange.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { DidKey, PortableDid } from '@web5/dids'; +import { BearerDid, DidKey } from '@web5/dids'; import type { Validated, PresentationDefinitionV2 } from '../src/presentation-exchange.js'; @@ -22,14 +22,13 @@ class OtherCredential { describe('PresentationExchange', () => { describe('Full Presentation Exchange', () => { - let issuerDid: PortableDid; + let issuerDid: BearerDid; let btcCredentialJwt: string; let presentationDefinition: PresentationDefinitionV2; let groupPresentationDefinition: PresentationDefinitionV2; before(async () => { - const did = await DidKey.create(); - issuerDid = await DidKey.toKeys({ did }); + issuerDid = await DidKey.create(); const vc = await VerifiableCredential.create({ type : 'StreetCred', diff --git a/packages/credentials/tests/verifiable-credential.spec.ts b/packages/credentials/tests/verifiable-credential.spec.ts index c9f58080e..4b7c5608b 100644 --- a/packages/credentials/tests/verifiable-credential.spec.ts +++ b/packages/credentials/tests/verifiable-credential.spec.ts @@ -1,16 +1,16 @@ -import type { PortableDid } from '@web5/dids'; +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, Did } from '@web5/dids'; import { Jwt } from '../src/jwt.js'; import { VerifiableCredential } from '../src/verifiable-credential.js'; import CredentialsVerifyTestVector from '../../../web5-spec/test-vectors/credentials/verify.json' assert { type: 'json' }; describe('Verifiable Credential Tests', async() => { - const did = await DidKey.create(); - let issuerDid: PortableDid; + // const did = await DidKey.create(); + let issuerDid: BearerDid; class StreetCredibility { constructor( @@ -20,7 +20,7 @@ describe('Verifiable Credential Tests', async() => { } beforeEach(async () => { - issuerDid = await DidKey.toKeys({did}); + issuerDid = await DidKey.create(); }); describe('Verifiable Credential (VC)', () => { @@ -42,8 +42,7 @@ describe('Verifiable Credential Tests', async() => { }); it('create and sign vc with did:key', async () => { - const bearerDid = await DidKey.create(); - const did = await DidKey.toKeys({ did: bearerDid }); + const did = await DidKey.create(); const vc = await VerifiableCredential.create({ type : 'TBDeveloperCredential', @@ -67,35 +66,8 @@ describe('Verifiable Credential Tests', async() => { } }); - it('create and sign vc with bearer did:key', async () => { - const bearerDid = await DidKey.create(); - - const vc = await VerifiableCredential.create({ - type : 'TBDeveloperCredential', - subject : bearerDid.uri!, - issuer : bearerDid.uri!, - data : { - username: 'nitro' - } - }); - - const vcJwt = await vc.sign({ did }); - console.log(vcJwt) - - 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 bearerDid = await DidKey.create(); - const did = await DidKey.toKeys({ did: bearerDid }); + const did = await DidIon.create(); const vc = await VerifiableCredential.create({ type : 'TBDeveloperCredential', @@ -187,8 +159,7 @@ describe('Verifiable Credential Tests', async() => { }); it('signing with secp256k1 key works', async () => { - const bearerDid = await DidKey.create({ options: { algorithm: 'secp256k1'} }); - const did = await DidKey.toKeys({ did: bearerDid }); + const did = await DidKey.create({ options: { algorithm: 'secp256k1'} }); const vc = await VerifiableCredential.create({ type : 'StreetCred', @@ -212,8 +183,8 @@ describe('Verifiable Credential Tests', async() => { }); it('parseJwt checks if missing vc property', async () => { - const bearerDid = await DidKey.create(); - const did = await DidKey.toKeys({ did: bearerDid }); + const did = await DidKey.create(); + // const did = await DidKey.toKeys({ did: bearerDid }); const jwt = await Jwt.sign({ signerDid : did, @@ -284,8 +255,7 @@ describe('Verifiable Credential Tests', async() => { }); it('verify throws exception if vc property does not exist', async () => { - const bearerDid = await DidKey.create(); - const did = await DidKey.toKeys({ did: bearerDid }); + const did = await DidKey.create(); const jwt = await Jwt.sign({ payload : { jti: 'hi' }, @@ -300,8 +270,7 @@ describe('Verifiable Credential Tests', async() => { }); it('verify throws exception if vc property is invalid', async () => { - const bearerDid = await DidKey.create(); - const did = await DidKey.toKeys({ did: bearerDid }); + const did = await DidKey.create(); const jwt = await Jwt.sign({ payload : { vc: 'hi' }, @@ -316,135 +285,101 @@ describe('Verifiable Credential Tests', async() => { } }); - // it('verify does not throw an exception with vaild vc signed by did:dht', async () => { - // const mockDocument: PortableDid = { - // keySet: { - // verificationMethodKeys: [ - // { - // privateKeyJwk: { - // d : '_8gihSI-m8aOCCM6jHg33d8kxdImPBN4C5_bZIu10XU', - // alg : 'EdDSA', - // crv : 'Ed25519', - // kty : 'OKP', - // ext : 'true', - // key_ops : [ - // 'sign' - // ], - // x : 'Qm88q6jAN9tfnrLt5V2zAiZs7wD_jnewHp7HIvM3dGo', - // kid : '0' - // }, - // publicKeyJwk: { - // alg : 'EdDSA', - // crv : 'Ed25519', - // kty : 'OKP', - // ext : 'true', - // key_ops : [ - // 'verify' - // ], - // x : 'Qm88q6jAN9tfnrLt5V2zAiZs7wD_jnewHp7HIvM3dGo', - // kid : '0' - // }, - // relationships: [ - // 'authentication', - // 'assertionMethod', - // 'capabilityInvocation', - // 'capabilityDelegation' - // ] - // } - // ] - - // }, - // did : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', - // document : { - // id : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', - // verificationMethod : [ - // { - // id : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy#0', - // type : 'JsonWebKey2020', - // controller : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', - // publicKeyJwk : { - // crv : 'Ed25519', - // kty : 'OKP', - // alg : 'EdDSA', - // kid : '0', - // x : 'Qm88q6jAN9tfnrLt5V2zAiZs7wD_jnewHp7HIvM3dGo' - // } - // } - // ], - // authentication: [ - // '#0' - // ], - // assertionMethod: [ - // '#0' - // ], - // capabilityInvocation: [ - // '#0' - // ], - // capabilityDelegation: [ - // '#0' - // ] - // } - // }; - // const didDhtCreateStub = sinon.stub(DidDhtMethod, 'create').resolves(mockDocument); - - // const alice = await DidDhtMethod.create({ publish: true }); - - // const vc = await VerifiableCredential.create({ - // type : 'StreetCred', - // issuer : alice.did, - // subject : alice.did, - // data : new StreetCredibility('high', true), - // }); - - // const dhtDidResolutionSpy = sinon.stub(DidDhtMethod, 'resolve').resolves({ - // '@context' : 'https://w3id.org/did-resolution/v1', - // didDocument : { - // id : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', - // verificationMethod : [ - // { - // id : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy#0', - // type : 'JsonWebKey2020', - // controller : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', - // publicKeyJwk : { - // crv : 'Ed25519', - // kty : 'OKP', - // alg : 'EdDSA', - // kid : '0', - // x : 'Qm88q6jAN9tfnrLt5V2zAiZs7wD_jnewHp7HIvM3dGo' - // } - // } - // ], - // authentication: [ - // '#0' - // ], - // assertionMethod: [ - // '#0' - // ], - // capabilityInvocation: [ - // '#0' - // ], - // capabilityDelegation: [ - // '#0' - // ] - // }, - // didDocumentMetadata : {}, - // didResolutionMetadata : { - // did: { - // didString : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', - // methodSpecificId : 'ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', - // method : 'dht' - // } - // } - // }); - - // const vcJwt = await vc.sign({did: alice}); - - // await VerifiableCredential.verify({ vcJwt }); - - // expect(didDhtCreateStub.calledOnce).to.be.true; - // expect(dhtDidResolutionSpy.calledOnce).to.be.true; - // sinon.restore(); - // }); + 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', + }, + purposes: [ + 'authentication', + 'assertionMethod', + 'capabilityDelegation', + 'capabilityInvocation', + ], + }, + ], + }; + + const bearerDid = await DidDht.fromKeys(portabldDid); + + const didDhtCreateStub = sinon.stub(DidDht, 'create').resolves(bearerDid); + + const alice = await DidDht.create({options: { publish: true }}); + + const vc = await VerifiableCredential.create({ + type : 'StreetCred', + issuer : alice.uri, + subject : alice.uri, + data : new StreetCredibility('high', true), + }); + + const dhtDidResolutionSpy = sinon.stub(DidDht, 'resolve').resolves({ + '@context' : 'https://w3id.org/did-resolution/v1', + didDocument : { + 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', + } + } + ], + authentication: [ + '#0' + ], + assertionMethod: [ + '#0' + ], + capabilityInvocation: [ + '#0' + ], + capabilityDelegation: [ + '#0' + ] + }, + didDocumentMetadata : {}, + didResolutionMetadata : { + did: { + didString : 'did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo', + methodSpecificId : 'ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo', + method : 'dht' + } + } + }); + + const vcJwt = await vc.sign({did: alice}); + + await VerifiableCredential.verify({ vcJwt }); + + expect(didDhtCreateStub.calledOnce).to.be.true; + expect(dhtDidResolutionSpy.calledOnce).to.be.true; + sinon.restore(); + }); }); describe('Web5TestVectorsCredentials', () => {