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