General Upgrade, NEW: WebServers, Better Docs
Some checks failed
CI / checks (push) Failing after 4m20s
Some checks failed
CI / checks (push) Failing after 4m20s
This commit is contained in:
parent
5f11d476b6
commit
62a8327239
122 changed files with 10187 additions and 5169 deletions
|
|
@ -1,35 +1,5 @@
|
|||
import * as bindings from "mtp/raw";
|
||||
|
||||
function utf8Encode(text: string): Uint8Array {
|
||||
if (typeof TextEncoder !== "undefined") {
|
||||
return new TextEncoder().encode(text);
|
||||
}
|
||||
if (typeof Buffer !== "undefined") {
|
||||
return new Uint8Array(Buffer.from(text, "utf-8"));
|
||||
}
|
||||
const bytes = new Uint8Array(text.length * 4);
|
||||
let len = 0;
|
||||
for (let i = 0; i < text.length; i += 1) {
|
||||
const code = text.codePointAt(i) as number;
|
||||
if (code < 0x80) {
|
||||
bytes[len++] = code;
|
||||
} else if (code < 0x800) {
|
||||
bytes[len++] = 0xc0 | (code >> 6);
|
||||
bytes[len++] = 0x80 | (code & 0x3f);
|
||||
} else if (code < 0x10000) {
|
||||
bytes[len++] = 0xe0 | (code >> 12);
|
||||
bytes[len++] = 0x80 | ((code >> 6) & 0x3f);
|
||||
bytes[len++] = 0x80 | (code & 0x3f);
|
||||
} else {
|
||||
bytes[len++] = 0xf0 | (code >> 18);
|
||||
bytes[len++] = 0x80 | ((code >> 12) & 0x3f);
|
||||
bytes[len++] = 0x80 | ((code >> 6) & 0x3f);
|
||||
bytes[len++] = 0x80 | (code & 0x3f);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
return bytes.subarray(0, len);
|
||||
}
|
||||
import { concatBytes, utf8Encode, writeU64BE } from "./utils.js";
|
||||
|
||||
export const HKDF_SALT_ROOT = "mtp-e2ee-v1-root";
|
||||
const HKDF_INITIATOR_SEND = "mtp-e2ee-v1-initiator-send";
|
||||
|
|
@ -55,10 +25,17 @@ export interface MTPSessionState {
|
|||
recvChainKey: Uint8Array;
|
||||
sendCount: number;
|
||||
recvCount: number;
|
||||
/** Derived receive keys retained for bounded out-of-order delivery. */
|
||||
skippedMessageKeys?: SkippedMessageKey[];
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
export interface SkippedMessageKey {
|
||||
messageNumber: number;
|
||||
key: Uint8Array;
|
||||
}
|
||||
|
||||
export interface MTPSessionStorage {
|
||||
getSession(conversationId: string): Promise<MTPSessionState | null>;
|
||||
setSession(state: MTPSessionState): Promise<void>;
|
||||
|
|
@ -68,15 +45,40 @@ export interface MTPSessionStorage {
|
|||
export class InMemorySessionStorage implements MTPSessionStorage {
|
||||
private store = new Map<string, MTPSessionState>();
|
||||
|
||||
private cloneSession(state: MTPSessionState): MTPSessionState {
|
||||
return {
|
||||
...state,
|
||||
peerPublicKey: state.peerPublicKey.slice(),
|
||||
sendChainKey: state.sendChainKey.slice(),
|
||||
recvChainKey: state.recvChainKey.slice(),
|
||||
skippedMessageKeys: (state.skippedMessageKeys ?? []).map((skipped) => ({
|
||||
messageNumber: skipped.messageNumber,
|
||||
key: skipped.key.slice(),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
private zeroizeSession(state: MTPSessionState): void {
|
||||
state.sendChainKey.fill(0);
|
||||
state.recvChainKey.fill(0);
|
||||
for (const skipped of state.skippedMessageKeys ?? []) skipped.key.fill(0);
|
||||
}
|
||||
|
||||
async getSession(conversationId: string): Promise<MTPSessionState | null> {
|
||||
return this.store.get(conversationId) ?? null;
|
||||
const state = this.store.get(conversationId);
|
||||
return state ? this.cloneSession(state) : null;
|
||||
}
|
||||
|
||||
async setSession(state: MTPSessionState): Promise<void> {
|
||||
this.store.set(state.conversationId, { ...state });
|
||||
const replacement = this.cloneSession(state);
|
||||
const previous = this.store.get(state.conversationId);
|
||||
if (previous) this.zeroizeSession(previous);
|
||||
this.store.set(state.conversationId, replacement);
|
||||
}
|
||||
|
||||
async deleteSession(conversationId: string): Promise<void> {
|
||||
const previous = this.store.get(conversationId);
|
||||
if (previous) this.zeroizeSession(previous);
|
||||
this.store.delete(conversationId);
|
||||
}
|
||||
}
|
||||
|
|
@ -90,27 +92,6 @@ function writeU32BE(value: number): Uint8Array {
|
|||
]);
|
||||
}
|
||||
|
||||
function writeU64BE(value: bigint): Uint8Array {
|
||||
if (value < 0n || value > 0xffff_ffff_ffff_ffffn)
|
||||
throw new Error("u64 out of range");
|
||||
const buf = new Uint8Array(8);
|
||||
for (let i = 7; i >= 0; i -= 1) {
|
||||
buf[i] = Number(value & 0xffn);
|
||||
value >>= 8n;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
function concatBytes(parts: Uint8Array[]): Uint8Array {
|
||||
const out = new Uint8Array(parts.reduce((sum, part) => sum + part.length, 0));
|
||||
let offset = 0;
|
||||
for (const part of parts) {
|
||||
out.set(part, offset);
|
||||
offset += part.length;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function transcriptField(label: string, value: Uint8Array): Uint8Array {
|
||||
const labelBytes = utf8Encode(label);
|
||||
return concatBytes([
|
||||
|
|
@ -240,6 +221,7 @@ export class MTPSessionManager {
|
|||
recvChainKey: args.role === "initiator" ? initiatorRecv : initiatorSend,
|
||||
sendCount: 0,
|
||||
recvCount: 0,
|
||||
skippedMessageKeys: [],
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue