@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
JavaScript
;
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,
};
}