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

feat(openid4vc-client): pre-authorized #1243

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
c3d24c5
feat(openid4vc-client): partial pre-auth flow
karimStekelenburg Jan 14, 2023
80c5c56
feat: add fancy logger
karimStekelenburg Jan 14, 2023
bfb93f4
feat: add log statements
karimStekelenburg Jan 15, 2023
51910c2
test: mock api response
karimStekelenburg Jan 17, 2023
5af64e2
feat: update json-ld validators
karimStekelenburg Jan 25, 2023
a31f609
fix: add await to service call
karimStekelenburg Jan 25, 2023
99480e5
feat: add complete `pre-authorized` flow
karimStekelenburg Jan 25, 2023
b6512f8
test(openid4vc-client): add pre-authorized tests
karimStekelenburg Jan 25, 2023
80b2921
chore: revert "feat: add fancy logger"
karimStekelenburg Jan 25, 2023
f62a734
fix(openid4vc-client): remove development funcs
karimStekelenburg Jan 25, 2023
e5def6b
fix: merge conflict
karimStekelenburg Jan 25, 2023
c0b5643
fix: remove dev logger stuff
karimStekelenburg Jan 25, 2023
4318002
fix: set test loglevel to info
karimStekelenburg Jan 25, 2023
44d1fb5
test: update JwsService tests
karimStekelenburg Jan 25, 2023
abac8d9
fix: update DidExchangeProtocol
karimStekelenburg Jan 25, 2023
ed33e23
style: run prettier
karimStekelenburg Jan 25, 2023
cdbb825
style: fix ts issues
karimStekelenburg Jan 25, 2023
f434d16
fix: add ts-ignore
karimStekelenburg Jan 25, 2023
9f20a68
fix: improve JSON-LD context validator
karimStekelenburg Jan 28, 2023
c6663b1
fix: allow arbitrary values
karimStekelenburg Jan 26, 2023
efc3aa4
fix: JWK type
karimStekelenburg Jan 26, 2023
f08d25d
refactor: make sure JwsService uses Key params
karimStekelenburg Jan 27, 2023
aff6fc7
fix: change logger to TestLogger
karimStekelenburg Jan 28, 2023
0217871
fix: extract dummy clientId to variable
karimStekelenburg Jan 28, 2023
d05355f
feat: add sig verification before storing cred
karimStekelenburg Jan 28, 2023
298da6b
fix: remove console.log hack from test
karimStekelenburg Jan 28, 2023
760e251
fix: simplyfy key to jwk conversion
karimStekelenburg Jan 28, 2023
f141bf1
fix: verify credential before storing
karimStekelenburg Jan 28, 2023
cb39e63
fix: set jest timeout to 20s
karimStekelenburg Jan 28, 2023
a1584da
fix: broken code
karimStekelenburg Jan 28, 2023
b48e0d7
fix: remove unneeded dependencies
karimStekelenburg Jan 28, 2023
468646a
fix: remove console.log
karimStekelenburg Jan 28, 2023
713598f
fix: remove unneeded import
karimStekelenburg Jan 28, 2023
ee025ae
fix: generalize DID resolvement
karimStekelenburg Jan 28, 2023
37c78a9
refactor: rename properties of JwsService
karimStekelenburg Jan 28, 2023
9c25b8e
fix: remove comment
karimStekelenburg Jan 28, 2023
376c7a4
style: making sure @blu3beri doesn't bug me
karimStekelenburg Jan 28, 2023
2cab0c4
style: run prettier
karimStekelenburg Jan 29, 2023
8759afe
fix: move from- and toJwk logic to Key class
karimStekelenburg Jan 29, 2023
c36136a
fix: forgot to save :(
karimStekelenburg Jan 29, 2023
a1ac5e2
fix: type issue
karimStekelenburg Jan 29, 2023
7e4488e
fix(openid): clearer error message
karimStekelenburg Jan 31, 2023
c3fba40
fix(openid): remove comment
karimStekelenburg Jan 31, 2023
fdcf110
fix(openId): naming issue
karimStekelenburg Feb 1, 2023
2c87887
fix(openid): redundant code
karimStekelenburg Feb 1, 2023
23a7d88
fix(core): export JwsService to public API
karimStekelenburg Jan 31, 2023
00a2d4f
fix(openid): remove empty file
karimStekelenburg Jan 31, 2023
79f25c0
fix(openid): remove clientId
karimStekelenburg Jan 31, 2023
023355d
feat(core): add jwtUtils
karimStekelenburg Feb 1, 2023
4128a74
feat(openid): check jwt alg against key type
karimStekelenburg Feb 1, 2023
54b6759
feat(core): update verify method
karimStekelenburg Feb 1, 2023
13e6b29
fix(core): remove unused import
karimStekelenburg Feb 1, 2023
bd8f92d
test(core): update JwsService tests
karimStekelenburg Feb 1, 2023
0257f8e
fix(core): remove unused import
karimStekelenburg Feb 1, 2023
4587c17
fix(core): remove unused param
karimStekelenburg Feb 1, 2023
290f68b
fix(core): move revocation error to callback
karimStekelenburg Feb 1, 2023
09354b5
fix(core): remove input
karimStekelenburg Feb 1, 2023
38e330b
fix(core): validator
karimStekelenburg Feb 1, 2023
7cfe9e5
refactor(openid): rename pre auth flow func
karimStekelenburg Feb 1, 2023
03f31c5
refactor(openid): move param to options
karimStekelenburg Feb 1, 2023
f67dd96
fix(openid): fetch resolve logger
karimStekelenburg Feb 1, 2023
03dff61
fix(openid): remove comment
karimStekelenburg Feb 1, 2023
45ba9ca
fix(openid): remove comment
karimStekelenburg Feb 1, 2023
0aa4fcb
test(openid): update test
karimStekelenburg Feb 1, 2023
fb85fe8
feat(openid): add check to verify cred format
karimStekelenburg Feb 1, 2023
8423b8b
style: run prettier
karimStekelenburg Feb 1, 2023
e747f76
docs(openid): update README
karimStekelenburg Feb 1, 2023
4237941
style: run prettier
karimStekelenburg Feb 1, 2023
20390fa
fix(core): update DIDExchangeProtocol
karimStekelenburg Feb 1, 2023
af43952
docs(openid): add full example to README
karimStekelenburg Feb 1, 2023
dd07b60
style: run prettier
karimStekelenburg Feb 1, 2023
133a2d2
fic(openid): type errror
karimStekelenburg Feb 1, 2023
40e6567
style: I hate prettier
karimStekelenburg Feb 1, 2023
87e773a
build(openid): set skipLibCheck to true
karimStekelenburg Feb 3, 2023
8dcc5bb
build(openid): set skipLibCheck to true
karimStekelenburg Feb 3, 2023
a8db416
test(core): fix JwsService tests
karimStekelenburg Feb 6, 2023
b93d04a
build(openid): set package to public
karimStekelenburg Feb 6, 2023
10d85ac
Merge branch 'main' into feat(openid4vc-client)/pre-auth
karimStekelenburg Feb 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/core/src/crypto/JwkTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface Jwk {
kty: 'EC' | 'OKP'
crv: 'Ed25519' | 'X25519' | 'P-256' | 'P-384' | 'secp256k1'
x: string
y?: string
}
111 changes: 82 additions & 29 deletions packages/core/src/crypto/JwsService.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Jwk } from './JwkTypes'
import type { Jws, JwsGeneralFormat } from './JwsTypes'
import type { AgentContext } from '../agent'
import type { Buffer } from '../utils'
Expand All @@ -17,25 +18,63 @@ const JWS_ALG = 'EdDSA'

