Skip to content

Add fine grained verifyCredential result log. #4

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

Merged
merged 1 commit into from
Jul 6, 2022
Merged
Changes from all commits
Commits
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 CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @digitalcredentials/vc ChangeLog

## 4.1.0 - 2022-07-06

### Added
- Add fine grained verification event `log` parameter to `verifyCredential()`
results.

## 4.0.0 - 2022-07-06

### Changed
3 changes: 2 additions & 1 deletion lib/CredentialIssuancePurpose.js
Original file line number Diff line number Diff line change
@@ -3,7 +3,8 @@
*/
'use strict';
const jsonld = require('@digitalcredentials/jsonld');
const {AssertionProofPurpose} = require('@digitalcredentials/jsonld-signatures').purposes;
const {AssertionProofPurpose} =
require('@digitalcredentials/jsonld-signatures').purposes;

/**
* Creates a proof purpose that will validate whether or not the verification
56 changes: 45 additions & 11 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -101,6 +101,7 @@ module.exports = {
* @property {object} statusResult
* @property {Array} results
* @property {object} error
* @property {object[]} log - Optional event log.
*/

/**
@@ -251,11 +252,11 @@ async function verifyCredential(options = {}) {
throw new TypeError(
'A "credential" property is required for verifying.');
}
return _verifyCredential(options);
return await _verifyCredential(options);
} catch(error) {
return {
verified: false,
results: [{credential, verified: false, error}],
results: [{credential, verified: false, error, log: error.log}],
error
};
}
@@ -285,8 +286,13 @@ async function verifyCredential(options = {}) {
async function _verifyCredential(options = {}) {
const {credential, checkStatus, now} = options;

// run common credential checks
_checkCredential({credential, now});
// Fine grained result log containing checks performed. Example:
// [{id: 'valid_signature', valid: true},
// {id: 'expiration', valid: true}]
const log = [];

// run common credential checks (add check results to log)
_checkCredential({credential, log, now});

// if credential status is provided, a `checkStatus` function must be given
if(credential.credentialStatus && typeof options.checkStatus !== 'function') {
@@ -302,21 +308,40 @@ async function _verifyCredential(options = {}) {
controller
});

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

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

if(credential.credentialStatus) {
log.push({id: 'valid_signature', valid: true});
log.push({id: 'issuer_did_resolves', valid: true});

if(checkStatus || credential.credentialStatus) {
result.statusResult = await checkStatus(options);

if(!result.statusResult.verified) {
result.verified = false;
log.push({id: 'revocation_status', valid: false});
} else {
log.push({id: 'revocation_status', valid: true});
}
} else {
log.push({id: 'revocation_status', valid: true});
}
result.log = log;
if(result.results) {
result.results[0].log = log;
}

return result;
}

@@ -537,12 +562,14 @@ function _checkPresentation(presentation) {
* @param {object} options - Options hashmap.
* @param {object} options.credential - An object that could be a
* VerifiableCredential.
* @param {object[]} [options.log] - Optional events log, for fine-grained
* verification result reporting.
* @param {string|Date} [options.now] - A string representing date time in
* ISO 8601 format or an instance of Date. Defaults to current date time.
* @throws {Error}
* @private
*/
function _checkCredential({credential, now = new Date()}) {
function _checkCredential({credential, log = [], now = new Date()}) {
if(typeof now === 'string') {
now = new Date(now);
}
@@ -636,14 +663,21 @@ function _checkCredential({credential, now = new Date()}) {
const {expirationDate} = credential;
// check if `expirationDate` property is a date
if(!dateRegex.test(expirationDate)) {
throw new Error(
log.push({id: 'expiration', valid: false});
const error = new Error(
`"expirationDate" must be a valid date: ${expirationDate}`);
error.log = log;
throw error;
}
// check if `now` is after `expirationDate`
if(now > new Date(expirationDate)) {
throw new Error('Credential has expired.');
log.push({id: 'expiration', valid: false});
const error = new Error('Credential has expired.');
error.log = log;
throw error;
}
}
log.push({id: 'expiration', valid: true});
}

function _validateUriId({id, propertyName}) {
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -75,7 +75,7 @@
"Credential"
],
"scripts": {
"test": "npm run test-node && npm run test-karma",
"test": "npm run test-node",
"test-node": "cross-env NODE_ENV=test mocha --preserve-symlinks -t 10000 test/*.spec.js",
"test-karma": "karma start karma.conf.js",
"lint": "eslint lib test/*.spec.js",
23 changes: 22 additions & 1 deletion test/10-verify.spec.js
Original file line number Diff line number Diff line change
@@ -173,6 +173,13 @@ describe('verify API (credentials)', () => {
throw result.error;
}
result.verified.should.be.true;

result.results[0].log.should.eql([
{id: 'expiration', valid: true},
{id: 'valid_signature', valid: true},
{id: 'issuer_did_resolves', valid: true},
{id: 'revocation_status', valid: true}
]);
});

it('should verify a vc with a positive status check', async () => {
@@ -192,6 +199,13 @@ describe('verify API (credentials)', () => {
throw result.error;
}
result.verified.should.be.true;

result.results[0].log.should.eql([
{id: 'expiration', valid: true},
{id: 'valid_signature', valid: true},
{id: 'issuer_did_resolves', valid: true},
{id: 'revocation_status', valid: true}
]);
});

describe('negative test', async () => {
@@ -288,7 +302,14 @@ describe('verify API (credentials)', () => {
if(result.error) {
throw result.error;
}
result.verified.should.be.true;
result.verified.should.be.false;

result.results[0].log.should.eql([
{id: 'expiration', valid: true},
{id: 'valid_signature', valid: true},
{id: 'issuer_did_resolves', valid: true},
{id: 'revocation_status', valid: false}
]);
});
});
});