Skip to content

Commit 7778ce6

Browse files
authored
Merge branch 'main' into example-usage-script
2 parents 2673e51 + 9975a7b commit 7778ce6

File tree

5 files changed

+78
-16
lines changed

5 files changed

+78
-16
lines changed

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
# @digitalbazaar/vc ChangeLog
22

3-
## 7.0.1 - 2024-xx-xx
3+
## 7.x.x - 2025-xx-xx
44

55
### Added
66
- Example roundtrip issue/verify script.
77

8+
## 7.1.0 - 2024-10-10
9+
10+
### Added
11+
- Add `maxClockSkew` parameter to time comparison functions with a default
12+
of 5 minutes. This also constitutes a fix for decentralized systems where
13+
clocks are not expected to be perfectly in sync.
14+
815
## 7.0.0 - 2024-08-01
916

1017
### Added

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ For signing, when setting up a signature suite, you will need to pass in
7272
a key pair containing a private key.
7373

7474
```js
75-
import vc from '@digitalbazaar/vc';
75+
import * as vc from '@digitalbazaar/vc';
7676

7777
// Required to set up a suite instance with private key
7878
import {Ed25519VerificationKey2020} from
@@ -94,7 +94,7 @@ Pre-requisites:
9494
Document and Public Key
9595

9696
```js
97-
const vc = require('@digitalbazaar/vc');
97+
import * as vc from '@digitalbazaar/vc';
9898

9999
// Sample unsigned credential
100100
const credential = {
@@ -477,8 +477,8 @@ Pre-requisites:
477477
// by requiring this first you ensure security
478478
// contexts are loaded from jsonld-signatures
479479
// and not an insecure source.
480+
import * as vc from '@digitalbazaar/vc';
480481
const {extendContextLoader} = require('jsonld-signatures');
481-
const vc = require('@digitalbazaar/vc');
482482
// @digitalbazaar/vc exports its own secure documentLoader.
483483
const {defaultDocumentLoader} = vc;
484484
// a valid json-ld @context.
@@ -516,6 +516,8 @@ Once you've created the presentation (either via `createPresentation()` or
516516
manually), you can sign it using `signPresentation()`:
517517

518518
```js
519+
import * as vc from '@digitalbazaar/vc';
520+
519521
const vp = await vc.signPresentation({
520522
presentation, suite, challenge, documentLoader
521523
});
@@ -578,6 +580,8 @@ Pre-requisites:
578580
To verify a verifiable presentation:
579581

580582
```js
583+
import * as vc from '@digitalbazaar/vc';
584+
581585
// challenge has been received from the requesting party - see 'challenge'
582586
// section below
583587

@@ -590,6 +594,8 @@ To verify an unsigned presentation, you must set the `unsignedPresentation`
590594
flag:
591595

