Skip to content

Commit

Permalink
fix merge
Browse files Browse the repository at this point in the history
  • Loading branch information
nitro-neal committed Feb 15, 2024
2 parents 4757e0d + dfb0187 commit 09ec382
Show file tree
Hide file tree
Showing 11 changed files with 674 additions and 346 deletions.
139 changes: 3 additions & 136 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/credentials/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ Sign a `VerifiableCredential` with a DID:
First create a `Did` object as follows:

```javascript
import { DidKeyMethod } from '@web5/dids';
const issuer = await DidKeyMethod.create();
import { DidKey } from '@web5/dids';
const issuer: BearerDid = await DidKey.create();
```

Then sign the VC using the `did` object
Expand Down
8 changes: 4 additions & 4 deletions packages/credentials/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@web5/credentials",
"version": "0.4.1",
"version": "0.4.2",
"description": "Verifiable Credentials",
"type": "module",
"main": "./dist/cjs/index.js",
Expand Down Expand Up @@ -75,9 +75,9 @@
},
"dependencies": {
"@sphereon/pex": "2.1.0",
"@web5/common": "0.2.2",
"@web5/crypto": "0.2.4",
"@web5/dids": "0.2.4"
"@web5/common": "0.2.3",
"@web5/crypto": "0.4.0",
"@web5/dids": "0.4.0"
},
"devDependencies": {
"@playwright/test": "1.40.1",
Expand Down
87 changes: 21 additions & 66 deletions packages/credentials/src/jwt.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import type { PortableDid } from '@web5/dids';
import { BearerDid } from '@web5/dids';
import type {
JwtPayload,
Web5Crypto,
CryptoAlgorithm,
JwtHeaderParams,
JwkParamsEcPrivate,
JwkParamsOkpPrivate,
JwkParamsEcPublic,
JwkParamsOkpPublic,
} from '@web5/crypto';

import { Convert } from '@web5/common';
import { EdDsaAlgorithm, EcdsaAlgorithm } from '@web5/crypto';
import { DidDhtMethod, DidIonMethod, DidKeyMethod, DidResolver, utils as didUtils } from '@web5/dids';
import { LocalKeyManager as CryptoApi } from '@web5/crypto';
import { DidDht, DidIon, DidKey, DidJwk, DidWeb, DidResolver, utils as didUtils } from '@web5/dids';

const crypto = new CryptoApi();

/**
* Result of parsing a JWT.
Expand Down Expand Up @@ -49,7 +47,7 @@ export type ParseJwtOptions = {
* Parameters for signing a JWT.
*/
export type SignJwtOptions = {
signerDid: PortableDid
signerDid: BearerDid
payload: JwtPayload
}

Expand All @@ -60,49 +58,16 @@ export type VerifyJwtOptions = {
jwt: string
}

/**
* Represents a signer with a specific cryptographic algorithm and options.
* @template T - The type of cryptographic options.
*/
type Signer<T extends Web5Crypto.Algorithm> = {
signer: CryptoAlgorithm,
options?: T | undefined
alg: string
crv: string
}

const secp256k1Signer: Signer<Web5Crypto.EcdsaOptions> = {
signer : new EcdsaAlgorithm(),
options : { name: 'ES256K'},
alg : 'ES256K',
crv : 'secp256k1'
};

const ed25519Signer: Signer<Web5Crypto.EdDsaOptions> = {
signer : new EdDsaAlgorithm(),
options : { name: 'EdDSA' },
alg : 'EdDSA',
crv : 'Ed25519'
};

/**
* Class for handling Compact JSON Web Tokens (JWTs).
* This class provides methods to create, verify, and decode JWTs using various cryptographic algorithms.
* More information on JWTs can be found [here](https://datatracker.ietf.org/doc/html/rfc7519)
*/
export class Jwt {
/** supported cryptographic algorithms. keys are `${alg}:${crv}`. */
static algorithms: { [alg: string]: Signer<Web5Crypto.EcdsaOptions | Web5Crypto.EdDsaOptions> } = {
'ES256K:' : secp256k1Signer,
'ES256K:secp256k1' : secp256k1Signer,
':secp256k1' : secp256k1Signer,
'EdDSA:Ed25519' : ed25519Signer
};

/**
* DID Resolver instance for resolving decentralized identifiers.
*/
static didResolver: DidResolver = new DidResolver({ didResolvers: [DidIonMethod, DidKeyMethod, DidDhtMethod] });
static didResolver: DidResolver = new DidResolver({ didResolvers: [DidDht, DidIon, DidKey, DidJwk, DidWeb] });

/**
* Creates a signed JWT.
Expand All @@ -117,17 +82,17 @@ export class Jwt {
*/
static async sign(options: SignJwtOptions): Promise<string> {
const { signerDid, payload } = options;
const privateKeyJwk = signerDid.keySet.verificationMethodKeys![0].privateKeyJwk! as JwkParamsEcPrivate | JwkParamsOkpPrivate;
const signer = await signerDid.getSigner();

let vmId = signerDid.document.verificationMethod![0].id;
let vmId = signer.keyId;
if (vmId.charAt(0) === '#') {
vmId = `${signerDid.did}${vmId}`;
vmId = `${signerDid.uri}${vmId}`;
}

const header: JwtHeaderParams = {
typ : 'JWT',
alg : privateKeyJwk.alg!,
kid : vmId
alg : signer.algorithm,
kid : vmId,
};

const base64UrlEncodedHeader = Convert.object(header).toBase64Url();
Expand All @@ -136,14 +101,8 @@ export class Jwt {
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({ data: toSignBytes });

const signatureBytes = await signer.sign({ key: privateKeyJwk, data: toSignBytes, algorithm: signatureAlgorithm! });
const base64UrlEncodedSignature = Convert.uint8Array(signatureBytes).toBase64Url();

return `${toSign}.${base64UrlEncodedSignature}`;
Expand All @@ -168,13 +127,13 @@ export class Jwt {
}

// TODO: should really be looking for verificationMethod with authentication verification relationship
const dereferenceResult = await Jwt.didResolver.dereference({ didUrl: decodedJwt.header.kid! });
const dereferenceResult = await Jwt.didResolver.dereference(decodedJwt.header.kid!);
if (dereferenceResult.dereferencingMetadata.error) {
throw new Error(`Failed to resolve ${decodedJwt.header.kid}`);
}

const verificationMethod = dereferenceResult.contentStream;
if (!verificationMethod || !didUtils.isVerificationMethod(verificationMethod)) { // ensure that appropriate verification method was found
if (!verificationMethod || !didUtils.isDidVerificationMethod(verificationMethod)) { // ensure that appropriate verification method was found
throw new Error('Verification failed: Expected kid in JWT header to dereference a DID Document Verification Method');
}

Expand All @@ -184,23 +143,19 @@ export class Jwt {
throw new Error('Verification failed: Expected kid in JWT header to dereference to a DID Document Verification Method with publicKeyJwk');
}

if(publicKeyJwk.alg && (publicKeyJwk.alg !== decodedJwt.header.alg)) {
throw new Error('Verification failed: Expected alg in JWT header to match DID Document Verification Method alg');
}

const signedData = `${encodedJwt.header}.${encodedJwt.payload}`;
const signedDataBytes = Convert.string(signedData).toUint8Array();

const signatureBytes = Convert.base64Url(encodedJwt.signature).toUint8Array();

const algorithmId = `${decodedJwt.header.alg}:${publicKeyJwk['crv'] || ''}`;
if (!(algorithmId in Jwt.algorithms)) {
throw new Error(`Verification failed: ${algorithmId} not supported`);
}

const { signer, options: signatureAlgorithm } = Jwt.algorithms[algorithmId];

const isSignatureValid = await signer.verify({
algorithm : signatureAlgorithm!,
const isSignatureValid = await crypto.verify({
key : publicKeyJwk,
signature : signatureBytes,
data : signedDataBytes,
signature : signatureBytes
});

if (!isSignatureValid) {
Expand Down
8 changes: 8 additions & 0 deletions packages/credentials/src/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from './verifiable-credential.js';

import { isValidXmlSchema112Timestamp } from './utils.js';
import { DEFAULT_VP_TYPE } from './verifiable-presentation.js';

export class SsiValidator {
static validateCredentialPayload(vc: VerifiableCredential): void {
Expand All @@ -34,6 +35,13 @@ export class SsiValidator {
}
}

static validateVpType(value: string | string[]): void {
const input = this.asArray(value);
if (input.length < 1 || input.indexOf(DEFAULT_VP_TYPE) === -1) {
throw new Error(`type is missing default "${DEFAULT_VP_TYPE}"`);
}
}

static validateCredentialSubject(value: ICredentialSubject | ICredentialSubject[]): void {
if (Object.keys(value).length === 0) {
throw new Error(`credentialSubject must not be empty`);
Expand Down
Loading

0 comments on commit 09ec382

Please sign in to comment.