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
at main 60 lines 1.6 kB view raw
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}