Skip to content

Commit

Permalink
Merge pull request #443 from docknetwork/fix-jwt-vc-verify
Browse files Browse the repository at this point in the history
  • Loading branch information
cykoder authored Aug 9, 2024
2 parents 42f79c2 + 5d8cd35 commit c5f7ee9
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 20 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docknetwork/sdk",
"version": "9.1.0",
"version": "9.2.0",
"main": "index.js",
"license": "MIT",
"repository": {
Expand Down Expand Up @@ -108,6 +108,7 @@
"@docknetwork/node-types": "^0.17.0",
"@juanelas/base64": "^1.0.5",
"@polkadot/api": "10.12.4",
"@sphereon/ssi-sdk-ext.did-resolver-jwk": "^0.24.0",
"@transmute/json-web-signature": "^0.7.0-unstable.80",
"base64url": "3.0.1",
"blake2b": "2.1.4",
Expand Down
20 changes: 20 additions & 0 deletions src/resolver/did/did-jwk-resolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { getDidJwkResolver } from '@sphereon/ssi-sdk-ext.did-resolver-jwk';
import DIDResolver from './did-resolver';

const jwkResolver = getDidJwkResolver();

export default class DIDJWKResolver extends DIDResolver {
static METHOD = 'jwk';

constructor() {
super(undefined);
}

async resolve(did) {
const { didDocument } = await jwkResolver.jwk(did);
return {
'@context': 'https://www.w3.org/ns/did/v1',
...didDocument,
};
}
}
1 change: 1 addition & 0 deletions src/resolver/did/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as DIDResolver } from './did-resolver';
export { default as DIDKeyResolver } from './did-key-resolver';
export { default as DIDJWKResolver } from './did-jwk-resolver';
export { default as DockDIDResolver } from './dock-did-resolver';
export { default as UniversalResolver } from './universal-resolver';
6 changes: 6 additions & 0 deletions src/utils/vc/contexts.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import dockPrettyVCContext from './contexts/prettyvc.json';
import jws2020V1Context from './contexts/jws-2020-v1.json';
import statusList21Context from './contexts/status-list-21';
import privateStatusList21Context from './contexts/private-status-list-21';
import sphereonId from './contexts/sphereon-wallet-identity-v1.json';

