Skip to content

Commit

Permalink
Merge pull request #31 from KeychainMDIP/21-support-challenge-for-unk…
Browse files Browse the repository at this point in the history
…nown-holder

Redesigned challenge/response features
  • Loading branch information
macterra authored Mar 19, 2024
2 parents 3ff3698 + 6128274 commit 13de9ef
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 104 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ Options:
-h, --help display help for command
Commands:
new-wallet <recovery-phrase> Create new wallet from a recovery phrase
create-wallet Create new wallet (or show existing wallet)
import-wallet <recovery-phrase> Create new wallet from a recovery phrase
show-wallet Show wallet
show-mnemonic Show recovery phrase for wallet
backup-wallet Backup wallet to encrypted DID
recover-wallet <did> Recover wallet from encrypted DID
create-id <name> <registry> Create a new decentralized ID
create-id <name> [registry] Create a new decentralized ID
resolve-id Resolves the current ID
backup-id Backup the current ID to its registry
recover-id <did> Recovers the ID from the DID
Expand All @@ -57,11 +58,10 @@ Commands:
sign-file <file> Sign a JSON file
verify-file <file> Verify the signature in a JSON file
create-credential <file> [name] Create credential from schema file
create-challenge <file> [name] Create challenge from a file
create-challenge [file] [name] Create challenge (optionally from a file)
create-challenge-cc <did> [name] Create challenge from a credential DID
issue-challenge <challenge> <user> Issue a challenge to a user
bind-credential <file> <did> Create bound credential for a user
attest-credential <file> <registry> [name] Sign and encrypt a bound credential file
attest-credential <file> [registry] [name] Sign and encrypt a bound credential file
revoke-credential <did> Revokes a verifiable credential
accept-credential <did> Save verifiable credential for current ID
publish-credential <did> Publish the existence of a credential to the current user manifest
Expand Down
21 changes: 4 additions & 17 deletions keychain-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,11 +285,12 @@ program
});