592596
```js
597+
import * as vc from '@digitalbazaar/vc';
598+
593599
const result = await vc.verify({
594600
presentation, suite, documentLoader, unsignedPresentation: true
595601
});

lib/helpers.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,21 @@ export function getContextForVersion({version}) {
101101
export function checkContextVersion({credential, version}) {
102102
return getContextVersion({credential}) === version;
103103
}
104+
105+
/**
106+
* Compares two times with consideration of max clock skew
107+
*
108+
* @param {object} options - Options.
109+
* @param {number} options.t1 - time 1
110+
* @param {number} options.t2 - time 2
111+
* @param {number} options.maxClockSkew - number of seconds
112+
* @returns {number} - A number greater or less than zero
113+
*/
114+
export function compareTime({t1, t2, maxClockSkew}) {
115+
// `maxClockSkew` is in seconds, so transform to milliseconds
116+
if(Math.abs(t1 - t2) < (maxClockSkew * 1000)) {
117+
// times are equal within the max clock skew
118+
return 0;
119+
}
120+
return t1 < t2 ? -1 : 1;
121+
}

lib/index.js

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
assertCredentialContext,
3939
assertDateString,
4040
checkContextVersion,
41+
compareTime,
4142
getContextForVersion
4243
} from './helpers.js';
4344
import {documentLoader as _documentLoader} from './documentLoader.js';
@@ -103,6 +104,10 @@ export {CredentialIssuancePurpose};
103104
* @param {object} [options.documentLoader] - A document loader.
104105
* @param {string|Date} [options.now] - A string representing date time in
105106
* ISO 8601 format or an instance of Date. Defaults to current date time.
107+
* @param {number} [options.maxClockSkew=300] - A maximum number of seconds
108+
* that clocks may be skewed when checking capability expiration date-times
109+
* against `date` and when comparing invocation proof creation time against
110+
* delegation proof creation time.
106111
*
107112
* @throws {Error} If missing required properties.
108113
*
@@ -112,7 +117,8 @@ export async function issue({
112117
credential, suite,
113118
purpose = new CredentialIssuancePurpose(),
114119
documentLoader = defaultDocumentLoader,
115-
now
120+
now,
121+
maxClockSkew = 300
116122
} = {}) {
117123
// check to make sure the `suite` has required params
118124
// Note: verificationMethod defaults to publicKey.id, in suite constructor
@@ -135,7 +141,7 @@ export async function issue({
135141
}
136142

137143
// run common credential checks
138-
_checkCredential({credential, now, mode: 'issue'});
144+
_checkCredential({credential, now, mode: 'issue', maxClockSkew});
139145

140146
return jsigs.sign(credential, {purpose, documentLoader, suite});
141147
}
@@ -219,6 +225,10 @@ export async function derive({
219225
* credential status if `credentialStatus` is present on the credential.
220226
* @param {string|Date} [options.now] - A string representing date time in
221227
* ISO 8601 format or an instance of Date. Defaults to current date time.
228+
* @param {number} [options.maxClockSkew=300] - A maximum number of seconds
229+
* that clocks may be skewed when checking capability expiration date-times
230+
* against `date` and when comparing invocation proof creation time against
231+
* delegation proof creation time.
222232
*
223233
* @returns {Promise<VerifyPresentationResult>} The verification result.
224234
*/
@@ -264,6 +274,10 @@ export async function verify(options = {}) {
264274
* credential status if `credentialStatus` is present on the credential.
265275
* @param {string|Date} [options.now] - A string representing date time in
266276
* ISO 8601 format or an instance of Date. Defaults to current date time.
277+
* @param {number} [options.maxClockSkew=300] - A maximum number of seconds
278+
* that clocks may be skewed when checking capability expiration date-times
279+
* against `date` and when comparing invocation proof creation time against
280+
* delegation proof creation time.
267281
*
268282
* @returns {Promise<VerifyCredentialResult>} The verification result.
269283
*/
@@ -295,6 +309,10 @@ export async function verifyCredential(options = {}) {
295309
* definition in the `verify()` docstring, for this param.
296310
* @param {string|Date} [options.now] - A string representing date time in
297311
* ISO 8601 format or an instance of Date. Defaults to current date time.
312+
* @param {number} [options.maxClockSkew=300] - A maximum number of seconds
313+
* that clocks may be skewed when checking capability expiration date-times
314+
* against `date` and when comparing invocation proof creation time against
315+
* delegation proof creation time.
298316
*
299317
* @throws {Error} If required parameters are missing (in `_checkCredential`).
300318
*
@@ -306,10 +324,10 @@ export async function verifyCredential(options = {}) {
306324
* @returns {Promise<VerifyCredentialResult>} The verification result.
307325
*/
308326
async function _verifyCredential(options = {}) {
309-
const {credential, checkStatus, now} = options;
327+
const {credential, checkStatus, now, maxClockSkew} = options;
310328

311329
// run common credential checks
312-
_checkCredential({credential, now});
330+
_checkCredential({credential, now, maxClockSkew});
313331

314332
// if credential status is provided, a `checkStatus` function must be given
315333
if(credential.credentialStatus && typeof options.checkStatus !== 'function') {
@@ -352,6 +370,10 @@ async function _verifyCredential(options = {}) {
352370
* @param {string} [options.holder] - Optional presentation holder url.
353371
* @param {string|Date} [options.now] - A string representing date time in
354372
* ISO 8601 format or an instance of Date. Defaults to current date time.
373+
* @param {number} [options.maxClockSkew=300] - A maximum number of seconds
374+
* that clocks may be skewed when checking capability expiration date-times
375+
* against `date` and when comparing invocation proof creation time against
376+
* delegation proof creation time.
355377
* @param {number} [options.version = 2.0] - The VC context version to use.
356378
*
357379
* @throws {TypeError} If verifiableCredential param is missing.
@@ -362,7 +384,7 @@ async function _verifyCredential(options = {}) {
362384
* VerifiablePresentation.
363385
*/
364386
export function createPresentation({
365-
verifiableCredential, id, holder, now, version = 2.0
387+
verifiableCredential, id, holder, now, version = 2.0, maxClockSkew = 300
366388
} = {}) {
367389
const initialContext = getContextForVersion({version});
368390
const presentation = {
@@ -373,7 +395,7 @@ export function createPresentation({
373395
const credentials = [].concat(verifiableCredential);
374396
// ensure all credentials are valid
375397
for(const credential of credentials) {
376-
_checkCredential({credential, now});
398+
_checkCredential({credential, now, maxClockSkew});
377399
}
378400
presentation.verifiableCredential = credentials;
379401
}
@@ -456,6 +478,10 @@ export async function signPresentation(options = {}) {
456478
* credential status if `credentialStatus` is present on the credential.
457479
* @param {string|Date} [options.now] - A string representing date time in
458480
* ISO 8601 format or an instance of Date. Defaults to current date time.
481+
* @param {number} [options.maxClockSkew=300] - A maximum number of seconds
482+
* that clocks may be skewed when checking capability expiration date-times
483+
* against `date` and when comparing invocation proof creation time against
484+
* delegation proof creation time.
459485
*
460486
* @throws {Error} If presentation is missing required params.
461487
*
@@ -571,14 +597,18 @@ const mustHaveType = [
571597
* VerifiableCredential.
572598
* @param {string|Date} [options.now] - A string representing date time in
573599
* ISO 8601 format or an instance of Date. Defaults to current date time.
600+
* @param {number} [options.maxClockSkew=300] - A maximum number of seconds
601+
* that clocks may be skewed when checking capability expiration date-times
602+
* against `date` and when comparing invocation proof creation time against
603+
* delegation proof creation time.
574604
* @param {string} [options.mode] - The mode of operation for this
575605
* validation function, either `issue` or `verify`.
576606
*
577607
* @throws {Error}
578608
* @private
579609
*/
580610
export function _checkCredential({
581-
credential, now = new Date(), mode = 'verify'
611+
credential, now = new Date(), mode = 'verify', maxClockSkew = 300
582612
} = {}) {
583613
if(typeof now === 'string') {
584614
now = new Date(now);
@@ -617,15 +647,16 @@ export function _checkCredential({
617647
assertDateString({credential, prop: 'expirationDate'});
618648
if(mode === 'verify') {
619649
// check if `now` is after `expirationDate`
620-
if(now > new Date(credential.expirationDate)) {
650+
const expirationDate = new Date(credential.expirationDate);
651+
if(compareTime({t1: now, t2: expirationDate, maxClockSkew}) > 0) {
621652
throw new Error('Credential has expired.');
622653
}
623654
}
624655
}
625656
// check if `now` is before `issuanceDate` on verification
626657
if(mode === 'verify') {
627658
const issuanceDate = new Date(credential.issuanceDate);
628-
if(now < issuanceDate) {
659+
if(compareTime({t1: issuanceDate, t2: now, maxClockSkew}) > 0) {
629660
throw new Error(
630661
`The current date time (${now.toISOString()}) is before the ` +
631662
`"issuanceDate" (${credential.issuanceDate}).`);
@@ -639,7 +670,7 @@ export function _checkCredential({
639670
assertDateString({credential, prop: 'validUntil'});
640671
if(mode === 'verify') {
641672
validUntil = new Date(credential.validUntil);
642-
if(now > validUntil) {
673+
if(compareTime({t1: now, t2: validUntil, maxClockSkew}) > 0) {
643674
throw new Error(
644675
`The current date time (${now.toISOString()}) is after ` +
645676
`"validUntil" (${credential.validUntil}).`);
@@ -651,7 +682,7 @@ export function _checkCredential({
651682
if(mode === 'verify') {
652683
// check if `now` is before `validFrom`
653684
validFrom = new Date(credential.validFrom);
654-
if(now < validFrom) {
685+
if(compareTime({t1: validFrom, t2: now, maxClockSkew}) > 0) {
655686
throw new Error(
656687
`The current date time (${now.toISOString()}) is before ` +
657688
`"validFrom" (${credential.validFrom}).`);

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@digitalbazaar/vc",
3-
"version": "7.0.1-0",
3+
"version": "7.1.1-0",
44
"description": "Verifiable Credentials JavaScript library.",
55
"homepage": "https://github.com/digitalbazaar/vc",
66
"author": {

0 commit comments

Comments
 (0)