// Lookup of following URLs will lead to loading data from the context directory, this is done as the Sr25519 keys are not
// supported in any W3C standard and vc-js has them stored locally. This is a temporary solution.
Expand Down Expand Up @@ -105,4 +106,9 @@ export default new Map([
'https://ld.dock.io/private-status-list-21',
privateStatusList21Context,
],
// Overriden due to 404ing
[
'https://sphereon-opensource.github.io/ssi-mobile-wallet/context/sphereon-wallet-identity-v1.jsonld',
sphereonId,
],
]);
17 changes: 17 additions & 0 deletions src/utils/vc/contexts/sphereon-wallet-identity-v1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"@context": {
"@version": 1.1,
"@protected": true,
"swi": "https://sphereon-opensource.github.io/ssi-mobile-wallet/context/sphereon-wallet-identity-v1#",
"schema": "https://schema.org/",
"SphereonWalletIdentityCredential": "swi:SphereonWalletIdentityCredential",
"id": "@id",
"type": "@type",
"firstName": "swi:firstName",
"lastName": "swi:lastName",
"emailAddress": {
"@id": "swi:emailAddress",
"@type": "schema:email"
}
}
}
38 changes: 20 additions & 18 deletions src/utils/vc/credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export function checkCredentialJSONLD(credential) {
* @param {object} credential - An object that could be a VerifiableCredential.
* @throws {Error}
*/
export function checkCredentialRequired(credential) {
export function checkCredentialRequired(credential, isJWT) {
// Ensure first context is DEFAULT_CONTEXT_V1_URL
if (credential['@context'][0] !== DEFAULT_CONTEXT_V1_URL) {
throw new Error(
Expand All @@ -176,21 +176,23 @@ export function checkCredentialRequired(credential) {
throw new Error('"credentialSubject" property is required.');
}

// Ensure issuer is valid
const issuer = getId(credential.issuer);
if (!issuer) {
throw new Error(
`"issuer" must be an object with ID property or a string. Got: ${credential.issuer}`,
);
} else if (!issuer.includes(':')) {
throw new Error('"issuer" id must be in URL format.');
}
// Ensure issuer and issue date is valid, only for non-jwt as that is defined in the header
if (!isJWT) {
const issuer = getId(credential.issuer);
if (!issuer) {
throw new Error(
`"issuer" must be an object with ID property or a string. Got: ${credential.issuer}`,
);
} else if (!issuer.includes(':')) {
throw new Error('"issuer" id must be in URL format.');
}

// Ensure there is an issuance date, if exists
if (!credential.issuanceDate) {
throw new Error('"issuanceDate" property is required.');
} else {
ensureValidDatetime(credential.issuanceDate);
// Ensure there is an issuance date, if exists
if (!credential.issuanceDate) {
throw new Error('"issuanceDate" property is required.');
} else {
ensureValidDatetime(credential.issuanceDate);
}
}
}

Expand Down Expand Up @@ -219,8 +221,8 @@ export function checkCredentialOptional(credential) {
* @param {object} credential - An object that could be a VerifiableCredential.
* @throws {Error}
*/
export function checkCredential(credential) {
checkCredentialRequired(credential);
export function checkCredential(credential, isJWT) {
checkCredentialRequired(credential, isJWT);
checkCredentialOptional(credential);
checkCredentialJSONLD(credential);
}
Expand Down Expand Up @@ -285,7 +287,7 @@ export async function verifyCredential(
}

// Check credential is valid
checkCredential(credential);
checkCredential(credential, isJWT);

// Check expiration date
if (verifyDates && 'expirationDate' in credential) {
Expand Down
45 changes: 45 additions & 0 deletions tests/unit/jwt-vc.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Mock fetch
import mockFetch from '../mocks/fetch';

import {
verifyCredential,
verifyPresentation,
} from '../../src/utils/vc/index';
import DIDJWKResolver from '../../src/resolver/did/did-jwk-resolver';

mockFetch();

const SPHEREON_ID_JWT_CREDENTIAL = 'eyJraWQiOiJkaWQ6andrOmV5SmhiR2NpT2lKRlV6STFOaUlzSW5WelpTSTZJbk5wWnlJc0ltdDBlU0k2SWtWRElpd2lZM0oySWpvaVVDMHlOVFlpTENKNElqb2lSRlZqTUVwMVNuRjFNbFV5U1dGNVN6TXlOMFJzVjE5b05VcHJPRzlqUmxSbVVsQktRVGxNTUVwQlVTSXNJbmtpT2lJd01qSlBWMk5IYmtvNFJFUmZkbmhGTFY5UldUSmhURUZQZUZSdVlUVjFabmRpWWpkMVNFRnhSM0YzSW4wIzAiLCJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vc3BoZXJlb24tb3BlbnNvdXJjZS5naXRodWIuaW8vc3NpLW1vYmlsZS13YWxsZXQvY29udGV4dC9zcGhlcmVvbi13YWxsZXQtaWRlbnRpdHktdjEuanNvbmxkIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJTcGhlcmVvbldhbGxldElkZW50aXR5Q3JlZGVudGlhbCJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJmaXJzdE5hbWUiOiJUZXN0IiwibGFzdE5hbWUiOiJUZXN0IiwiZW1haWxBZGRyZXNzIjoidGVzdEB0ZXN0LmNvbSJ9fSwic3ViIjoiZGlkOmp3azpleUpoYkdjaU9pSkZVekkxTmlJc0luVnpaU0k2SW5OcFp5SXNJbXQwZVNJNklrVkRJaXdpWTNKMklqb2lVQzB5TlRZaUxDSjRJam9pUkZWak1FcDFTbkYxTWxVeVNXRjVTek15TjBSc1YxOW9OVXByT0c5alJsUm1VbEJLUVRsTU1FcEJVU0lzSW5raU9pSXdNakpQVjJOSGJrbzRSRVJmZG5oRkxWOVJXVEpoVEVGUGVGUnVZVFYxWm5kaVlqZDFTRUZ4UjNGM0luMCIsImp0aSI6InVybjp1dWlkOmNiOWUzYzZhLTZjOTYtNGFiYS1iNWY0LWFiM2RmMDM4Y2MyMiIsIm5iZiI6MTcyMjk3NDM5OSwiaXNzIjoiZGlkOmp3azpleUpoYkdjaU9pSkZVekkxTmlJc0luVnpaU0k2SW5OcFp5SXNJbXQwZVNJNklrVkRJaXdpWTNKMklqb2lVQzB5TlRZaUxDSjRJam9pUkZWak1FcDFTbkYxTWxVeVNXRjVTek15TjBSc1YxOW9OVXByT0c5alJsUm1VbEJLUVRsTU1FcEJVU0lzSW5raU9pSXdNakpQVjJOSGJrbzRSRVJmZG5oRkxWOVJXVEpoVEVGUGVGUnVZVFYxWm5kaVlqZDFTRUZ4UjNGM0luMCJ9.Y9CBYHA_sgfA_V40i69SYrqsAK1OZ6rUW8NlrZwavbPxcVS_LX3tFvRRU0jkslUbuf7rColxf2f8zo-YMan-_w';

// Test constants
const vpId = 'https://example.com/credentials/12345';
const vpHolder = 'https://example.com/credentials/1234567890';

function getSamplePres(presentationCredentials) {
return {
'@context': ['https://www.w3.org/2018/credentials/v1'],
type: ['VerifiablePresentation'],
verifiableCredential: presentationCredentials,
id: vpId,
holder: vpHolder,
};
}

describe('Static JWT-VC verification', () => {
const resolver = new DIDJWKResolver();

test('Sphereon ID credential', async () => {
const result = await verifyCredential(SPHEREON_ID_JWT_CREDENTIAL, {
resolver,
});
expect(result.verified).toBe(true);
});

test('Sphereon ID credential in presentation', async () => {
const result = await verifyPresentation(getSamplePres([SPHEREON_ID_JWT_CREDENTIAL]), {
resolver,
unsignedPresentation: true,
});
expect(result.verified).toBe(true);
});
});
66 changes: 65 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4714,6 +4714,27 @@
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.5.tgz#1d85d17269fe97694b9c592552dd9e5e33552157"
integrity sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==

"@sd-jwt/decode@^0.6.1":
version "0.6.1"
resolved "https://registry.yarnpkg.com/@sd-jwt/decode/-/decode-0.6.1.tgz#141f7782df53bab7159a75d91ed4711e1c14a7ea"
integrity sha512-QgTIoYd5zyKKLgXB4xEYJTrvumVwtsj5Dog0v0L9UH9ZvHekDaeexS247X7A4iSdzTvmZzUpGskgABOa4D8NmQ==
dependencies:
"@sd-jwt/types" "0.6.1"
"@sd-jwt/utils" "0.6.1"

"@sd-jwt/[email protected]":
version "0.6.1"
resolved "https://registry.yarnpkg.com/@sd-jwt/types/-/types-0.6.1.tgz#fc4235e00cf40d35a21d6bc02e44e12d7162aa9b"
integrity sha512-LKpABZJGT77jNhOLvAHIkNNmGqXzyfwBT+6r+DN9zNzMx1CzuNR0qXk1GMUbast9iCfPkGbnEpUv/jHTBvlIvg==

"@sd-jwt/[email protected]":
version "0.6.1"
resolved "https://registry.yarnpkg.com/@sd-jwt/utils/-/utils-0.6.1.tgz#33273b20c9eb1954e4eab34118158b646b574ff9"
integrity sha512-1NHZ//+GecGQJb+gSdDicnrHG0DvACUk9jTnXA5yLZhlRjgkjyfJLNsCZesYeCyVp/SiyvIC9B+JwoY4kI0TwQ==
dependencies:
"@sd-jwt/types" "0.6.1"
js-base64 "^3.7.6"

"@sinclair/typebox@^0.27.8":
version "0.27.8"
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
Expand Down Expand Up @@ -4757,6 +4778,27 @@
str2buf "^1.3.0"
webcrypto-shim "^0.1.7"

"@sphereon/ssi-sdk-ext.did-resolver-jwk@^0.24.0":
version "0.24.0"
resolved "https://registry.yarnpkg.com/@sphereon/ssi-sdk-ext.did-resolver-jwk/-/ssi-sdk-ext.did-resolver-jwk-0.24.0.tgz#7603028542d1e1f13d13975a436150b0d0e6de43"
integrity sha512-74to/yE8q3A7O935AlvHWhvBItHuC0HCiKq2UiYa5Adw3NVU9mElXUOik7IRJrhuD6wJNaAxDmQYAFIWRdIG1g==
dependencies:
"@sphereon/ssi-types" "0.28.0"
base64url "^3.0.1"
debug "^4.3.4"
did-resolver "^4.1.0"
uint8arrays "^3.1.1"

"@sphereon/[email protected]":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@sphereon/ssi-types/-/ssi-types-0.28.0.tgz#3eccb6dd7be19af26c5820885f35f3232d038a2d"
integrity sha512-NkTkrsBoQUZzJutlk5XD3snBxL9kfsxKdQvBbGUEaUDOiW8siTNUoJuQFeA+bI0eJY99up95bmMKdJeDc1VDfg==
dependencies:
"@sd-jwt/decode" "^0.6.1"
debug "^4.3.5"
events "^3.3.0"
jwt-decode "^3.1.2"

"@stablelib/aead@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@stablelib/aead/-/aead-1.0.1.tgz#c4b1106df9c23d1b867eb9b276d8f42d5fc4c0c3"
Expand Down Expand Up @@ -6776,6 +6818,13 @@ debug@^3.2.7:
dependencies:
ms "^2.1.1"

debug@^4.3.5:
version "4.3.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b"
integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==
dependencies:
ms "2.1.2"

decimal.js@^10.2.0:
version "10.4.2"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.2.tgz#0341651d1d997d86065a2ce3a441fbd0d8e8b98e"
Expand Down Expand Up @@ -6909,6 +6958,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-2.1.1.tgz#43796f8a3e921644e5fb15a8147684ca87019cfd"
integrity sha512-FYLTkNWofjYNDGV1HTQlyVu1OqZiFxR4I8KM+oxGVOkbXva15NfWzbzciqdXUDqOhe6so5aroAdrVip6gSAYSA==

did-resolver@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-4.1.0.tgz#740852083c4fd5bf9729d528eca5d105aff45eb6"
integrity sha512-S6fWHvCXkZg2IhS4RcVHxwuyVejPR7c+a4Go0xbQ9ps5kILa8viiYQgrM4gfTyeTjJ0ekgJH9gk/BawTpmkbZA==

diff-sequences@^29.6.3:
version "29.6.3"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
Expand Down Expand Up @@ -9689,6 +9743,11 @@ jose@^4.3.8:
resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.5.tgz#6475d0f467ecd3c630a1b5dadd2735a7288df706"
integrity sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==

js-base64@^3.7.6:
version "3.7.7"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.7.tgz#e51b84bf78fbf5702b9541e2cb7bfcb893b43e79"
integrity sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==

[email protected]:
version "0.9.0"
resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966"
Expand Down Expand Up @@ -9922,6 +9981,11 @@ just-debounce-it@^3.0.1:
resolved "https://registry.yarnpkg.com/just-debounce-it/-/just-debounce-it-3.2.0.tgz#4352265f4af44188624ce9fdbc6bff4d49c63a80"
integrity sha512-WXzwLL0745uNuedrCsCs3rpmfD6DBaf7uuVwaq98/8dafURfgQaBsSpjiPp5+CW6Vjltwy9cOGI6qE71b3T8iQ==

jwt-decode@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59"
integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==

keccak@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.2.tgz#4c2c6e8c54e04f2670ee49fa734eb9da152206e0"
Expand Down Expand Up @@ -12734,7 +12798,7 @@ uint8arrays@^2.0.5:
dependencies:
multiformats "^9.4.2"

uint8arrays@^3.0.0:
uint8arrays@^3.0.0, uint8arrays@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.1.1.tgz#2d8762acce159ccd9936057572dade9459f65ae0"
integrity sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==
Expand Down

0 comments on commit c5f7ee9

Please sign in to comment.