Security News
ESLint is Now Language-Agnostic: Linting JSON, Markdown, and Beyond
ESLint has added JSON and Markdown linting support with new officially-supported plugins, expanding its versatility beyond JavaScript.
@digitalcredentials/credential-status-manager-git
Advanced tools
A Typescript library for managing the status of Verifiable Credentials in Git using Status List 2021
A Typescript library for managing the status of Verifiable Credentials in Git using Status List 2021
Credentials are dynamic artifacts with a robust lifecycle that goes well beyond issuance. This lifecycle is liable to span revocation, suspension, and expiry, among other common states. Many proposals have been put forth to capture this model in Verifiable Credentials. One of the most mature specifications for this is Status List 2021. This library provides an implementation of this specification that leverages Git source control services like GitHub and GitLab for storage and authentication.
To install via NPM:
npm install @digitalcredentials/credential-status-manager-git
To install locally (for development):
git clone https://github.com/digitalcredentials/credential-status-manager-git.git
cd credential-status-manager-git
npm install
The createStatusManager
function is the only exported pure function of this library. It is an asynchronous function that accepts configuration options and returns a credential status manager that aligns with these options. Here are all the possible configuration options:
Key | Description | Type | Required |
---|---|---|---|
service | name of the source control service that will host the credential status resources | github | gitlab | yes |
ownerAccountName | name of the owner account (personal or organization) in the source control service that will host the credential status resources | string | yes |
repoName | name of the credential status repository | string | yes |
repoId | ID of the credential status repository | string | yes (if service = gitlab ) |
metaRepoName | name of the credential status metadata repository | string | yes |
metaRepoId | ID of the credential status metadata repository | string | yes (if service = gitlab ) |
repoAccessToken | access token for the credential status repository in the source control service API | string | yes |
metaRepoAccessToken | access token for the credential status metadata repository in the source control service API | string | yes |
didMethod | name of the DID method used for signing | key | web | yes |
didSeed | seed used to deterministically generate DID | string | yes |
didWebUrl | URL for did:web | string | yes (if didMethod = web ) |
signUserCredential | whether or not to sign user credentials | boolean | no (default: false ) |
signStatusCredential | whether or not to sign status credentials | boolean | no (default: false ) |
Here is a sample call to createStatusManager
:
import { createStatusManager } from '@digitalcredentials/credential-status-manager-git';
const statusManager = await createStatusManager({
service: 'github',
ownerAccountName: 'university-xyz', // Please create an owner account (personal or organization) in your source control service of choice
repoName: 'credential-status', // Please create a unique credential status repository in the owner account
metaRepoName: 'credential-status-metadata', // Please create a unique credential status metadata repository in the owner account
repoAccessToken: 'abc123', // Please create your own access token in your source control service of choice (see Dependencies section for detailed instructions)
metaRepoAccessToken: 'def456', // Please create your own access token in your source control service of choice (see Dependencies section for detailed instructions)
didMethod: 'key',
didSeed: 'DsnrHBHFQP0ab59dQELh3uEwy7i5ArcOTwxkwRO2hM87CBRGWBEChPO7AjmwkAZ2', // Please create your own DID seed (see Dependencies section for detailed instructions)
signStatusCredential: true
});
Note: A Status List 2021 credential can be found in the designated status repository (repoName
) of the designated owner account (ownerAccountName
) which is populated by createStatusManager
. Additionally, relevant historical data can be found in the designated status metadata repository (metaRepoName
) in the same owner account. Note that these repositories need to be manually created prior to calling createStatusManager
. Finally, you can find a publicly visible version of the aforementioned Status List 2021 credential at the relevant URL for hosted sites in the source control service of choice (e.g., https://ownerAccountName
.github.io/repoName
/statusListId
for GitHub, where statusListId
is the name of a file that is automatically generated in repoName
).
The allocateStatus
is an instance method that is called on a credential status manager initialized by createStatusManager
. It is an asynchronous method that accepts a credential as input, records its status in the caller's source control service of choice, and returns the credential with status metadata attached.
Here is a sample call to allocateStatus
:
const credential = {
'@context': [
'https://www.w3.org/2018/credentials/v1',
'https://w3id.org/security/suites/ed25519-2020/v1'
],
id: 'https://university-xyz.edu/credentials/3732',
type: [
'VerifiableCredential'
],
issuer: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC',
issuanceDate: '2020-03-10T04:24:12.164Z',
credentialSubject: {
id: 'did:example:abcdef'
}
};
const credentialWithStatus = await statusManager.allocateStatus(credential);
console.log(credentialWithStatus);
/*
{
'@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://university-xyz.edu/credentials/3732',
type: [ 'VerifiableCredential' ],
issuer: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC',
issuanceDate: '2020-03-10T04:24:12.164Z',
credentialSubject: { id: 'did:example:abcdef' },
credentialStatus: {
id: 'https://university-xyz.github.io/credential-status/V27UAUYPNR#1',
type: 'StatusList2021Entry',
statusPurpose: 'revocation',
statusListIndex: 1,
statusListCredential: 'https://university-xyz.github.io/credential-status/V27UAUYPNR'
}
}
*/
Note: If the caller invokes allocateStatus
multiple times with the same credential ID against the same instance of a credential status manager, the library will not allocate a new status list entry. It will just return a credential with the same status info as it did in the previous invocation.
The updateStatus
is an instance method that is called on a credential status manager initialized by createStatusManager
. It is an asynchronous method that accepts a credential ID and desired credential status as input (options: active
| revoked
), records its new status in the caller's source control service of choice, and returns the status credential.
Here is a sample call to updateStatus
:
const statusCredential = await statusManager.updateStatus({
credentialId: credentialWithStatus.id,
credentialStatus: 'revoked'
});
console.log(statusCredential);
/*
{
'@context': [
'https://www.w3.org/2018/credentials/v1',
'https://w3id.org/vc/status-list/2021/v1'
],
id: 'https://university-xyz.github.io/credential-status/V27UAUYPNR',
type: [ 'VerifiableCredential', 'StatusList2021Credential' ],
credentialSubject: {
id: 'https://university-xyz.github.io/credential-status/V27UAUYPNR#list',
type: 'StatusList2021',
encodedList: 'H4sIAAAAAAAAA-3BMQ0AAAACIGf_0LbwAhoAAAAAAAAAAAAAAIC_AfqBUGnUMAAA',
statusPurpose: 'revocation'
},
issuer: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC',
issuanceDate: '2023-03-15T19:21:54.093Z'
}
*/
The checkStatus
is an instance method that is called on a credential status manager initialized by createStatusManager
. It is an asynchronous method that accepts a credential ID as input and returns status information for the credential.
Here is a sample call to checkStatus
:
const credentialStatus = await statusManager.checkStatus(credentialWithStatus.id);
console.log(credentialStatus);
/*
{
timestamp: '2023-03-15T19:39:06.023Z',
credentialId: 'https://university-xyz.edu/credentials/3732',
credentialIssuer: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC',
credentialSubject: 'did:example:abcdef',
credentialState: 'revoked',
verificationMethod: 'did:key:z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC#z6MkhVTX9BF3NGYX6cc7jWpbNnR7cAjH8LUffabZP8Qu4ysC',
statusListId: 'V27UAUYPNR',
statusListIndex: 1
}
*/
The hasStatusAuthority
is an instance method that is called on a credential status manager initialized by createStatusManager
. It is an asynchronous method that accepts an access token for the API of the caller's source control service of choice, and reports whether the caller has the authority to update the status of credentials.
Here is a sample call to hasStatusAuthority
in the context of Express.js middleware:
// retrieves status list manager
export async function getCredentialStatusManager(req, res, next) {
try {
req.statusManager = await getStatusManager();
next();
} catch (error) {
return res.send('Failed to retrieve credential status list manager');
}
}
// extracts access token from request header
function extractAccessToken(headers) {
if (!headers.authorization) {
return;
}
const [scheme, token] = headers.authorization.split(' ');
if (scheme === 'Bearer') {
return token;
}
}
// verifies whether caller has access to status repo
async function verifyStatusRepoAccess(req, res, next) {
const { headers } = req;
// verify that access token was included in request
const repoAccessToken = extractAccessToken(headers);
if (!repoAccessToken) {
return res.send('Failed to provide access token in request');
}
// check if caller has access to status repo
const hasAccess = await req.statusManager.hasStatusAuthority(repoAccessToken);
if (!hasAccess) {
return res.send('Caller is unauthorized to access status repo');
}
next();
}
Note: This code assumes that getStatusManager
either calls createStatusManager
or retrieves an existing status manager instance created at an earlier point in time.
GitHub
*Note: The names you choose for the owner account, credential status repository, and credential status metadata repository should be passed in respectively as ownerAccountName
, repoName
, and metaRepoName
in invocations of createStatusManager
. When you create these repositories, be sure not to add any files (including common default files like .gitignore
, README.md
, or LICENSE
).
GitLab
*Note: The names you choose for the owner account, credential status repository, and credential status metadata repository, along with their IDs (which can be found at the main view for the repository/group), should be passed in respectively as ownerAccountName
, repoName
(ID: repoId
), and metaRepoName
(ID: metaRepoId
) in invocations of createStatusManager
. When you create these repositories, be sure not to add any files (including common default files like .gitignore
, README.md
, or LICENSE
).
GitHub
ownerAccountName
)repoName
and/or metaRepoName
) *repoAccessToken
and/or metaRepoAccessToken
in invocations of createStatusManager
and hasStatusAuthority
**Note: For the credential status metadata repository, you can either generate a separate access token and use that as the value for metaRepoAccessToken
or include it along with repoAccessToken
when selecting repositories. Whatever you decide, make sure to pass values for both of these values in invocations of createStatusManager
(even though the latter option will result in the same value for these properties). If you are using an organization as the owner account for the credential status manager and ownerAccountName
is not listed, follow these instructions to set a personal access token policy for it.
GitLab*
ownerAccountName
)repoName
)repoAccessToken
in invocations of createStatusManager
and hasStatusAuthority
metaRepoName
and metaRepoAccessToken
*Note: At the time of this writing, group access tokens are only available in paid GitLab plans (i.e., Premium SaaS and Ultimate SaaS). Additionally, unlike other services, you cannot use the same access token for multiple repositories at this time (hence the need for repoAccessToken
and metaRepoAccessToken
). Finally, if you are unable to create access tokens, you are either on e free plan or you need to enable project access token creation.
In order to generate a DID seed, you will need to use software that is capable of creating it in a format that corresponds to a valid DID document. Here is sample code that does this:
import { generateSecretKeySeed } from '@digitalcredentials/bnid';
// Set `didSeed` key to this value
const secretKeySeed = await generateSecretKeySeed();
If didMethod
= web
, you must also generate a DID document and host it at didWebUrl
/.well-known/did.json. Here is sample code that does this:
import { decodeSecretKeySeed } from '@digitalcredentials/bnid';
import { Ed25519VerificationKey2020 } from '@digitalcredentials/ed25519-verification-key-2020';
import { X25519KeyAgreementKey2020 } from '@digitalcredentials/x25519-key-agreement-key-2020';
import * as DidWeb from '@interop/did-web-resolver';
import { CryptoLD } from '@digitalcredentials/crypto-ld';
const cryptoLd = new CryptoLD();
cryptoLd.use(Ed25519VerificationKey2020);
cryptoLd.use(X25519KeyAgreementKey2020);
const didWebDriver = DidWeb.driver({ cryptoLd });
const decodedSeed = decodeSecretKeySeed({secretKeySeed});
// Host this document at `didWebUrl`/.well-known/did.json
const didWebUrl = 'https://university-xyz.edu';
const didDocument = didWebDriver.generate({ url: didWebUrl, seed: decodedSeed });
PRs accepted.
If editing the Readme, please conform to the standard-readme specification.
MIT License © 2023 Digital Credentials Consortium.
FAQs
A Typescript library for managing the status of Verifiable Credentials in Git using Status List 2021
We found that @digitalcredentials/credential-status-manager-git demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 9 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
ESLint has added JSON and Markdown linting support with new officially-supported plugins, expanding its versatility beyond JavaScript.
Security News
Members Hub is conducting large-scale campaigns to artificially boost Discord server metrics, undermining community trust and platform integrity.
Security News
NIST has failed to meet its self-imposed deadline of clearing the NVD's backlog by the end of the fiscal year. Meanwhile, CVE's awaiting analysis have increased by 33% since June.