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