Skip to content

Commit

Permalink
fix(sd-jwt): resolved most of the feedback
Browse files Browse the repository at this point in the history
Signed-off-by: Berend Sliedrecht <blu3beri@proton.me>
  • Loading branch information
berendsliedrecht committed Nov 2, 2023
1 parent d697756 commit e7eb71b
Show file tree
Hide file tree
Showing 10 changed files with 33 additions and 62 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/crypto/jose/jwk/Jwk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export abstract class Jwk {
public use?: string

public toJson(): JwkJson {
return this.use ? { use: this.use, kty: this.kty } : { kty: this.kty }
return { use: this.use, kty: this.kty }
}

public get key() {
Expand Down
18 changes: 2 additions & 16 deletions packages/core/src/crypto/jose/jwk/transform.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { JwkJson, Jwk } from './Jwk'
import type { Key } from '../../Key'
import type { JwaSignatureAlgorithm } from '../jwa'

import { AriesFrameworkError } from '../../../error'
import { KeyType } from '../../KeyType'
import { JwaSignatureAlgorithm, JwaCurve, JwaKeyType } from '../jwa'
import { JwaCurve, JwaKeyType } from '../jwa'

import { Ed25519Jwk } from './Ed25519Jwk'
import { P256Jwk } from './P256Jwk'
Expand Down Expand Up @@ -47,18 +48,3 @@ export function getJwkClassFromJwaSignatureAlgorithm(alg: JwaSignatureAlgorithm
export function getJwkClassFromKeyType(keyType: KeyType) {
return JwkClasses.find((jwkClass) => jwkClass.keyType === keyType)
}

/**
* Get a JSON Web Algorithm (JWA) from a key type.
*
* if it cannot be detected, it will throw an error
*/
export function getJwaFromKey(key: Key, override?: JwaSignatureAlgorithm): JwaSignatureAlgorithm {
if (override) return override
if (key.keyType === KeyType.Ed25519) return JwaSignatureAlgorithm.EdDSA
if (key.keyType === KeyType.P256) return JwaSignatureAlgorithm.ES256
if (key.keyType === KeyType.P384) return JwaSignatureAlgorithm.ES384
if (key.keyType === KeyType.P521) return JwaSignatureAlgorithm.ES512

throw new AriesFrameworkError(`Cannot create JWA from key type. Unsupported key type '${key.keyType}'.`)
}
12 changes: 2 additions & 10 deletions packages/sd-jwt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,15 @@

### Installation

Make sure you have set up the correct version of Aries Framework JavaScript according to the AFJ repository. To find out which version of AFJ you need to have installed you can run the following command. This will list the required peer dependency for `@aries-framework/core`.

```sh
npm info "@aries-framework/sd-jwt" peerDependencies
```

Then add the sd-jwt module to your project.
Add the `sd-jwt` module to your project.

```sh
yarn add @aries-framework/sd-jwt
```

### Quick start

In order for this module to work, we have to inject it into the agent to access agent functionality. See the example for more information.

### Example of usage
After the installation you can follow the [guide to setup your agent](https://aries.js.org/guides/0.4/getting-started/set-up) and add the following to your agent modules.

```ts
import { SdJwtModule } from '@aries-framework/sd-jwt'
Expand Down
4 changes: 2 additions & 2 deletions packages/sd-jwt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@aries-framework/sd-jwt",
"main": "build/index",
"types": "build/index",
"version": "0.4.1",
"version": "0.4.2",
"files": [
"build"
],
Expand All @@ -28,7 +28,7 @@
"@aries-framework/core": "^0.4.2",
"class-transformer": "0.5.1",
"class-validator": "0.14.0",
"jwt-sd": "^0.0.1-alpha.20"
"jwt-sd": "^0.1.0"
},
"devDependencies": {
"@hyperledger/aries-askar-nodejs": "^0.1.0",
Expand Down
7 changes: 2 additions & 5 deletions packages/sd-jwt/src/SdJwtApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,8 @@ export class SdJwtApi {
return await this.sdJwtService.verify<Header, Payload>(this.agentContext, sdJwtCompact, options)
}

public async getCredentialRecordByIdM<
Header extends Record<string, unknown> = Record<string, unknown>,
Payload extends Record<string, unknown> = Record<string, unknown>
>(id: string): Promise<SdJwtRecord<Header, Payload>> {
return await this.sdJwtService.getCredentialRecordById<Header, Payload>(this.agentContext, id)
public async getCredentialRecordById(id: string): Promise<SdJwtRecord> {
return await this.sdJwtService.getCredentialRecordById(this.agentContext, id)
}

public async getAllCredentialRecords(): Promise<Array<SdJwtRecord>> {
Expand Down
4 changes: 2 additions & 2 deletions packages/sd-jwt/src/SdJwtOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { DisclosureFrame } from 'jwt-sd'
export type SdJwtCreateOptions<Payload extends Record<string, unknown> = Record<string, unknown>> = {
holderDidUrl: string
issuerDidUrl: string
issuerOverrideJsonWebAlgorithm?: JwaSignatureAlgorithm
jsonWebAlgorithm?: JwaSignatureAlgorithm
disclosureFrame?: DisclosureFrame<Payload>
hashingAlgorithm?: HashName
}
Expand All @@ -18,7 +18,7 @@ export type SdJwtReceiveOptions = {
* `includedDisclosureIndices` is not the best API, but it is the best alternative until something like `PEX` is supported
*/
export type SdJwtPresentOptions = {
holderOverrideJsonWebAlgorithm?: JwaSignatureAlgorithm
jsonWebAlgorithm?: JwaSignatureAlgorithm
includedDisclosureIndices?: Array<number>

/**
Expand Down
42 changes: 19 additions & 23 deletions packages/sd-jwt/src/SdJwtService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import type { AgentContext, JwkJson, Query } from '@aries-framework/core'
import type { Signer, SdJwtVcVerificationResult, Verifier, HasherAndAlgorithm } from 'jwt-sd'

import {
parseDid,
DidResolverService,
getJwaFromKey,
getKeyFromVerificationMethod,
getJwkFromJson,
Key,
Expand Down Expand Up @@ -49,8 +49,7 @@ export class SdJwtService {
algorithm: HasherAlgorithm.Sha256,
hasher: (input: string) => {
const serializedInput = TypedArrayEncoder.fromString(input)
const hash = Hasher.hash(serializedInput, 'sha2-256')
return TypedArrayEncoder.toBase64URL(hash)
return Hasher.hash(serializedInput, 'sha2-256')
},
}
}
Expand Down Expand Up @@ -102,15 +101,15 @@ export class SdJwtService {
holderDidUrl,
disclosureFrame,
hashingAlgorithm = 'sha2-256',
issuerOverrideJsonWebAlgorithm,
jsonWebAlgorithm,
}: SdJwtCreateOptions<Payload>
): Promise<{ sdJwtRecord: SdJwtRecord; compact: string }> {
if (hashingAlgorithm !== 'sha2-256') {
throw new SdJwtError(`Unsupported hashing algorithm used: ${hashingAlgorithm}`)
}

const issuerKeyId = issuerDidUrl.split('#')[1]
if (!issuerKeyId) {
const parsedDid = parseDid(issuerDidUrl)
if (!parsedDid.fragment) {
throw new SdJwtError(
`issuer did url '${issuerDidUrl}' does not contain a '#'. Unable to derive key from did document`
)
Expand All @@ -121,7 +120,7 @@ export class SdJwtService {
issuerDidUrl
)
const issuerKey = getKeyFromVerificationMethod(issuerVerificationMethod)
const alg = getJwaFromKey(issuerKey, issuerOverrideJsonWebAlgorithm)
const alg = jsonWebAlgorithm ?? getJwkFromKey(issuerKey).supportedSignatureAlgorithms[0]

const { verificationMethod: holderVerificationMethod } = await this.resolveDidUrl(agentContext, holderDidUrl)
const holderKey = getKeyFromVerificationMethod(holderVerificationMethod)
Expand All @@ -130,7 +129,7 @@ export class SdJwtService {
const header = {
alg: alg.toString(),
typ: 'vc+sd-jwt',
kid: issuerKeyId,
kid: parsedDid.fragment,
}

const sdJwtVc = new SdJwtVc<typeof header, Payload>({}, { disclosureFrame })
Expand Down Expand Up @@ -181,16 +180,16 @@ export class SdJwtService {
sdJwtCompact: string,
{ issuerDidUrl, holderDidUrl }: SdJwtReceiveOptions
): Promise<SdJwtRecord> {
const sdJwt = SdJwtVc.fromCompact<Header, Payload>(sdJwtCompact)
const sdJwtVc = SdJwtVc.fromCompact<Header, Payload>(sdJwtCompact)

if (!sdJwt.signature) {
if (!sdJwtVc.signature) {
throw new SdJwtError('A signature must be included for an sd-jwt')
}

const { verificationMethod: issuerVerificationMethod } = await this.resolveDidUrl(agentContext, issuerDidUrl)
const issuerKey = getKeyFromVerificationMethod(issuerVerificationMethod)

const isSignatureValid = await sdJwt.verifySignature(this.verifier(agentContext, issuerKey))
const { isSignatureValid } = await sdJwtVc.verify(this.verifier(agentContext, issuerKey))

if (!isSignatureValid) {
throw new SdJwtError('sd-jwt has an invalid signature from the issuer')
Expand All @@ -200,14 +199,14 @@ export class SdJwtService {
const holderKey = getKeyFromVerificationMethod(holderVerificiationMethod)
const holderKeyJwk = getJwkFromKey(holderKey).toJson()

sdJwt.assertClaimInPayload('cnf', { jwk: holderKeyJwk })
sdJwtVc.assertClaimInPayload('cnf', { jwk: holderKeyJwk })

const sdJwtRecord = new SdJwtRecord<Header, Payload>({
sdJwt: {
header: sdJwt.header,
payload: sdJwt.payload,
signature: sdJwt.signature,
disclosures: sdJwt.disclosures?.map((d) => d.decoded),
header: sdJwtVc.header,
payload: sdJwtVc.payload,
signature: sdJwtVc.signature,
disclosures: sdJwtVc.disclosures?.map((d) => d.decoded),
holderDidUrl,
},
})
Expand All @@ -220,14 +219,14 @@ export class SdJwtService {
public async present(
agentContext: AgentContext,
sdJwtRecord: SdJwtRecord,
{ includedDisclosureIndices, verifierMetadata, holderOverrideJsonWebAlgorithm }: SdJwtPresentOptions
{ includedDisclosureIndices, verifierMetadata, jsonWebAlgorithm }: SdJwtPresentOptions
): Promise<string> {
const { verificationMethod: holderVerificationMethod } = await this.resolveDidUrl(
agentContext,
sdJwtRecord.sdJwt.holderDidUrl
)
const holderKey = getKeyFromVerificationMethod(holderVerificationMethod)
const alg = getJwaFromKey(holderKey, holderOverrideJsonWebAlgorithm)
const alg = jsonWebAlgorithm ?? getJwkFromKey(holderKey).supportedSignatureAlgorithms[0]

const header = {
alg: alg.toString(),
Expand Down Expand Up @@ -312,11 +311,8 @@ export class SdJwtService {
}
}

public async getCredentialRecordById<
Header extends Record<string, unknown> = Record<string, unknown>,
Payload extends Record<string, unknown> = Record<string, unknown>
>(agentContext: AgentContext, id: string): Promise<SdJwtRecord<Header, Payload>> {
return (await this.sdJwtRepository.getById(agentContext, id)) as SdJwtRecord<Header, Payload>
public async getCredentialRecordById(agentContext: AgentContext, id: string): Promise<SdJwtRecord> {
return (await this.sdJwtRepository.getById(agentContext, id)) as SdJwtRecord
}

public async getAllCredentialRecords(agentContext: AgentContext): Promise<Array<SdJwtRecord>> {
Expand Down
1 change: 1 addition & 0 deletions packages/sd-jwt/src/__tests__/SdJwtService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
const agent = new Agent({
config: { label: 'sdjwtserviceagent', walletConfig: { id: utils.uuid(), key: utils.uuid() } },
modules: {
// @ts-ignore
askar: new AskarModule({ ariesAskar }),
dids: new DidsModule({
resolvers: [new KeyDidResolver()],
Expand Down
3 changes: 1 addition & 2 deletions packages/sd-jwt/src/repository/SdJwtRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ export class SdJwtRecord<
algorithm: HasherAlgorithm.Sha256,
hasher: (input: string) => {
const serializedInput = TypedArrayEncoder.fromString(input)
const hash = Hasher.hash(serializedInput, 'sha2-256')
return TypedArrayEncoder.toBase64URL(hash)
return Hasher.hash(serializedInput, 'sha2-256')
},
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/sd-jwt/tests/sdJwtVc.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const getAgent = (label: string) =>
config: { label, walletConfig: { id: utils.uuid(), key: utils.uuid() } },
modules: {
sdJwt: new SdJwtModule(),
// @ts-ignore
askar: new AskarModule({ ariesAskar }),
dids: new DidsModule({
resolvers: [new KeyDidResolver()],
Expand Down Expand Up @@ -122,7 +123,6 @@ describe('sd-jwt-vc end to end test', () => {
validation: { isValid },
} = await verifier.modules.sdJwt.verify(presentation, {
holderDidUrl,
issuerDidUrl,
verifierDid,
requiredClaimKeys: [
'is_over_65',
Expand Down

0 comments on commit e7eb71b

Please sign in to comment.