Phase 2 green: implement crypto primitives
Each stub is replaced with a thin wrapper over libsodium-wrappers-sumo:
* init() awaits sodium.ready
* toBase64 / toBase64URL / fromBase64 use sodium's base64 variants;
fromBase64 tries all four (standard, standard-no-pad, URL-safe,
URL-safe-no-pad) so callers don't have to know which form Ente
delivered
* deriveKEK is sodium.crypto_pwhash with ALG_ARGON2ID13 and 32-byte
output
* deriveLoginSubkey is sodium.crypto_kdf_derive_from_key(32, 1,
'loginctx', kek).slice(0, 16) per the upstream Ente clients
* decryptBox is sodium.crypto_secretbox_open_easy
* decryptSealed is sodium.crypto_box_seal_open
* initStreamPull / pullStreamChunk wrap the secretstream pull API,
throwing on authentication failure rather than returning false
All 32 tests pass; make check is green.
This commit is contained in:
@@ -1,22 +1,38 @@
|
||||
// Stub: see the README "Development workflow" section for TDD policy.
|
||||
import sodium from "libsodium-wrappers-sumo";
|
||||
|
||||
// Plaintext chunk size used by Ente for file content streams. Hard-coded by
|
||||
// the server; clients must match.
|
||||
export const STREAM_CHUNK_SIZE = 4 * 1024 * 1024;
|
||||
|
||||
// Per-chunk overhead added by libsodium's secretstream construction:
|
||||
// 16 bytes of Poly1305 tag plus 1 byte of secretstream tag.
|
||||
export const STREAM_CHUNK_OVERHEAD = 17;
|
||||
|
||||
export interface StreamPullState {
|
||||
readonly _opaque: unique symbol;
|
||||
}
|
||||
// Opaque handle to libsodium's secretstream pull state. Threaded through
|
||||
// successive pullStreamChunk calls.
|
||||
export type StreamPullState = sodium.StateAddress;
|
||||
|
||||
// Initialise a pull stream from the per-file decryption header and the
|
||||
// per-file key.
|
||||
export const initStreamPull = (
|
||||
_header: Uint8Array,
|
||||
_key: Uint8Array,
|
||||
): StreamPullState => {
|
||||
throw new Error("crypto.initStreamPull not implemented");
|
||||
};
|
||||
header: Uint8Array,
|
||||
key: Uint8Array,
|
||||
): StreamPullState =>
|
||||
sodium.crypto_secretstream_xchacha20poly1305_init_pull(header, key);
|
||||
|
||||
// Decrypt one ciphertext chunk. Returns the plaintext and the secretstream
|
||||
// tag (0=MESSAGE, 1=PUSH, 2=REKEY, 3=FINAL). The caller should verify the
|
||||
// stream ended on TAG_FINAL to detect truncation.
|
||||
export const pullStreamChunk = (
|
||||
_state: StreamPullState,
|
||||
_ciphertext: Uint8Array,
|
||||
state: StreamPullState,
|
||||
ciphertext: Uint8Array,
|
||||
): { plaintext: Uint8Array; tag: number } => {
|
||||
throw new Error("crypto.pullStreamChunk not implemented");
|
||||
const result = sodium.crypto_secretstream_xchacha20poly1305_pull(
|
||||
state,
|
||||
ciphertext,
|
||||
);
|
||||
if (result === false) {
|
||||
throw new Error("secretstream chunk authentication failed");
|
||||
}
|
||||
return { plaintext: result.message, tag: result.tag };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user