Skip to content
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

Add max clock skew #189

Merged
merged 4 commits into from
Oct 10, 2024
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# @digitalbazaar/vc ChangeLog

## 7.0.1 - 2024-10-xx

### Added
- Add maxClockSkew variable to time comparison functions.

## 7.0.0 - 2024-08-01

### Added
Expand Down
18 changes: 18 additions & 0 deletions lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,21 @@ export function getContextForVersion({version}) {
export function checkContextVersion({credential, version}) {
return getContextVersion({credential}) === version;
}

/**
* Compares two times with consideration of max clock skew
*
* @param {object} options - Options.
* @param {number} options.t1 - time 1
* @param {number} options.t2 - time 2
* @param {number} options.maxClockSkew - number of seconds
* @returns {number} - A number greater or less than zero
*/
export function compareTime({t1, t2, maxClockSkew}) {
// `maxClockSkew` is in seconds, so transform to milliseconds
if(Math.abs(t1 - t2) < (maxClockSkew * 1000)) {
// times are equal within the max clock skew
return 0;
}
return t1 < t2 ? -1 : 1;
}
53 changes: 42 additions & 11 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
assertCredentialContext,
assertDateString,
checkContextVersion,
compareTime,
getContextForVersion
} from './helpers.js';
import {documentLoader as _documentLoader} from './documentLoader.js';
Expand Down Expand Up @@ -103,6 +104,10 @@ export {CredentialIssuancePurpose};
* @param {object} [options.documentLoader] - A document loader.
* @param {string|Date} [options.now] - A string representing date time in
* ISO 8601 format or an instance of Date. Defaults to current date time.
* @param {number} [options.maxClockSkew=300] - A maximum number of seconds
* that clocks may be skewed when checking capability expiration date-times
* against `date` and when comparing invocation proof creation time against
* delegation proof creation time.
*
* @throws {Error} If missing required properties.
*
Expand All @@ -112,7 +117,8 @@ export async function issue({
credential, suite,
purpose = new CredentialIssuancePurpose(),
documentLoader = defaultDocumentLoader,
now
now,
maxClockSkew = 300
} = {}) {
// check to make sure the `suite` has required params
// Note: verificationMethod defaults to publicKey.id, in suite constructor
Expand All @@ -135,7 +141,7 @@ export async function issue({
}

// run common credential checks
_checkCredential({credential, now, mode: 'issue'});
_checkCredential({credential, now, mode: 'issue', maxClockSkew});

return jsigs.sign(credential, {purpose, documentLoader, suite});
}
Expand Down Expand Up @@ -219,6 +225,10 @@ export async function derive({
* credential status if `credentialStatus` is present on the credential.
* @param {string|Date} [options.now] - A string representing date time in
* ISO 8601 format or an instance of Date. Defaults to current date time.
* @param {number} [options.maxClockSkew=300] - A maximum number of seconds
* that clocks may be skewed when checking capability expiration date-times
* against `date` and when comparing invocation proof creation time against
* delegation proof creation time.
*
* @returns {Promise<VerifyPresentationResult>} The verification result.
*/
Expand Down Expand Up @@ -264,6 +274,10 @@ export async function verify(options = {}) {
* credential status if `credentialStatus` is present on the credential.
* @param {string|Date} [options.now] - A string representing date time in
* ISO 8601 format or an instance of Date. Defaults to current date time.
* @param {number} [options.maxClockSkew=300] - A maximum number of seconds
* that clocks may be skewed when checking capability expiration date-times
* against `date` and when comparing invocation proof creation time against
* delegation proof creation time.
*
* @returns {Promise<VerifyCredentialResult>} The verification result.
*/
Expand Down Expand Up @@ -295,6 +309,10 @@ export async function verifyCredential(options = {}) {
* definition in the `verify()` docstring, for this param.
* @param {string|Date} [options.now] - A string representing date time in
* ISO 8601 format or an instance of Date. Defaults to current date time.
* @param {number} [options.maxClockSkew=300] - A maximum number of seconds
* that clocks may be skewed when checking capability expiration date-times
* against `date` and when comparing invocation proof creation time against
* delegation proof creation time.
*
* @throws {Error} If required parameters are missing (in `_checkCredential`).
*
Expand All @@ -306,10 +324,10 @@ export async function verifyCredential(options = {}) {
* @returns {Promise<VerifyCredentialResult>} The verification result.
*/
async function _verifyCredential(options = {}) {
const {credential, checkStatus, now} = options;
const {credential, checkStatus, now, maxClockSkew} = options;

// run common credential checks
_checkCredential({credential, now});
_checkCredential({credential, now, maxClockSkew});

// if credential status is provided, a `checkStatus` function must be given
if(credential.credentialStatus && typeof options.checkStatus !== 'function') {
Expand Down Expand Up @@ -352,6 +370,10 @@ async function _verifyCredential(options = {}) {
* @param {string} [options.holder] - Optional presentation holder url.
* @param {string|Date} [options.now] - A string representing date time in
* ISO 8601 format or an instance of Date. Defaults to current date time.
* @param {number} [options.maxClockSkew=300] - A maximum number of seconds
* that clocks may be skewed when checking capability expiration date-times
* against `date` and when comparing invocation proof creation time against
* delegation proof creation time.
* @param {number} [options.version = 2.0] - The VC context version to use.
*
* @throws {TypeError} If verifiableCredential param is missing.
Expand All @@ -362,7 +384,7 @@ async function _verifyCredential(options = {}) {
* VerifiablePresentation.
*/
export function createPresentation({
verifiableCredential, id, holder, now, version = 2.0
verifiableCredential, id, holder, now, version = 2.0, maxClockSkew = 300
} = {}) {
const initialContext = getContextForVersion({version});
const presentation = {
Expand All @@ -373,7 +395,7 @@ export function createPresentation({
const credentials = [].concat(verifiableCredential);
// ensure all credentials are valid
for(const credential of credentials) {
_checkCredential({credential, now});
_checkCredential({credential, now, maxClockSkew});
}
presentation.verifiableCredential = credentials;
}
Expand Down Expand Up @@ -456,6 +478,10 @@ export async function signPresentation(options = {}) {
* credential status if `credentialStatus` is present on the credential.
* @param {string|Date} [options.now] - A string representing date time in
* ISO 8601 format or an instance of Date. Defaults to current date time.
* @param {number} [options.maxClockSkew=300] - A maximum number of seconds
* that clocks may be skewed when checking capability expiration date-times
* against `date` and when comparing invocation proof creation time against
* delegation proof creation time.
*
* @throws {Error} If presentation is missing required params.
*
Expand Down Expand Up @@ -571,14 +597,18 @@ const mustHaveType = [
* VerifiableCredential.
* @param {string|Date} [options.now] - A string representing date time in
* ISO 8601 format or an instance of Date. Defaults to current date time.
* @param {number} [options.maxClockSkew=300] - A maximum number of seconds
* that clocks may be skewed when checking capability expiration date-times
* against `date` and when comparing invocation proof creation time against
* delegation proof creation time.
* @param {string} [options.mode] - The mode of operation for this
* validation function, either `issue` or `verify`.
*
* @throws {Error}
* @private
*/
export function _checkCredential({
credential, now = new Date(), mode = 'verify'
credential, now = new Date(), mode = 'verify', maxClockSkew = 300
} = {}) {
if(typeof now === 'string') {
now = new Date(now);
Expand Down Expand Up @@ -617,15 +647,16 @@ export function _checkCredential({
assertDateString({credential, prop: 'expirationDate'});
if(mode === 'verify') {
// check if `now` is after `expirationDate`
if(now > new Date(credential.expirationDate)) {
const expirationDate = new Date(credential.expirationDate);
if(compareTime({t1: now, t2: expirationDate, maxClockSkew}) > 0) {
throw new Error('Credential has expired.');
}
}
}
// check if `now` is before `issuanceDate` on verification
if(mode === 'verify') {
const issuanceDate = new Date(credential.issuanceDate);
if(now < issuanceDate) {
if(compareTime({t1: issuanceDate, t2: now, maxClockSkew}) > 0) {
throw new Error(
`The current date time (${now.toISOString()}) is before the ` +
`"issuanceDate" (${credential.issuanceDate}).`);
Expand All @@ -639,7 +670,7 @@ export function _checkCredential({
assertDateString({credential, prop: 'validUntil'});
if(mode === 'verify') {
validUntil = new Date(credential.validUntil);
if(now > validUntil) {
if(compareTime({t1: now, t2: validUntil, maxClockSkew}) > 0) {
throw new Error(
`The current date time (${now.toISOString()}) is after ` +
`"validUntil" (${credential.validUntil}).`);
Expand All @@ -651,7 +682,7 @@ export function _checkCredential({
if(mode === 'verify') {
// check if `now` is before `validFrom`
validFrom = new Date(credential.validFrom);
if(now < validFrom) {
if(compareTime({t1: validFrom, t2: now, maxClockSkew}) > 0) {
throw new Error(
`The current date time (${now.toISOString()}) is before ` +
`"validFrom" (${credential.validFrom}).`);
Expand Down
Loading