@injectable()
export class JwsService {
public async createJws(
agentContext: AgentContext,
{ payload, verkey, header }: CreateJwsOptions
): Promise<JwsGeneralFormat> {
const base64Payload = TypedArrayEncoder.toBase64URL(payload)
const base64Protected = JsonEncoder.toBase64URL(this.buildProtected(verkey))
const key = Key.fromPublicKeyBase58(verkey, KeyType.Ed25519)
public static supportedKeyTypes = [KeyType.Ed25519]

private async createJwsBase(agentContext: AgentContext, options: CreateJwsBaseOptions) {
if (!JwsService.supportedKeyTypes.includes(options.key.keyType)) {
throw new AriesFrameworkError(
`Only ${JwsService.supportedKeyTypes.join(',')} key type(s) supported for creating JWS`
)
}
const base64Payload = TypedArrayEncoder.toBase64URL(options.payload)
karimStekelenburg marked this conversation as resolved.
Show resolved Hide resolved
const base64UrlProtectedHeader = JsonEncoder.toBase64URL(this.buildProtected(options.protectedHeaderOptions))

const signature = TypedArrayEncoder.toBase64URL(
await agentContext.wallet.sign({ data: TypedArrayEncoder.fromString(`${base64Protected}.${base64Payload}`), key })
await agentContext.wallet.sign({
data: TypedArrayEncoder.fromString(`${base64UrlProtectedHeader}.${base64Payload}`),
key: options.key,
})
)

return {
protected: base64Protected,
base64Payload,
base64UrlProtectedHeader,
signature,
}
}

public async createJws(
agentContext: AgentContext,
{ payload, key, header, protectedHeaderOptions }: CreateJwsOptions
): Promise<JwsGeneralFormat> {
const { base64UrlProtectedHeader, signature } = await this.createJwsBase(agentContext, {
payload,
key,
protectedHeaderOptions,
})

return {
protected: base64UrlProtectedHeader,
signature,
header,
}
}

/**
* @see {@link https://www.rfc-editor.org/rfc/rfc7515#section-3.1}
* */
public async createJwsCompact(
agentContext: AgentContext,
{ payload, key, protectedHeaderOptions }: CreateCompactJwsOptions
): Promise<string> {
const { base64Payload, base64UrlProtectedHeader, signature } = await this.createJwsBase(agentContext, {
payload,
key,
protectedHeaderOptions,
})
return `${base64UrlProtectedHeader}.${base64Payload}.${signature}`
}

/**
* Verify a JWS
*/
Expand All @@ -47,7 +86,7 @@ export class JwsService {
throw new AriesFrameworkError('Unable to verify JWS: No entries in JWS signatures array.')
}

const signerVerkeys = []
const signerKeys: Key[] = []
for (const jws of signatures) {
const protectedJson = JsonEncoder.fromBase64(jws.protected)

Expand All @@ -62,17 +101,17 @@ export class JwsService {
const data = TypedArrayEncoder.fromString(`${jws.protected}.${base64Payload}`)
const signature = TypedArrayEncoder.fromBase64(jws.signature)

const verkey = TypedArrayEncoder.toBase58(TypedArrayEncoder.fromBase64(protectedJson?.jwk?.x))
const key = Key.fromPublicKeyBase58(verkey, KeyType.Ed25519)
signerVerkeys.push(verkey)
const publicKey = TypedArrayEncoder.fromBase64(protectedJson?.jwk?.x)
const key = Key.fromPublicKey(publicKey, KeyType.Ed25519)
signerKeys.push(key)

try {
const isValid = await agentContext.wallet.verify({ key, data, signature })

if (!isValid) {
return {
isValid: false,
signerVerkeys: [],
signerKeys: [],
}
}
} catch (error) {
Expand All @@ -81,45 +120,59 @@ export class JwsService {
if (error instanceof WalletError) {
return {
isValid: false,
signerVerkeys: [],
signerKeys: [],
}
}

throw error
}
}

return { isValid: true, signerVerkeys }
return { isValid: true, signerKeys: signerKeys }
}

/**
* @todo This currently only work with a single alg, key type and curve
* This needs to be extended with other formats in the future
*/
private buildProtected(verkey: string) {
private buildProtected(options: ProtectedHeaderOptions) {
if (!options.jwk && !options.kid) {
throw new AriesFrameworkError('Both JWK and kid are undefined. Please provide one or the other.')
}
if (options.jwk && options.kid) {
throw new AriesFrameworkError('Both JWK and kid are provided. Please only provide one of the two.')
}

return {
alg: 'EdDSA',
jwk: {
kty: 'OKP',
crv: 'Ed25519',
x: TypedArrayEncoder.toBase64URL(TypedArrayEncoder.fromBase58(verkey)),
},
alg: options.alg,
karimStekelenburg marked this conversation as resolved.
Show resolved Hide resolved
jwk: options.jwk,
kid: options.kid,
}
}
}

export interface CreateJwsOptions {
verkey: string
key: Key
payload: Buffer
header: Record<string, unknown>
protectedHeaderOptions: ProtectedHeaderOptions
}

type CreateJwsBaseOptions = Omit<CreateJwsOptions, 'header'>

type CreateCompactJwsOptions = Omit<CreateJwsOptions, 'header'>

export interface VerifyJwsOptions {
karimStekelenburg marked this conversation as resolved.
Show resolved Hide resolved
jws: Jws
payload: Buffer
}

export interface VerifyJwsResult {
isValid: boolean
signerVerkeys: string[]
signerKeys: Key[]
}

export type kid = string

export interface ProtectedHeaderOptions {
alg: string
jwk?: Jwk
kid?: kid
[key: string]: any
}
23 changes: 22 additions & 1 deletion packages/core/src/crypto/Key.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { KeyType } from './KeyType'
import type { Jwk } from './JwkTypes'

import { AriesFrameworkError } from '../error'
import { Buffer, MultiBaseEncoder, TypedArrayEncoder, VarintEncoder } from '../utils'

import { KeyType } from './KeyType'
import { getKeyTypeByMultiCodecPrefix, getMultiCodecPrefixByKeytype } from './multiCodecKey'

export class Key {
Expand Down Expand Up @@ -50,4 +52,23 @@ export class Key {
public get publicKeyBase58() {
return TypedArrayEncoder.toBase58(this.publicKey)
}

karimStekelenburg marked this conversation as resolved.
Show resolved Hide resolved
public toJwk(): Jwk {
if (this.keyType !== KeyType.Ed25519) {
throw new AriesFrameworkError(`JWK creation is only supported for Ed25519 key types. Received ${this.keyType}`)
}

return {
kty: 'OKP',
crv: 'Ed25519',
x: TypedArrayEncoder.toBase64URL(this.publicKey),
}
}

public static fromJwk(jwk: Jwk) {
if (jwk.crv !== 'Ed25519') {
throw new AriesFrameworkError('Only JWKs with Ed25519 key type is supported.')
}
return Key.fromPublicKeyBase58(TypedArrayEncoder.toBase58(TypedArrayEncoder.fromBase64(jwk.x)), KeyType.Ed25519)
}
}
30 changes: 17 additions & 13 deletions packages/core/src/crypto/__tests__/JwsService.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { AgentContext } from '../../agent'
import type { Wallet } from '@aries-framework/core'
import type { Key, Wallet } from '@aries-framework/core'

import { getAgentConfig, getAgentContext } from '../../../tests/helpers'
import { DidKey } from '../../modules/dids'
Expand All @@ -16,7 +16,8 @@ describe('JwsService', () => {
let wallet: Wallet
let agentContext: AgentContext
let jwsService: JwsService

let didJwsz6MkfKey: Key
let didJwsz6MkvKey: Key
beforeAll(async () => {
const config = getAgentConfig('JwsService')
wallet = new IndyWallet(config.agentDependencies, config.logger, new SigningProviderRegistry([]))
Expand All @@ -27,6 +28,8 @@ describe('JwsService', () => {
await wallet.createAndOpen(config.walletConfig!)

jwsService = new JwsService()
didJwsz6MkfKey = await wallet.createKey({ seed: didJwsz6Mkf.SEED, keyType: KeyType.Ed25519 })
didJwsz6MkvKey = await wallet.createKey({ seed: didJwsz6Mkv.SEED, keyType: KeyType.Ed25519 })
})

afterAll(async () => {
Expand All @@ -35,16 +38,17 @@ describe('JwsService', () => {

describe('createJws', () => {
it('creates a jws for the payload with the key associated with the verkey', async () => {
const key = await wallet.createKey({ seed: didJwsz6Mkf.SEED, keyType: KeyType.Ed25519 })

const payload = JsonEncoder.toBuffer(didJwsz6Mkf.DATA_JSON)
const kid = new DidKey(key).did
const kid = new DidKey(didJwsz6MkfKey).did

const jws = await jwsService.createJws(agentContext, {
payload,
// FIXME: update to use key instance instead of verkey
verkey: key.publicKeyBase58,
key: didJwsz6MkfKey,
header: { kid },
protectedHeaderOptions: {
alg: 'EdDSA',
jwk: didJwsz6MkfKey.toJwk(),
},
})

expect(jws).toEqual(didJwsz6Mkf.JWS_JSON)
Expand All @@ -55,37 +59,37 @@ describe('JwsService', () => {
it('returns true if the jws signature matches the payload', async () => {
const payload = JsonEncoder.toBuffer(didJwsz6Mkf.DATA_JSON)

const { isValid, signerVerkeys } = await jwsService.verifyJws(agentContext, {
const { isValid, signerKeys } = await jwsService.verifyJws(agentContext, {
payload,
jws: didJwsz6Mkf.JWS_JSON,
})

expect(isValid).toBe(true)
expect(signerVerkeys).toEqual([didJwsz6Mkf.VERKEY])
expect(signerKeys).toEqual([didJwsz6MkfKey])
})

it('returns all verkeys that signed the jws', async () => {
const payload = JsonEncoder.toBuffer(didJwsz6Mkf.DATA_JSON)

const { isValid, signerVerkeys } = await jwsService.verifyJws(agentContext, {
const { isValid, signerKeys } = await jwsService.verifyJws(agentContext, {
payload,
jws: { signatures: [didJwsz6Mkf.JWS_JSON, didJwsz6Mkv.JWS_JSON] },
})

expect(isValid).toBe(true)
expect(signerVerkeys).toEqual([didJwsz6Mkf.VERKEY, didJwsz6Mkv.VERKEY])
expect(signerKeys).toEqual([didJwsz6MkfKey, didJwsz6MkvKey])
})

it('returns false if the jws signature does not match the payload', async () => {
const payload = JsonEncoder.toBuffer({ ...didJwsz6Mkf.DATA_JSON, did: 'another_did' })

const { isValid, signerVerkeys } = await jwsService.verifyJws(agentContext, {
const { isValid, signerKeys } = await jwsService.verifyJws(agentContext, {
payload,
jws: didJwsz6Mkf.JWS_JSON,
})

expect(isValid).toBe(false)
expect(signerVerkeys).toMatchObject([])
expect(signerKeys).toMatchObject([])
})

it('throws an error if the jws signatures array does not contain a JWS', async () => {
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/crypto/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
export { Jwk } from './JwkTypes'
export { JwsService } from './JwsService'

export * from './jwtUtils'

export { KeyType } from './KeyType'
export { Key } from './Key'

export * from './signing-provider'
13 changes: 13 additions & 0 deletions packages/core/src/crypto/jwtUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const jwtKeyAlgMapping = {
HMAC: ['HS256', 'HS384', 'HS512'],
RSA: ['RS256', 'RS384', 'RS512'],
ECDSA: ['ES256', 'ES384', 'ES512'],
'RSA-PSS': ['PS256', 'PS384', 'PS512'],
EdDSA: ['Ed25519'],
}

export type JwtAlgorithm = keyof typeof jwtKeyAlgMapping

export function isJwtAlgorithm(value: string): value is JwtAlgorithm {
return Object.keys(jwtKeyAlgMapping).includes(value)
}
12 changes: 8 additions & 4 deletions packages/core/src/modules/connections/DidExchangeProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,10 +464,14 @@ export class DidExchangeProtocol {

const jws = await this.jwsService.createJws(agentContext, {
payload,
verkey,
key,
header: {
kid,
},
protectedHeaderOptions: {
alg: 'EdDSA',
jwk: key.toJwk(),
},
})
didDocAttach.addJws(jws)
})
Expand Down Expand Up @@ -510,7 +514,7 @@ export class DidExchangeProtocol {
this.logger.trace('DidDocument JSON', json)

const payload = JsonEncoder.toBuffer(json)
const { isValid, signerVerkeys } = await this.jwsService.verifyJws(agentContext, { jws, payload })
const { isValid, signerKeys } = await this.jwsService.verifyJws(agentContext, { jws, payload })

const didDocument = JsonTransformer.fromJSON(json, DidDocument)
const didDocumentKeysBase58 = didDocument.authentication
Expand All @@ -525,9 +529,9 @@ export class DidExchangeProtocol {
})
.concat(invitationKeysBase58)

this.logger.trace('JWS verification result', { isValid, signerVerkeys, didDocumentKeysBase58 })
this.logger.trace('JWS verification result', { isValid, signerKeys, didDocumentKeysBase58 })

if (!isValid || !signerVerkeys.every((verkey) => didDocumentKeysBase58?.includes(verkey))) {
if (!isValid || !signerKeys.every((key) => didDocumentKeysBase58?.includes(key.publicKeyBase58))) {
const problemCode =
message instanceof DidExchangeRequestMessage
? DidExchangeProblemReportReason.RequestNotAccepted
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/modules/dids/DidsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class DidsApi {
*
* You can call `${@link DidsModule.resolve} to resolve the did document based on the did itself.
*/
public getCreatedDids({ method }: { method?: string } = {}) {
return this.didRepository.getCreatedDids(this.agentContext, { method })
public getCreatedDids({ method, did }: { method?: string; did?: string } = {}) {
return this.didRepository.getCreatedDids(this.agentContext, { method, did })
}
}
Loading