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(
12 atob(s)
13 .split('')
14 .map((c) => c.charCodeAt(0)),
15 )
16}
17
18async function derive(password: string, salt: Uint8Array) {
19 const k = await crypto.subtle.importKey(
20 'raw',
21 new TextEncoder().encode(password),
22 'PBKDF2',
23 false,
24 ['deriveKey'],
25 )
26 return crypto.subtle.deriveKey(
27 { name: 'PBKDF2', salt, iterations: 250000, hash: 'SHA-256' },
28 k,
29 { name: 'AES-GCM', length: 256 },
30 false,
31 ['encrypt', 'decrypt'],
32 )
33}
34
35export async function encryptPayload(
36 password: string,
37 text: string,
38): Promise<EncryptedPayload> {
39 const salt = crypto.getRandomValues(new Uint8Array(16))
40 const iv = crypto.getRandomValues(new Uint8Array(12))
41 const key = await derive(password, salt)
42 const ct = await crypto.subtle.encrypt(
43 { name: 'AES-GCM', iv },
44 key,
45 new TextEncoder().encode(text),
46 )
47 return {
48 ciphertext: JSON.stringify({
49 v: 1,
50 salt: b64(salt),
51 ct: b64(new Uint8Array(ct)),
52 }),
53 iv: b64(iv),
54 }
55}
56
57export async function decryptPayload(
58 password: string,
59 ciphertext: string,
60 iv: string,
61) {
62 const d = JSON.parse(ciphertext)
63 const key = await derive(password, ub64(d.salt))
64 const pt = await crypto.subtle.decrypt(
65 { name: 'AES-GCM', iv: ub64(iv) },
66 key,
67 ub64(d.ct),
68 )
69 return new TextDecoder().decode(pt)
70}
71
72export function isEncryptedPayload(value: unknown): value is EncryptedPayload {
73 if (!value || typeof value !== 'object') return false
74 return 'ciphertext' in value && 'iv' in value
75}