Skip to content

Commit

Permalink
Merge pull request #326 from docknetwork/fix/bbs-verify-with-schema
Browse files Browse the repository at this point in the history
Fix BBS+ verify with schema
  • Loading branch information
cykoder authored Nov 8, 2022
2 parents 1d2d0c9 + 09a1d83 commit 7fb56c7
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 10 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docknetwork/sdk",
"version": "2.6.2",
"version": "2.6.3",
"main": "index.js",
"license": "MIT",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion src/utils/vc/credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export async function verifyCredential(credential, {

// Validate schema
if (schemaApi) {
await getAndValidateSchemaIfPresent(expandedCredential, schemaApi, credential[credentialContextField]);
await getAndValidateSchemaIfPresent(expandedCredential, schemaApi, credential[credentialContextField], docLoader);
}

// Verify with jsonld-signatures
Expand Down
31 changes: 30 additions & 1 deletion src/utils/vc/document-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,32 @@ import axios from 'axios';
import cachedUris from './contexts';
import DIDResolver from '../../did-resolver'; // eslint-disable-line

function parseEmbeddedDataURI(embedded) {
// Strip new lines
const dataUri = embedded.replace(/\r?\n/g, '');

// split the URI up into the "metadata" and the "data" portions
const firstComma = dataUri.indexOf(',');
if (firstComma === -1) {
throw new Error('Malformed data URI');
}

// Remove the scheme and parse metadata
const meta = dataUri.substring(5, firstComma).split(';'); // 'data:'.length = 5
if (meta[0] !== 'application/json') {
throw new Error(`Expected media type application/json but was ${meta[0]}`);
}

const isBase64 = meta.indexOf('base64') !== -1;
if (isBase64) {
throw new Error('Base64 embedded JSON is not yet supported');
}

// Extract data string
const dataStr = decodeURIComponent(dataUri.substring(firstComma + 1));
return JSON.parse(dataStr);
}

/**
* Takes a resolver and returns a function that returns a document or throws an error when the document
* cannot be found.
Expand All @@ -19,7 +45,10 @@ function documentLoader(resolver = null) {
async function loadDocument(uri) {
let document;
const uriString = uri.toString();
if (resolver && uriString.startsWith('did:')) {

if (uriString.startsWith('data:')) {
document = parseEmbeddedDataURI(uriString);
} else if (resolver && uriString.startsWith('did:')) {
// Try to resolve a DID and throw if cannot resolve
document = await resolver.resolve(uriString);
} else {
Expand Down
30 changes: 23 additions & 7 deletions src/utils/vc/schema.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import jsonld from 'jsonld';
import { validate } from 'jsonschema';
import Schema from '../../modules/schema';
import defaultDocumentLoader from './document-loader';

import {
expandedSubjectProperty,
Expand All @@ -17,7 +18,7 @@ import {
* @param context
* @returns {Promise<Boolean>} - Returns promise to a boolean or throws error
*/
export async function validateCredentialSchema(credential, schema, context) {
export async function validateCredentialSchema(credential, schema, context, documentLoader) {
const requiresID = schema.required && schema.required.indexOf('id') > -1;
const credentialSubject = credential[expandedSubjectProperty] || [];
const subjects = credentialSubject.length ? credentialSubject : [credentialSubject];
Expand All @@ -28,7 +29,7 @@ export async function validateCredentialSchema(credential, schema, context) {
delete subject[credentialIDField];
}

const compacted = await jsonld.compact(subject, context); // eslint-disable-line
const compacted = await jsonld.compact(subject, context, { documentLoader: documentLoader || defaultDocumentLoader() }); // eslint-disable-line
delete compacted[credentialContextField];

if (Object.keys(compacted).length === 0) {
Expand All @@ -49,19 +50,34 @@ export async function validateCredentialSchema(credential, schema, context) {
* a schema doc. For now, the object specifies the type as key and the value as the API, but the structure can change
* as we support more APIs there are more details associated with each API. Only Dock is supported as of now.
* @param {object} context - the context
* @param {object} documentLoader - the document loader
* @returns {Promise<void>}
*/
export async function getAndValidateSchemaIfPresent(credential, schemaApi, context) {
// eslint-disable-next-line sonarjs/cognitive-complexity
export async function getAndValidateSchemaIfPresent(credential, schemaApi, context, documentLoader) {
const schemaList = credential[expandedSchemaProperty];
if (schemaList) {
const schema = schemaList[0];
if (credential[expandedSubjectProperty] && schema) {
if (!schemaApi.dock) {
throw new Error('Only Dock schemas are supported as of now.');
let schemaObj;
const schemaUri = schema[credentialIDField];

if (schemaUri.startsWith('blob:dock')) {
if (!schemaApi.dock) {
throw new Error('Only Dock schemas are supported as of now.');
}
schemaObj = await Schema.get(schemaUri, schemaApi.dock);
} else {
const { document } = await documentLoader(schemaUri);
schemaObj = document;
}

if (!schemaObj) {
throw new Error(`Could not load schema URI: ${schemaUri}`);
}

try {
const schemaObj = await Schema.get(schema[credentialIDField], schemaApi.dock);
await validateCredentialSchema(credential, schemaObj, context);
await validateCredentialSchema(credential, schemaObj, context, documentLoader);
} catch (e) {
throw new Error(`Schema validation failed: ${e}`);
}
Expand Down
8 changes: 8 additions & 0 deletions tests/integration/anoncreds/issuing.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ describe('BBS+ Module', () => {
getProofMatcherDoc(),
),
);

// Ensure embedding the schema doesnt conflict with the other schema resolutions
const resultWithSchema = await verifyCredential(credential, { resolver, schemaApi: { dock }, });
expect(resultWithSchema).toMatchObject(
expect.objectContaining(
getProofMatcherDoc(),
),
);
}, 30000);

test('Can issue+verify a BBS+ credential with default schema', async () => {
Expand Down

0 comments on commit 7fb56c7

Please sign in to comment.