Skip to content

Commit

Permalink
added tests for disclosure creation
Browse files Browse the repository at this point in the history
Signed-off-by: Berend Sliedrecht <[email protected]>
  • Loading branch information
berendsliedrecht committed Oct 4, 2023
1 parent ee9085d commit 6f981c7
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 18 deletions.
27 changes: 27 additions & 0 deletions src/createDecoys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Hasher } from './sdJwt'
import { OrPromise } from './types'

/**
* Key should not be used to generate the salt as it needs to be unique. It is used for testing here
*/
export type SaltGenerator = () => OrPromise<string>

const createDecoy = async (saltGenerator: SaltGenerator, hasher: Hasher) => {
const salt = await saltGenerator()
const decoy = await hasher(salt)

return decoy
}

export const createDecoys = async (
count: number,
saltGenerator: SaltGenerator,
hasher: Hasher,
) => {
const decoys: Array<string> = []
for (let i = 0; i < count; i++) {
const decoy = await createDecoy(saltGenerator, hasher)
decoys.push(decoy)
}
return decoys
}
44 changes: 44 additions & 0 deletions src/createDisclosure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { SdJwtError } from './error'
import { Base64url } from './base64url'

export const createObjectDisclosure = (
salt: string,
key: string,
value: unknown,
) => {
if (typeof value === 'number' && isNaN(value)) {
throw new SdJwtError('NaN is not allowed to be used in a disclosure.')
}

if (typeof value === 'number' && !isFinite(value)) {
throw new SdJwtError(
'Infinite is not allowed to be used in a disclosure.',
)
}

if (Array.isArray(value)) {
throw new SdJwtError(
'Array is not allowed. Use `createArrayDisclosure()`.',
)
}

const disclosure = [salt, key, value]

return Base64url.encode(JSON.stringify(disclosure))
}

export const createArrayDisclosure = (salt: string, value: unknown) => {
if (typeof value === 'number' && isNaN(value)) {
throw new SdJwtError('NaN is not allowed to be used in a disclosure.')
}

if (typeof value === 'number' && !isFinite(value)) {
throw new SdJwtError(
'Infinite is not allowed to be used in a disclosure.',
)
}

const disclosure = [salt, value]

return Base64url.encode(JSON.stringify(disclosure))
}
45 changes: 27 additions & 18 deletions src/sdJwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Base64url } from './base64url'
import { SdJwtError } from './error'
import { HasherAlgorithm } from './hasherAlgorithm'
import { deleteByPath } from './util'
import { SaltGenerator, createDecoys } from './createDecoys'
import { createObjectDisclosure } from './createDisclosure'

type ReturnSdJwtWithHeaderAndPayload<T extends SdJwt> = MakePropertyRequired<
T,
Expand Down Expand Up @@ -49,11 +51,6 @@ export type HasherAndAlgorithm = {
algorithm: string | HasherAlgorithm
}

/**
* Key should not be used to generate the salt as it needs to be unique. It is used for testing here
*/
export type SaltGenerator = (key: string) => OrPromise<string>

export type Signer<
Header extends Record<string, unknown> = Record<string, unknown>,
> = (input: string, header: Header) => OrPromise<Uint8Array>
Expand Down Expand Up @@ -205,12 +202,16 @@ export class SdJwt<
return this as ReturnSdJwtWithSignature<this>
}

