UNPKG

@sebastianp265/safe-server-side-storage-client

Version:

Library for Confidential Server-Side Message Storage Using the Labyrinth Protocol

88 lines (87 loc) 5.86 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.joinAllEpochs = joinAllEpochs; const DeviceKeyBundle_1 = require("../device/key-bundles/DeviceKeyBundle"); const VirtualDevice_1 = require("../device/virtual-device/VirtualDevice"); const key_derivation_1 = require("../crypto/key-derivation"); const utils_1 = require("../crypto/utils"); const public_key_encryption_1 = require("../crypto/public-key-encryption"); const authenticated_symmetric_encryption_1 = require("../crypto/authenticated-symmetric-encryption"); const BytesSerializer_1 = require("../BytesSerializer"); class InvalidEpochStorageAuthKey extends Error { constructor() { super("Sender epoch storage auth key is corrupted"); Object.setPrototypeOf(this, InvalidEpochStorageAuthKey.prototype); } } // TODO: Virtual devices require only virtual device info to chain forward, however for backwards chaining, // it's info is not needed, could fasten initialize process when backward chaining is skipped, // TODO: Refactor to do lazy loading and fasten application startup // NOT EXPLICITLY TOLD IN PROTOCOL // Performs joining to epoch with desiredEpochSequenceId, function mutates passed epochStorage async function joinAllEpochs(device, epochStorage, joinEpochServerClient) { const chainForwardPromise = chainForward(device, epochStorage, joinEpochServerClient); // TODO: Add rolling forward support // const chainBackwardsPromise = chainBackwards( // epochStorage, // joinEpochServerClient, // ); await chainForwardPromise; // await chainBackwardsPromise; } async function chainForward(device, epochStorage, serverClient) { const { newestEpochSequenceId } = await serverClient.getNewestEpochSequenceId(); let newestKnownEpoch = epochStorage.getNewestEpoch(); while (newestEpochSequenceId !== newestKnownEpoch.sequenceId) { newestKnownEpoch = await joinNewerEpoch(device, newestKnownEpoch, serverClient); epochStorage.add(newestKnownEpoch); } } async function joinNewerEpoch(device, newestKnownEpoch, joinEpochWebClient) { const newerEpochSequenceId = (BigInt(newestKnownEpoch.sequenceId) + BigInt(1)).toString(); const { epochId: newerEpochId, encryptedEpochEntropy: encryptedNewerEpochEntropy, senderDevicePublicKeyBundle, } = await getEpochJoinDataForDeviceInEpochWithSequenceId(device, newerEpochSequenceId, joinEpochWebClient); const senderDevice = DeviceKeyBundle_1.DevicePublicKeyBundle.deserialize(senderDevicePublicKeyBundle); const isValidEpochStorageAuthKey = senderDevice.deviceKeyPub.verify(senderDevice.epochStorageAuthKeySig, Uint8Array.of(0x31), senderDevice.epochStorageAuthKeyPub.getX25519PublicKeyBytes()); if (!isValidEpochStorageAuthKey) { throw new InvalidEpochStorageAuthKey(); } const [newerEpochChainingKey, newerEpochDistributionPreSharedKey] = (0, key_derivation_1.kdfTwoKeys)(newestKnownEpoch.rootKey, null, (0, utils_1.asciiStringToBytes)(`epoch_chaining_${newestKnownEpoch.sequenceId}_${newestKnownEpoch.id}`)); const newerEpochEntropy = (0, public_key_encryption_1.labyrinth_hpke_decrypt)(device.keyBundle.pub.epochStorageKeyPub, device.keyBundle.priv.epochStorageKeyPriv, senderDevice.epochStorageAuthKeyPub, newerEpochDistributionPreSharedKey, (0, utils_1.asciiStringToBytes)(`epoch_${newerEpochSequenceId}`), BytesSerializer_1.bytesSerializerProvider.bytesSerializer.deserialize(encryptedNewerEpochEntropy)); const newerEpochRootKey = (0, key_derivation_1.kdfOneKey)(newerEpochEntropy, newerEpochChainingKey, (0, utils_1.asciiStringToBytes)("epoch_root_key")); return { id: newerEpochId, sequenceId: newerEpochSequenceId, rootKey: newerEpochRootKey, }; } function getEpochJoinDataForDeviceInEpochWithSequenceId(device, epochSequenceId, joinEpochWebClient) { if (device instanceof VirtualDevice_1.VirtualDevice) { return joinEpochWebClient.getNewerEpochJoinDataForVirtualDevice(epochSequenceId); } else { return joinEpochWebClient.getNewerEpochJoinDataForDevice(epochSequenceId, device.id); } } // @ts-ignore async function chainBackwards(epochStorage, joinEpochWebClient) { let oldestKnownEpoch = epochStorage.getOldestEpoch(); while (oldestKnownEpoch.sequenceId !== "0") { oldestKnownEpoch = await joinOlderEpoch(oldestKnownEpoch, joinEpochWebClient); epochStorage.add(oldestKnownEpoch); } } async function joinOlderEpoch(oldestKnownEpoch, joinEpochWebClient) { const olderEpochSequenceId = (BigInt(oldestKnownEpoch.sequenceId) - BigInt(1)).toString(); const { epochId: olderEpochId, encryptedEpochSequenceId: encryptedOlderEpochSequenceId, encryptedEpochRootKey: encryptedOlderEpochRootKey, } = await joinEpochWebClient.getOlderEpochJoinData(olderEpochSequenceId); const olderEpochDataStorageKey = (0, key_derivation_1.kdfOneKey)(oldestKnownEpoch.rootKey, null, (0, utils_1.asciiStringToBytes)(`epoch_data_storage_${oldestKnownEpoch.sequenceId}`)); const expectedOlderEpochSequenceId = (0, utils_1.bytesToAsciiString)((0, authenticated_symmetric_encryption_1.decrypt)(olderEpochDataStorageKey, (0, utils_1.asciiStringToBytes)("epoch_data_metadata"), BytesSerializer_1.bytesSerializerProvider.bytesSerializer.deserialize(encryptedOlderEpochSequenceId))); if (olderEpochSequenceId !== expectedOlderEpochSequenceId) { throw new Error("Older epoch metadata has been corrupted"); } const olderEpochRootKey = (0, authenticated_symmetric_encryption_1.decrypt)(olderEpochDataStorageKey, (0, utils_1.asciiStringToBytes)("epoch_data_metadata"), BytesSerializer_1.bytesSerializerProvider.bytesSerializer.deserialize(encryptedOlderEpochRootKey)); return { id: olderEpochId, sequenceId: olderEpochSequenceId, rootKey: olderEpochRootKey, }; }