client/packages/crypto/src/callSecret.ts
Alex 2a55c87df1
Some checks failed
Dependency builds / Build web (pull_request) Has been skipped
Dependency builds / Build desktop (pull_request) Has been skipped
Dependency builds / Test native MTP (pull_request) Has been skipped
Dependency builds / Build mobile (pull_request) Has been skipped
/ build-web (push) Failing after 3m32s
/ build-desktop (linux) (push) Failing after 2m47s
/ build-mobile (push) Failing after 5m29s
/ release (push) Has been skipped
[Updt] Mtp 0.3.0
2026-08-20 17:05:53 +02:00

117 lines
2.8 KiB
TypeScript

import { crypto } from "mtp";
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
export const CALL_SECRET_WRAPPING_SCHEME =
"mtp-call-secret-kem-chacha20poly1305-hkdf-sha256-v1";
const CALL_SECRET_SALT = textEncoder.encode("tensamin-call-secret-v1");
export function deriveCallSecretId(callId: string): string {
return `call:${callId}:main`;
}
export function ownKemPublicKeyFromKeyring(keyring: string): Uint8Array {
return crypto.keyringToKeys({ value: keyring, encoding: "base64" })
.kemPublicKey;
}
export function kemPublicKeyFromPublicKeyBundle(publicKey: string): Uint8Array {
return crypto.publicKeyBundleToKeys({ value: publicKey, encoding: "base64" })
.kemPublicKey;
}
export async function wrapCallSecret(args: {
callSecret: string;
recipientKemPublicKey: Uint8Array;
callId: string;
secretId: string;
version: number;
}): Promise<{
encryptedSecret: Uint8Array;
kemCiphertext: Uint8Array;
wrappingScheme: string;
}> {
const enc = crypto.encapsulate(args.recipientKemPublicKey);
try {
const wrappingKey = deriveWrappingKey({
sharedSecret: enc.shared_secret,
callId: args.callId,
secretId: args.secretId,
version: args.version,
});
try {
return {
encryptedSecret: await crypto.encrypt(
wrappingKey,
textEncoder.encode(args.callSecret),
),
kemCiphertext: enc.ciphertext,
wrappingScheme: CALL_SECRET_WRAPPING_SCHEME,
};
} finally {
wrappingKey.fill(0);
}
} finally {
enc.shared_secret.fill(0);
}
}
export async function unwrapCallSecret(args: {
encryptedSecret: Uint8Array;
kemCiphertext: Uint8Array;
keyring: string;
callId: string;
secretId: string;
version: number;
wrappingScheme: string;
}): Promise<string> {
if (args.wrappingScheme !== CALL_SECRET_WRAPPING_SCHEME) {
throw new Error(
`Unsupported call secret wrapping scheme: ${args.wrappingScheme}`,
);
}
const ownKeys = crypto.keyringToKeys({
value: args.keyring,
encoding: "base64",
});
const sharedSecret = crypto.decapsulate(
ownKeys.kemSecretKey,
args.kemCiphertext,
);
try {
const wrappingKey = deriveWrappingKey({
sharedSecret,
callId: args.callId,
secretId: args.secretId,
version: args.version,
});
try {
return textDecoder.decode(
await crypto.decrypt(wrappingKey, args.encryptedSecret),
);
} finally {
wrappingKey.fill(0);
}
} finally {
sharedSecret.fill(0);
}
}
function deriveWrappingKey(args: {
sharedSecret: Uint8Array;
callId: string;
secretId: string;
version: number;
}): Uint8Array {
return crypto.deriveEncryptionKey(
args.sharedSecret,
CALL_SECRET_SALT,
textEncoder.encode(`${args.callId}:${args.secretId}:${args.version}`),
);
}