UNPKG

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

Version:

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

77 lines (76 loc) 3.81 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.InvalidRecoveryCodeFormatError = exports.IDENTIFIER = exports.VERSION_NUMBER = exports.VirtualDevice = void 0; const VirtualDeviceEncryptedRecoverySecrets_1 = require("./VirtualDeviceEncryptedRecoverySecrets"); const VirtualDeviceKeyBundle_1 = require("../key-bundles/VirtualDeviceKeyBundle"); const utils_1 = require("../../crypto/utils"); const key_derivation_1 = require("../../crypto/key-derivation"); const BytesSerializer_1 = require("../../BytesSerializer"); class VirtualDevice { id; keyBundle; constructor(id, keyBundle) { this.id = id; this.keyBundle = keyBundle; } static initialize(userId) { const recoveryCode = generateRecoveryCode(); const { virtualDeviceId, virtualDeviceDecryptionKey } = deriveVirtualDeviceIdAndDecryptionKey(userId, recoveryCode); const keyBundle = VirtualDeviceKeyBundle_1.VirtualDeviceKeyBundle.generate(); const virtualDevice = new VirtualDevice(virtualDeviceId, keyBundle); return { virtualDevice, virtualDeviceDecryptionKey, recoveryCode, }; } static async fromRecoveryCode(userId, recoveryCode, webClient) { const { virtualDeviceId, virtualDeviceDecryptionKey } = deriveVirtualDeviceIdAndDecryptionKey(userId, recoveryCode); const { epochId, virtualDeviceEncryptedRecoverySecrets, expectedVirtualDevicePublicKeyBundle, } = await webClient.getVirtualDeviceRecoverySecrets({ virtualDeviceId: BytesSerializer_1.bytesSerializerProvider.bytesSerializer.serialize(virtualDeviceId), }); const { virtualDeviceKeyBundle, epochWithoutId } = (0, VirtualDeviceEncryptedRecoverySecrets_1.decryptVirtualDeviceRecoverySecrets)(virtualDeviceDecryptionKey, VirtualDeviceEncryptedRecoverySecrets_1.VirtualDeviceEncryptedRecoverySecrets.deserialize(virtualDeviceEncryptedRecoverySecrets), VirtualDeviceKeyBundle_1.VirtualDevicePublicKeyBundle.deserialize(expectedVirtualDevicePublicKeyBundle)); const epoch = { id: epochId, ...epochWithoutId, }; const virtualDevice = new VirtualDevice(virtualDeviceId, virtualDeviceKeyBundle); return { virtualDevice, epoch, }; } } exports.VirtualDevice = VirtualDevice; exports.VERSION_NUMBER = 1; exports.IDENTIFIER = 0; const ALPHABET = "ACDEFHJKLMNPQRSTUVWXYZ0123456789"; const ALPHABET_LENGTH = ALPHABET.length; (0, utils_1.cryptoAssert)(ALPHABET_LENGTH === 32); function generateRecoveryCode() { const randomBytes = (0, utils_1.random)(34); let entropy = ""; for (const randomByte of randomBytes) { entropy += ALPHABET[randomByte % ALPHABET_LENGTH]; } // TODO: Error correction code not implemented yet - XXXX return `${exports.VERSION_NUMBER}${exports.IDENTIFIER}${entropy}XXXX`; } class InvalidRecoveryCodeFormatError extends Error { constructor(message) { super(message); Object.setPrototypeOf(this, InvalidRecoveryCodeFormatError.prototype); } } exports.InvalidRecoveryCodeFormatError = InvalidRecoveryCodeFormatError; function deriveVirtualDeviceIdAndDecryptionKey(userId, recoveryCode) { if (recoveryCode.length !== 40) throw new InvalidRecoveryCodeFormatError("Recovery code has to be 40 characters long"); const ikm = (0, utils_1.asciiStringToBytes)(recoveryCode.slice(3, 37)); const info = (0, utils_1.asciiStringToBytes)(`BackupRecoveryCode_v${recoveryCode[0]}_${recoveryCode[1]}_${userId}`); const [virtualDeviceId, virtualDeviceDecryptionKey] = (0, key_derivation_1.kdfTwoKeys)(ikm, null, info, 16, 32); return { virtualDeviceId: virtualDeviceId, virtualDeviceDecryptionKey, }; }