Skip to content

Commit

Permalink
Add initial version of sd-jwt-js (openwallet-foundation#8)
Browse files Browse the repository at this point in the history
Signed-off-by: Lukas.J.Han <[email protected]>
Signed-off-by: Lukas <[email protected]>
  • Loading branch information
lukasjhan authored Dec 15, 2023
1 parent 5135c25 commit 232fdf4
Show file tree
Hide file tree
Showing 55 changed files with 5,623 additions and 21 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
dist

.vscode
5 changes: 5 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"trailingComma": "all",
"tabWidth": 2,
"singleQuote": true
}
5 changes: 5 additions & 0 deletions NOTE
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
decoy: 1~5 random by default -> number of decoy is increased, size of sd-jwt is increased
random number of hashing salt is better. decoy value: hash(hash(hash(salt)))
salt default length is 128

kb-jwt and all needed disclosure checking logic in verifier is required
64 changes: 43 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ The design of "Selective Disclosure for JWT" is centered around flexibility, eff

By adhering to these design principles, "Selective Disclosure for JWT" aims to set a new standard in the secure and efficient handling of JWTs across diverse JavaScript environments.

# Architecture (diagram)
# Architecture

![Architecture diagram](images/diagram.png)

# How to use (interface code)
# How to use

## Installation

Expand All @@ -49,42 +49,64 @@ Here's a basic example of how to use this library:
```jsx
const sdJWT = require('@hopae/sd-jwt');

const credential: JWT = sdJWT.issue(payload, signer);
const claims = {
firstname: 'John',
lastname: 'Doe',
ssn: '123-45-6789',
id: '1234',
};
const credential = await sdJWT.issue(claims, privateKey, disclosureFrame);

const presentation: JWT = sdJWT.present(credential, {
presentationFrame: { _sd: ['name', 'address'] },
holder: holdersKey,
});
const presentationFrame = ['firstname', 'id'];
const presentation = await sdjwt.present(encodedSdjwt, presentationFrame);

const result: boolean = sdJWT.verify(presentation);
const verified = sdJWT.verify(presentation, publicKey, ['firstname', 'id']);
```

Check out more details in our [documentation](https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki)

## Dependencies

