Skip to content

Commit

Permalink
feat: Use only confirmed keys (#257)
Browse files Browse the repository at this point in the history
* Added tests

* Use only confirmed keys

* Fixed encrypt and decrypt and unit tests

* Fixed full workflow

* Refactored fetchKeyPair

* Prevent key rotation when versions unconfirmed

* Fixed verifyUpdate
  • Loading branch information
macterra authored Jul 29, 2024
1 parent 6f68fba commit 8f20aa0
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 80 deletions.
6 changes: 3 additions & 3 deletions src/gatekeeper-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,10 @@ async function verifyCreateAsset(operation) {
throw exceptions.INVALID_OPERATION;
}

const doc = await resolveDID(operation.signature.signer, { atTime: operation.signature.signed });
const doc = await resolveDID(operation.signature.signer, { confirm: true, atTime: operation.signature.signed });

if (doc.mdip.registry === 'local' && operation.mdip.registry !== 'local') {
throw exceptions.INVALID_OPERATION;
throw exceptions.INVALID_REGISTRY;
}

const operationCopy = JSON.parse(JSON.stringify(operation));
Expand Down Expand Up @@ -265,7 +265,7 @@ async function verifyUpdate(operation, doc) {
}

if (doc.didDocument.controller) {
const controllerDoc = await resolveDID(doc.didDocument.controller, { atTime: operation.signature.signed });
const controllerDoc = await resolveDID(doc.didDocument.controller, { confirm: true, atTime: operation.signature.signed });
return verifyUpdate(operation, controllerDoc);
}

Expand Down
3 changes: 2 additions & 1 deletion src/gatekeeper.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,8 @@ describe('createDID', () => {
await gatekeeper.createDID(assetOp);
throw exceptions.EXPECTED_EXCEPTION;
} catch (error) {
expect(error).toBe(exceptions.INVALID_OPERATION);
// Can't let local IDs create assets on other registries
expect(error).toBe(exceptions.INVALID_REGISTRY);
}

try {
Expand Down
56 changes: 35 additions & 21 deletions src/keymaster-app/src/keymaster-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,23 +394,36 @@ function hdKeyPair() {
return cipher.generateJwk(hdkey.privateKey);
}

function fetchKeyPair(name = null) {
async function fetchKeyPair(name = null) {
const wallet = loadWallet();
const id = fetchId(name);
const hdkey = cipher.generateHDKeyJSON(wallet.seed.hdkey);
const path = `m/44'/0'/${id.account}'/0/${id.index}`;
const didkey = hdkey.derive(path);
const doc = await resolveDID(id.did, { confirm: true });
const confirmedPublicKeyJwk = doc.didDocument.verificationMethod[0].publicKeyJwk;

for (let i = id.index; i >= 0; i--) {
const path = `m/44'/0'/${id.account}'/0/${i}`;
const didkey = hdkey.derive(path);
const keypair = cipher.generateJwk(didkey.privateKey);

if (keypair.publicJwk.x === confirmedPublicKeyJwk.x &&
keypair.publicJwk.y === confirmedPublicKeyJwk.y
)
{
return keypair;
}
}

return cipher.generateJwk(didkey.privateKey);
return null;
}

export async function encrypt(msg, did, encryptForSender = true, registry = defaultRegistry) {
const id = fetchId();
const keypair = fetchKeyPair();
const doc = await resolveDID(did);
const publicJwk = doc.didDocument.verificationMethod[0].publicKeyJwk;
const cipher_sender = encryptForSender ? cipher.encryptMessage(keypair.publicJwk, keypair.privateJwk, msg) : null;
const cipher_receiver = cipher.encryptMessage(publicJwk, keypair.privateJwk, msg);
const senderKeypair = await fetchKeyPair();
const doc = await resolveDID(did, { confirm: true });
const receivePublicJwk = doc.didDocument.verificationMethod[0].publicKeyJwk;
const cipher_sender = encryptForSender ? cipher.encryptMessage(senderKeypair.publicJwk, senderKeypair.privateJwk, msg) : null;
const cipher_receiver = cipher.encryptMessage(receivePublicJwk, senderKeypair.privateJwk, msg);
const msgHash = cipher.hashMessage(msg);

return await createAsset({
Expand All @@ -431,18 +444,19 @@ export async function decrypt(did) {
throw exceptions.INVALID_PARAMETER;
}

const doc = await resolveDID(crypt.sender, { atTime: crypt.created });
const publicJwk = doc.didDocument.verificationMethod[0].publicKeyJwk;
const doc = await resolveDID(crypt.sender, { confirm: true, atTime: crypt.created });
const senderPublicJwk = doc.didDocument.verificationMethod[0].publicKeyJwk;
const hdkey = cipher.generateHDKeyJSON(wallet.seed.hdkey);
const ciphertext = (crypt.sender === id.did && crypt.cipher_sender) ? crypt.cipher_sender : crypt.cipher_receiver;

// Try all private keys for this ID, starting with the most recent and working backward
let index = id.index;
while (index >= 0) {
const path = `m/44'/0'/${id.account}'/0/${index}`;
const didkey = hdkey.derive(path);
const keypair = cipher.generateJwk(didkey.privateKey);
const receiverKeypair = cipher.generateJwk(didkey.privateKey);
try {
return cipher.decryptMessage(publicJwk, keypair.privateJwk, ciphertext);
return cipher.decryptMessage(senderPublicJwk, receiverKeypair.privateJwk, ciphertext);
}
catch (error) {
index -= 1;
Expand All @@ -465,7 +479,7 @@ export async function decryptJSON(did) {
export async function addSignature(obj, controller = null) {
// Fetches current ID if name is missing
const id = fetchId(controller);
const keypair = fetchKeyPair(controller);
const keypair = await fetchKeyPair(controller);

try {
const msgHash = cipher.hashJSON(obj);
Expand Down Expand Up @@ -861,9 +875,9 @@ export async function testAgent(id) {
return doc?.mdip?.type === 'agent';
}

export async function createCredential(schema) {
export async function createCredential(schema, registry) {
// TBD validate schema
return createAsset(schema);
return createAsset(schema, registry);
}

export async function bindCredential(schemaId, subjectId, validUntil = null) {
Expand Down Expand Up @@ -1010,7 +1024,7 @@ export async function unpublishCredential(did) {
throw exceptions.INVALID_PARAMETER;
}

export async function createChallenge(challenge) {
export async function createChallenge(challenge, registry = ephemeralRegistry) {

if (!challenge) {
challenge = { credentials: [] };
Expand All @@ -1030,7 +1044,7 @@ export async function createChallenge(challenge) {
throw exceptions.INVALID_PARAMETER;
}

return createAsset(challenge, ephemeralRegistry);
return createAsset(challenge, registry);
}

async function findMatchingCredential(credential) {
Expand Down Expand Up @@ -1075,7 +1089,7 @@ async function findMatchingCredential(credential) {
}
}

export async function createResponse(did) {
export async function createResponse(did, registry = ephemeralRegistry) {
const challenge = lookupDID(did);

if (!challenge) {
Expand Down Expand Up @@ -1108,7 +1122,7 @@ export async function createResponse(did) {

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

Expand All @@ -1127,7 +1141,7 @@ export async function createResponse(did) {
ephemeral: { validUntil: expires.toISOString() }
};

return await encryptJSON(response, requestor, true, ephemeralRegistry);
return await encryptJSON(response, requestor, true, registry);
}

export async function verifyResponse(responseDID, challengeDID) {
Expand Down
61 changes: 40 additions & 21 deletions src/keymaster-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,23 +394,36 @@ function hdKeyPair() {
return cipher.generateJwk(hdkey.privateKey);
}

function fetchKeyPair(name = null) {
async function fetchKeyPair(name = null) {
const wallet = loadWallet();
const id = fetchId(name);
const hdkey = cipher.generateHDKeyJSON(wallet.seed.hdkey);
const path = `m/44'/0'/${id.account}'/0/${id.index}`;
const didkey = hdkey.derive(path);
const doc = await resolveDID(id.did, { confirm: true });
const confirmedPublicKeyJwk = doc.didDocument.verificationMethod[0].publicKeyJwk;

return cipher.generateJwk(didkey.privateKey);
for (let i = id.index; i >= 0; i--) {
const path = `m/44'/0'/${id.account}'/0/${i}`;
const didkey = hdkey.derive(path);
const keypair = cipher.generateJwk(didkey.privateKey);

if (keypair.publicJwk.x === confirmedPublicKeyJwk.x &&
keypair.publicJwk.y === confirmedPublicKeyJwk.y
)
{
return keypair;
}
}

return null;
}

export async function encrypt(msg, did, encryptForSender = true, registry = defaultRegistry) {
const id = fetchId();
const keypair = fetchKeyPair();
const doc = await resolveDID(did);
const publicJwk = doc.didDocument.verificationMethod[0].publicKeyJwk;
const cipher_sender = encryptForSender ? cipher.encryptMessage(keypair.publicJwk, keypair.privateJwk, msg) : null;
const cipher_receiver = cipher.encryptMessage(publicJwk, keypair.privateJwk, msg);
const senderKeypair = await fetchKeyPair();
const doc = await resolveDID(did, { confirm: true });
const receivePublicJwk = doc.didDocument.verificationMethod[0].publicKeyJwk;
const cipher_sender = encryptForSender ? cipher.encryptMessage(senderKeypair.publicJwk, senderKeypair.privateJwk, msg) : null;
const cipher_receiver = cipher.encryptMessage(receivePublicJwk, senderKeypair.privateJwk, msg);
const msgHash = cipher.hashMessage(msg);

return await createAsset({
Expand All @@ -431,18 +444,19 @@ export async function decrypt(did) {
throw exceptions.INVALID_PARAMETER;
}

const doc = await resolveDID(crypt.sender, { atTime: crypt.created });
const publicJwk = doc.didDocument.verificationMethod[0].publicKeyJwk;
const doc = await resolveDID(crypt.sender, { confirm: true, atTime: crypt.created });
const senderPublicJwk = doc.didDocument.verificationMethod[0].publicKeyJwk;
const hdkey = cipher.generateHDKeyJSON(wallet.seed.hdkey);
const ciphertext = (crypt.sender === id.did && crypt.cipher_sender) ? crypt.cipher_sender : crypt.cipher_receiver;

// Try all private keys for this ID, starting with the most recent and working backward
let index = id.index;
while (index >= 0) {
const path = `m/44'/0'/${id.account}'/0/${index}`;
const didkey = hdkey.derive(path);
const keypair = cipher.generateJwk(didkey.privateKey);
const receiverKeypair = cipher.generateJwk(didkey.privateKey);
try {
return cipher.decryptMessage(publicJwk, keypair.privateJwk, ciphertext);
return cipher.decryptMessage(senderPublicJwk, receiverKeypair.privateJwk, ciphertext);
}
catch (error) {
index -= 1;
Expand All @@ -465,7 +479,7 @@ export async function decryptJSON(did) {
export async function addSignature(obj, controller = null) {
// Fetches current ID if name is missing
const id = fetchId(controller);
const keypair = fetchKeyPair(controller);
const keypair = await fetchKeyPair(controller);

try {
const msgHash = cipher.hashJSON(obj);
Expand Down Expand Up @@ -738,6 +752,11 @@ export async function rotateKeys() {
const didkey = hdkey.derive(path);
const keypair = cipher.generateJwk(didkey.privateKey);
const doc = await resolveDID(id.did);

if (!doc.didDocumentMetadata.confirmed) {
throw new Error('Cannot rotate keys');
}

const vmethod = doc.didDocument.verificationMethod[0];

vmethod.id = `#key-${nextIndex + 1}`;
Expand Down Expand Up @@ -861,9 +880,9 @@ export async function testAgent(id) {
return doc?.mdip?.type === 'agent';
}

export async function createCredential(schema) {
export async function createCredential(schema, registry) {
// TBD validate schema
return createAsset(schema);
return createAsset(schema, registry);
}

export async function bindCredential(schemaId, subjectId, validUntil = null) {
Expand Down Expand Up @@ -1010,7 +1029,7 @@ export async function unpublishCredential(did) {
throw exceptions.INVALID_PARAMETER;
}

export async function createChallenge(challenge) {
export async function createChallenge(challenge, registry = ephemeralRegistry) {

if (!challenge) {
challenge = { credentials: [] };
Expand All @@ -1030,7 +1049,7 @@ export async function createChallenge(challenge) {
throw exceptions.INVALID_PARAMETER;
}

return createAsset(challenge, ephemeralRegistry);
return createAsset(challenge, registry);
}

async function findMatchingCredential(credential) {
Expand Down Expand Up @@ -1075,7 +1094,7 @@ async function findMatchingCredential(credential) {
}
}

export async function createResponse(did) {
export async function createResponse(did, registry = ephemeralRegistry) {
const challenge = lookupDID(did);

if (!challenge) {
Expand Down Expand Up @@ -1108,7 +1127,7 @@ export async function createResponse(did) {

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

Expand All @@ -1127,7 +1146,7 @@ export async function createResponse(did) {
ephemeral: { validUntil: expires.toISOString() }
};

return await encryptJSON(response, requestor, true, ephemeralRegistry);
return await encryptJSON(response, requestor, true, registry);
}

export async function verifyResponse(responseDID, challengeDID) {
Expand Down
Loading

0 comments on commit 8f20aa0

Please sign in to comment.