One Calendar is a privacy-first calendar web app built with Next.js. It has modern security features, including e2ee, password-protected sharing, and self-destructing share links 馃搮
calendar.xyehr.cn
1export type EncryptedPayload = {
2 ciphertext: string
3 iv: string
4}
5
6function b64(u: Uint8Array) {
7 return btoa(String.fromCharCode(...u))
8}
9
10function ub64(s: string) {
11 return new Uint8Array(atob(s).split("").map((c) => c.charCodeAt(0)))
12}
13
14async function derive(password: string, salt: Uint8Array) {
15 const k = await crypto.subtle.importKey(
16 "raw",
17 new TextEncoder().encode(password),
18 "PBKDF2",
19 false,
20 ["deriveKey"],
21 )
22 return crypto.subtle.deriveKey(
23 { name: "PBKDF2", salt, iterations: 250000, hash: "SHA-256" },
24 k,
25 { name: "AES-GCM", length: 256 },
26 false,
27 ["encrypt", "decrypt"],
28 )
29}
30
31export async function encryptPayload(password: string, text: string): Promise<EncryptedPayload> {
32 const salt = crypto.getRandomValues(new Uint8Array(16))
33 const iv = crypto.getRandomValues(new Uint8Array(12))
34 const key = await derive(password, salt)
35 const ct = await crypto.subtle.encrypt(
36 { name: "AES-GCM", iv },
37 key,
38 new TextEncoder().encode(text),
39 )
40 return {
41 ciphertext: JSON.stringify({ v: 1, salt: b64(salt), ct: b64(new Uint8Array(ct)) }),
42 iv: b64(iv),
43 }
44}
45
46export async function decryptPayload(password: string, ciphertext: string, iv: string) {
47 const d = JSON.parse(ciphertext)
48 const key = await derive(password, ub64(d.salt))
49 const pt = await crypto.subtle.decrypt(
50 { name: "AES-GCM", iv: ub64(iv) },
51 key,
52 ub64(d.ct),
53 )
54 return new TextDecoder().decode(pt)
55}
56
57export function isEncryptedPayload(value: unknown): value is EncryptedPayload {
58 if (!value || typeof value !== "object") return false
59 return "ciphertext" in value && "iv" in value
60}