[web] Crypto API cleanup (non functional) (#6178)
This commit is contained in:
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
)));
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
));
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user