- [jose](https://github.com/panva/jose)

## Build

To build this projects

```bash
pnpm install

pnpm run build
```

## Testing

To run the test suite, execute:

```bash
npm test
# Unit tests
pnpm test

# E2E tests
pnpm test:e2e
```

We use [Jest](https://jestjs.io/) for our testing framework. Ensure you have written tests for all new features.

## Security

- [ ] [Mandatory Signing of the Issuer-signed JWT](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-mandatory-signing-of-the-is)
- [ ] [Manipulation of Disclosures](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-manipulation-of-disclosures)
- [ ] [Entropy of the salt](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-entropy-of-the-salt)
- [ ] [Minimum length of the salt](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-minimum-length-of-the-salt)
- [ ] [Choice of a Hash Algorithm](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-choice-of-a-hash-algorithm)
- [ ] [Key Binding](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-key-binding)
- [ ] [Blinding Claim Names](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-blinding-claim-names)
- [ ] [Selectively-Disclosable Validity Claims](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-selectively-disclosable-val)
- [ ] [Issuer Signature Key Distribution and Rotation](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-issuer-signature-key-distri)
- [ ] [Forwarding Credentials](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-forwarding-credentials)
- [ ] [Integrity of Presentation](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-integrity-of-presentation)
- [ ] [Explicit Typing](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-explicit-typing)
- [x] [Mandatory Signing of the Issuer-signed JWT](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-mandatory-signing-of-the-is)
- [x] [Manipulation of Disclosures](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-manipulation-of-disclosures)
- [x] [Entropy of the salt](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-entropy-of-the-salt)
- [x] [Minimum length of the salt](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-minimum-length-of-the-salt)
- [x] [Choice of a Hash Algorithm](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-choice-of-a-hash-algorithm)
- [x] [Key Binding](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-key-binding)
- [x] [Blinding Claim Names](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-blinding-claim-names)
- [x] [Selectively-Disclosable Validity Claims](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-selectively-disclosable-val)
- [x] [Issuer Signature Key Distribution and Rotation](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-issuer-signature-key-distri)
- [x] [Forwarding Credentials](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-forwarding-credentials)
- [x] [Integrity of Presentation](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-integrity-of-presentation)
- [x] [Explicit Typing](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-explicit-typing)

## Contributing

Expand Down
32 changes: 32 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# SD JWT Examples

This directory contains examples of how to use the SD JWT(sd-jwt-js) library.

## How to run the examples

```bash
pnpm install
```

## Run the example

```bash
pnpm run {example_file_name}

# example
pnpm run all
```

### Example lists

- all : Example of issue, present and verify the comprehensive data.
- issue: Example of issue SD JWT
- present: Example of present SD JWT
- verify: Example of verify SD JWT
- validate: Example of validate SD JWT
- custom: Example of using custom hasher and salt generator for SD JWT
- decoy: Example of adding decoy digest in SD JWT

## More examples from tests

You can find more examples from [tests](../test).
74 changes: 74 additions & 0 deletions examples/all.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import sdjwt, { DisclosureFrame } from '@hopae/sd-jwt';
import Crypto from 'node:crypto';

export const createKeyPair = () => {
const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519');
return { privateKey, publicKey };
};

(async () => {
const { privateKey, publicKey } = createKeyPair();
const claims = {
firstname: 'John',
lastname: 'Doe',
ssn: '123-45-6789',
id: '1234',
data: {
firstname: 'John',
lastname: 'Doe',
ssn: '123-45-6789',
list: [{ r: '1' }, 'b', 'c'],
},
data2: {
hi: 'bye',
},
};
const disclosureFrame: DisclosureFrame<typeof claims> = {
_sd: ['firstname', 'id', 'data2'],
data: {
_sd: ['list'],
_sd_decoy: 2,
list: {
_sd: [0, 2],
_sd_decoy: 1,
0: {
_sd: ['r'],
},
},
},
data2: {
_sd: ['hi'],
},
};
const encodedSdjwt = await sdjwt.issue(claims, privateKey, disclosureFrame);
console.log('encodedJwt:', encodedSdjwt);
const validated = await sdjwt.validate(encodedSdjwt, publicKey);
console.log('validated:', validated);

const decoded = sdjwt.decode(encodedSdjwt);
console.log({ keys: await decoded.keys() });
const payloads = await decoded.getClaims();
const keys = await decoded.presentableKeys();
console.log({
payloads: JSON.stringify(payloads, null, 2),
disclosures: JSON.stringify(decoded.disclosures, null, 2),
claim: JSON.stringify(decoded.jwt?.payload, null, 2),
keys,
});

console.log(
'================================================================',
);

const presentationFrame = ['firstname', 'id'];
const presentedSDJwt = await sdjwt.present(encodedSdjwt, presentationFrame);
console.log('presentedSDJwt:', presentedSDJwt);

const requiredClaimKeys = ['firstname', 'id', 'data.ssn'];
const verified = await sdjwt.verify(
encodedSdjwt,
publicKey,
requiredClaimKeys,
);
console.log('verified:', verified);
})();
75 changes: 75 additions & 0 deletions examples/custom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import sdjwt, { DisclosureFrame } from '@hopae/sd-jwt';
import Crypto from 'node:crypto';

export const salt = (length: number): string => {
const saltBytes = Crypto.randomBytes(length);
const salt = saltBytes.toString('hex');
return salt;
};

export const digest = async (
data: string,
algorithm: string = 'SHA-256',
): Promise<string> => {
const hash = Crypto.createHash(algorithm);
hash.update(data);
return hash.digest('hex');
};

export const createKeyPair = () => {
const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519');
return { privateKey, publicKey };
};

(async () => {
const SDJwtInstance = sdjwt.create({ hasher: digest, saltGenerator: salt });

const { privateKey, publicKey } = createKeyPair();
const claims = {
firstname: 'John',
lastname: 'Doe',
ssn: '123-45-6789',
id: '1234',
};
const disclosureFrame: DisclosureFrame<typeof claims> = {
_sd: ['firstname', 'id'],
};
const encodedSdjwt = await SDJwtInstance.issue(
claims,
privateKey,
disclosureFrame,
);
console.log('encodedJwt:', encodedSdjwt);
const validated = await SDJwtInstance.validate(encodedSdjwt, publicKey);
console.log('validated:', validated);

const decoded = SDJwtInstance.decode(encodedSdjwt);
console.log({ keys: await decoded.keys() });
const payloads = await decoded.getClaims();
const keys = await decoded.presentableKeys();
console.log({
payloads: JSON.stringify(payloads, null, 2),
disclosures: JSON.stringify(decoded.disclosures, null, 2),
claim: JSON.stringify(decoded.jwt?.payload, null, 2),
keys,
});

console.log(
'================================================================',
);

const presentationFrame = ['firstname', 'id'];
const presentedSDJwt = await SDJwtInstance.present(
encodedSdjwt,
presentationFrame,
);
console.log('presentedSDJwt:', presentedSDJwt);

const requiredClaimKeys = ['firstname', 'id'];
const verified = await SDJwtInstance.verify(
encodedSdjwt,
publicKey,
requiredClaimKeys,
);
console.log('verified:', verified);
})();
22 changes: 22 additions & 0 deletions examples/decoy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import sdjwt, { DisclosureFrame } from '@hopae/sd-jwt';
import Crypto from 'node:crypto';

export const createKeyPair = () => {
const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519');
return { privateKey, publicKey };
};

(async () => {
const { privateKey, publicKey } = createKeyPair();
const claims = {
lastname: 'Doe',
ssn: '123-45-6789',
id: '1234',
};
const disclosureFrame: DisclosureFrame<typeof claims> = {
_sd: ['id'],
_sd_decoy: 1,
};
const encodedSdjwt = await sdjwt.issue(claims, privateKey, disclosureFrame);
console.log('encodedSdjwt:', encodedSdjwt);
})();
22 changes: 22 additions & 0 deletions examples/issue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import sdjwt, { DisclosureFrame } from '@hopae/sd-jwt';
import Crypto from 'node:crypto';

export const createKeyPair = () => {
const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519');
return { privateKey, publicKey };
};

(async () => {
const { privateKey, publicKey } = createKeyPair();
const claims = {
firstname: 'John',
lastname: 'Doe',
ssn: '123-45-6789',
id: '1234',
};
const disclosureFrame: DisclosureFrame<typeof claims> = {
_sd: ['firstname', 'id'],
};
const encodedSdjwt = await sdjwt.issue(claims, privateKey, disclosureFrame);
console.log('encodedSdjwt:', encodedSdjwt);
})();
27 changes: 27 additions & 0 deletions examples/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "sdjwt-examples",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"prepare": "cd ../ && pnpm install && pnpm build",
"all": "ts-node all.ts",
"issue": "ts-node issue.ts",
"present": "ts-node present.ts",
"validate": "ts-node validate.ts",
"verify": "ts-node verify.ts",
"custom": "ts-node custom.ts",
"decoy": "ts-node decoy.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^20.10.4",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
},
"dependencies": {
"@hopae/sd-jwt": "link:.."
}
}
Loading

0 comments on commit 232fdf4

Please sign in to comment.