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
Show file tree
Hide file tree
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
Expand Down
3 changes: 2 additions & 1 deletion lib/CredentialIssuancePurpose.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
56 changes: 45 additions & 11 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ module.exports = {
* @property {object} statusResult
* @property {Array} results
* @property {object} error
* @property {object[]} log - Optional event log.
*/

/**
Expand Down Expand Up @@ -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
};
}
Expand Down Expand Up @@ -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') {
Expand All @@ -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;
}

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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}) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
23 changes: 22 additions & 1 deletion test/10-verify.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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}
]);
});
});
});
Expand Down