summaryrefslogtreecommitdiff
path: root/client-web/source/protocol/crypto.ts
blob: c541188546231c139072cf025ecd236d656da6f9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/*
    This file is part of keks-meet (https://codeberg.org/metamuffin/keks-meet)
    which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
    Copyright (C) 2023 metamuffin <metamuffin.org>
*/
import { log } from "../logger.ts";

//! I am not a crypto expert at all! Please read carefully and report any issues to me. 

const IV_LENGTH = 12

export async function crypto_seeded_key(seed: string): Promise<CryptoKey> {
    log("crypto", "importing seed…")
    const seed_key = await window.crypto.subtle.importKey(
        "raw",
        new TextEncoder().encode(seed),
        "PBKDF2",
        false,
        ["deriveKey"]
    )
    const salt = base64_to_buf("thisisagoodsaltAAAAAAA==") // valid "unique" 16-byte base-64 string
    log("crypto", "deriving key…")
    const key = await window.crypto.subtle.deriveKey(
        { name: "PBKDF2", salt, iterations: 250000, hash: "SHA-256" },
        seed_key,
        { name: "AES-GCM", length: 256 },
        false,
        ["encrypt", "decrypt"]
    )
    log("crypto", "ready")
    return key
}

export async function crypto_hash(input: string): Promise<string> {
    const buf = new TextEncoder().encode("also-a-very-good-salt" + input)
    const h = await window.crypto.subtle.digest({ name: "SHA-512" }, buf)
    const hex = buf_to_hex(new Uint8Array(h))
    return hex
}

export async function crypto_encrypt(key: CryptoKey, data: string): Promise<string> {
    const iv = window.crypto.getRandomValues(new Uint8Array(IV_LENGTH));
    const ciphertext = new Uint8Array(await window.crypto.subtle.encrypt(
        { name: "AES-GCM", iv },
        key,
        new TextEncoder().encode(data)
    ));
    const buf = new Uint8Array(iv.byteLength + ciphertext.byteLength);
    buf.set(iv, 0);
    buf.set(ciphertext, iv.byteLength);
    const b64 = buf_to_base64(buf);
    return b64;
}

export async function crypt_decrypt(key: CryptoKey, data: string): Promise<string> {
    try {
        const buf = base64_to_buf(data);
        const iv = buf.slice(0, IV_LENGTH);
        const ciphertext = buf.slice(IV_LENGTH);
        const decryptedContent = await window.crypto.subtle.decrypt(
            { name: "AES-GCM", iv },
            key,
            ciphertext
        );
        const plain = new TextDecoder().decode(decryptedContent);
        return plain
    } catch (_e) {
        log({ scope: "crypto", warn: true }, "unable to decrypt")
        return "{}" // :)
    }
}

//* Code that might be useful in the future for signing
// const ECDSA_PARAMS = { name: "ECDSA", namedCurve: "P-521", hash: { name: "SHA-384" } }
// export async function crypto_sign(key: CryptoKey, message: string): Promise<string> {
//     const signature = await crypto.subtle.sign(
//         ECDSA_PARAMS,
//         key,
//         new TextEncoder().encode(message)
//     )
//     return buf_to_base64(new Uint8Array(signature))
// }
// export async function crypto_generate_signing_key(): Promise<CryptoKeyPair> {
//     return await crypto.subtle.generateKey(
//         ECDSA_PARAMS,
//         false,
//         ["sign", "verify"]
//     )
// }
// export async function crypto_verify(key: CryptoKey, message: string, signature: string): Promise<boolean> {
//     return await crypto.subtle.verify(
//         ECDSA_PARAMS,
//         key,
//         base64_to_buf(signature).buffer,
//         new TextEncoder().encode(message)
//     )
// }
// export async function export_public_signing_key(key: CryptoKey): Promise<string> {
//     const buf = await crypto.subtle.exportKey("spki", key)
//     return buf_to_base64(new Uint8Array(buf))
// }
// export async function import_public_signing_key(der: string): Promise<CryptoKey | undefined> {
//     const bin_der = base64_to_buf(der).buffer; // TODO safety
//     return await crypto.subtle.importKey("spki", bin_der, ECDSA_PARAMS, true, ["verify"]) // TODO safety
// }

export function base64_to_buf(data: string): Uint8Array {
    const binary_string = globalThis.atob(data);
    const bytes = new Uint8Array(binary_string.length);
    for (let i = 0; i < binary_string.length; i++) {
        bytes[i] = binary_string.charCodeAt(i);
    }
    return bytes;
}

export function buf_to_base64(bytes: Uint8Array): string {
    let binary = '';
    for (let i = 0; i < bytes.byteLength; i++) {
        binary += String.fromCharCode(bytes[i]);
    }
    return globalThis.btoa(binary);
}

export function buf_to_hex(bytes: Uint8Array): string {
    return Array.from(bytes).map((b) => b.toString(16).padStart(2, '0')).join('');
}