Skip to content

Commit

Permalink
feat: JWE JWT compact agent methods
Browse files Browse the repository at this point in the history
  • Loading branch information
nklomp committed Oct 2, 2024
1 parent bf9a4b6 commit 6324f97
Show file tree
Hide file tree
Showing 11 changed files with 909 additions and 233 deletions.
54 changes: 54 additions & 0 deletions packages/jwt-service/__tests__/jwe.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as u8a from 'uint8arrays'
import {CompactJwtEncrypter} from "../src/functions/JWE";

describe('JWE test', () => {

const ietfPublicJwk = {
kty: 'EC',
crv: 'P-256',
x: 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
y: 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
alg: "ECDH-ES",
"use": "enc",
// d: 'jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI',
}

const ietfPrivateJwk = {
...ietfPublicJwk,
d: 'jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI',
}


it('should encrypt', async () => {
const pubKey = await crypto.subtle.importKey('jwk', ietfPublicJwk, {
name: 'ECDH',
namedCurve: 'P-256',
}, true, [])
const encrypter = new CompactJwtEncrypter({
alg: 'ECDH-ES',
enc: 'A256GCM',
keyManagementParams: {apu: u8a.fromString('apu'), apv: u8a.fromString('apv')},
key: pubKey
})

const encrypted = await encrypter.encryptCompactJWT({'hello': 'world'}, {})
console.log(encrypted)

const secKey = await crypto.subtle.importKey('jwk', ietfPrivateJwk, {
name: 'ECDH',
namedCurve: 'P-256',
}, true, ["deriveKey", "deriveBits"])
const decrypted = await CompactJwtEncrypter.decryptCompactJWT(encrypted, secKey )
console.log(JSON.stringify(decrypted, null, 2))
})

it ('should decrypt agent example', async () => {
const jwe = 'eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJ4IjoiYkIza0VMaWFtOTBEWExKVU8zQXFCa3RSMmd3TVFWSFBEWUJWUkJ3NEpWWSIsImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ5IjoiMXVNRTFlWHJQVjR2VVhiZHNYRGpBNno2NGMyYmQ3M0stWWtBVHlRRzNrTSJ9LCJhcHUiOiJZWEIxIiwiYXB2IjoiWVhCMiJ9..gT7grdO892xezIiy.mzWRiE0ajMnqVqVRs3medXCtH4knMBLGWWaPTap8CwCw_TpkVSV2azzz7MsTz6pjGo5iDHWa_AMxuGRCTZVBew.S5WfGjVhFnFwgqPtYdBJzQ'
const secKey = await crypto.subtle.importKey('jwk', ietfPrivateJwk, {
name: 'ECDH',
namedCurve: 'P-256',
}, true, ["deriveKey", "deriveBits"])
const decrypted = await CompactJwtEncrypter.decryptCompactJWT(jwe, secKey )
console.log(JSON.stringify(decrypted, null, 2))
})
})
216 changes: 126 additions & 90 deletions packages/jwt-service/__tests__/shared/jwtServiceTest.ts
Original file line number Diff line number Diff line change
@@ -1,98 +1,134 @@
import { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
import { JWK } from '@sphereon/ssi-types'
import { IDIDManager, IKeyManager, TAgent } from '@veramo/core'
import { IJwtService } from '../../src'
import {IIdentifierResolution} from '@sphereon/ssi-sdk-ext.identifier-resolution'
import {JWK} from '@sphereon/ssi-types'
import {IDIDManager, IKeyManager, TAgent} from '@veramo/core'
import {decodeJwt} from "jose";

import * as u8a from 'uint8arrays'
import {IJwtService} from '../../src'

type ConfiguredAgent = TAgent<IKeyManager & IDIDManager & IIdentifierResolution & IJwtService>

export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Promise<boolean>; tearDown: () => Promise<boolean> }) => {
let agent: ConfiguredAgent
// let key: IKey

const ietfJwk = {
kty: 'EC',
crv: 'P-256',
x: 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
y: 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
// d: 'jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI',
}
// tbe above key as hex
const privateKeyHex = '8E9B109E719098BF980487DF1F5D77E9CB29606EBED2263B5F57C213DF84F4B2'.toLowerCase()
const publicKeyHex = '037fcdce2770f6c45d4183cbee6fdb4b7b580733357be9ef13bacf6e3c7bd15445'
const kid = publicKeyHex

beforeAll(async () => {
await testContext.setup().then(() => (agent = testContext.getAgent()))
await agent.keyManagerImport({ kid: 'test', type: 'Secp256r1', kms: 'local', privateKeyHex })
})
afterAll(testContext.tearDown)

describe('jwt-service', () => {
it('should sign with ietf key', async () => {
const jwt = await agent.jwtCreateJwsCompactSignature({
// Example payloads from IETF spec
issuer: { identifier: kid, noIdentifierInHeader: true },
protectedHeader: { alg: 'ES256' },
payload: 'eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ',
})

const [header, payload, signature] = jwt.jwt.split('.')
expect(header).toStrictEqual('eyJhbGciOiJFUzI1NiJ9')
expect(payload).toStrictEqual('eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ')
// ES256 uses a nonce, so the signature will never be the same as the ietf version
expect(signature).toEqual('e4ZrhZdbFQ7630Tq51E6RQiJaae9bFNGJszIhtusEwzvO21rzH76Wer6yRn2Zb34VjIm3cVRl0iQctbf4uBY3w')
export default (testContext: {
getAgent: () => ConfiguredAgent;
setup: () => Promise<boolean>;
tearDown: () => Promise<boolean>
}) => {
let agent: ConfiguredAgent
// let key: IKey

const ietfJwk = {
kty: 'EC',
crv: 'P-256',
x: 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
y: 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
// d: 'jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI',
}
// tbe above key as hex
const privateKeyHex = '8E9B109E719098BF980487DF1F5D77E9CB29606EBED2263B5F57C213DF84F4B2'.toLowerCase()
const publicKeyHex = '037fcdce2770f6c45d4183cbee6fdb4b7b580733357be9ef13bacf6e3c7bd15445'
const kid = publicKeyHex

beforeAll(async () => {
await testContext.setup().then(() => (agent = testContext.getAgent()))
await agent.keyManagerImport({kid: 'test', type: 'Secp256r1', kms: 'local', privateKeyHex})
})
afterAll(testContext.tearDown)

describe('jwt-service', () => {
it('should sign with ietf key', async () => {
const jwt = await agent.jwtCreateJwsCompactSignature({
// Example payloads from IETF spec
issuer: {identifier: kid, noIdentifierInHeader: true},
protectedHeader: {alg: 'ES256'},
payload: 'eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ',
})

const [header, payload, signature] = jwt.jwt.split('.')
expect(header).toStrictEqual('eyJhbGciOiJFUzI1NiJ9')
expect(payload).toStrictEqual('eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ')
// ES256 uses a nonce, so the signature will never be the same as the ietf version
expect(signature).toEqual('e4ZrhZdbFQ7630Tq51E6RQiJaae9bFNGJszIhtusEwzvO21rzH76Wer6yRn2Zb34VjIm3cVRl0iQctbf4uBY3w')
})

it('should verify with ietf jwk', async () => {
const jwt = await agent.jwtCreateJwsCompactSignature({
// Example payloads from IETF spec
issuer: {identifier: kid, noIdentifierInHeader: true},
protectedHeader: {alg: 'ES256'},
payload: 'eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ',
})

const result = await agent.jwtVerifyJwsSignature({
jws: jwt.jwt,
jwk: ietfJwk as JWK,
})

it('should verify with ietf jwk', async () => {
const jwt = await agent.jwtCreateJwsCompactSignature({
// Example payloads from IETF spec
issuer: { identifier: kid, noIdentifierInHeader: true },
protectedHeader: { alg: 'ES256' },
payload: 'eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ',
})

const result = await agent.jwtVerifyJwsSignature({
jws: jwt.jwt,
jwk: ietfJwk as JWK,
})

expect(result).toMatchObject({
critical: false,
error: false,
jws: {
payload: 'eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ',
signatures: [
{
identifier: {
jwk: {
crv: 'P-256',
kty: 'EC',
x: 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
y: 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
expect(result).toMatchObject({
critical: false,
error: false,
jws: {
payload: 'eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ',
signatures: [
{
identifier: {
jwk: {
crv: 'P-256',
kty: 'EC',
x: 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
y: 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
},
jwks: [
{
jwk: {
crv: 'P-256',
kty: 'EC',
x: 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
y: 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
},
jwkThumbprint: 'oKIywvGUpTVTyxMQ3bwIIeQUudfr_CkLMjCE19ECD-U',
publicKeyHex: '037fcdce2770f6c45d4183cbee6fdb4b7b580733357be9ef13bacf6e3c7bd15445',
},
],
method: 'jwk',
},
protected: 'eyJhbGciOiJFUzI1NiJ9',
signature: 'e4ZrhZdbFQ7630Tq51E6RQiJaae9bFNGJszIhtusEwzvO21rzH76Wer6yRn2Zb34VjIm3cVRl0iQctbf4uBY3w',
},
],
},
jwks: [
{
jwk: {
crv: 'P-256',
kty: 'EC',
x: 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
y: 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
},
jwkThumbprint: 'oKIywvGUpTVTyxMQ3bwIIeQUudfr_CkLMjCE19ECD-U',
publicKeyHex: '037fcdce2770f6c45d4183cbee6fdb4b7b580733357be9ef13bacf6e3c7bd15445',
},
],
method: 'jwk',
},
protected: 'eyJhbGciOiJFUzI1NiJ9',
signature: 'e4ZrhZdbFQ7630Tq51E6RQiJaae9bFNGJszIhtusEwzvO21rzH76Wer6yRn2Zb34VjIm3cVRl0iQctbf4uBY3w',
},
],
},
message: 'Signature validated',
name: 'jws',
// verificationTime: expect.any(Date),
})
message: 'Signature validated',
name: 'jws',
// verificationTime: expect.any(Date),
})
})


it('should encrypt with public key', async () => {
const jwt = await agent.jwtCreateJwsCompactSignature({
// Example payloads from IETF spec
issuer: {identifier: kid, noIdentifierInHeader: true},
protectedHeader: {alg: 'ES256'},
payload: 'eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ',
})

const [header, payload, signature] = jwt.jwt.split('.')
expect(header).toStrictEqual('eyJhbGciOiJFUzI1NiJ9')
expect(payload).toStrictEqual('eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ')
// ES256 uses a nonce, so the signature will never be the same as the ietf version
expect(signature).toEqual('e4ZrhZdbFQ7630Tq51E6RQiJaae9bFNGJszIhtusEwzvO21rzH76Wer6yRn2Zb34VjIm3cVRl0iQctbf4uBY3w')


const jwe = await agent.jwtEncryptJweCompactJwt({
alg: "ECDH-ES",
enc: "A256GCM",
payload: decodeJwt(jwt.jwt),
keyManagementParams: {apu: u8a.fromString('apu'), apv: u8a.fromString('apv')},
// @ts-ignore
recipientKey: await agent.identifierExternalResolveByJwk({identifier: ietfJwk})
})

console.log(jwe)
})
})
})

}
2 changes: 2 additions & 0 deletions packages/jwt-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
"@sphereon/ssi-sdk-ext.x509-utils": "workspace:*",
"@sphereon/ssi-sdk-ext.identifier-resolution": "workspace:*",
"@sphereon/ssi-sdk-ext.key-manager": "workspace:*",
"@stablelib/random": "^1.0.2",
"jose": "^5.9.3",
"uint8arrays": "^3.1.1",
"jwt-decode": "^4.0.0",
"@veramo/core": "4.2.0",
Expand Down
Loading

0 comments on commit 6324f97

Please sign in to comment.