Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Credential Signing #401

Merged
merged 13 commits into from
Feb 14, 2024
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
6 changes: 3 additions & 3 deletions packages/credentials/package.json
Original file line number Diff line number Diff line change
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",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the latest versions right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes -- 0.4.0 for both dids and crypto -- 0.2.3 for common.

"@web5/crypto": "0.4.0",
"@web5/dids": "0.4.0"
},
"devDependencies": {
"@playwright/test": "1.40.1",
Expand Down
82 changes: 16 additions & 66 deletions packages/credentials/src/jwt.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
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';

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

Expand All @@ -60,49 +56,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 +80,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 +99,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});

nitro-neal marked this conversation as resolved.
Show resolved Hide resolved
const signatureBytes = await signer.sign({ key: privateKeyJwk, data: toSignBytes, algorithm: signatureAlgorithm! });
const base64UrlEncodedSignature = Convert.uint8Array(signatureBytes).toBase64Url();

return `${toSign}.${base64UrlEncodedSignature}`;
Expand All @@ -168,13 +125,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 @@ -189,18 +146,11 @@ export class Jwt {

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 crypto = new CryptoApi();
const isSignatureValid = await crypto.verify({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we just have is initialized once at the top?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good

key : publicKeyJwk,
signature : signatureBytes,
data : signedDataBytes,
signature : signatureBytes
});

if (!isSignatureValid) {
Expand Down
4 changes: 2 additions & 2 deletions packages/credentials/src/verifiable-credential.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PortableDid } from '@web5/dids';
import type { BearerDid } from '@web5/dids';
import type { ICredential, ICredentialSubject} from '@sphereon/ssi-types';

import { utils as cryptoUtils } from '@web5/crypto';
Expand Down Expand Up @@ -41,7 +41,7 @@ export type VerifiableCredentialCreateOptions = {
* @param did - The issuer DID of the credential, represented as a PortableDid.
*/
export type VerifiableCredentialSignOptions = {
did: PortableDid;
did: BearerDid;
};

type CredentialSubject = ICredentialSubject;
Expand Down
Loading
Loading