-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
372 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
const fs = require('fs'); | ||
const { v4: uuidv4 } = require('uuid'); | ||
const { schemas } = require('traceability-schemas'); | ||
const { issuer, verifier } = require('../utils'); | ||
|
||
const upgradeClaimset = (example) => { | ||
const claimset = JSON.parse(JSON.stringify(example)); | ||
// claimset['@context'][0] = 'https://www.w3.org/ns/credentials/v2'; | ||
claimset.id = `urn:uuid:${uuidv4()}`; | ||
if (claimset.issuer.id) { | ||
claimset.issuer.id = issuer.id; | ||
} else { | ||
claimset.issuer = issuer.id; | ||
} | ||
// TODO: https://github.com/w3c-ccg/traceability-vocab/issues/787 | ||
// claimset.validFrom = claimset.issuanceDate; | ||
// delete claimset.issuanceDate; | ||
delete claimset.proof; | ||
return claimset; | ||
}; | ||
|
||
describe('regression tests', () => { | ||
const fixture = { privateKeyJwk: issuer.privateKeyJwk, examples: [] }; | ||
schemas.forEach(async (schema) => { | ||
// only apply the upgrade to tagged schemas | ||
if (schema.tags && schema.tags.includes('VCDMv2')) { | ||
it(schema.title, async () => { | ||
const claimset = upgradeClaimset(JSON.parse(schema.example), { issuer }); | ||
// issuer validates against a schema before signing... | ||
const issuerValidation = await issuer | ||
.validator(claimset.credentialSchema.id) | ||
.validate(claimset); | ||
expect(issuerValidation).toBe(true); | ||
// credential issuance | ||
const vc = await issuer.signer.sign(claimset); | ||
// credential verification | ||
const { payload, protectedHeader } = await verifier.verify(vc); | ||
// verifier validates against a schema after verifying... | ||
const verifierValidation = await verifier | ||
.validator(claimset.credentialSchema.id) | ||
.validate(claimset); | ||
expect(verifierValidation).toBe(true); | ||
expect(protectedHeader.iss).toBe(issuer.id); | ||
expect(protectedHeader.cty).toBe('vc+ld+json'); | ||
fixture.examples.push({ issued: vc, verified: { protectedHeader, payload } }); | ||
}); | ||
} | ||
}); | ||
|
||
afterAll(() => { | ||
// all these examples will be broken because of the unresolved issue here: | ||
// https://github.com/w3c-ccg/traceability-vocab/issues/786 | ||
// except for the schemas that are tagged VCDMv2 | ||
fs.writeFileSync('./examples/v2.json', JSON.stringify(fixture, null, 2)); | ||
}); | ||
}); |
102 changes: 102 additions & 0 deletions
102
packages/traceability-tests/tests/sanity/context-validation.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
const Ajv = require('ajv'); | ||
const yaml = require('js-yaml'); | ||
|
||
const ajv = new Ajv({ | ||
strict: false, | ||
// In order to get strict mode back on, and warnings off, | ||
// We may need these if statements in the future | ||
// https://github.com/ajv-validator/ajv/issues/1417#issuecomment-1431435887 | ||
}); | ||
|
||
describe('v2', () => { | ||
const validate = ajv.compile( | ||
yaml.load(` | ||
title: Example Title | ||
description: Example Description | ||
type: object | ||
properties: | ||
'@context': | ||
type: array | ||
readOnly: true | ||
default: | ||
- https://www.w3.org/2018/credentials/v2 | ||
- https://w3id.org/traceability/v1 | ||
items: | ||
- type: string | ||
const: https://www.w3.org/2018/credentials/v2 | ||
additionalItems: | ||
type: string | ||
enum: | ||
- https://w3id.org/traceability/v1 | ||
- https://w3id.org/vc/status-list/2021/v1 | ||
- https://ref.gs1.org/gs1/vc/licence-context/ | ||
- https://ref.gs1.org/gs1/vc/trade-item-context/ | ||
- https://ref.gs1.org/gs1/vc/declaration-context/ | ||
`) | ||
); | ||
|
||
it('minimal context', async () => { | ||
const instance1 = { | ||
'@context': ['https://www.w3.org/2018/credentials/v2'], | ||
}; | ||
const validation1 = validate(instance1); | ||
expect(validation1).toBe(true); | ||
}); | ||
|
||
describe('extended contexts', () => { | ||
describe('valid', () => { | ||
it('https://w3id.org/traceability/v1', async () => { | ||
const instance1 = { | ||
'@context': [ | ||
'https://www.w3.org/2018/credentials/v2', | ||
'https://w3id.org/traceability/v1', | ||
], | ||
}; | ||
const validation1 = validate(instance1); | ||
expect(validation1).toBe(true); | ||
}); | ||
it('https://w3id.org/vc/status-list/2021/v1', async () => { | ||
const instance1 = { | ||
'@context': [ | ||
'https://www.w3.org/2018/credentials/v2', | ||
'https://w3id.org/vc/status-list/2021/v1', | ||
], | ||
}; | ||
const validation1 = validate(instance1); | ||
expect(validation1).toBe(true); | ||
}); | ||
it('https://ref.gs1.org/gs1/vc/licence-context/', async () => { | ||
const instance1 = { | ||
'@context': [ | ||
'https://www.w3.org/2018/credentials/v2', | ||
'https://ref.gs1.org/gs1/vc/licence-context/', | ||
], | ||
}; | ||
const validation1 = validate(instance1); | ||
expect(validation1).toBe(true); | ||
}); | ||
}); | ||
describe('invalid', () => { | ||
it('unknown url', async () => { | ||
const instance1 = { | ||
'@context': [ | ||
'https://www.w3.org/2018/credentials/v2', | ||
'https://extension.example/v42', // not in allow list | ||
], | ||
}; | ||
const validation1 = validate(instance1); | ||
expect(validation1).toBe(false); | ||
}); | ||
it('unknown object', async () => { | ||
const instance2 = { | ||
'@context': [ | ||
'https://www.w3.org/2018/credentials/v2', | ||
{ '@vocab': 'https://extension.example/#' }, // not in allow list | ||
], | ||
}; | ||
const validation2 = validate(instance2); | ||
expect(validation2).toBe(false); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// https://github.com/digitalbazaar/jsonld.js/issues/516#issuecomment-1485959372 | ||
const { kyPromise } = require('@digitalbazaar/http-client'); | ||
const { canonize } = require('jsonld'); | ||
|
||
beforeAll(async () => { | ||
// https://github.com/digitalbazaar/jsonld.js/issues/516#issuecomment-1485959372 | ||
await kyPromise; | ||
}); | ||
|
||
it('canonize', async () => { | ||
expect( | ||
await canonize( | ||
{ | ||
'@context': { '@vocab': 'https://www.w3.org/2018/credentials#' }, | ||
type: ['VerifiableCredential'], | ||
issuer: 'https://example.org/issuers/565049', | ||
issuanceDate: '2010-01-01T19:23:24Z', | ||
credentialSubject: { | ||
id: 'did:example:ebfeb1f712ebc6f1c276e12ec21', | ||
}, | ||
}, | ||
{ | ||
format: 'application/n-quads', | ||
} | ||
) | ||
) | ||
.toBe(`_:c14n0 <https://www.w3.org/2018/credentials#id> "did:example:ebfeb1f712ebc6f1c276e12ec21" . | ||
_:c14n1 <https://www.w3.org/2018/credentials#credentialSubject> _:c14n0 . | ||
_:c14n1 <https://www.w3.org/2018/credentials#issuanceDate> "2010-01-01T19:23:24Z" . | ||
_:c14n1 <https://www.w3.org/2018/credentials#issuer> "https://example.org/issuers/565049" . | ||
_:c14n1 <https://www.w3.org/2018/credentials#type> "VerifiableCredential" . | ||
`); | ||
}); |
49 changes: 49 additions & 0 deletions
49
packages/traceability-tests/tests/sanity/traceable-presentation.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// https://github.com/digitalbazaar/jsonld.js/issues/516#issuecomment-1485959372 | ||
const { kyPromise } = require('@digitalbazaar/http-client'); | ||
const { canonize } = require('jsonld'); | ||
|
||
beforeAll(async () => { | ||
// https://github.com/digitalbazaar/jsonld.js/issues/516#issuecomment-1485959372 | ||
await kyPromise; | ||
}); | ||
|
||
it('canonize', async () => { | ||
const data = await canonize( | ||
{ | ||
'@context': [ | ||
'https://www.w3.org/2018/credentials/v1', | ||
{ | ||
'@vocab': 'https://w3id.org/traceability#', | ||
TraceablePresentation: { | ||
'@id': 'https://w3id.org/traceability#TraceablePresentation', | ||
'@context': { | ||
'@vocab': 'https://w3id.org/traceability#' | ||
}, | ||
}, | ||
}, | ||
], | ||
id: 'urn:uuid:00000000-8596-4c3a-a978-8fcaba3903c5', | ||
type: ['VerifiablePresentation', 'TraceablePresentation'], | ||
workflow: { | ||
definition: ['urn:uuid:11111111-cc91-4bb3-91f1-5466a0be084e'], | ||
instance: ['urn:uuid:22222222-b0b1-41b8-89b0-331ni58b7ee0'], | ||
}, | ||
holder: { | ||
id: 'did:web:sender.example', | ||
type: 'Organization', | ||
}, | ||
}, | ||
{ | ||
format: 'application/n-quads', | ||
} | ||
) | ||
expect(data) | ||
.toBe(`<did:web:sender.example> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/traceability#Organization> . | ||
<urn:uuid:00000000-8596-4c3a-a978-8fcaba3903c5> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/traceability#TraceablePresentation> . | ||
<urn:uuid:00000000-8596-4c3a-a978-8fcaba3903c5> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://www.w3.org/2018/credentials#VerifiablePresentation> . | ||
<urn:uuid:00000000-8596-4c3a-a978-8fcaba3903c5> <https://w3id.org/traceability#workflow> _:c14n0 . | ||
<urn:uuid:00000000-8596-4c3a-a978-8fcaba3903c5> <https://www.w3.org/2018/credentials#holder> <did:web:sender.example> . | ||
_:c14n0 <https://w3id.org/traceability#definition> "urn:uuid:11111111-cc91-4bb3-91f1-5466a0be084e" . | ||
_:c14n0 <https://w3id.org/traceability#instance> "urn:uuid:22222222-b0b1-41b8-89b0-331ni58b7ee0" . | ||
`); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
const issuer = require('./issuer'); | ||
const verifier = require('./verifier'); | ||
|
||
const api = { issuer, verifier }; | ||
|
||
module.exports = api; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
const jose = require('jose'); | ||
const privateKeyJwk = require('./privateKeyJwk'); | ||
const validator = require('./validator'); | ||
|
||
const { d, ...publicKeyJwk } = privateKeyJwk; | ||
|
||
const id = `did:jwk:${jose.base64url.encode(JSON.stringify(publicKeyJwk))}`; | ||
|
||
const signer = (privateKey) => ({ | ||
sign: async ( | ||
claimset, | ||
header = { | ||
iss: id, | ||
kid: '#0', | ||
alg: publicKeyJwk.alg, | ||
cty: 'vc+ld+json', | ||
} | ||
) => { | ||
const jwt = await new jose.CompactSign(Buffer.from(JSON.stringify(claimset))) | ||
.setProtectedHeader(header) | ||
.sign(await jose.importJWK(privateKey)); | ||
return jwt; | ||
}, | ||
}); | ||
|
||
const api = { | ||
id, | ||
alg: publicKeyJwk.alg, | ||
signer: signer(privateKeyJwk), | ||
// exported here for test vectors, normally not exposed. | ||
privateKeyJwk, | ||
// normally this would be applied before sign above, exported here for test readability. | ||
...validator | ||
}; | ||
|
||
module.exports = api; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
const privateKeyJwk = { | ||
kty: 'EC', | ||
crv: 'P-384', | ||
alg: 'ES384', | ||
d: 'yJ8crrNOPXauJl2uHneamQp33w_iWW3pso3MGcpPvPioe5P5qnmywOBBkRMwhQFM', | ||
x: '9jsFRVVjqJY5JIyJr58gEBurjksjLpMBoYVCe6e0BzS8AS3LAyr7xKxSgPOiPcGM', | ||
y: 'bttL_fUktdr-LhxMczIjitdZVpoiiziPig8mg81bteTRm3BjZ-l8uNjvJKakgR3B', | ||
}; | ||
|
||
module.exports = privateKeyJwk; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
const Ajv = require('ajv'); | ||
const addFormats = require('ajv-formats').default; | ||
const jose = require('jose'); | ||
const axios = require('axios'); | ||
const yaml = require('js-yaml'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
|
||
const dereferenceSchema = async (id) => { | ||
// For example: https://w3c-ccg.github.io/traceability-vocab/openapi/components/schemas/credentials/ActivityPubActorCard.yml | ||
if (id.endsWith('/ActivityPubActorCard.yml')) { | ||
const schemaYml = fs | ||
.readFileSync( | ||
path.resolve( | ||
__dirname, | ||
'../../../docs/openapi/components/schemas/credentials/ActivityPubActorCard.yml' | ||
) | ||
) | ||
.toString(); | ||
const schemaJson = JSON.parse(JSON.stringify(yaml.load(schemaYml))); | ||
return schemaJson; | ||
} | ||
// handle relative refs... (they don't start with https) | ||
const base = 'https://w3id.org/traceability/openapi/components/schemas/'; | ||
if (!id.startsWith('https')) { | ||
const { data } = await axios.get(`${base}${id}`); | ||
const loaded = yaml.load(data); | ||
const json = JSON.parse(JSON.stringify(loaded)); | ||
return json; | ||
} | ||
throw new Error(`Unresolvable schema: ${id}`); | ||
}; | ||
|
||
const ajv = new Ajv({ | ||
strict: false, // see https://github.com/w3c-ccg/traceability-vocab/issues/786 | ||
loadSchema: dereferenceSchema, | ||
}); | ||
addFormats(ajv); | ||
|
||
const validator = (id) => ({ | ||
validate: async (instance) => { | ||
const schema = await dereferenceSchema(id); | ||
const validate = await ajv.compileAsync(schema); | ||
const valid = validate(instance); | ||
if (validate.errors) { | ||
console.error(validate.errors); | ||
} | ||
return valid; | ||
}, | ||
}); | ||
|
||
const api = { | ||
validator, | ||
}; | ||
|
||
module.exports = api; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
const jose = require('jose'); | ||
const validator = require('./validator'); | ||
|
||
// normally, you only accept certain issuers... | ||
// this is for testing purposes... | ||
const getPublicKey = async (didJwkUrl) => jose.importJWK(JSON.parse(jose.base64url.decode(didJwkUrl.split(':')[2].split('#')[0]))); | ||
|
||
const verifier = { | ||
verify: async (token) => { | ||
// decode header first, | ||
// don't look at payload until after verify. | ||
const { iss, kid } = jose.decodeProtectedHeader(token); | ||
const publicKey = await getPublicKey(`${iss}#${kid}`); | ||
const { payload, protectedHeader } = await jose.jwtVerify(token, publicKey); | ||
return { payload, protectedHeader }; | ||
}, | ||
// normally this would be applied before after verify above, exported here for test readability. | ||
...validator, | ||
}; | ||
|
||
const api = verifier; | ||
module.exports = api; |