did-jwt
Version:
Library for Signing and Verifying JWTs that use DIDs as issuers and JWEs that use DIDs as recipients
1,261 lines (1,145 loc) • 54.5 kB
text/typescript
import { jest, describe, expect, it } from '@jest/globals'
import { base64ToBytes, bytesToBase64url, decodeBase64url, hexToBytes } from '../util.js'
import type { Resolvable, VerificationMethod } from 'did-resolver'
import { TokenVerifier } from 'jsontokens'
import MockDate from 'mockdate'
import { getAddress } from '@ethersproject/address'
import {
createJWS,
createJWT,
decodeJWT,
NBF_SKEW,
resolveAuthenticator,
SELF_ISSUED_V0_1,
SELF_ISSUED_V2,
verifyJWS,
verifyJWT,
} from '../JWT.js'
import { EdDSASigner } from '../signers/EdDSASigner.js'
import { ES256KSigner } from '../signers/ES256KSigner.js'
// add declarations for ES256 Tests
import { ES256Signer } from '../signers/ES256Signer'
// @ts-ignore
import jwt from 'jsonwebtoken'
// @ts-ignore
import jwkToPem from 'jwk-to-pem'
const NOW = 1485321133
MockDate.set(NOW * 1000 + 123)
const audAddress = '0x20c769ec9c0996ba7737a4826c2aaff00b1b2040'
const aud = `did:ethr:${audAddress}`
const address = '0xf3beac30c498d9e26865f34fcaa57dbb935b0d74'
const did = `did:ethr:${address}`
const alg = 'ES256K'
const privateKey = '278a5de700e29faae8e40e366ec5012b5ec63d36ec77e8a2417154cc1d25383f'
const publicKey = '03fdd57adec3d438ea237fe46b33ee1e016eda6b585c3e27ea66686c2ea5358479'
const verifier = new TokenVerifier(alg, publicKey)
const signer = ES256KSigner(hexToBytes(privateKey))
const recoverySigner = ES256KSigner(hexToBytes(privateKey), true)
const publicKeyJwk = {
crv: 'secp256k1',
kty: 'EC',
x: '_dV63sPUOOojf-RrM-4eAW7aa1hcPifqZmhsLqU1hHk',
y: 'Rjk_gUUlLupor-Z-KHs-2bMWhbpsOwAGCnO5sSQtaPc',
}
const didDocLegacy = {
'@context': 'https://w3id.org/did/v1',
id: did,
publicKey: [
{
id: `${did}#keys-1`,
type: 'Secp256k1VerificationKey2018',
owner: did,
publicKeyHex: publicKey,
},
],
authentication: [
{
type: 'Secp256k1SignatureAuthentication2018',
publicKey: `${did}#keys-1`,
},
],
}
const didDocJwk = {
'@context': 'https://w3id.org/did/v1',
id: did,
verificationMethod: [
{
id: `#keys-1`,
type: 'EcdsaSecp256k1VerificationKey2019',
controller: did,
publicKeyJwk,
},
],
authentication: [`#keys-1`],
}
const didDoc = {
didDocument: {
'@context': 'https://w3id.org/did/v1',
id: did,
verificationMethod: [
{
id: `${did}#keys-1`,
type: 'EcdsaSecp256k1VerificationKey2019',
controller: did,
publicKeyHex: publicKey,
},
],
authentication: [`${did}#keys-1`],
assertionMethod: [`${did}#keys-1`],
capabilityInvocation: [`${did}#keys-1`],
capabilityDelegation: [`${did}#some-key-that-does-not-exist`],
},
}
const audDidDoc = {
didDocument: {
'@context': 'https://w3id.org/did/v1',
id: aud,
verificationMethod: [
{
id: `${aud}#keys-1`,
type: 'EcdsaSecp256k1VerificationKey2019',
controller: did,
publicKeyHex: publicKey,
},
],
authentication: [`${aud}#keys-1`],
assertionMethod: [`${aud}#keys-1`],
capabilityInvocation: [`${aud}#keys-1`],
capabilityDelegation: [`${aud}#some-key-that-does-not-exist`],
},
}
describe('createJWT()', () => {
describe('ES256', () => {
const privateKey = '736f625c9dda78a94bb16840c82779bb7bc18014b8ede52f0f03429902fc4ba8'
const publicKey_x = '14c58e581c7656ba153195669fe4ce53ff78dd5ede60a4039771a90c58cb41de'
const publicKey_y = 'ec41869995bd661849414c523c7dff9a96f1c8dbc2e5e78172118f91c7199869'
// construct did:key for secp256r1 (unlike did for secp256k1 which is for an Ethereum Address)
// This originally was constructed by `encodeDIDfromHextString` imported from `did-key-creator`
// package, but that dependency was removed, so now `did` is just hardcoded
// const multicodecName = 'p256-pub';
// const publicKey = '0314c58e581c7656ba153195669fe4ce53ff78dd5ede60a4039771a90c58cb41de'
// const did = encodeDIDfromHexString(multicodecName,publicKey)
const did = 'did:key:zDnaej4NHntda4rNW4FBUJgFzdcgEAXKGRVGE8LuVfRbuMuc1'
const signer = ES256Signer(hexToBytes(privateKey))
// verifyTokenFormAndValidity
function verifyTokenFormAndValidity(token: string, pemPublic: string): boolean {
let result
try {
jwt.verify(token, pemPublic)
result = true
} catch (e: any) {
console.error(e.name + ': ' + e.message)
result = false
}
return result
}
// input public key in hex, and export pem
function publicToJWK(
publicPointHex_x: string,
publicPointHex_y: string,
kty_value: string,
crv_value: string
): JsonWebKey {
if (publicPointHex_x.length % 2 != 0) {
publicPointHex_x = '0' + publicPointHex_x
}
if (publicPointHex_y.length % 2 != 0) {
publicPointHex_y = '0' + publicPointHex_y
}
const publicPointUint8_x = hexToBytes(publicPointHex_x)
const publicPointBase64URL_x = bytesToBase64url(publicPointUint8_x)
const publicPointUint8_y = hexToBytes(publicPointHex_y)
const publicPointBase64URL_y = bytesToBase64url(publicPointUint8_y)
return {
kty: kty_value,
crv: crv_value,
x: publicPointBase64URL_x,
y: publicPointBase64URL_y,
}
}
it('creates a valid JWT', async () => {
expect.assertions(1)
const jwt = await createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer }, { alg: 'ES256' })
const pemPublic = jwkToPem(publicToJWK(publicKey_x, publicKey_y, 'EC', 'P-256') as any)
expect(verifyTokenFormAndValidity(jwt, pemPublic)).toBe(true)
})
it('creates a valid JWT using a MNID', async () => {
expect.assertions(1)
const jwt = await createJWT({ requested: ['name', 'phone'] }, { issuer: address, signer }, { alg: 'ES256' })
const pemPublic = jwkToPem(publicToJWK(publicKey_x, publicKey_y, 'EC', 'P-256') as any)
expect(verifyTokenFormAndValidity(jwt, pemPublic)).toBe(true)
})
it('creates a JWT with correct format', async () => {
expect.assertions(1)
const jwt = await createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer }, { alg: 'ES256' })
return expect(decodeJWT(jwt)).toMatchSnapshot()
})
it('creates a JWT with correct legacy format', async () => {
expect.assertions(1)
const jwt = await createJWT({ requested: ['name', 'phone'] }, { issuer: address, signer }, { alg: 'ES256' })
return expect(decodeJWT(jwt)).toMatchSnapshot()
})
it('creates a JWT with expiry in 10000 seconds', async () => {
expect.assertions(1)
const jwt = await createJWT(
{
requested: ['name', 'phone'],
nbf: Math.floor(new Date().getTime() / 1000),
},
{ issuer: did, signer, expiresIn: 10000 },
{ alg: 'ES256' }
)
const { payload } = decodeJWT(jwt)
return expect(payload.exp).toEqual(payload.nbf!! + 10000)
})
it('Uses iat if nbf is not defined but expiresIn is included', async () => {
expect.assertions(1)
const { payload } = decodeJWT(
await createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer, expiresIn: 10000 }, { alg: 'ES256' })
)
return expect(payload.exp).toEqual(payload.iat!! + 10000)
})
it('sets iat to the current time by default', async () => {
expect.assertions(1)
const timestamp = Math.floor(Date.now() / 1000)
const { payload } = decodeJWT(
await createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer }, { alg: 'ES256' })
)
return expect(payload.iat).toEqual(timestamp)
})
it('sets iat to the value passed in payload', async () => {
expect.assertions(1)
const timestamp = 2000000
const { payload } = decodeJWT(
await createJWT({ requested: ['name', 'phone'], iat: timestamp }, { issuer: did, signer }, { alg: 'ES256' })
)
return expect(payload.iat).toEqual(timestamp)
})
it('does not set iat if value in payload is undefined', async () => {
expect.assertions(1)
const { payload } = decodeJWT(
await createJWT({ requested: ['name', 'phone'], iat: undefined }, { issuer: did, signer }, { alg: 'ES256' })
)
return expect(payload.iat).toBeUndefined()
})
it('throws an error if unsupported algorithm is passed in', async () => {
expect.assertions(1)
await expect(
createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer, alg: 'BADALGO' })
).rejects.toThrowError('Unsupported algorithm BADALGO')
})
})
})
describe('createJWT()', () => {
describe('ES256K', () => {
it('creates a valid JWT', async () => {
expect.assertions(1)
const jwt = await createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer })
expect(verifier.verify(jwt)).toBe(true)
})
it('creates a valid JWT using a MNID', async () => {
expect.assertions(1)
const jwt = await createJWT({ requested: ['name', 'phone'] }, { issuer: address, signer })
expect(verifier.verify(jwt)).toBe(true)
})
it('creates a JWT with correct format', async () => {
expect.assertions(1)
const jwt = await createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer })
return expect(decodeJWT(jwt)).toMatchSnapshot()
})
it('creates a JWT with correct legacy format', async () => {
expect.assertions(1)
const jwt = await createJWT({ requested: ['name', 'phone'] }, { issuer: address, signer })
return expect(decodeJWT(jwt)).toMatchSnapshot()
})
it('creates a JWT with expiry in 10000 seconds', async () => {
expect.assertions(1)
const jwt = await createJWT(
{
requested: ['name', 'phone'],
nbf: Math.floor(new Date().getTime() / 1000),
},
{ issuer: did, signer, expiresIn: 10000 }
)
const { payload } = decodeJWT(jwt)
return expect(payload.exp).toEqual(payload.nbf!! + 10000)
})
it('Uses iat if nbf is not defined but expiresIn is included', async () => {
expect.assertions(1)
const { payload } = decodeJWT(
await createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer, expiresIn: 10000 })
)
return expect(payload.exp).toEqual(payload.iat!! + 10000)
})
it('sets iat to the current time by default', async () => {
expect.assertions(1)
const timestamp = Math.floor(Date.now() / 1000)
const { payload } = decodeJWT(await createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer }))
return expect(payload.iat).toEqual(timestamp)
})
it('sets iat to the value passed in payload', async () => {
expect.assertions(1)
const timestamp = 2000000
const { payload } = decodeJWT(
await createJWT({ requested: ['name', 'phone'], iat: timestamp }, { issuer: did, signer })
)
return expect(payload.iat).toEqual(timestamp)
})
it('does not set iat if value in payload is undefined', async () => {
expect.assertions(1)
const { payload } = decodeJWT(
await createJWT({ requested: ['name', 'phone'], iat: undefined }, { issuer: did, signer })
)
return expect(payload.iat).toBeUndefined()
})
it('throws an error if unsupported algorithm is passed in', async () => {
expect.assertions(1)
await expect(
createJWT({ requested: ['name', 'phone'] }, { issuer: did, signer, alg: 'BADALGO' })
).rejects.toThrowError('Unsupported algorithm BADALGO')
})
})
describe('Ed25519', () => {
const ed25519PrivateKey = 'nlXR4aofRVuLqtn9+XVQNlX4s1nVQvp+TOhBBtYls1IG+sHyIkDP/WN+rWZHGIQp+v2pyct+rkM4asF/YRFQdQ=='
const did = 'did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU'
const signer = EdDSASigner(base64ToBytes(ed25519PrivateKey))
const alg = 'Ed25519'
const resolver = {
resolve: jest.fn().mockReturnValue({
didDocumentMetadata: {},
didResolutionMetadata: {},
didDocument: {
id: 'did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU',
publicKey: [
{
id: 'did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU#key1',
type: 'ED25519SignatureVerification',
owner: 'did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU',
publicKeyBase64: 'BvrB8iJAz/1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU=',
},
],
authentication: [],
},
}),
} as Resolvable
it('creates a valid JWT with did:nacl issuer', async () => {
expect.assertions(1)
const jwt = await createJWT({ requested: ['name', 'phone'] }, { alg, issuer: did, signer })
const { payload } = await verifyJWT(jwt, { resolver })
expect(payload).toEqual({
iat: 1485321133,
iss: 'did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU',
requested: ['name', 'phone'],
})
})
it('can create a jwt in the default non-canonical way', async () => {
expect.assertions(1)
// Same payload, slightly different ordering
const jwtA = await createJWT(
{ reason: 'verification', requested: ['name', 'phone'] },
{ alg, issuer: did, signer }
)
const jwtB = await createJWT(
{ requested: ['name', 'phone'], reason: 'verification' },
{ alg, issuer: did, signer }
)
expect(jwtA).not.toEqual(jwtB)
})
it('can create a jwt in a canonical way', async () => {
expect.assertions(1)
// Same payload, slightly different ordering
const jwtA = await createJWT(
{ reason: 'verification', requested: ['name', 'phone'] },
{ alg, issuer: did, signer, canonicalize: true }
)
const jwtB = await createJWT(
{ requested: ['name', 'phone'], reason: 'verification' },
{ alg, issuer: did, signer, canonicalize: true }
)
expect(jwtA).toEqual(jwtB)
})
it('creates a JWT with correct format', async () => {
expect.assertions(1)
const jwt = await createJWT({ requested: ['name', 'phone'] }, { alg, issuer: did, signer })
return expect(decodeJWT(jwt)).toMatchSnapshot()
})
it('creates a JWT with expiry in 10000 seconds', async () => {
expect.assertions(1)
const jwt = await createJWT(
{
requested: ['name', 'phone'],
nbf: Math.floor(new Date().getTime() / 1000),
},
{ alg, issuer: did, signer, expiresIn: 10000 }
)
const { payload } = decodeJWT(jwt)
return expect(payload.exp).toEqual(payload.nbf!! + 10000)
})
})
})
describe('verifyJWT() for ES256', () => {
// const privateKey = '736f625c9dda78a94bb16840c82779bb7bc18014b8ede52f0f03429902fc4ba8'
const publicKey = '0314c58e581c7656ba153195669fe4ce53ff78dd5ede60a4039771a90c58cb41de'
const did = 'did:key:zDnaej4NHntda4rNW4FBUJgFzdcgEAXKGRVGE8LuVfRbuMuc1'
const didDoc = {
didDocument: {
'@context': 'https://w3id.org/did/v1',
id: did,
verificationMethod: [
{
id: `${did}#keys-1`,
type: 'EcdsaSecp256r1VerificationKey2019',
controller: did,
publicKeyHex: publicKey,
},
],
authentication: [`${did}#keys-1`],
assertionMethod: [`${did}#keys-1`],
capabilityInvocation: [`${did}#keys-1`],
capabilityDelegation: [`${did}#some-key-that-does-not-exist`],
},
}
const resolver = {
resolve: jest.fn(async (didUrl: string) => {
if (didUrl.includes(did)) {
return {
didDocument: didDoc.didDocument,
didDocumentMetadata: {},
didResolutionMetadata: { contentType: 'application/did+ld+json' },
}
}
return {
didDocument: null,
didDocumentMetadata: {},
didResolutionMetadata: {
error: 'notFound',
message: 'resolver_error: DID document not found',
},
}
}),
} as Resolvable
describe('pregenerated JWT', () => {
const incomingJwt =
'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0ODUzMjExMzMsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXSwiaXNzIjoiZGlkOmtleTp6RG5hZWo0TkhudGRhNHJOVzRGQlVKZ0Z6ZGNnRUFYS0dSVkdFOEx1VmZSYnVNdWMxIn0.aMYFY0jitx2Bq9_wGBhEeIyVvzr2XkouCyEP662P8TbAPTpXOC3UrGQONaPD7wleLrMhGdvfod7idSxKXLl64Q'
it('verifies the JWT and return correct payload', async () => {
expect.assertions(1)
const { payload } = await verifyJWT(incomingJwt, { resolver })
return expect(payload).toMatchSnapshot()
})
it('verifies the JWT and return correct profile', async () => {
expect.assertions(1)
const {
didResolutionResult: { didDocument },
} = await verifyJWT(incomingJwt, { resolver })
return expect(didDocument).toEqual(didDoc.didDocument)
})
it('verifies the JWT and return correct did for the iss', async () => {
expect.assertions(1)
const { issuer } = await verifyJWT(incomingJwt, { resolver })
return expect(issuer).toEqual(did)
})
it('verifies the JWT and return correct signer', async () => {
expect.assertions(1)
const { signer } = await verifyJWT(incomingJwt, { resolver })
return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
})
it('verifies the JWT requiring authentication and return correct signer', async () => {
expect.assertions(1)
const { signer } = await verifyJWT(incomingJwt, { resolver, auth: true })
return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
})
it('verifies the JWT requiring authentication proofPurpose and return correct signer', async () => {
expect.assertions(1)
const { signer } = await verifyJWT(incomingJwt, { resolver, proofPurpose: 'authentication' })
return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
})
it('verifies the JWT requiring assertionMethod and return correct signer', async () => {
expect.assertions(1)
const { signer } = await verifyJWT(incomingJwt, { resolver, proofPurpose: 'assertionMethod' })
return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
})
it('verifies the JWT requiring capabilityInvocation and return correct signer', async () => {
expect.assertions(1)
const { signer } = await verifyJWT(incomingJwt, { resolver, proofPurpose: 'capabilityInvocation' })
return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
})
it('rejects the JWT requiring capabilityDelegation when not present in document', async () => {
expect.assertions(1)
await expect(() =>
verifyJWT(incomingJwt, { resolver, proofPurpose: 'capabilityDelegation' })
).rejects.toThrowError(
`DID document for ${did} does not have public keys suitable for ES256 with capabilityDelegation purpose`
)
})
it('rejects the JWT requiring unknown proofPurpose', async () => {
expect.assertions(1)
await expect(() => verifyJWT(incomingJwt, { resolver, proofPurpose: 'impossible' as any })).rejects.toThrowError(
`DID document for ${did} does not have public keys suitable for ES256 with impossible purpose`
)
})
})
})
describe('verifyJWT() for ES256K', () => {
const resolver = {
resolve: jest.fn(async (didUrl: string) => {
if (didUrl.includes(did)) {
return {
didDocument: didDoc.didDocument,
didDocumentMetadata: {},
didResolutionMetadata: { contentType: 'application/did+ld+json' },
}
}
if (didUrl.includes(aud)) {
return {
didDocument: audDidDoc.didDocument,
didDocumentMetadata: {},
didResolutionMetadata: { contentType: 'application/did+ld+json' },
}
}
return {
didDocument: null,
didDocumentMetadata: {},
didResolutionMetadata: {
error: 'notFound',
message: 'resolver_error: DID document not found',
},
}
}),
} as Resolvable
describe('pregenerated JWT', () => {
const incomingJwt =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXSwiaXNzIjoiZGlkOmV0aHI6MHhmM2JlYWMzMGM0OThkOWUyNjg2NWYzNGZjYWE1N2RiYjkzNWIwZDc0In0.tU96omPNxCfQoEADOpLywXUDCMjKXOfTaG61EZwmfvHJrDFQhNbSDzCP2Pe7WdXySosTCuI1T-IQ6SddcWuj_A'
it('verifies the JWT and return correct payload', async () => {
expect.assertions(1)
const { payload } = await verifyJWT(incomingJwt, { resolver })
return expect(payload).toMatchSnapshot()
})
it('verifies the JWT and return correct profile', async () => {
expect.assertions(1)
const {
didResolutionResult: { didDocument },
} = await verifyJWT(incomingJwt, { resolver })
return expect(didDocument).toEqual(didDoc.didDocument)
})
it('verifies the JWT and return correct did for the iss', async () => {
expect.assertions(1)
const { issuer } = await verifyJWT(incomingJwt, { resolver })
return expect(issuer).toEqual(did)
})
it('verifies the JWT and return correct signer', async () => {
expect.assertions(1)
const { signer } = await verifyJWT(incomingJwt, { resolver })
return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
})
it('verifies the JWT requiring authentication and return correct signer', async () => {
expect.assertions(1)
const { signer } = await verifyJWT(incomingJwt, { resolver, auth: true })
return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
})
it('verifies the JWT requiring authentication proofPurpose and return correct signer', async () => {
expect.assertions(1)
const { signer } = await verifyJWT(incomingJwt, { resolver, proofPurpose: 'authentication' })
return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
})
it('verifies the JWT requiring assertionMethod and return correct signer', async () => {
expect.assertions(1)
const { signer } = await verifyJWT(incomingJwt, { resolver, proofPurpose: 'assertionMethod' })
return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
})
it('verifies the JWT requiring capabilityInvocation and return correct signer', async () => {
expect.assertions(1)
const { signer } = await verifyJWT(incomingJwt, { resolver, proofPurpose: 'capabilityInvocation' })
return expect(signer).toEqual(didDoc.didDocument.verificationMethod[0])
})
it('rejects the JWT requiring capabilityDelegation when not present in document', async () => {
expect.assertions(1)
await expect(() =>
verifyJWT(incomingJwt, { resolver, proofPurpose: 'capabilityDelegation' })
).rejects.toThrowError(
`DID document for ${did} does not have public keys suitable for ES256K with capabilityDelegation purpose`
)
})
it('rejects the JWT requiring unknown proofPurpose', async () => {
expect.assertions(1)
await expect(() => verifyJWT(incomingJwt, { resolver, proofPurpose: 'impossible' as any })).rejects.toThrowError(
`DID document for ${did} does not have public keys suitable for ES256K with impossible purpose`
)
})
})
describe('pregenerated JWT with publicKeyJwk in DID doc', () => {
const incomingJwt =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsImlzcyI6ImRpZDpldGhyOjB4OTBlNDVkNzViZDEyNDZlMDkyNDg3MjAxODY0N2RiYTk5NmE4ZTdiOSIsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXX0.KIG2zUO8Quf3ucb9jIncZ1CmH0v-fAZlsKvesfsd9x4RzU0qrvinVd9d30DOeZOwdwEdXkET_wuPoOECwU0IKA'
const jwkResolver = { resolve: jest.fn().mockReturnValue(didDocJwk) } as Resolvable
it('verifies the JWT and return correct payload', async () => {
expect.assertions(1)
const { payload } = await verifyJWT(incomingJwt, { resolver: jwkResolver })
return expect(payload).toMatchObject({
iat: 1485321133,
iss: 'did:ethr:0x90e45d75bd1246e0924872018647dba996a8e7b9',
requested: ['name', 'phone'],
})
})
it('verifies the JWT and return correct payload when using assertionMethod', async () => {
expect.assertions(1)
const { payload } = await verifyJWT(incomingJwt, { resolver: jwkResolver, proofPurpose: 'assertionMethod' })
return expect(payload).toMatchObject({
iat: 1485321133,
iss: 'did:ethr:0x90e45d75bd1246e0924872018647dba996a8e7b9',
requested: ['name', 'phone'],
})
})
})
describe('pregenerated JWT with legacy resolver', () => {
const incomingJwt =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsImlzcyI6ImRpZDpldGhyOjB4OTBlNDVkNzViZDEyNDZlMDkyNDg3MjAxODY0N2RiYTk5NmE4ZTdiOSIsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXX0.KIG2zUO8Quf3ucb9jIncZ1CmH0v-fAZlsKvesfsd9x4RzU0qrvinVd9d30DOeZOwdwEdXkET_wuPoOECwU0IKA'
const legacyResolver = { resolve: jest.fn().mockReturnValue(didDocLegacy) } as Resolvable
it('verifies the JWT and return correct payload', async () => {
expect.assertions(1)
const { payload } = await verifyJWT(incomingJwt, { resolver: legacyResolver })
return expect(payload).toMatchSnapshot()
})
it('verifies the JWT and return correct profile', async () => {
expect.assertions(1)
const {
didResolutionResult: { didDocument },
} = await verifyJWT(incomingJwt, { resolver: legacyResolver })
return expect(didDocument).toEqual(didDocLegacy)
})
it('verifies the JWT and return correct did for the iss', async () => {
expect.assertions(1)
const { issuer } = await verifyJWT(incomingJwt, { resolver: legacyResolver })
return expect(issuer).toEqual('did:ethr:0x90e45d75bd1246e0924872018647dba996a8e7b9')
})
it('verifies the JWT and return correct signer', async () => {
expect.assertions(1)
const { signer } = await verifyJWT(incomingJwt, { resolver: legacyResolver })
return expect(signer).toEqual(didDocLegacy.publicKey[0])
})
it('verifies the JWT requiring authentication and return correct signer', async () => {
expect.assertions(1)
const { signer } = await verifyJWT(incomingJwt, { resolver: legacyResolver, auth: true })
return expect(signer).toEqual(didDocLegacy.publicKey[0])
})
it('verifies the JWT requiring assertionMethod and return correct signer', async () => {
expect.assertions(1)
const { signer } = await verifyJWT(incomingJwt, { resolver: legacyResolver, proofPurpose: 'assertionMethod' })
return expect(signer).toEqual(didDocLegacy.publicKey[0])
})
})
describe('badJwt', () => {
const badJwt =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsImlzcyI6ImRpZDpldGhyOjB4MjBjNzY5ZWM5YzA5OTZiYTc3MzdhNDgyNmMyYWFmZjAwYjFiMjA0MCIsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXX0.TTpuw77fUbd_AY3GJcCumd6F6hxnkskMDJYNpJlI2DQi5MKKudXya9NlyM9e8-KFgTLe-WnXgq9EjWLvjpdiXA'
it('rejects a JWT with bad signature', async () => {
expect.assertions(1)
await expect(verifyJWT(badJwt, { resolver })).rejects.toThrowError(
/invalid_signature: no matching public key found/
)
})
})
describe('validFrom timestamp', () => {
it('passes when nbf is in the past', async () => {
expect.assertions(1)
const jwt =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsIm5iZiI6MTQ4NTI2MTEzMywiaXNzIjoiZGlkOmV0aHI6MHhmM2JlYWMzMGM0OThkOWUyNjg2NWYzNGZjYWE1N2RiYjkzNWIwZDc0In0.FUasGkOYqGVxQ7S-QQvh4abGO6Dwr961UjjOxtRTyUDnl6q6ElqHqAK-WMDTmOir21pFPKLYZMtLZ4LTLpm3cQ'
// const jwt = await createJWT({nbf: PAST}, {issuer:did, signer})
await expect(verifyJWT(jwt, { resolver })).resolves.not.toThrow()
})
it('passes when nbf is in the past and iat is in the future', async () => {
expect.assertions(1)
const jwt =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzODExMzMsIm5iZiI6MTQ4NTI2MTEzMywiaXNzIjoiZGlkOmV0aHI6MHhmM2JlYWMzMGM0OThkOWUyNjg2NWYzNGZjYWE1N2RiYjkzNWIwZDc0In0.8BPiSG2e6UBn1osnJ6PJYbPjtPMPaCeutTA9OCp-ZzI-QvvwPCVrrWqTu2YELbzUPwDIJCQ8v8N77xCEjIYSmQ'
// const jwt = await createJWT({nbf:PAST,iat:FUTURE},{issuer:did,signer})
await expect(verifyJWT(jwt, { resolver })).resolves.not.toThrow()
})
it('fails when nbf is in the future', async () => {
expect.assertions(1)
const jwt =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsIm5iZiI6MTQ4NTM4MTEzMywiaXNzIjoiZGlkOnVwb3J0OjJuUXRpUUc2Q2dtMUdZVEJhYUtBZ3I3NnVZN2lTZXhVa3FYIn0.rcFuhVHtie3Y09pWxBSf1dnjaVh6FFQLHh-83N-uLty3M5ADJ-jVFFkyt_Eupl8Kr735-oPGn_D1Nj9rl4s_Kw'
// const jwt = await createJWT({nbf:FUTURE},{issuer:did,signer})
await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError()
})
it('passes when nbf is in the future and policy for nbf is false', async () => {
expect.assertions(2)
// const jwt = await createJWT({nbf:FUTURE},{issuer:did,signer})
const jwt = await createJWT(
{ requested: ['name', 'phone'], nbf: new Date().getTime() + 1000000 },
{
issuer: did,
signer,
}
)
expect(verifier.verify(jwt)).toBe(true)
const { payload } = await verifyJWT(jwt, { resolver, policies: { nbf: false } })
return expect(payload).toBeDefined()
})
it('passes when nbf is in the future and now is provided to be higher than nbf', async () => {
expect.assertions(2)
// const jwt = await createJWT({nbf:FUTURE},{issuer:did,signer})
const jwt = await createJWT(
{ requested: ['name', 'phone'], nbf: new Date().getTime() + 10000 },
{
issuer: did,
signer,
}
)
expect(verifier.verify(jwt)).toBe(true)
const { payload } = await verifyJWT(jwt, { resolver, policies: { now: new Date().getTime() + 100000 } })
return expect(payload).toBeDefined()
})
it('fails when nbf is in the future and iat is in the past', async () => {
expect.assertions(1)
const jwt =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUyNjExMzMsIm5iZiI6MTQ4NTM4MTEzMywiaXNzIjoiZGlkOmV0aHI6MHhmM2JlYWMzMGM0OThkOWUyNjg2NWYzNGZjYWE1N2RiYjkzNWIwZDc0In0.JjEn_huxI9SsBY_3PlD0ShpXvrRgUGFDKAgxJBc1Q5GToVpUTw007-o9BTt7JNi_G2XWmcu2aXXnDn0QFsRIrg'
// const jwt = await createJWT({nbf:FUTURE,iat:PAST},{issuer:did,signer})
await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError()
})
it('passes when nbf is in the future and iat is in the past with nbf policy false', async () => {
expect.assertions(1)
const jwt =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUyNjExMzMsIm5iZiI6MTQ4NTM4MTEzMywiaXNzIjoiZGlkOmV0aHI6MHhmM2JlYWMzMGM0OThkOWUyNjg2NWYzNGZjYWE1N2RiYjkzNWIwZDc0In0.JjEn_huxI9SsBY_3PlD0ShpXvrRgUGFDKAgxJBc1Q5GToVpUTw007-o9BTt7JNi_G2XWmcu2aXXnDn0QFsRIrg'
// const jwt = await createJWT({nbf:FUTURE,iat:PAST},{issuer:did,signer})
const { payload } = await verifyJWT(jwt, { resolver, policies: { nbf: false } })
expect(payload).toBeDefined()
})
it('passes when nbf is missing and iat is in the past', async () => {
expect.assertions(1)
const jwt =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUyNjExMzMsImlzcyI6ImRpZDpldGhyOjB4ZjNiZWFjMzBjNDk4ZDllMjY4NjVmMzRmY2FhNTdkYmI5MzViMGQ3NCJ9.jkzN5kIVtuRU-Fjte8w5r-ttf9OfhdN38oFJd61CWdI5WnvU1dPCvnx1_kdk2D6Xg-uPqp1VXAb7KA2ZECivmg'
// const jwt = await createJWT({iat:PAST},{issuer:did,signer})
await expect(verifyJWT(jwt, { resolver })).resolves.not.toThrowError()
})
it('fails when nbf is missing and iat is in the future', async () => {
expect.assertions(1)
const jwt =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzODExMzMsImlzcyI6ImRpZDpldGhyOjB4ZjNiZWFjMzBjNDk4ZDllMjY4NjVmMzRmY2FhNTdkYmI5MzViMGQ3NCJ9.FJuHvf9Tby7b4I54Cm1nh8CvLg4QH2wt2K0WfyQaLqlr3NKKI5hAdLalgZksI25gLhNrZwQFnC-nzEOs9PI1SQ'
// const jwt = await createJWT({iat:FUTURE},{issuer:did,signer})
await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError()
})
it('passes when nbf is missing and iat is in the future with iat policy to be false', async () => {
expect.assertions(1)
const jwt =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzODExMzMsImlzcyI6ImRpZDpldGhyOjB4ZjNiZWFjMzBjNDk4ZDllMjY4NjVmMzRmY2FhNTdkYmI5MzViMGQ3NCJ9.FJuHvf9Tby7b4I54Cm1nh8CvLg4QH2wt2K0WfyQaLqlr3NKKI5hAdLalgZksI25gLhNrZwQFnC-nzEOs9PI1SQ'
// const jwt = await createJWT({iat:FUTURE},{issuer:did,signer})
const { payload } = await verifyJWT(jwt, { resolver, policies: { iat: false } })
return expect(payload).toBeDefined()
})
it('passes when nbf and iat are both missing', async () => {
expect.assertions(1)
const jwt =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6ZXRocjoweGYzYmVhYzMwYzQ5OGQ5ZTI2ODY1ZjM0ZmNhYTU3ZGJiOTM1YjBkNzQifQ.KgnwgMMz-QSOtpba2QMGHMWJoLvhp-H4odjjX1QKnqj4-8dkcK12y7rj7Zq24-1d-1ne86aJCdWtx5VJv3rM7w'
// const jwt = await createJWT({iat:undefined},{issuer:did,signer})
await expect(verifyJWT(jwt, { resolver })).resolves.not.toThrowError()
})
})
it('handles ES256K-R algorithm', async () => {
expect.assertions(1)
const jwt = await createJWT({ hello: 'world' }, { issuer: did, signer: recoverySigner, alg: 'ES256K-R' })
const { payload } = await verifyJWT(jwt, { resolver })
return expect(payload).toMatchSnapshot()
})
it('handles ES256K-R algorithm with publicKeyHex address', async () => {
expect.assertions(1)
const jwt = await createJWT({ hello: 'world' }, { issuer: aud, signer: recoverySigner, alg: 'ES256K-R' })
const { payload } = await verifyJWT(jwt, { resolver })
return expect(payload).toMatchSnapshot()
})
it('handles ES256K algorithm with ethereum address - github #14', async () => {
expect.assertions(1)
const ethResolver = {
resolve: jest.fn().mockReturnValue({
didDocument: {
id: did,
publicKey: [
{
id: `${did}#keys-1`,
type: 'Secp256k1VerificationKey2018',
owner: did,
ethereumAddress: address,
},
],
},
}),
} as Resolvable
const jwt = await createJWT({ hello: 'world' }, { issuer: aud, signer, alg: 'ES256K' })
const { payload } = await verifyJWT(jwt, { resolver: ethResolver })
return expect(payload).toMatchSnapshot()
})
it('handles ES256K algorithm with blockchainAccountId - github #14, #155', async () => {
expect.assertions(1)
const ethResolver = {
resolve: jest.fn().mockReturnValue({
didDocument: {
id: did,
publicKey: [
{
id: `${did}#keys-1`,
type: 'EcdsaSecp256k1RecoveryMethod2020',
owner: did,
blockchainAccountId: `${address}@eip155:1`,
},
],
},
}),
} as Resolvable
const jwt = await createJWT({ hello: 'world' }, { issuer: aud, signer, alg: 'ES256K' })
const { payload } = await verifyJWT(jwt, { resolver: ethResolver })
return expect(payload).toMatchSnapshot()
})
it('handles ES256K-R algorithm with checksum address in blockchainAccountId - github #231', async () => {
expect.assertions(1)
const verificationMethod = {
id: `${did}#keys-1`,
type: 'EcdsaSecp256k1RecoveryMethod2020',
owner: did,
blockchainAccountId: `eip155:1:${getAddress(address)}`,
}
const ethResolver = {
resolve: jest.fn().mockReturnValue({
didDocument: {
id: did,
verificationMethod: [verificationMethod],
},
}),
} as Resolvable
const jwt = await createJWT({ hello: 'world' }, { issuer: aud, signer: recoverySigner, alg: 'ES256K-R' })
const result = await verifyJWT(jwt, { resolver: ethResolver })
return expect(result.signer).toEqual(verificationMethod)
})
it('accepts a valid exp', async () => {
expect.assertions(1)
const jwt = await createJWT({ exp: NOW }, { issuer: did, signer })
const { payload } = await verifyJWT(jwt, { resolver })
return expect(payload).toBeDefined()
})
it('rejects an expired JWT', async () => {
expect.assertions(1)
const jwt = await createJWT({ exp: NOW - NBF_SKEW - 1 }, { issuer: did, signer })
await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(/JWT has expired/)
})
it('accepts an expired JWT with exp policy false', async () => {
expect.assertions(1)
const jwt = await createJWT({ exp: NOW - NBF_SKEW - 1 }, { issuer: did, signer })
const { payload } = await verifyJWT(jwt, { resolver, policies: { exp: false } })
return expect(payload).toBeDefined()
})
it('rejects an expired JWT without skew time', async () => {
expect.assertions(1)
const jwt = await createJWT({ exp: NOW - 1 }, { issuer: did, signer })
await expect(verifyJWT(jwt, { resolver, skewTime: 0 })).rejects.toThrowError(/JWT has expired/)
})
it('accepts an expired JWT without skew time but exp policy false', async () => {
expect.assertions(1)
const jwt = await createJWT({ exp: NOW - 1 }, { issuer: did, signer })
const { payload } = await verifyJWT(jwt, { resolver, skewTime: 0, policies: { exp: false } })
return expect(payload).toBeDefined()
})
it('accepts a valid audience', async () => {
expect.assertions(1)
const jwt = await createJWT({ aud }, { issuer: did, signer })
const { payload } = await verifyJWT(jwt, { resolver, audience: aud })
return expect(payload).toMatchSnapshot()
})
it('accepts multiple audiences', async () => {
expect.assertions(1)
const jwt = await createJWT({ aud: [did, aud] }, { issuer: did, signer })
const { payload } = await verifyJWT(jwt, { resolver, audience: aud })
return expect(payload).toMatchSnapshot()
})
it('rejects invalid multiple audiences', async () => {
expect.assertions(1)
const jwt = await createJWT({ aud: [did, did] }, { issuer: did, signer })
await expect(verifyJWT(jwt, { resolver, audience: aud })).rejects.toThrowError(
/JWT audience does not match your DID/
)
})
it('accepts a valid audience using callback_url', async () => {
expect.assertions(1)
const jwt = await createJWT({ aud: 'http://pututu.uport.me/unique' }, { issuer: did, signer })
const { payload } = await verifyJWT(jwt, {
resolver,
callbackUrl: 'http://pututu.uport.me/unique',
})
return expect(payload).toMatchSnapshot()
})
it('rejects invalid audience', async () => {
expect.assertions(1)
const jwt = await createJWT({ aud }, { issuer: did, signer })
await expect(verifyJWT(jwt, { resolver, audience: did })).rejects.toThrowError(
/JWT audience does not match your DID or callback url/
)
})
it('accepts invalid audience when override policy is used', async () => {
expect.assertions(2)
const jwt = await createJWT({ aud }, { issuer: did, signer })
const { payload, issuer } = await verifyJWT(jwt, {
resolver,
policies: { aud: false },
})
expect(payload).toBeDefined()
expect(issuer).toEqual(did)
})
it('rejects an invalid audience using callback_url where callback is wrong', async () => {
expect.assertions(1)
const jwt = await createJWT({ aud: 'http://pututu.uport.me/unique' }, { issuer: did, signer })
await expect(
verifyJWT(jwt, {
resolver,
callbackUrl: 'http://pututu.uport.me/unique/1',
})
).rejects.toThrowError(/JWT audience does not match your DID or callback url/)
})
it('rejects an invalid audience using callback_url where callback is missing', async () => {
expect.assertions(1)
const jwt = await createJWT({ aud: 'http://pututu.uport.me/unique' }, { issuer: did, signer })
await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(
'JWT audience is required but your app address has not been configured'
)
})
it('rejects invalid audience as no address is present', async () => {
expect.assertions(1)
const jwt = await createJWT({ aud }, { issuer: did, signer })
await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(
/JWT audience is required but your app address has not been configured/
)
})
it('rejects a pregenerated JWT without iss or client_id', async () => {
expect.assertions(1)
const jwt =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzN9.aa3_8ZH99MjFoHTrNjOm7Pgq5VL5A13DHR5MTd_dBw2B_pWgNuz4N1tbrocTP0MgDlRbovKmTTDrGNjNMPqH3g'
await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(/JWT iss or client_id are required/)
})
it('rejects a self-issued v2 JWT without sub', async () => {
expect.assertions(1)
const jwt = await createJWT({}, { issuer: SELF_ISSUED_V2, signer })
await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(/JWT sub is required/)
})
it('rejects a self-issued v2 JWT (sub type: did) with an invalid payload.sub DID', async () => {
expect.assertions(2)
const jwt = await createJWT({ sub: 'sub' }, { issuer: SELF_ISSUED_V2, signer })
await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(/DID document not found/)
expect(resolver.resolve).toHaveBeenCalledWith('sub', { accept: 'application/did+json' })
})
it('accepts a self-issued v2 JWT (sub type: did) with a valid payload.sub DID', async () => {
expect.assertions(1)
const jwt = await createJWT({ sub: did }, { issuer: SELF_ISSUED_V2, signer })
const { payload } = await verifyJWT(jwt, { resolver })
return expect(payload).toBeDefined()
})
it('rejects a self-issued v2 JWT (sub type: jkt) without a header.kid DID', async () => {
expect.assertions(1)
const jwt = await createJWT({ sub: 'sub', sub_jwk: {} }, { issuer: SELF_ISSUED_V2, signer })
await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(/No DID has been found in the JWT/)
})
it('rejects a self-issued v2 JWT (sub type: jkt) with an invalid header.kid DID', async () => {
expect.assertions(2)
const jwt = await createJWT({ sub: 'sub', sub_jwk: {} }, { issuer: SELF_ISSUED_V2, signer }, { kid: 'kid' })
await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(/DID document not found/)
expect(resolver.resolve).toHaveBeenCalledWith('kid', { accept: 'application/did+json' })
})
it('accepts a self-issued v2 JWT (sub type: jkt) with a valid header.kid DID', async () => {
expect.assertions(1)
const jwt = await createJWT({ sub: 'sub', sub_jwk: {} }, { issuer: SELF_ISSUED_V2, signer }, { kid: did })
const { payload } = await verifyJWT(jwt, { resolver })
return expect(payload).toBeDefined()
})
it('rejects a self-issued v0.1 JWT without did property', async () => {
expect.assertions(1)
const jwt = await createJWT({}, { issuer: SELF_ISSUED_V0_1, signer })
await expect(verifyJWT(jwt, { resolver })).rejects.toThrowError(/JWT did is required/)
})
it('accepts a self-issued v0.1 JWT with did property', async () => {
expect.assertions(1)
const jwt = await createJWT({ did }, { issuer: SELF_ISSUED_V0_1, signer })
const { payload } = await verifyJWT(jwt, { resolver })
return expect(payload).toBeDefined()
})
})
describe('JWS', () => {
it('createJWS works with JSON payload', async () => {
expect.assertions(2)
const payload = { some: 'data' }
const jws = await createJWS(payload, signer)
expect(jws).toMatchSnapshot()
expect(JSON.parse(decodeBase64url(jws.split('.')[1]))).toEqual(payload)
})
it('createJWS can canonicalize a JSON payload', async () => {
expect.assertions(3)
const payload = { z: 'z', a: 'a' }
const jws = await createJWS(payload, signer, {}, { canonicalize: true })
expect(jws).toMatchSnapshot()
const parsedPayload = JSON.parse(decodeBase64url(jws.split('.')[1]))
expect(parsedPayload).toEqual(payload)
expect(JSON.stringify(parsedPayload)).toEqual(JSON.stringify({ a: 'a', z: 'z' }))
})
it('createJWS works with base64url payload', async () => {
expect.assertions(2)
// use the hex public key as an arbitrary payload
const encodedPayload = bytesToBase64url(hexToBytes(publicKey))
const jws = await createJWS(encodedPayload, signer)
expect(jws).toMatchSnapshot()
expect(jws.split('.')[1]).toEqual(encodedPayload)
})
it('verifyJWS works with JSON payload', async () => {
expect.assertions(1)
const payload = { some: 'data' }
const jws = await createJWS(payload, signer)
expect(() => verifyJWS(jws, { publicKeyHex: publicKey } as VerificationMethod)).not.toThrow()
})
it('verifyJWS works with base64url payload', async () => {
expect.assertions(1)
const encodedPayload = bytesToBase64url(hexToBytes(publicKey))
const jws = await createJWS(encodedPayload, signer)
expect(() => verifyJWS(jws, { publicKeyHex: publicKey } as VerificationMethod)).not.toThrow()
})
it('verifyJWS fails with bad input', async () => {
expect.assertions(1)
const badJws = 'abrewguer.fjreoiwfoiew.foirheogu.reoguhwehrg'
expect(() => verifyJWS(badJws, { publicKeyHex: publicKey } as VerificationMethod)).toThrow('Incorrect format JWS')
})
})
describe('resolveAuthenticator()', () => {
const ecKey1 = {
id: `${did}#keys-1`,
type: 'Secp256k1VerificationKey2018',
owner: did,
publicKeyHex:
'04613bb3a4874d27032618f020614c21cbe4c4e4781687525f6674089f9bd3d6c7f6eb13569053d31715a3ba32e0b791b97922af6387f087d6b5548c06944ab061',
}
const ecKey2 = {
id: `${did}#keys-2`,
type: 'Secp256k1SignatureVerificationKey2018',
owner: did,
publicKeyHex:
'04613bb3a4874d27032618f020614c21cbe4c4e4781687525f6674089f9bd3d6c7f6eb13569053d31715a3ba32e0b791b97922af6387f087d6b5548c06944ab062',
}
const ecKey3 = {
id: `${did}#keys-3`,
type: 'Secp256k1SignatureVerificationKey2018',
owner: did,
publicKeyHex:
'04613bb3a4874d27032618f020614c21cbe4c4e4781687525f6674089f9bd3d6c7f6eb13569053d31715a3ba32e0b791b97922af6387f087d6b5548c06944ab063',
}
const encKey1 = {
id: `${did}#keys-4`,
type: 'Curve25519EncryptionPublicKey',
owner: did,
publicKeyBase64: 'QCFPBLm5pwmuTOu+haxv0+Vpmr6Rrz/DEEvbcjktQnQ=',
}
const edKey = {
id: `${did}#keys-5`,
type: 'ED25519SignatureVerification',
owner: did,
publicKeyBase64: 'BvrB8iJAz/1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU=',
}
const edKey2 = {
id: `${did}#keys-6`,
type: 'ED25519SignatureVerification',
owner: did,
publicKeyBase64: 'SI+tzELqRb8XKuRE3Cj7uWGgkEQ86X87ZjhGAok+Ujc=',
}
const authKey1 = {
type: 'Secp256k1SignatureAuthentication2018',
publicKey: ecKey1.id,
}
const authKey2 = {
type: 'Secp256k1SignatureAuthentication2018',
publicKey: ecKey2.id,
}
const edAuthKey = {
type: 'ED25519SigningAuthentication',
publicKey: edKey.id,
}
const edKey6 = {
id: `${did}#keys-auth6`,
type: 'ED25519SignatureVerification',
owner: did,
publicKeyBase58: 'dummyvalue',
}
const ecKey7 = {
id: `${did}#keys-auth7`,
type: 'EcdsaSecp256k1VerificationKey2019',
owner: did,
publicKeyBase58: 'dummyvalue',
}
const edKey8 = {
id: `${did}#keys-auth8`,
type: 'Ed25519VerificationKey2018',
owner: did,
publicKeyBase58: 'dummyvalue',
}
const singleKey = {
didDocument: {
'@context': 'https://w3id.org/did/v1',
id: did,
publicKey: [ecKey1],
},
}
const multipleKeysLegacy = {
didDocument: {
'@context': 'https://w3id.org/did/v1',
id: did,
publicKey: [ecKey1, ecKey2, ecKey3, encKey1, edKey, edKey2],
authentication: [authKey1, authKey2, edAuthKey],
},
}
const multipleAuthTypes = {
didDocument: {
'@context': 'https://w3id.org/did/v1',
id: did,
publicKey: [ecKey1, ecKey2, ecKey3, encKey1, edKey, edKey2, edKey6, ecKey7],
authentication: [authKey1, authKey2, edAuthKey, `${did}#keys-auth6`, `${did}#keys-auth7`, edKey8],
},
}
const unsupportedFormat = {
didDocument: {
'@context': 'https://w3id.org/did/v1',
id: did,
publicKey: [encKey1],
},
}
const noPublicKey = {
didDocument: {
'@context': 'https://w3id.org/did/v1',
id: did,
},
}
describe('DID', () => {
describe('ES256K', () => {
it('finds public key', async () => {
expect.assertions(1)
const authenticators = await resolveAuthenticator(
{ resolve: jest.fn().mockReturnValue(singleKey) } as Resolvable,
alg,
did
)
return expect(authenticators).toEqual({
authenticators: [ecKey1],
issuer: did,
didResolutionResult: singleKey,
})
})
it('filters out irrelevant public keys', async () => {
expect.assertions(1)
const authenticators = await resolveAuthenticator(
{ resolve: jest.fn().mockReturnValue(multipleKeysLegacy) } as Resolvable,
alg,
did
)
return expect(authenticators).toEqual({
authenticators: [ecKey1, ecKey2, ecKey3],
issuer: did,
didResolutionResult: multipleKeysLegacy,
})
})
it('only list authenticators able to authenticate a user', async () => {
expect.assertions(1)
const authenticators = await resolveAuthenticator(
{ resolve: jest.fn().mockReturnValue(multipleKeysLegacy) } as Resolvable,
alg,
did,
'authentication'
)
return expect(authenticators).toEqual({
authenticators: [ecKey1, ecKey2],
issuer: did,
didResolutionResult: multipleKeysLegacy,