Skip to content

Commit

Permalink
Add a fallback/override for legacy OBv3 VCs.
Browse files Browse the repository at this point in the history
  • Loading branch information
dmitrizagidulin committed Jul 17, 2023
1 parent f7d200e commit fa4dd43
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 100 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# @digitalcredentials/vc ChangeLog

## 6.0.0 -
### Changed
- **BREAKING**: Add a fallback/override for legacy OBv3 VCs.

## 5.0.0 - 2022-11-03

### Changed
Expand Down
39 changes: 36 additions & 3 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ const jsonld = require('@digitalcredentials/jsonld');
const jsigs = require('@digitalcredentials/jsonld-signatures');
const {AuthenticationProofPurpose} = jsigs.purposes;
const CredentialIssuancePurpose = require('./CredentialIssuancePurpose');
const wrapWithLegacyLoader = require('./legacyDocumentLoader');

const defaultDocumentLoader = jsigs.extendContextLoader(
require('./documentLoader'));
const {constants: {CREDENTIALS_CONTEXT_V1_URL}} =
Expand Down Expand Up @@ -251,12 +253,15 @@ async function verify(options = {}) {
*/
async function verifyCredential(options = {}) {
const {credential} = options;
let result;
try {
if(!credential) {
throw new TypeError(
'A "credential" property is required for verifying.');
}
return await _verifyCredential(options);
result = await _verifyCredential(options);

return result;
} catch(error) {
if(error instanceof TypeError) {
throw error;
Expand Down Expand Up @@ -315,9 +320,20 @@ async function _verifyCredential(options = {}) {
throw error;
}

// if verification has already failed, skip status check
if(!result.verified) {
return result;
const contexts = credential['@context'];
// Custom processing to handle legacy OBv3 BETA VCs
if(Array.isArray(contexts) && contexts
.includes('https://purl.imsglobal.org/spec/ob/v3p0/context.json')) {

result = await _verifyOBv3LegacySignature(credential,
{purpose, documentLoader, ...options});
}

// if verification has already failed, skip status check
if(!result.verified) {
return result;
}
}

// run common credential checks (add check results to log)
Expand Down Expand Up @@ -352,6 +368,23 @@ async function _verifyCredential(options = {}) {
return result;
}

async function _verifyOBv3LegacySignature(credential,
{purpose, documentLoader, ...options}) {
let result;

const legacyLoader = wrapWithLegacyLoader(documentLoader);
try {
result = await jsigs.verify(
credential, {purpose, documentLoader: legacyLoader, ...options});
} catch(error) {
error.log = error.log &&
error.log.push({id: 'valid_signature', valid: false});
throw error;
}

return result;
}

/**
* Creates an unsigned presentation from a given verifiable credential.
*
Expand Down
20 changes: 20 additions & 0 deletions lib/legacyDocumentLoader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*!
* Copyright (c) 2023 Digital Credentials Consortium. All rights reserved.
*/
'use strict';

const obCtx = require('@digitalcredentials/open-badges-context');

module.exports = function wrapWithLegacyLoader(existingLoader) {
return async function documentLoader(url) {
if(url === 'https://purl.imsglobal.org/spec/ob/v3p0/context.json') {
return {
contextUrl: null,
documentUrl: url,
document: obCtx.contexts.get(obCtx.CONTEXT_URL_V3_BETA)
};
}

return existingLoader(url);
};
};
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
"dependencies": {
"@digitalbazaar/vc-status-list": "^7.0.0",
"@digitalcredentials/ed25519-signature-2020": "^3.0.2",
"@digitalcredentials/jsonld": "^5.2.2",
"@digitalcredentials/jsonld-signatures": "^9.3.1",
"@digitalcredentials/security-document-loader": "^2.0.0",
"@digitalcredentials/jsonld": "^6.0.0",
"@digitalcredentials/jsonld-signatures": "^9.3.2",
"@digitalcredentials/open-badges-context": "^2.0.1",
"@digitalcredentials/vc-status-list": "^5.0.2",
"credentials-context": "^2.0.0",
"fix-esm": "^1.0.1"
Expand All @@ -30,6 +30,7 @@
"@babel/runtime": "^7.13.9",
"@digitalbazaar/ed25519-signature-2018": "^2.0.1",
"@digitalbazaar/ed25519-verification-key-2018": "^3.0.0",
"@digitalcredentials/security-document-loader": "^3.1.0",
"babel-loader": "^8.2.2",
"chai": "^4.3.3",
"cross-env": "^7.0.3",
Expand Down
63 changes: 11 additions & 52 deletions test/10-verify.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ const {Ed25519VerificationKey2018} =
const jsigs = require('@digitalcredentials/jsonld-signatures');
const jsonld = require('@digitalcredentials/jsonld');
const {Ed25519Signature2018} = require('@digitalbazaar/ed25519-signature-2018');
const {Ed25519Signature2020} = require('@digitalcredentials/ed25519-signature-2020');
const {Ed25519Signature2020} =
require('@digitalcredentials/ed25519-signature-2020');
const CredentialIssuancePurpose = require('../lib/CredentialIssuancePurpose');
const { checkStatus } = require("fix-esm").require('@digitalcredentials/vc-status-list');

const { securityLoader } = require('@digitalcredentials/security-document-loader');
const {securityLoader} =
require('@digitalcredentials/security-document-loader');

const mockData = require('./mocks/mock.data');
const {v4: uuid} = require('uuid');
Expand All @@ -22,6 +22,7 @@ const MultiLoader = require('./MultiLoader');
const realContexts = require('../lib/contexts');
const invalidContexts = require('./contexts');
const mockCredential = require('./mocks/credential');
const legacyOBv3Credential = require('./mocks/credential-legacy-obv3');
const assertionController = require('./mocks/assertionController');
const mockDidDoc = require('./mocks/didDocument');
const mockDidKeys = require('./mocks/didKeys');
Expand Down Expand Up @@ -161,58 +162,16 @@ describe('vc.signPresentation()', () => {
});

describe('verify API (credentials)', () => {
it.skip('should verify a vc', async () => {
// verifiableCredential = await vc.issue({
// credential: mockCredential,
// suite
// });
const documentLoader = securityLoader({ fetchRemoteContexts: true }).build()
const verifiableCredential = {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://w3id.org/security/suites/ed25519-2020/v1",
"https://w3id.org/vc/status-list/2021/v1"
],
"id": "https://credentials-dcc-credentials-dev.raccoongang.com/verifiable_credentials/api/v1/status-list/2021/v1/did:key:z6MkkePoGJV8CQJJULSHHUEv71okD9PsrqXnZpNQuoUfb3id/",
"type": [
"VerifiableCredential",
"StatusList2021Credential"
],
"credentialSubject": {
"id": "https://credentials-dcc-credentials-dev.raccoongang.com/verifiable_credentials/api/v1/status-list/2021/v1/did:key:z6MkkePoGJV8CQJJULSHHUEv71okD9PsrqXnZpNQuoUfb3id/#list",
"encodedList": "H4sIAHo1bmQC/+3BAQ0AAADCoPdPbQ43oAAAAAAAAAAAAODfAC7KO00QJwAA",
"statusPurpose": "revocation",
"type": "StatusList2021"
},
"issuer": {
"id": "did:key:z6MkkePoGJV8CQJJULSHHUEv71okD9PsrqXnZpNQuoUfb3id"
},
"issuanceDate": "2023-05-24T16:02:32Z",
"proof": {
"type": "Ed25519Signature2020",
"proofPurpose": "assertionMethod",
"proofValue": "zyvtkuyYKhCgJ62j2oRvzmqADcpDzRhFvdcdbeVtEYnTeZ8zjhXNoyPvvUnmNUMEb13Ua7kSr1p5Lxp5EFup9Q5z",
"verificationMethod": "did:key:z6MkkePoGJV8CQJJULSHHUEv71okD9PsrqXnZpNQuoUfb3id#z6MkkePoGJV8CQJJULSHHUEv71okD9PsrqXnZpNQuoUfb3id",
"created": "2023-05-24T16:04:10.179Z"
},
"validFrom": "2023-05-24T16:02:32Z",
"issued": "2023-05-24T16:02:32Z"
}
const suite = new Ed25519Signature2020()

it('should verify an OBv3 vc', async () => {
const result = await vc.verifyCredential({
credential: verifiableCredential,
checkStatus,
suite,
documentLoader
credential: legacyOBv3Credential,
suite: new Ed25519Signature2020(),
documentLoader: securityLoader().build()
});

if(result.error) {
throw result.error;
}

console.log(result)

result.verified.should.be.true;

result.results[0].log.should.eql([
Expand Down Expand Up @@ -381,8 +340,8 @@ describe('verify API (presentations)', () => {
const {presentation, suite: vcSuite, documentLoader} =
await _generatePresentation({unsigned: true});

console.log(JSON.stringify(presentation, null, 2))
console.log(vcSuite)
console.log(JSON.stringify(presentation, null, 2));
console.log(vcSuite);

const result = await vc.verify({
documentLoader,
Expand Down
85 changes: 43 additions & 42 deletions test/20-checkStatus.spec.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,56 @@
const { checkStatus } = require("fix-esm").require('@digitalcredentials/vc-status-list');
/* eslint-disable */
const {checkStatus} = require('fix-esm').require('@digitalcredentials/vc-status-list');
const {Ed25519Signature2020} = require('@digitalcredentials/ed25519-signature-2020');
const { securityLoader } = require('@digitalcredentials/security-document-loader');
const {securityLoader} = require('@digitalcredentials/security-document-loader');
const vc = require('..');

const mockCredential = {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://w3id.org/security/suites/ed25519-2020/v1",
"https://w3id.org/dcc/v1",
"https://w3id.org/vc/status-list/2021/v1"
'@context': [
'https://www.w3.org/2018/credentials/v1',
'https://w3id.org/security/suites/ed25519-2020/v1',
'https://w3id.org/dcc/v1',
'https://w3id.org/vc/status-list/2021/v1'
],
"type": [
"VerifiableCredential",
"Assertion"
type: [
'VerifiableCredential',
'Assertion'
],
"issuer": {
"id": "did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC",
"name": "Example University",
"url": "https://cs.example.edu",
"image": "https://user-images.githubusercontent.com/947005/133544904-29d6139d-2e7b-4fe2-b6e9-7d1022bb6a45.png"
issuer: {
id: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC',
name: 'Example University',
url: 'https://cs.example.edu',
image: 'https://user-images.githubusercontent.com/947005/133544904-29d6139d-2e7b-4fe2-b6e9-7d1022bb6a45.png'
},
"issuanceDate": "2020-08-16T12:00:00.000+00:00",
"credentialSubject": {
"id": "did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC",
"name": "Kayode Ezike",
"hasCredential": {
"type": [
"EducationalOccupationalCredential"
issuanceDate: '2020-08-16T12:00:00.000+00:00',
credentialSubject: {
id: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC',
name: 'Kayode Ezike',
hasCredential: {
type: [
'EducationalOccupationalCredential'
],
"name": "GT Guide",
"description": "The holder of this credential is qualified to lead new student orientations."
name: 'GT Guide',
description: 'The holder of this credential is qualified to lead new student orientations.'
}
},
"expirationDate": "2025-08-16T12:00:00.000+00:00",
"credentialStatus": {
"id": "https://digitalcredentials.github.io/credential-status-playground/JWZM3H8WKU#3",
"type": "StatusList2021Entry",
"statusPurpose": "revocation",
"statusListIndex": 3,
"statusListCredential": "https://digitalcredentials.github.io/credential-status-playground/JWZM3H8WKU"
expirationDate: '2025-08-16T12:00:00.000+00:00',
credentialStatus: {
id: 'https://digitalcredentials.github.io/credential-status-playground/JWZM3H8WKU#3',
type: 'StatusList2021Entry',
statusPurpose: 'revocation',
statusListIndex: 3,
statusListCredential: 'https://digitalcredentials.github.io/credential-status-playground/JWZM3H8WKU'
},
"proof": {
"type": "Ed25519Signature2020",
"created": "2022-08-19T06:58:29Z",
"verificationMethod": "did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC#z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC",
"proofPurpose": "assertionMethod",
"proofValue": "z33Wy3kvx8UEoPHdQWYHVCXAjW19AZpA88NnikwfJqcH9oNmHyqSkt6wiVS31ewytAX7m2vneVEm8Awo4xzqKHYUp"
proof: {
type: 'Ed25519Signature2020',
created: '2022-08-19T06:58:29Z',
verificationMethod: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC#z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC',
proofPurpose: 'assertionMethod',
proofValue: 'z33Wy3kvx8UEoPHdQWYHVCXAjW19AZpA88NnikwfJqcH9oNmHyqSkt6wiVS31ewytAX7m2vneVEm8Awo4xzqKHYUp'
}
}
};

const documentLoader = securityLoader().build()
const documentLoader = securityLoader().build();

describe('checkStatus', () => {
it.skip('should verify', async () => {
Expand All @@ -61,6 +62,6 @@ describe('checkStatus', () => {
checkStatus
});

console.log(JSON.stringify(result, null, 2))
})
})
console.log(JSON.stringify(result, null, 2));
});
});
42 changes: 42 additions & 0 deletions test/mocks/credential-legacy-obv3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* eslint-disable */
const credential = {
"@context": ["https://www.w3.org/2018/credentials/v1", "https://purl.imsglobal.org/spec/ob/v3p0/context.json", "https://w3id.org/security/suites/ed25519-2020/v1"],
"id": "urn:uuid:e7af51df-d51f-4ac3-bb57-c229c0e61679",
"type": ["VerifiableCredential", "OpenBadgeCredential"],
"name": "Digital Credentials Consortium Demo",
"issuer": {
"type": ["Profile"],
"id": "did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC",
"name": "Digital Credentials Consortium",
"url": "https://dcconsortium.org/",
"image": "https://user-images.githubusercontent.com/752326/230469660-8f80d264-eccf-4edd-8e50-ea634d407778.png"
},
"issuanceDate": "2023-04-13T21:00:48.141Z",
"credentialSubject": {
"type": ["AchievementSubject"],
"achievement": {
"id": "urn:uuid:bd6d9316-f7ae-4073-a1e5-2f7f5bd22922",
"type": ["Achievement"],
"achievementType": "Badge",
"name": "Digital Credentials Consortium Demo",
"description": "Digital Credentials Consortium demo credential.",
"criteria": {
"type": "Criteria",
"narrative": "The recipient successfully installed Learner Credential Wallet (https://lcw.app/) and added a credential."
},
"image": {
"id": "https://user-images.githubusercontent.com/752326/214947713-15826a3a-b5ac-4fba-8d4a-884b60cb7157.png",
"type": "Image"
}
}
},
"proof": {
"type": "Ed25519Signature2020",
"created": "2023-04-13T21:00:48Z",
"verificationMethod": "did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC#z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC",
"proofPurpose": "assertionMethod",
"proofValue": "z5pBsZaMcEv76AvDtsWpNrCB2ZXp3ZVXSxdQovH8AVV5E8k8jUTpnZ8fFSDHHEdewq544Cdi2shH8gJdj6xidcxCz"
}
}

module.exports = credential

0 comments on commit fa4dd43

Please sign in to comment.