[web] Crypto API cleanup (non functional) (#6178)

This commit is contained in:
Manav Rathi
2025-06-05 17:40:09 +05:30
committed by GitHub
16 changed files with 472 additions and 264 deletions

View File

@@ -41,7 +41,7 @@ import {
type ModalVisibilityProps,
} from "ente-base/components/utils/modal";
import { useBaseContext } from "ente-base/context";
import { sharedCryptoWorker } from "ente-base/crypto";
import { deriveInteractiveKey } from "ente-base/crypto";
import { isHTTP4xxError } from "ente-base/http";
import { formattedDateTime } from "ente-base/i18n-date";
import log from "ente-base/log";
@@ -1659,14 +1659,11 @@ const SetPublicLinkPassword: React.FC<SetPublicLinkPasswordProps> = ({
};
const enablePublicUrlPassword = async (password: string) => {
const cryptoWorker = await sharedCryptoWorker();
const kekSalt = await cryptoWorker.generateSaltToDeriveKey();
const kek = await cryptoWorker.deriveInteractiveKey(password, kekSalt);
const kek = await deriveInteractiveKey(password);
return updatePublicShareURLHelper({
collectionID: collection.id,
passHash: kek.key,
nonce: kekSalt,
nonce: kek.salt,
opsLimit: kek.opsLimit,
memLimit: kek.memLimit,
});

View File

@@ -72,9 +72,10 @@ const createCollection = async (
const token = getToken();
const collectionKey = await cryptoWorker.generateKey();
const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } =
await cryptoWorker.encryptToB64(collectionKey, encryptionKey);
await cryptoWorker.encryptBox(collectionKey, encryptionKey);
const { encryptedData: encryptedName, nonce: nameDecryptionNonce } =
await cryptoWorker.encryptUTF8(collectionName, collectionKey);
await cryptoWorker.encryptBoxUTF8(collectionName, collectionKey);
let encryptedMagicMetadata: EncryptedMagicMetadata;
if (magicMetadataProps) {
const magicMetadata = await updateMagicMetadata(magicMetadataProps);
@@ -476,7 +477,7 @@ export const renameCollection = async (
const token = getToken();
const cryptoWorker = await sharedCryptoWorker();
const { encryptedData: encryptedName, nonce: nameDecryptionNonce } =
await cryptoWorker.encryptUTF8(newCollectionName, collection.key);
await cryptoWorker.encryptBoxUTF8(newCollectionName, collection.key);
const collectionRenameRequest = {
collectionID: collection.id,
encryptedName,

View File

@@ -325,9 +325,11 @@ export const getPublicCollection = async (
const collectionName = (fetchedCollection.name =
fetchedCollection.name ||
(await cryptoWorker.decryptToUTF8(
fetchedCollection.encryptedName,
fetchedCollection.nameDecryptionNonce,
(await cryptoWorker.decryptBoxUTF8(
{
encryptedData: fetchedCollection.encryptedName,
nonce: fetchedCollection.nameDecryptionNonce,
},
collectionKey,
)));

View File

@@ -10,6 +10,7 @@ import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect";
import {
convertBase64ToBuffer,
convertBufferToBase64,
deriveSRPPassword,
generateSRPClient,
generateSRPSetupAttributes,
} from "ente-accounts/services/srp";
@@ -25,9 +26,9 @@ import type {
} from "ente-accounts/services/user";
import { LinkButton } from "ente-base/components/LinkButton";
import { sharedCryptoWorker } from "ente-base/crypto";
import type { DerivedKey } from "ente-base/crypto/types";
import {
generateAndSaveIntermediateKeyAttributes,
generateLoginSubKey,
saveKeyInSessionStore,
} from "ente-shared/crypto/helpers";
import { getData, setData } from "ente-shared/storage/localStorage";
@@ -60,27 +61,24 @@ const Page: React.FC = () => {
const cryptoWorker = await sharedCryptoWorker();
const key = await getActualKey();
const keyAttributes: KeyAttributes = getData("keyAttributes");
const kekSalt = await cryptoWorker.generateSaltToDeriveKey();
let kek: { key: string; opsLimit: number; memLimit: number };
let kek: DerivedKey;
try {
kek = await cryptoWorker.deriveSensitiveKey(passphrase, kekSalt);
kek = await cryptoWorker.deriveSensitiveKey(passphrase);
} catch {
setFieldError("confirm", t("password_generation_failed"));
return;
}
const encryptedKeyAttributes = await cryptoWorker.encryptToB64(
key,
kek.key,
);
const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } =
await cryptoWorker.encryptBox(key, kek.key);
const updatedKey: UpdatedKey = {
kekSalt,
encryptedKey: encryptedKeyAttributes.encryptedData,
keyDecryptionNonce: encryptedKeyAttributes.nonce,
encryptedKey,
keyDecryptionNonce,
kekSalt: kek.salt,
opsLimit: kek.opsLimit,
memLimit: kek.memLimit,
};
const loginSubKey = await generateLoginSubKey(kek.key);
const loginSubKey = await deriveSRPPassword(kek.key);
const { srpUserID, srpSalt, srpVerifier } =
await generateSRPSetupAttributes(loginSubKey);

View File

@@ -23,6 +23,7 @@ import {
import { checkSessionValidity } from "ente-accounts/services/session";
import {
configureSRP,
deriveSRPPassword,
generateSRPSetupAttributes,
loginViaSRP,
} from "ente-accounts/services/srp";
@@ -39,7 +40,6 @@ import log from "ente-base/log";
import {
decryptAndStoreToken,
generateAndSaveIntermediateKeyAttributes,
generateLoginSubKey,
saveKeyInSessionStore,
} from "ente-shared/crypto/helpers";
import { CustomError } from "ente-shared/error";
@@ -292,7 +292,7 @@ const Page: React.FC = () => {
}
log.debug(() => `userSRPSetupPending ${!srpAttributes}`);
if (!srpAttributes) {
const loginSubKey = await generateLoginSubKey(kek);
const loginSubKey = await deriveSRPPassword(kek);
const srpSetupAttributes =
await generateSRPSetupAttributes(loginSubKey);
await configureSRP(srpSetupAttributes);

View File

@@ -1,7 +1,6 @@
import type { KeyAttributes } from "ente-accounts/services/user";
import { sharedCryptoWorker } from "ente-base/crypto";
import { deriveSubKeyBytes, sharedCryptoWorker, toB64 } from "ente-base/crypto";
import log from "ente-base/log";
import { generateLoginSubKey } from "ente-shared/crypto/helpers";
import { getToken } from "ente-shared/storage/localStorage/helpers";
import { SRP, SrpClient } from "fast-srp-hap";
import { v4 as uuidv4 } from "uuid";
@@ -17,6 +16,22 @@ import type { UserVerificationResponse } from "./user";
const SRP_PARAMS = SRP.params["4096"];
/**
* Derive a "password" (which is really an arbitrary binary value, not human
* generated) for use as the SRP user password by applying a deterministic KDF
* (Key Derivation Function) to the provided {@link kek}.
*
* @param kek The user's kek (key encryption key) as a base64 string.
*
* @returns A string that can be used as the SRP user password.
*/
export const deriveSRPPassword = async (kek: string) => {
const kekSubKeyBytes = await deriveSubKeyBytes(kek, 32, 1, "loginctx");
// Use the first 16 bytes (128 bits) of the KEK's KDF subkey as the SRP
// password (instead of entire 32 bytes).
return toB64(kekSubKeyBytes.slice(0, 16));
};
export const configureSRP = async ({
srpSalt,
srpUserID,
@@ -59,7 +74,7 @@ export const generateSRPSetupAttributes = async (
): Promise<SRPSetupAttributes> => {
const cryptoWorker = await sharedCryptoWorker();
const srpSalt = await cryptoWorker.generateSaltToDeriveKey();
const srpSalt = await cryptoWorker.generateDeriveKeySalt();
// Museum schema requires this to be a UUID.
const srpUserID = uuidv4();
@@ -87,7 +102,7 @@ export const loginViaSRP = async (
kek: string,
): Promise<UserVerificationResponse> => {
try {
const loginSubKey = await generateLoginSubKey(kek);
const loginSubKey = await deriveSRPPassword(kek);
const srpClient = await generateSRPClient(
srpAttributes.srpSalt,
srpAttributes.srpUserID,
@@ -173,47 +188,42 @@ export async function generateKeyAndSRPAttributes(
const cryptoWorker = await sharedCryptoWorker();
const masterKey = await cryptoWorker.generateKey();
const recoveryKey = await cryptoWorker.generateKey();
const kekSalt = await cryptoWorker.generateSaltToDeriveKey();
const kek = await cryptoWorker.deriveSensitiveKey(passphrase, kekSalt);
const kek = await cryptoWorker.deriveSensitiveKey(passphrase);
const masterKeyEncryptedWithKek = await cryptoWorker.encryptToB64(
masterKey,
kek.key,
);
const masterKeyEncryptedWithRecoveryKey = await cryptoWorker.encryptToB64(
masterKey,
recoveryKey,
);
const recoveryKeyEncryptedWithMasterKey = await cryptoWorker.encryptToB64(
recoveryKey,
masterKey,
);
const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } =
await cryptoWorker.encryptBox(masterKey, kek.key);
const {
encryptedData: masterKeyEncryptedWithRecoveryKey,
nonce: masterKeyDecryptionNonce,
} = await cryptoWorker.encryptBox(masterKey, recoveryKey);
const {
encryptedData: recoveryKeyEncryptedWithMasterKey,
nonce: recoveryKeyDecryptionNonce,
} = await cryptoWorker.encryptBox(recoveryKey, masterKey);
const keyPair = await cryptoWorker.generateKeyPair();
const encryptedKeyPairAttributes = await cryptoWorker.encryptToB64(
keyPair.privateKey,
masterKey,
);
const {
encryptedData: encryptedSecretKey,
nonce: secretKeyDecryptionNonce,
} = await cryptoWorker.encryptBox(keyPair.privateKey, masterKey);
const loginSubKey = await generateLoginSubKey(kek.key);
const loginSubKey = await deriveSRPPassword(kek.key);
const srpSetupAttributes = await generateSRPSetupAttributes(loginSubKey);
const keyAttributes: KeyAttributes = {
kekSalt,
encryptedKey: masterKeyEncryptedWithKek.encryptedData,
keyDecryptionNonce: masterKeyEncryptedWithKek.nonce,
publicKey: keyPair.publicKey,
encryptedSecretKey: encryptedKeyPairAttributes.encryptedData,
secretKeyDecryptionNonce: encryptedKeyPairAttributes.nonce,
encryptedKey,
keyDecryptionNonce,
kekSalt: kek.salt,
opsLimit: kek.opsLimit,
memLimit: kek.memLimit,
masterKeyEncryptedWithRecoveryKey:
masterKeyEncryptedWithRecoveryKey.encryptedData,
masterKeyDecryptionNonce: masterKeyEncryptedWithRecoveryKey.nonce,
recoveryKeyEncryptedWithMasterKey:
recoveryKeyEncryptedWithMasterKey.encryptedData,
recoveryKeyDecryptionNonce: recoveryKeyEncryptedWithMasterKey.nonce,
publicKey: keyPair.publicKey,
encryptedSecretKey,
secretKeyDecryptionNonce,
masterKeyEncryptedWithRecoveryKey,
masterKeyDecryptionNonce,
recoveryKeyEncryptedWithMasterKey,
recoveryKeyDecryptionNonce,
};
return { keyAttributes, masterKey, srpSetupAttributes };

View File

@@ -105,6 +105,14 @@ export interface KeyAttributes {
* (https://doc.libsodium.org/public-key_cryptography/authenticated_encryption#key-pair-generation),
* who possibly chose public + secret instead of public + private to avoid
* confusion with shorthand notation (pk).
*
* However, the library author later changed their mind on this, so while
* libsodium itself (the C library) and the documentation uses "secretKey",
* the JavaScript implementation (libsodium.js) uses "privateKey".
*
* This structure uses the term "secretKey" since that is what the remote
* protocol already was based on. Within the web app codebase, we use
* "privateKey" since that is what the underlying libsodium.js uses.
*/
encryptedSecretKey: string;
/**

View File

@@ -13,16 +13,18 @@ export const _fromHex = libsodium.fromHex;
export const _generateKey = libsodium.generateKey;
export const _generateBlobOrStreamKey = libsodium.generateBlobOrStreamKey;
export const _encryptBox = libsodium.encryptBox;
export const _encryptBlobBytes = libsodium.encryptBlobBytes;
export const _encryptBoxUTF8 = libsodium.encryptBoxUTF8;
export const _encryptBlob = libsodium.encryptBlob;
export const _encryptBlobBytes = libsodium.encryptBlobBytes;
export const _encryptMetadataJSON = libsodium.encryptMetadataJSON;
export const _encryptStreamBytes = libsodium.encryptStreamBytes;
export const _initChunkEncryption = libsodium.initChunkEncryption;
export const _encryptStreamChunk = libsodium.encryptStreamChunk;
export const _decryptBoxBytes = libsodium.decryptBoxBytes;
export const _decryptBox = libsodium.decryptBox;
export const _decryptBlobBytes = libsodium.decryptBlobBytes;
export const _decryptBoxBytes = libsodium.decryptBoxBytes;
export const _decryptBoxUTF8 = libsodium.decryptBoxUTF8;
export const _decryptBlob = libsodium.decryptBlob;
export const _decryptBlobBytes = libsodium.decryptBlobBytes;
export const _decryptMetadataJSON = libsodium.decryptMetadataJSON;
export const _decryptStreamBytes = libsodium.decryptStreamBytes;
export const _initChunkDecryption = libsodium.initChunkDecryption;
@@ -33,6 +35,8 @@ export const _chunkHashFinal = libsodium.chunkHashFinal;
export const _generateKeyPair = libsodium.generateKeyPair;
export const _boxSeal = libsodium.boxSeal;
export const _boxSealOpen = libsodium.boxSealOpen;
export const _generateDeriveKeySalt = libsodium.generateDeriveKeySalt;
export const _deriveKey = libsodium.deriveKey;
export const _deriveSensitiveKey = libsodium.deriveSensitiveKey;
export const _deriveInteractiveKey = libsodium.deriveInteractiveKey;
export const _deriveSubKeyBytes = libsodium.deriveSubKeyBytes;

View File

@@ -27,6 +27,8 @@
* to functions exposed by `crypto/libsodium.ts`, but they automatically defer
* to a worker thread if we're not already running on one.
*
* ---
*
* [Note: Using libsodium in worker thread]
*
* `crypto/ente-impl.ts` and `crypto/worker.ts` are logic-less internal files
@@ -53,16 +55,53 @@
* Also, some code (e.g. the uploader) creates it own crypto worker instances,
* and thus directly calls the functions in the web worker that it created
* instead of going through this file.
*
* ---
*
* [Note: Crypto layer API data types]
*
* There are two primary types used when exchanging data with these functions:
*
* 1. Base64 strings. Unqualified strings are taken as base64 encoded
* representations of the underlying data. Usually, the unqualified "base"
* function deals with Base64 strings, since they also are the data type in
* which we usually send the encryted data etc to remote.
*
* 2. Raw bytes. Uint8Arrays are byte arrays. The functions that deal with bytes
* are indicated by a *Bytes suffix in their name.
*
* Where possible and useful, functions also accept a union of these two - a
* {@link BytesOrB64} where the implementation will automatically convert
* to/from base64 to bytes if needed, thus saving on unnecessary conversions at
* the caller side.
*
* Apart from these two, there are other secondary and one off types.
*
* 1. "Regular" JavaScript strings. These are indicated by the *UTF8 suffix on
* the function that deals with them. These strings will be obtained by utf-8
* encoding (or decoding) the underlying bytes.
*
* 2. Hex representations of the bytes. These are indicated by the *Hex suffix
* on the functions dealing with them.
*
* 2. JSON values. These are indicated by the *JSON suffix on the functions
* dealing with them.
*/
import { ComlinkWorker } from "ente-base/worker/comlink-worker";
import { type StateAddress } from "libsodium-wrappers-sumo";
import { inWorker } from "../env";
import * as ei from "./ente-impl";
import type {
BytesOrB64,
DerivedKey,
EncryptedBlob,
EncryptedBlobB64,
EncryptedBlobBytes,
EncryptedBox,
EncryptedBoxB64,
EncryptedFile,
InitChunkDecryptionResult,
InitChunkEncryptionResult,
SodiumStateAddress,
} from "./types";
import type { CryptoWorker } from "./worker";
@@ -74,7 +113,7 @@ let _comlinkWorker: ComlinkWorker<typeof CryptoWorker> | undefined;
/**
* Lazily created, cached, instance of a CryptoWorker web worker.
*/
export const sharedCryptoWorker = async () =>
export const sharedCryptoWorker = () =>
(_comlinkWorker ??= createComlinkCryptoWorker()).remote;
/** A shorter alias of {@link sharedCryptoWorker} for use within this file. */
@@ -93,13 +132,13 @@ export const createComlinkCryptoWorker = () =>
/**
* Convert bytes ({@link Uint8Array}) to a base64 string.
*/
export const toB64 = (bytes: Uint8Array) =>
export const toB64 = (bytes: Uint8Array): Promise<string> =>
inWorker() ? ei._toB64(bytes) : sharedWorker().then((w) => w.toB64(bytes));
/**
* URL safe variant of {@link toB64}.
*/
export const toB64URLSafe = (bytes: Uint8Array) =>
export const toB64URLSafe = (bytes: Uint8Array): Promise<string> =>
inWorker()
? ei._toB64URLSafe(bytes)
: sharedWorker().then((w) => w.toB64URLSafe(bytes));
@@ -107,7 +146,7 @@ export const toB64URLSafe = (bytes: Uint8Array) =>
/**
* Convert a base64 string to bytes ({@link Uint8Array}).
*/
export const fromB64 = (b64String: string) =>
export const fromB64 = (b64String: string): Promise<Uint8Array> =>
inWorker()
? ei._fromB64(b64String)
: sharedWorker().then((w) => w.fromB64(b64String));
@@ -115,7 +154,7 @@ export const fromB64 = (b64String: string) =>
/**
* Convert a base64 string to the hex representation of the underlying bytes.
*/
export const toHex = (b64String: string) =>
export const toHex = (b64String: string): Promise<string> =>
inWorker()
? ei._toHex(b64String)
: sharedWorker().then((w) => w.toHex(b64String));
@@ -123,7 +162,7 @@ export const toHex = (b64String: string) =>
/**
* Convert a hex string to the base64 representation of the underlying bytes.
*/
export const fromHex = (hexString: string) =>
export const fromHex = (hexString: string): Promise<string> =>
inWorker()
? ei._fromHex(hexString)
: sharedWorker().then((w) => w.fromHex(hexString));
@@ -134,7 +173,7 @@ export const fromHex = (hexString: string) =>
* The returned key is suitable for use with the *Box encryption functions, and
* as a general encryption key (e.g. as the user's master key or recovery key).
*/
export const generateKey = () =>
export const generateKey = (): Promise<string> =>
inWorker()
? ei._generateKey()
: sharedWorker().then((w) => w.generateKey());
@@ -143,7 +182,7 @@ export const generateKey = () =>
* Return a new randomly generated 256-bit key (as a base64 string) suitable for
* use with the *Blob or *Stream encryption functions.
*/
export const generateBlobOrStreamKey = () =>
export const generateBlobOrStreamKey = (): Promise<string> =>
inWorker()
? ei._generateBlobOrStreamKey()
: sharedWorker().then((w) => w.generateBlobOrStreamKey());
@@ -161,11 +200,26 @@ export const generateBlobOrStreamKey = () =>
* >
* > See: [Note: 3 forms of encryption (Box | Blob | Stream)]
*/
export const encryptBox = (data: BytesOrB64, key: BytesOrB64) =>
export const encryptBox = (
data: BytesOrB64,
key: BytesOrB64,
): Promise<EncryptedBoxB64> =>
inWorker()
? ei._encryptBox(data, key)
: sharedWorker().then((w) => w.encryptBox(data, key));
/**
* A variant of {@link encryptBox} that first UTF-8 encodes the input string to
* obtain bytes, which it then encrypts.
*/
export const encryptBoxUTF8 = (
data: string,
key: BytesOrB64,
): Promise<EncryptedBoxB64> =>
inWorker()
? ei._encryptBoxUTF8(data, key)
: sharedWorker().then((w) => w.encryptBoxUTF8(data, key));
/**
* Encrypt the given data, returning a blob containing the encrypted data and a
* decryption header as base64 strings.
@@ -180,7 +234,10 @@ export const encryptBox = (data: BytesOrB64, key: BytesOrB64) =>
* >
* > See: [Note: 3 forms of encryption (Box | Blob | Stream)]
*/
export const encryptBlob = (data: BytesOrB64, key: BytesOrB64) =>
export const encryptBlob = (
data: BytesOrB64,
key: BytesOrB64,
): Promise<EncryptedBlobB64> =>
inWorker()
? ei._encryptBlob(data, key)
: sharedWorker().then((w) => w.encryptBlob(data, key));
@@ -191,42 +248,14 @@ export const encryptBlob = (data: BytesOrB64, key: BytesOrB64) =>
*
* Use {@link decryptBlob} or {@link decryptBlobBytes} to decrypt the result.
*/
export const encryptBlobBytes = (data: BytesOrB64, key: BytesOrB64) =>
export const encryptBlobBytes = (
data: BytesOrB64,
key: BytesOrB64,
): Promise<EncryptedBlobBytes> =>
inWorker()
? ei._encryptBlobBytes(data, key)
: sharedWorker().then((w) => w.encryptBlobBytes(data, key));
/**
* Encrypt the given data using chunked streaming encryption, but process all
* the chunks in one go.
*/
export const encryptStreamBytes = async (data: Uint8Array, key: BytesOrB64) =>
inWorker()
? ei._encryptStreamBytes(data, key)
: sharedWorker().then((w) => w.encryptStreamBytes(data, key));
/**
* Prepare for chunked streaming encryption using {@link encryptStreamChunk}.
*/
export const initChunkEncryption = async (key: BytesOrB64) =>
inWorker()
? ei._initChunkEncryption(key)
: sharedWorker().then((w) => w.initChunkEncryption(key));
/**
* Encrypt a chunk as part of a chunked streaming encryption.
*/
export const encryptStreamChunk = async (
data: Uint8Array,
state: StateAddress,
isFinalChunk: boolean,
) =>
inWorker()
? ei._encryptStreamChunk(data, state, isFinalChunk)
: sharedWorker().then((w) =>
w.encryptStreamChunk(data, state, isFinalChunk),
);
/**
* Encrypt the JSON metadata associated with an Ente object (file, collection or
* entity) using the object's key.
@@ -247,16 +276,58 @@ export const encryptStreamChunk = async (
*
* @param key The encryption key.
*/
export const encryptMetadataJSON = (jsonValue: unknown, key: BytesOrB64) =>
export const encryptMetadataJSON = (
jsonValue: unknown,
key: BytesOrB64,
): Promise<EncryptedBlobB64> =>
inWorker()
? ei._encryptMetadataJSON(jsonValue, key)
: sharedWorker().then((w) => w.encryptMetadataJSON(jsonValue, key));
/**
* Encrypt the given data using chunked streaming encryption, but process all
* the chunks in one go.
*/
export const encryptStreamBytes = (
data: Uint8Array,
key: BytesOrB64,
): Promise<EncryptedFile> =>
inWorker()
? ei._encryptStreamBytes(data, key)
: sharedWorker().then((w) => w.encryptStreamBytes(data, key));
/**
* Prepare for chunked streaming encryption using {@link encryptStreamChunk}.
*/
export const initChunkEncryption = (
key: BytesOrB64,
): Promise<InitChunkEncryptionResult> =>
inWorker()
? ei._initChunkEncryption(key)
: sharedWorker().then((w) => w.initChunkEncryption(key));
/**
* Encrypt a chunk as part of a chunked streaming encryption.
*/
export const encryptStreamChunk = (
data: Uint8Array,
state: SodiumStateAddress,
isFinalChunk: boolean,
): Promise<Uint8Array> =>
inWorker()
? ei._encryptStreamChunk(data, state, isFinalChunk)
: sharedWorker().then((w) =>
w.encryptStreamChunk(data, state, isFinalChunk),
);
/**
* Decrypt a box encrypted using {@link encryptBox} and returns the decrypted
* bytes as a base64 string.
*/
export const decryptBox = (box: EncryptedBox, key: BytesOrB64) =>
export const decryptBox = (
box: EncryptedBox,
key: BytesOrB64,
): Promise<string> =>
inWorker()
? ei._decryptBox(box, key)
: sharedWorker().then((w) => w.decryptBox(box, key));
@@ -265,16 +336,35 @@ export const decryptBox = (box: EncryptedBox, key: BytesOrB64) =>
* Variant of {@link decryptBox} that returns the decrypted bytes as it is
* (without encoding them to base64).
*/
export const decryptBoxBytes = (box: EncryptedBox, key: BytesOrB64) =>
export const decryptBoxBytes = (
box: EncryptedBox,
key: BytesOrB64,
): Promise<Uint8Array> =>
inWorker()
? ei._decryptBoxBytes(box, key)
: sharedWorker().then((w) => w.decryptBoxBytes(box, key));
/**
* Variant of {@link decryptBoxBytes} that returns the decrypted bytes as a
* "JavaScript string", specifically a UTF-8 string. That is, after decryption
* we obtain raw bytes, which we interpret as a UTF-8 string.
*/
export const decryptBoxUTF8 = (
box: EncryptedBox,
key: BytesOrB64,
): Promise<string> =>
inWorker()
? ei._decryptBoxUTF8(box, key)
: sharedWorker().then((w) => w.decryptBoxUTF8(box, key));
/**
* Decrypt a blob encrypted using either {@link encryptBlobBytes} or
* {@link encryptBlob} and return it as a base64 encoded string.
*/
export const decryptBlob = (blob: EncryptedBlob, key: BytesOrB64) =>
export const decryptBlob = (
blob: EncryptedBlob,
key: BytesOrB64,
): Promise<string> =>
inWorker()
? ei._decryptBlob(blob, key)
: sharedWorker().then((w) => w.decryptBlob(blob, key));
@@ -283,7 +373,10 @@ export const decryptBlob = (blob: EncryptedBlob, key: BytesOrB64) =>
* A variant of {@link decryptBlobBytes} that returns the result bytes directly
* (instead of encoding them as a base64 string).
*/
export const decryptBlobBytes = (blob: EncryptedBlob, key: BytesOrB64) =>
export const decryptBlobBytes = (
blob: EncryptedBlob,
key: BytesOrB64,
): Promise<Uint8Array> =>
inWorker()
? ei._decryptBlobBytes(blob, key)
: sharedWorker().then((w) => w.decryptBlobBytes(blob, key));
@@ -291,10 +384,10 @@ export const decryptBlobBytes = (blob: EncryptedBlob, key: BytesOrB64) =>
/**
* Decrypt the result of {@link encryptStreamBytes}.
*/
export const decryptStreamBytes = async (
export const decryptStreamBytes = (
file: EncryptedFile,
key: BytesOrB64,
) =>
): Promise<Uint8Array> =>
inWorker()
? ei._decryptStreamBytes(file, key)
: sharedWorker().then((w) => w.decryptStreamBytes(file, key));
@@ -303,7 +396,10 @@ export const decryptStreamBytes = async (
* Prepare to decrypt the encrypted result produced using {@link initChunkEncryption} and
* {@link encryptStreamChunk}.
*/
export const initChunkDecryption = async (header: string, key: BytesOrB64) =>
export const initChunkDecryption = (
header: string,
key: BytesOrB64,
): Promise<InitChunkDecryptionResult> =>
inWorker()
? ei._initChunkDecryption(header, key)
: sharedWorker().then((w) => w.initChunkDecryption(header, key));
@@ -313,10 +409,10 @@ export const initChunkDecryption = async (header: string, key: BytesOrB64) =>
*
* This function is used in tandem with {@link initChunkDecryption}.
*/
export const decryptStreamChunk = async (
export const decryptStreamChunk = (
data: Uint8Array,
state: StateAddress,
) =>
state: SodiumStateAddress,
): Promise<Uint8Array> =>
inWorker()
? ei._decryptStreamChunk(data, state)
: sharedWorker().then((w) => w.decryptStreamChunk(data, state));
@@ -327,7 +423,10 @@ export const decryptStreamChunk = async (
* @returns The decrypted JSON value. Since TypeScript does not have a native
* JSON type, we need to return it as an `unknown`.
*/
export const decryptMetadataJSON = (blob: EncryptedBlob, key: BytesOrB64) =>
export const decryptMetadataJSON = (
blob: EncryptedBlob,
key: BytesOrB64,
): Promise<unknown> =>
inWorker()
? ei._decryptMetadataJSON(blob, key)
: sharedWorker().then((w) => w.decryptMetadataJSON(blob, key));
@@ -335,7 +434,10 @@ export const decryptMetadataJSON = (blob: EncryptedBlob, key: BytesOrB64) =>
/**
* Generate a new public/private keypair.
*/
export const generateKeyPair = async () =>
export const generateKeyPair = (): Promise<{
publicKey: string;
privateKey: string;
}> =>
inWorker()
? ei._generateKeyPair()
: sharedWorker().then((w) => w.generateKeyPair());
@@ -343,7 +445,7 @@ export const generateKeyPair = async () =>
/**
* Public key encryption.
*/
export const boxSeal = async (data: string, publicKey: string) =>
export const boxSeal = (data: string, publicKey: string): Promise<string> =>
inWorker()
? ei._boxSeal(data, publicKey)
: sharedWorker().then((w) => w.boxSeal(data, publicKey));
@@ -351,26 +453,37 @@ export const boxSeal = async (data: string, publicKey: string) =>
/**
* Decrypt the result of {@link boxSeal}.
*/
export const boxSealOpen = async (
export const boxSealOpen = (
encryptedData: string,
publicKey: string,
secretKey: string,
) =>
): Promise<string> =>
inWorker()
? ei._boxSealOpen(encryptedData, publicKey, secretKey)
: sharedWorker().then((w) =>
w.boxSealOpen(encryptedData, publicKey, secretKey),
);
/**
* Return a new randomly generated 128-bit salt (as a base64 string).
*
* The returned salt is suitable for use with {@link deriveKey}, and also as a
* general 128-bit salt.
*/
export const generateDeriveKeySalt = (): Promise<string> =>
inWorker()
? ei._generateDeriveKeySalt()
: sharedWorker().then((w) => w.generateDeriveKeySalt());
/**
* Derive a key by hashing the given {@link passphrase} using Argon 2id.
*/
export const deriveKey = async (
export const deriveKey = (
passphrase: string,
salt: string,
opsLimit: number,
memLimit: number,
) =>
): Promise<string> =>
inWorker()
? ei._deriveKey(passphrase, salt, opsLimit, memLimit)
: sharedWorker().then((w) =>
@@ -380,15 +493,34 @@ export const deriveKey = async (
/**
* Derive a sensitive key from the given {@link passphrase}.
*/
export const deriveSensitiveKey = async (passphrase: string, salt: string) =>
export const deriveSensitiveKey = (passphrase: string): Promise<DerivedKey> =>
inWorker()
? ei._deriveSensitiveKey(passphrase, salt)
: sharedWorker().then((w) => w.deriveSensitiveKey(passphrase, salt));
? ei._deriveSensitiveKey(passphrase)
: sharedWorker().then((w) => w.deriveSensitiveKey(passphrase));
/**
* Derive an interactive key from the given {@link passphrase}.
* Derive an key suitable for interactive use from the given {@link passphrase}.
*/
export const deriveInteractiveKey = async (passphrase: string, salt: string) =>
export const deriveInteractiveKey = (
passphrase: string,
): Promise<DerivedKey> =>
inWorker()
? ei._deriveInteractiveKey(passphrase, salt)
: sharedWorker().then((w) => w.deriveInteractiveKey(passphrase, salt));
? ei._deriveInteractiveKey(passphrase)
: sharedWorker().then((w) => w.deriveInteractiveKey(passphrase));
/**
* Derive a subkey of the given {@link key} using the specified parameters.
*
* @returns the bytes of the derived subkey.
*/
export const deriveSubKeyBytes = async (
key: string,
subKeyLength: number,
subKeyID: number,
context: string,
): Promise<Uint8Array> =>
inWorker()
? ei._deriveSubKeyBytes(key, subKeyLength, subKeyID, context)
: sharedWorker().then((w) =>
w.deriveSubKeyBytes(key, subKeyLength, subKeyID, context),
);

View File

@@ -9,15 +9,19 @@
* To see where this code fits, see [Note: Crypto code hierarchy].
*/
import { mergeUint8Arrays } from "ente-utils/array";
import sodium, { type StateAddress } from "libsodium-wrappers-sumo";
import sodium from "libsodium-wrappers-sumo";
import type {
BytesOrB64,
DerivedKey,
EncryptedBlob,
EncryptedBlobB64,
EncryptedBlobBytes,
EncryptedBox,
EncryptedBoxB64,
EncryptedFile,
InitChunkDecryptionResult,
InitChunkEncryptionResult,
SodiumStateAddress,
} from "./types";
/**
@@ -35,7 +39,7 @@ export const toB64 = async (input: Uint8Array) => {
*
* This is the converse of {@link toBase64}.
*/
export const fromB64 = async (input: string) => {
export const fromB64 = async (input: string): Promise<Uint8Array> => {
await sodium.ready;
return sodium.from_base64(input, sodium.base64_variants.ORIGINAL);
};
@@ -116,7 +120,7 @@ export const toHex = async (input: string) => {
*
* This is the inverse of {@link toHex}.
*/
export const fromHex = async (input: string) => {
export const fromHex = async (input: string): Promise<string> => {
await sodium.ready;
return await toB64(sodium.from_hex(input));
};
@@ -283,6 +287,18 @@ export const encryptBox = async (
};
};
/**
* A variant of {@link encryptBox} that first converts the input string into
* bytes using a UTF-8 encoding, and then encrypts those bytes.
*/
export const encryptBoxUTF8 = async (
data: string,
key: BytesOrB64,
): Promise<EncryptedBoxB64> => {
await sodium.ready;
return encryptBox(sodium.from_string(data), key);
};
/**
* Encrypt the given data using libsodium's secretstream APIs without chunking.
*
@@ -435,7 +451,9 @@ export const encryptStreamBytes = async (
* to subsequent calls to {@link encryptStreamChunk} along with the chunks's
* contents.
*/
export const initChunkEncryption = async (key: BytesOrB64) => {
export const initChunkEncryption = async (
key: BytesOrB64,
): Promise<InitChunkEncryptionResult> => {
await sodium.ready;
const keyBytes = await bytes(key);
const { state, header } =
@@ -463,9 +481,9 @@ export const initChunkEncryption = async (key: BytesOrB64) => {
*/
export const encryptStreamChunk = async (
data: Uint8Array,
pushState: sodium.StateAddress,
pushState: SodiumStateAddress,
isFinalChunk: boolean,
) => {
): Promise<Uint8Array> => {
await sodium.ready;
const tag = isFinalChunk
? sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
@@ -501,6 +519,18 @@ export const decryptBox = (
key: BytesOrB64,
): Promise<string> => decryptBoxBytes(box, key).then(toB64);
/**
* Variant of {@link decryptBoxBytes} that returns the data after decoding the
* decrypted bytes as a utf-8 string.
*/
export const decryptBoxUTF8 = async (
box: EncryptedBox,
key: BytesOrB64,
): Promise<string> => {
await sodium.ready;
return sodium.to_string(await decryptBoxBytes(box, key));
};
/**
* Decrypt the result of {@link encryptBlobBytes} or {@link encryptBlob}.
*/
@@ -552,7 +582,7 @@ export const decryptMetadataJSON = async (
export const decryptStreamBytes = async (
{ encryptedData, decryptionHeader }: EncryptedFile,
key: BytesOrB64,
) => {
): Promise<Uint8Array> => {
await sodium.ready;
const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull(
await fromB64(decryptionHeader),
@@ -598,7 +628,7 @@ export const decryptStreamBytes = async (
export const initChunkDecryption = async (
decryptionHeader: string,
key: BytesOrB64,
) => {
): Promise<InitChunkDecryptionResult> => {
await sodium.ready;
const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull(
await fromB64(decryptionHeader),
@@ -621,8 +651,8 @@ export const initChunkDecryption = async (
*/
export const decryptStreamChunk = async (
data: Uint8Array,
pullState: StateAddress,
) => {
pullState: SodiumStateAddress,
): Promise<Uint8Array> => {
await sodium.ready;
const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull(
pullState,
@@ -638,7 +668,7 @@ export interface B64EncryptionResult {
}
/** Deprecated, use {@link encryptBox} instead */
export async function encryptToB64(data: string, keyB64: string) {
async function encryptToB64(data: string, keyB64: string) {
await sodium.ready;
const encrypted = await encryptBox(data, keyB64);
return {
@@ -654,29 +684,6 @@ export async function generateKeyAndEncryptToB64(data: string) {
return await encryptToB64(data, await toB64(key));
}
export async function encryptUTF8(data: string, key: string) {
await sodium.ready;
const b64Data = await toB64(sodium.from_string(data));
return await encryptToB64(b64Data, key);
}
/** Deprecated */
export async function decryptToUTF8(
encryptedData: string,
nonce: string,
keyB64: string,
) {
await sodium.ready;
const decrypted = await decryptBoxBytes({ encryptedData, nonce }, keyB64);
return sodium.to_string(decrypted);
}
/**
* An opaque object meant to be threaded through {@link chunkHashInit},
* {@link chunkHashUpdate} and {@link chunkHashFinal}.
*/
export type ChunkHashState = sodium.StateAddress;
/**
* Initialize and return new state that can be used to hash the chunks of data
* in a streaming manner.
@@ -693,7 +700,7 @@ export type ChunkHashState = sodium.StateAddress;
* (along with the data to hash) to {@link chunkHashUpdate}, and the final hash
* obtained using {@link chunkHashFinal}.
*/
export const chunkHashInit = async (): Promise<ChunkHashState> => {
export const chunkHashInit = async (): Promise<SodiumStateAddress> => {
await sodium.ready;
return sodium.crypto_generichash_init(
null,
@@ -711,7 +718,7 @@ export const chunkHashInit = async (): Promise<ChunkHashState> => {
* @param chunk The data (bytes) to hash.
*/
export const chunkHashUpdate = async (
hashState: ChunkHashState,
hashState: SodiumStateAddress,
chunk: Uint8Array,
) => {
await sodium.ready;
@@ -728,7 +735,7 @@ export const chunkHashUpdate = async (
*
* @returns The hash of all the chunks (as a base64 string).
*/
export const chunkHashFinal = async (hashState: ChunkHashState) => {
export const chunkHashFinal = async (hashState: SodiumStateAddress) => {
await sodium.ready;
const hash = sodium.crypto_generichash_final(
hashState,
@@ -795,6 +802,17 @@ export const boxSealOpen = async (
);
};
/**
* Generate a new randomly generated 128-bit salt suitable for use with the key
* derivation functions ({@link deriveKey} and its variants).
*
* @returns The base64 representation of a randomly generated 128-bit salt.
*/
export const generateDeriveKeySalt = async () => {
await sodium.ready;
return await toB64(sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES));
};
/**
* Derive a key by hashing the given {@link passphrase} using Argon 2id.
*
@@ -847,9 +865,13 @@ export const deriveKey = async (
* during the derivation (this information will be needed the user's other
* clients to derive the same result).
*/
export const deriveSensitiveKey = async (passphrase: string, salt: string) => {
export const deriveSensitiveKey = async (
passphrase: string,
): Promise<DerivedKey> => {
await sodium.ready;
const salt = await generateDeriveKeySalt();
const desiredStrength =
sodium.crypto_pwhash_MEMLIMIT_SENSITIVE *
sodium.crypto_pwhash_OPSLIMIT_SENSITIVE;
@@ -878,7 +900,7 @@ export const deriveSensitiveKey = async (passphrase: string, salt: string) => {
while (memLimit > minMemLimit) {
try {
const key = await deriveKey(passphrase, salt, opsLimit, memLimit);
return { key, opsLimit, memLimit };
return { key, salt, opsLimit, memLimit };
} catch {
opsLimit *= 2;
memLimit /= 2;
@@ -893,33 +915,54 @@ export const deriveSensitiveKey = async (passphrase: string, salt: string) => {
*/
export const deriveInteractiveKey = async (
passphrase: string,
salt: string,
) => {
): Promise<DerivedKey> => {
const salt = await generateDeriveKeySalt();
const opsLimit = sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE;
const memLimit = sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE;
const key = await deriveKey(passphrase, salt, opsLimit, memLimit);
return { key, opsLimit, memLimit };
return { key, salt, opsLimit, memLimit };
};
export async function generateSaltToDeriveKey() {
await sodium.ready;
return await toB64(sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES));
}
export async function generateSubKey(
key: string,
/**
* Derive a {@link subKeyID}-th subkey of length {@link subKeyLength} bytes by
* applying a KDF (Key Derivation Function) for the given {@link key} and the
* {@link context}.
*
* Multiple secret subkeys can be (deterministically) derived from a single
* high-entropy key. Knowledge of the derived key does not impact the security
* of the key from which it was derived, or of its potential sibling subkeys.
*
* See: https://doc.libsodium.org/key_derivation
*
* @param key The key whose subkey we are deriving. In the context of
* key derivation, this is usually referred to as the "master key", but we
* deemphasize that nomenclature to avoid confusion with the user's master key.
*
* @param subKeyLength The length of the required subkey.
*
* @param subKeyID An identifier of the subkey.
*
* @param context A short but otherwise arbitrary string (non-secret) used to
* separate domains in which the subkeys are going to be used.
*
* @returns The bytes of the subkey.
*
* Note that returning bytes is a bit unusual, usually we'd return the base64
* string from functions. However, this particular function is used in only one
* place in our code, and so we adapt the interface for its convenience.
*/
export const deriveSubKeyBytes = async (
key: BytesOrB64,
subKeyLength: number,
subKeyID: number,
context: string,
) {
): Promise<Uint8Array> => {
await sodium.ready;
return await toB64(
sodium.crypto_kdf_derive_from_key(
subKeyLength,
subKeyID,
context,
await fromB64(key),
),
return sodium.crypto_kdf_derive_from_key(
subKeyLength,
subKeyID,
context,
await bytes(key),
);
}
};

View File

@@ -1,3 +1,11 @@
import { type StateAddress } from "libsodium-wrappers-sumo";
/**
* An opaque object meant to be threaded through various functions that deal
* with resumable chunk based processing.
*/
export type SodiumStateAddress = StateAddress;
/**
* Data provided either as bytes ({@link Uint8Array}) or their base64 string
* representation.
@@ -129,3 +137,60 @@ export interface EncryptedFile {
*/
decryptionHeader: string;
}
/**
* An object returned by the init function of chunked encryption routines.
*/
export interface InitChunkEncryptionResult {
/**
* A base64 string containing the decryption header.
*
* While the exact contents of the header are libsodium's internal details,
* it effectively contains a random nonce generated by libsodium. It does
* not need to be secret, but it is required to decrypt the data.
*/
decryptionHeader: string;
/**
* An opaque value that refers to the internal state used by the resumable
* calls in the encryption sequence.
*/
pushState: SodiumStateAddress;
}
/**
* An object returned by the init function of chunked decryption routines.
*/
export interface InitChunkDecryptionResult {
/**
* An opaque value that refers to the internal state used by the resumable
* calls in the decryption sequence.
*/
pullState: SodiumStateAddress;
/**
* The expected size of each chunk.
*/
decryptionChunkSize: number;
}
/**
* A key derived from a user provided passphrase, and the various attributes
* that were used during the key derivation.
*/
export interface DerivedKey {
/**
* The newly derived key itself, as a base64 encoded string.
*/
key: string;
/**
* The randomly generated salt (as a base64 string) that was used when deriving the key.
*/
salt: string;
/**
* opsLimit used during key derivation.
*/
opsLimit: number;
/**
* memLimit used during key derivation.
* */
memLimit: number;
}

View File

@@ -21,16 +21,18 @@ export class CryptoWorker {
generateKey = ei._generateKey;
generateBlobOrStreamKey = ei._generateBlobOrStreamKey;
encryptBox = ei._encryptBox;
encryptBlobBytes = ei._encryptBlobBytes;
encryptBoxUTF8 = ei._encryptBoxUTF8;
encryptBlob = ei._encryptBlob;
encryptBlobBytes = ei._encryptBlobBytes;
encryptMetadataJSON = ei._encryptMetadataJSON;
encryptStreamBytes = ei._encryptStreamBytes;
initChunkEncryption = ei._initChunkEncryption;
encryptStreamChunk = ei._encryptStreamChunk;
decryptBoxBytes = ei._decryptBoxBytes;
decryptBox = ei._decryptBox;
decryptBlobBytes = ei._decryptBlobBytes;
decryptBoxBytes = ei._decryptBoxBytes;
decryptBoxUTF8 = ei._decryptBoxUTF8;
decryptBlob = ei._decryptBlob;
decryptBlobBytes = ei._decryptBlobBytes;
decryptMetadataJSON = ei._decryptMetadataJSON;
decryptStreamBytes = ei._decryptStreamBytes;
initChunkDecryption = ei._initChunkDecryption;
@@ -41,40 +43,17 @@ export class CryptoWorker {
generateKeyPair = ei._generateKeyPair;
boxSeal = ei._boxSeal;
boxSealOpen = ei._boxSealOpen;
generateDeriveKeySalt = ei._generateDeriveKeySalt;
deriveKey = ei._deriveKey;
deriveSensitiveKey = ei._deriveSensitiveKey;
deriveInteractiveKey = ei._deriveInteractiveKey;
deriveSubKeyBytes = ei._deriveSubKeyBytes;
// TODO: -- AUDIT BELOW --
async decryptToUTF8(data: string, nonce: string, key: string) {
return libsodium.decryptToUTF8(data, nonce, key);
}
async encryptToB64(data: string, key: string) {
return libsodium.encryptToB64(data, key);
}
async generateKeyAndEncryptToB64(data: string) {
return libsodium.generateKeyAndEncryptToB64(data);
}
async encryptUTF8(data: string, key: string) {
return libsodium.encryptUTF8(data, key);
}
async generateSaltToDeriveKey() {
return libsodium.generateSaltToDeriveKey();
}
async generateSubKey(
key: string,
subKeyLength: number,
subKeyID: number,
context: string,
) {
return libsodium.generateSubKey(key, subKeyLength, subKeyID, context);
}
}
expose(CryptoWorker);

View File

@@ -1,6 +1,5 @@
import { z } from "zod/v4";
import { decryptBoxBytes } from "./crypto";
import { toB64 } from "./crypto/libsodium";
import { decryptBoxBytes, toB64 } from "./crypto";
/**
* Remove all data stored in session storage (data tied to the browser tab).

View File

@@ -1425,7 +1425,7 @@ const constructPublicMagicMetadata = async (
const encryptFile = async (
file: FileWithMetadata,
encryptionKey: string,
collectionKey: string,
worker: CryptoWorker,
) => {
const fileKey = await worker.generateBlobOrStreamKey();
@@ -1467,7 +1467,7 @@ const encryptFile = async (
};
}
const encryptedKey = await worker.encryptToB64(fileKey, encryptionKey);
const encryptedFileKey = await worker.encryptBox(fileKey, collectionKey);
return {
encryptedFilePieces: {
@@ -1478,10 +1478,7 @@ const encryptFile = async (
pubMagicMetadata: encryptedPubMagicMetadata,
localID: localID,
},
encryptedFileKey: {
encryptedData: encryptedKey.encryptedData,
nonce: encryptedKey.nonce,
},
encryptedFileKey,
};
};

View File

@@ -226,9 +226,11 @@ export const getCollectionWithSecrets = async (
}
const collectionName =
collection.name ||
(await cryptoWorker.decryptToUTF8(
collection.encryptedName,
collection.nameDecryptionNonce,
(await cryptoWorker.decryptBoxUTF8(
{
encryptedData: collection.encryptedName,
nonce: collection.nameDecryptionNonce,
},
collectionKey,
));

View File

@@ -4,11 +4,6 @@ import { masterKeyFromSession } from "ente-base/session";
import { getData, setData, setLSUser } from "ente-shared/storage/localStorage";
import { type SessionKey, setKey } from "ente-shared/storage/sessionStorage";
const LOGIN_SUB_KEY_LENGTH = 32;
const LOGIN_SUB_KEY_ID = 1;
const LOGIN_SUB_KEY_CONTEXT = "loginctx";
const LOGIN_SUB_KEY_BYTE_LENGTH = 16;
export async function decryptAndStoreToken(
keyAttributes: KeyAttributes,
masterKey: string,
@@ -52,20 +47,14 @@ export async function generateAndSaveIntermediateKeyAttributes(
key: string,
): Promise<KeyAttributes> {
const cryptoWorker = await sharedCryptoWorker();
const intermediateKekSalt = await cryptoWorker.generateSaltToDeriveKey();
const intermediateKek = await cryptoWorker.deriveInteractiveKey(
passphrase,
intermediateKekSalt,
);
const encryptedKeyAttributes = await cryptoWorker.encryptToB64(
key,
intermediateKek.key,
);
const intermediateKek = await cryptoWorker.deriveInteractiveKey(passphrase);
const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } =
await cryptoWorker.encryptBox(key, intermediateKek.key);
const intermediateKeyAttributes = Object.assign(existingKeyAttributes, {
kekSalt: intermediateKekSalt,
encryptedKey: encryptedKeyAttributes.encryptedData,
keyDecryptionNonce: encryptedKeyAttributes.nonce,
encryptedKey,
keyDecryptionNonce,
kekSalt: intermediateKek.salt,
opsLimit: intermediateKek.opsLimit,
memLimit: intermediateKek.memLimit,
});
@@ -73,24 +62,6 @@ export async function generateAndSaveIntermediateKeyAttributes(
return intermediateKeyAttributes;
}
export const generateLoginSubKey = async (kek: string) => {
const cryptoWorker = await sharedCryptoWorker();
const kekSubKeyString = await cryptoWorker.generateSubKey(
kek,
LOGIN_SUB_KEY_LENGTH,
LOGIN_SUB_KEY_ID,
LOGIN_SUB_KEY_CONTEXT,
);
const kekSubKey = await cryptoWorker.fromB64(kekSubKeyString);
// use first 16 bytes of generated kekSubKey as loginSubKey
const loginSubKey = await cryptoWorker.toB64(
kekSubKey.slice(0, LOGIN_SUB_KEY_BYTE_LENGTH),
);
return loginSubKey;
};
export const saveKeyInSessionStore = async (
keyType: SessionKey,
key: string,