UNPKG

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

Version:

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

97 lines (96 loc) 6.99 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.InvalidVirtualDeviceServerRepresentationError = void 0; exports.openNewEpochBasedOnCurrent = openNewEpochBasedOnCurrent; const DeviceAndVirtualDeviceCommonKeyBundle_1 = require("../device/key-bundles/DeviceAndVirtualDeviceCommonKeyBundle"); const key_derivation_1 = require("../crypto/key-derivation"); const utils_1 = require("../crypto/utils"); const authenticate_device_to_epoch_1 = require("./authenticate-device-to-epoch"); const keys_1 = require("../crypto/keys"); const authenticated_symmetric_encryption_1 = require("../crypto/authenticated-symmetric-encryption"); const public_key_encryption_1 = require("../crypto/public-key-encryption"); const BytesSerializer_1 = require("../BytesSerializer"); async function openNewEpochBasedOnCurrent(currentEpoch, webClient, thisDevice) { const devicesInEpochPromise = webClient.getDevicesInEpoch(currentEpoch.id); const [epochChainingKey, epochDistributionPreSharedKey] = (0, key_derivation_1.kdfTwoKeys)(currentEpoch.rootKey, null, (0, utils_1.asciiStringToBytes)(`epoch_chaining_${currentEpoch.sequenceId}_${currentEpoch.id}`)); const newEpochEntropy = (0, utils_1.random)(32); const newEpochWithoutId = { rootKey: (0, key_derivation_1.kdfOneKey)(newEpochEntropy, epochChainingKey, (0, utils_1.asciiStringToBytes)("epoch_root_key")), sequenceId: (BigInt(currentEpoch.sequenceId) + BigInt(1)).toString(), }; const devicesInEpoch = await devicesInEpochPromise; const encryptedNewEpochEntropyForEveryDeviceInEpoch = await encryptNewEpochEntropyForEveryDeviceInEpoch(currentEpoch, newEpochWithoutId, thisDevice, devicesInEpoch.devices.map((v) => { return { id: v.id, mac: BytesSerializer_1.bytesSerializerProvider.bytesSerializer.deserialize(v.mac), publicKeyBundle: DeviceAndVirtualDeviceCommonKeyBundle_1.CommonPublicKeyBundle.deserialize(v.keyBundle), }; }), { mac: BytesSerializer_1.bytesSerializerProvider.bytesSerializer.deserialize(devicesInEpoch.virtualDevice.mac), publicKeyBundle: DeviceAndVirtualDeviceCommonKeyBundle_1.CommonPublicKeyBundle.deserialize(devicesInEpoch.virtualDevice.keyBundle), }, epochDistributionPreSharedKey, newEpochEntropy); const { openedEpochId } = await webClient.openNewEpochBasedOnCurrent(currentEpoch.id, thisDevice.id, { encryptedNewEpochEntropyForEveryDeviceInEpoch: { deviceIdToEncryptedNewEpochEntropyMap: Object.fromEntries(Object.entries(encryptedNewEpochEntropyForEveryDeviceInEpoch.deviceIdToEncryptedNewEpochEntropyMap).map((e) => { const [k, v] = e; return [ k, BytesSerializer_1.bytesSerializerProvider.bytesSerializer.serialize(v), ]; })), virtualDeviceEncryptedNewEpochEntropy: BytesSerializer_1.bytesSerializerProvider.bytesSerializer.serialize(encryptedNewEpochEntropyForEveryDeviceInEpoch.virtualDeviceEncryptedNewEpochEntropy), }, newEpochMembershipProof: { epochThisDeviceMac: BytesSerializer_1.bytesSerializerProvider.bytesSerializer.serialize((0, authenticate_device_to_epoch_1.generateEpochDeviceMac)(newEpochWithoutId, thisDevice.keyBundle.pub.deviceKeyPub)), epochVirtualDeviceMac: BytesSerializer_1.bytesSerializerProvider.bytesSerializer.serialize((0, authenticate_device_to_epoch_1.generateEpochDeviceMac)(newEpochWithoutId, keys_1.PublicKey.deserialize(devicesInEpoch.virtualDevice.keyBundle .deviceKeyPub))), }, }); return { id: openedEpochId, sequenceId: newEpochWithoutId.sequenceId, rootKey: newEpochWithoutId.rootKey, }; } // @ts-ignore async function encryptCurrentEpochJoinData(currentEpoch, newEpochWithoutId) { const newEpochDataStorageKey = (0, key_derivation_1.kdfOneKey)(newEpochWithoutId.rootKey, null, (0, utils_1.asciiStringToBytes)(`epoch_data_storage_${newEpochWithoutId.sequenceId}`)); const encryptedCurrentEpochSequenceId = (0, authenticated_symmetric_encryption_1.encryptWithRandomNonce)(newEpochDataStorageKey, (0, utils_1.asciiStringToBytes)("epoch_data_metadata"), (0, utils_1.asciiStringToBytes)(currentEpoch.sequenceId)); const encryptedCurrentEpochRootKey = (0, authenticated_symmetric_encryption_1.encryptWithRandomNonce)(newEpochDataStorageKey, (0, utils_1.asciiStringToBytes)("epoch_data_metadata"), currentEpoch.rootKey); return { encryptedEpochSequenceId: BytesSerializer_1.bytesSerializerProvider.bytesSerializer.serialize(encryptedCurrentEpochSequenceId), encryptedEpochRootKey: BytesSerializer_1.bytesSerializerProvider.bytesSerializer.serialize(encryptedCurrentEpochRootKey), }; } class InvalidVirtualDeviceServerRepresentationError extends Error { constructor() { super("Epoch can't be opened when virtual device server representation is invalid"); Object.setPrototypeOf(this, InvalidVirtualDeviceServerRepresentationError.prototype); } } exports.InvalidVirtualDeviceServerRepresentationError = InvalidVirtualDeviceServerRepresentationError; async function encryptNewEpochEntropyForEveryDeviceInEpoch(currentEpoch, newEpochWithoutId, thisDevice, devicesInEpoch, virtualDeviceInEpoch, epochDistributionPreSharedKey, newEpochEntropy) { function encryptNewEpochEntropyForDeviceInEpoch(deviceInEpoch) { const expectedEpochDeviceMac = (0, authenticate_device_to_epoch_1.generateEpochDeviceMac)(currentEpoch, deviceInEpoch.publicKeyBundle.deviceKeyPub); if (!(0, utils_1.bytes_equal)(deviceInEpoch.mac, expectedEpochDeviceMac)) { return null; } const isValidEpochStorageKey = deviceInEpoch.publicKeyBundle.deviceKeyPub.verify(deviceInEpoch.publicKeyBundle.epochStorageKeySig, Uint8Array.of(0x30), deviceInEpoch.publicKeyBundle.epochStorageKeyPub.getX25519PublicKeyBytes()); if (!isValidEpochStorageKey) { return null; } return (0, public_key_encryption_1.labyrinth_hpke_encrypt)(deviceInEpoch.publicKeyBundle.epochStorageKeyPub, thisDevice.keyBundle.pub.epochStorageAuthKeyPub, thisDevice.keyBundle.priv.epochStorageAuthKeyPriv, epochDistributionPreSharedKey, (0, utils_1.asciiStringToBytes)(`epoch_${newEpochWithoutId.sequenceId}`), newEpochEntropy); } const virtualDeviceEncryptedNewEpochEntropy = encryptNewEpochEntropyForDeviceInEpoch(virtualDeviceInEpoch); if (virtualDeviceEncryptedNewEpochEntropy === null) { throw new InvalidVirtualDeviceServerRepresentationError(); } const deviceIdToEncryptedNewEpochEntropyMap = Object.fromEntries((await Promise.all(devicesInEpoch.map(async (device) => [ device.id, encryptNewEpochEntropyForDeviceInEpoch(device), ]))).filter((e) => e[1] !== null)); return { deviceIdToEncryptedNewEpochEntropyMap, virtualDeviceEncryptedNewEpochEntropy, }; }