private createDisclosure(key: string, value: unknown): string {
private async createDisclosure(
key: string,
value: unknown,
): Promise<string> {
this.assertSaltGenerator()

const disclosure = [this.saltGenerator!(key), key, value]
const salt = await this.saltGenerator!()
const disclosure = createObjectDisclosure(salt, key, value)

return Base64url.encode(JSON.stringify(disclosure))
return disclosure
}

private async hashDisclosure(disclosure: string): Promise<string> {
Expand All @@ -219,16 +220,16 @@ export class SdJwt<
return await this.hasherAndAlgorithm!.hasher(disclosure)
}

private async createDecoy(count: number): Promise<Array<string>> {
private async createDecoys(count: number): Promise<Array<string>> {
this.assertSaltGenerator()
this.assertPayload()
this.assertHashAndAlgorithm()

const decoys = await createDecoys(
count,
this.saltGenerator!,
this.hasherAndAlgorithm!.hasher,
)

const decoys: Array<string> = []
for (let i = 0; i < count; i++) {
const salt = await this.saltGenerator!(i.toString())
const decoy = await this.hashDisclosure(salt)
decoys.push(decoy)
}
return decoys
}

Expand All @@ -248,7 +249,7 @@ export class SdJwt<
const sd: Array<string> = Array.from(
(object._sd as string[]) ?? [],
)
const decoy = await this.createDecoy(value)
const decoy = await this.createDecoys(value)
decoy.forEach((digest) => sd.push(digest))
// @ts-ignore
object._sd = sd.sort()
Expand All @@ -257,12 +258,20 @@ export class SdJwt<
const sd: Array<string> = Array.from(
(object._sd as string[]) ?? [],
)
const disclosure = this.createDisclosure(key, object[key])

const disclosure = await this.createDisclosure(
key,
object[key],
)

disclosures.push(disclosure)

const digest = await this.hashDisclosure(disclosure)
sd.push(digest)

//@ts-ignore
object._sd = sd.sort()

cleanup.push(newKeys)
}
} else if (typeof value === 'object' && !Array.isArray(value)) {
Expand Down
Empty file added tests/createDecoys.test.ts
Empty file.
161 changes: 161 additions & 0 deletions tests/createDisclosure.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { describe, it } from 'node:test'

import assert from 'node:assert'

import {
createArrayDisclosure,
createObjectDisclosure,
} from '../src/createDisclosure'
import { c14n } from './utils'

describe('createDisclosure', () => {
describe('Specification Example 5.5', () => {
it('Claim given_name', async () => {
const disclosure = createObjectDisclosure(
'2GLC42sKQveCfGfryNRN9w',
'given_name',
'John',
)

assert.strictEqual(
c14n(disclosure),
c14n(
'WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiSm9obiJd',
),
)
})

it('Claim family_name', async () => {
const disclosure = createObjectDisclosure(
'eluV5Og3gSNII8EYnsxA_A',
'family_name',
'Doe',
)

assert.strictEqual(
c14n(disclosure),
c14n(
'WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImZhbWlseV9uYW1lIiwgIkRvZSJd',
),
)
})

it('Claim email', async () => {
const disclosure = createObjectDisclosure(
'6Ij7tM-a5iVPGboS5tmvVA',
'email',
'[email protected]',
)

assert.strictEqual(
c14n(disclosure),
c14n(
'WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ',
),
)
})

it('Claim phone_number', async () => {
const disclosure = createObjectDisclosure(
'eI8ZWm9QnKPpNPeNenHdhQ',
'phone_number',
'+1-202-555-0101',
)

assert.strictEqual(
c14n(disclosure),
c14n(
'WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgInBob25lX251bWJlciIsICIrMS0yMDItNTU1LTAxMDEiXQ',
),
)
})
it('Claim phone_number_verified', async () => {
const disclosure = createObjectDisclosure(
'Qg_O64zqAxe412a108iroA',
'phone_number_verified',
true,
)

assert.strictEqual(
c14n(disclosure),
c14n(
'WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgInBob25lX251bWJlcl92ZXJpZmllZCIsIHRydWVd',
),
)
})

it('Claim address', async () => {
const disclosure = createObjectDisclosure(
'AJx-095VPrpTtN4QMOqROA',
'address',
{
street_address: '123 Main St',
locality: 'Anytown',
region: 'Anystate',
country: 'US',
},
)

assert.strictEqual(
c14n(disclosure),
c14n(
'WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgImFkZHJlc3MiLCB7InN0cmVldF9hZGRyZXNzIjogIjEyMyBNYWluIFN0IiwgImxvY2FsaXR5IjogIkFueXRvd24iLCAicmVnaW9uIjogIkFueXN0YXRlIiwgImNvdW50cnkiOiAiVVMifV0',
),
)
})

it('Claim birthdate', async () => {
const disclosure = createObjectDisclosure(
'Pc33JM2LchcU_lHggv_ufQ',
'birthdate',
'1940-01-01',
)

assert.strictEqual(
c14n(disclosure),
c14n(
'WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgImJpcnRoZGF0ZSIsICIxOTQwLTAxLTAxIl0',
),
)
})

it('Claim updated_at', async () => {
const disclosure = createObjectDisclosure(
'G02NSrQfjFXQ7Io09syajA',
'updated_at',
1570000000,
)

assert.strictEqual(
c14n(disclosure),
c14n(
'WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ',
),
)
})

it('Array Entry 01', async () => {
const disclosure = createArrayDisclosure(
'lklxF5jMYlGTPUovMNIvCA',
'US',
)

assert.strictEqual(
c14n(disclosure),
c14n('WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIlVTIl0'),
)
})

it('Array Entry 02', async () => {
const disclosure = createArrayDisclosure(
'nPuoQnkRFq3BIeAm7AnXFA',
'DE',
)

assert.strictEqual(
c14n(disclosure),
c14n('WyJuUHVvUW5rUkZxM0JJZUFtN0FuWEZBIiwgIkRFIl0'),
)
})
})
})
3 changes: 3 additions & 0 deletions tests/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Base64url } from '../src/base64url'

export const c14n = (x: string) => JSON.stringify(Base64url.decodeToJson(x))

0 comments on commit 6f981c7

Please sign in to comment.