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

Redesigned challenge/response features #31

Merged
merged 5 commits into from
Mar 19, 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
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
Loading