program
.command('create-challenge <file> [name]')
.description('Create challenge from a file')
.command('create-challenge [file] [name]')
.description('Create challenge (optionally from a file)')
.action(async (file, name) => {
try {
const challenge = JSON.parse(fs.readFileSync(file).toString());
const defaultChallenge = { credentials: [] };
const challenge = file ? JSON.parse(fs.readFileSync(file).toString()) : defaultChallenge;
const did = await keymaster.createChallenge(challenge);

if (name) {
Expand Down Expand Up @@ -323,20 +324,6 @@ program
}
});

program
.command('issue-challenge <challenge> <user>')
.description('Issue a challenge to a user')
.action(async (challenge, user) => {
try {
const did = await keymaster.issueChallenge(challenge, user);
console.log(did);
}
catch (error) {
console.error(error);
}
});


program
.command('bind-credential <file> <did>')
.description('Create bound credential for a user')
Expand Down
59 changes: 29 additions & 30 deletions keymaster.js
Original file line number Diff line number Diff line change
Expand Up @@ -657,25 +657,6 @@ export async function createChallenge(challenge) {
return createData(challenge);
}

export async function issueChallenge(challenge, holder, expiresIn = 24) {
const id = getCurrentId();
const now = new Date();
const expires = new Date();
expires.setHours(now.getHours() + expiresIn);
const challengeDID = lookupDID(challenge);
const holderDID = lookupDID(holder);
const issue = {
challenge: challengeDID,
from: id.did,
to: holderDID,
validFrom: now.toISOString(),
validUntil: expires.toISOString(),
};
const signed = await addSignature(issue);
const cipherDid = await encryptJSON(signed, holderDID);
return cipherDid;
}

async function findMatchingCredential(credential) {
const id = getCurrentId();

Expand Down Expand Up @@ -723,15 +704,20 @@ async function findMatchingCredential(credential) {
}

export async function createResponse(did) {
const id = getCurrentId();
const challenge = lookupDID(did);
const boundChallenge = await decryptJSON(challenge);

if (!boundChallenge.challenge || boundChallenge.to !== id.did) {
if (!challenge) {
throw "Invalid challenge";
}

const doc = await resolveDID(challenge);
const requestor = doc.didDocument.controller;
const { credentials } = await resolveAsset(challenge);

if (!credentials) {
throw "Invalid challenge";
}

const { credentials } = await resolveAsset(boundChallenge.challenge);
const matches = [];

for (let credential of credentials) {
Expand All @@ -750,24 +736,37 @@ export async function createResponse(did) {

for (let vcDid of matches) {
const plaintext = await decrypt(vcDid);
const vpDid = await encrypt(plaintext, boundChallenge.from);
const vpDid = await encrypt(plaintext, requestor);
pairs.push({ vc: vcDid, vp: vpDid });
}

const responseDid = await createData({
challenge: did,
const requested = credentials.length;
const fulfilled = matches.length;
const match = (requested === fulfilled);
const response = {
challenge: challenge,
credentials: pairs,
});
requested: requested,
fulfilled: fulfilled,
match: match,
};

const responseDid = await encryptJSON(response, requestor);

return responseDid;
}

export async function verifyResponse(did) {
const response = lookupDID(did);
const { credentials } = await resolveAsset(response);
const responseDID = lookupDID(did);

if (!responseDID) {
throw "Invalid response";
}

const response = await decryptJSON(responseDID);
const vps = [];

for (let credential of credentials) {
for (let credential of response.credentials) {
const vcData = await resolveAsset(credential.vc);
const vpData = await resolveAsset(credential.vp);

Expand Down
58 changes: 6 additions & 52 deletions keymaster.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1185,49 +1185,6 @@ describe('createChallenge', () => {
});
});

describe('issueChallenge', () => {

afterEach(() => {
mockFs.restore();
});

it('should create a challenge bound to a user', async () => {
mockFs({});

const alice = await keymaster.createId('Alice');
const bob = await keymaster.createId('Bob');

keymaster.useId('Alice');

const credentialDid = await keymaster.createCredential(mockSchema);
const challenge = {
credentials: [
{
schema: credentialDid,
attestors: [alice, bob]
}
]
};
const challengeDid = await keymaster.createChallenge(challenge);
const challengeForBob = await keymaster.issueChallenge(challengeDid, bob);
const boundChallenge = await keymaster.decryptJSON(challengeForBob);

expect(boundChallenge.challenge).toBe(challengeDid);
expect(boundChallenge.from).toBe(alice);
expect(boundChallenge.to).toBe(bob);

const isValid = await keymaster.verifySignature(boundChallenge);
expect(isValid).toBe(true);

const validFrom = new Date(boundChallenge.validFrom);
const validUntil = new Date(boundChallenge.validUntil);
const now = new Date();

expect(validFrom < now).toBe(true);
expect(validUntil > now).toBe(true);
});
});

describe('createResponse', () => {

afterEach(() => {
Expand Down Expand Up @@ -1267,14 +1224,12 @@ describe('createResponse', () => {
]
};
const challengeDid = await keymaster.createChallenge(challenge);
const challengeForBob = await keymaster.issueChallenge(challengeDid, bob);

keymaster.useId('Bob');
const vpDid = await keymaster.createResponse(challengeForBob);
const vpDoc = await keymaster.resolveDID(vpDid);
const data = vpDoc.didDocumentMetadata.data;
const vpDid = await keymaster.createResponse(challengeDid);
const data = await keymaster.decryptJSON(vpDid);

expect(data.challenge).toBe(challengeForBob);
expect(data.challenge).toBe(challengeDid);
expect(data.credentials.length).toBe(1);
expect(data.credentials[0].vc).toBe(vcDid);
});
Expand Down Expand Up @@ -1346,13 +1301,12 @@ describe('verifyResponse', () => {
]
};
const challengeDid = await keymaster.createChallenge(challenge);
const challengeForCarol = await keymaster.issueChallenge(challengeDid, carol);

keymaster.useId('Carol');
const vpDid = await keymaster.createResponse(challengeForCarol);
const data = await keymaster.resolveAsset(vpDid);
const vpDid = await keymaster.createResponse(challengeDid);
const data = await keymaster.decryptJSON(vpDid);

expect(data.challenge).toBe(challengeForCarol);
expect(data.challenge).toBe(challengeDid);
expect(data.credentials.length).toBe(4);

keymaster.useId('Victor');
Expand Down

0 comments on commit 13de9ef

Please sign in to comment.