(feat): more crypto migration
This commit is contained in:
parent
f3ecb8f3dd
commit
2777ba34ca
11 changed files with 504 additions and 715 deletions
143
packages/crypto/src/chatSecret.ts
Normal file
143
packages/crypto/src/chatSecret.ts
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
import { crypto } from "mtp";
|
||||
|
||||
const textEncoder = new TextEncoder();
|
||||
|
||||
export const CHAT_SECRET_WRAPPING_SCHEME =
|
||||
"mtp-chat-secret-kem-chacha20poly1305-hkdf-sha256-v1";
|
||||
|
||||
const CHAT_SECRET_SALT = textEncoder.encode("tensamin-chat-secret-v1");
|
||||
const CHAT_MESSAGE_SALT = textEncoder.encode("tensamin-chat-message-v1");
|
||||
|
||||
export function deriveChatId(ownUserId: number, peerUserId: number): string {
|
||||
const ids = [ownUserId, peerUserId].sort((a, b) => a - b);
|
||||
return `${ids[0]}:${ids[1]}`;
|
||||
}
|
||||
|
||||
export function deriveChatSecretId(chatId: string): string {
|
||||
return `chat:${chatId}:main`;
|
||||
}
|
||||
|
||||
export function randomChatSecret(): Uint8Array {
|
||||
return globalThis.crypto.getRandomValues(new Uint8Array(32));
|
||||
}
|
||||
|
||||
export function ownKemPublicKeyFromKeyring(keyring: string): Uint8Array {
|
||||
return crypto.keyringToKeys(keyring).kemPublicKey;
|
||||
}
|
||||
|
||||
export function kemPublicKeyFromPublicKeyBundle(publicKey: string): Uint8Array {
|
||||
return crypto.publicKeyBundleToKeys(publicKey).kemPublicKey;
|
||||
}
|
||||
|
||||
export async function wrapChatSecret(args: {
|
||||
chatSecret: Uint8Array;
|
||||
recipientKemPublicKey: Uint8Array;
|
||||
chatId: 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,
|
||||
chatId: args.chatId,
|
||||
secretId: args.secretId,
|
||||
version: args.version,
|
||||
});
|
||||
|
||||
try {
|
||||
return {
|
||||
encryptedSecret: await crypto.encrypt(wrappingKey, args.chatSecret),
|
||||
kemCiphertext: enc.ciphertext,
|
||||
wrappingScheme: CHAT_SECRET_WRAPPING_SCHEME,
|
||||
};
|
||||
} finally {
|
||||
wrappingKey.fill(0);
|
||||
}
|
||||
} finally {
|
||||
enc.shared_secret.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
export async function unwrapChatSecret(args: {
|
||||
encryptedSecret: Uint8Array;
|
||||
kemCiphertext: Uint8Array;
|
||||
keyring: string;
|
||||
chatId: string;
|
||||
secretId: string;
|
||||
version: number;
|
||||
wrappingScheme: string;
|
||||
}): Promise<Uint8Array> {
|
||||
if (args.wrappingScheme !== CHAT_SECRET_WRAPPING_SCHEME) {
|
||||
throw new Error(`Unsupported chat secret wrapping scheme: ${args.wrappingScheme}`);
|
||||
}
|
||||
|
||||
const ownKeys = crypto.keyringToKeys(args.keyring);
|
||||
const sharedSecret = crypto.decapsulate(ownKeys.kemSecretKey, args.kemCiphertext);
|
||||
|
||||
try {
|
||||
const wrappingKey = deriveWrappingKey({
|
||||
sharedSecret,
|
||||
chatId: args.chatId,
|
||||
secretId: args.secretId,
|
||||
version: args.version,
|
||||
});
|
||||
|
||||
try {
|
||||
return await crypto.decrypt(wrappingKey, args.encryptedSecret);
|
||||
} finally {
|
||||
wrappingKey.fill(0);
|
||||
}
|
||||
} finally {
|
||||
sharedSecret.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
export async function encryptChatText(
|
||||
chatSecret: Uint8Array,
|
||||
plaintext: string,
|
||||
): Promise<string> {
|
||||
const key = deriveMessageKey(chatSecret);
|
||||
try {
|
||||
return await crypto.encryptText(key, plaintext);
|
||||
} finally {
|
||||
key.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
export async function decryptChatText(
|
||||
chatSecret: Uint8Array,
|
||||
ciphertext: string,
|
||||
): Promise<string> {
|
||||
const key = deriveMessageKey(chatSecret);
|
||||
try {
|
||||
return await crypto.decryptText(key, ciphertext);
|
||||
} finally {
|
||||
key.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
function deriveWrappingKey(args: {
|
||||
sharedSecret: Uint8Array;
|
||||
chatId: string;
|
||||
secretId: string;
|
||||
version: number;
|
||||
}): Uint8Array {
|
||||
return crypto.deriveEncryptionKey(
|
||||
args.sharedSecret,
|
||||
CHAT_SECRET_SALT,
|
||||
textEncoder.encode(`${args.chatId}:${args.secretId}:${args.version}`),
|
||||
);
|
||||
}
|
||||
|
||||
function deriveMessageKey(chatSecret: Uint8Array): Uint8Array {
|
||||
return crypto.deriveEncryptionKey(
|
||||
chatSecret,
|
||||
CHAT_MESSAGE_SALT,
|
||||
textEncoder.encode("message-content"),
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue