File size: 4,462 Bytes
73d7d26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
// SecureChat E2EE Crypto Layer
// Uses Web Crypto API: RSA-OAEP for key exchange, AES-GCM for messages

const Crypto = {
  // Generate RSA-OAEP key pair for E2EE
  async generateKeyPair() {
    const keyPair = await window.crypto.subtle.generateKey(
      { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" },
      true,
      ["encrypt", "decrypt"]
    );
    const publicKeyJwk = await window.crypto.subtle.exportKey("jwk", keyPair.publicKey);
    const privateKeyJwk = await window.crypto.subtle.exportKey("jwk", keyPair.privateKey);
    return { publicKeyJwk, privateKeyJwk, keyPair };
  },

  // Export public key as JSON string (stored on server)
  async exportPublicKey(keyPair) {
    return JSON.stringify(await window.crypto.subtle.exportKey("jwk", keyPair.publicKey));
  },

  // Import a public key from JWK JSON string
  async importPublicKey(jwkString) {
    const jwk = typeof jwkString === "string" ? JSON.parse(jwkString) : jwkString;
    return window.crypto.subtle.importKey("jwk", jwk, { name: "RSA-OAEP", hash: "SHA-256" }, false, ["encrypt"]);
  },

  // Import a private key from JWK JSON string
  async importPrivateKey(jwkString) {
    const jwk = typeof jwkString === "string" ? JSON.parse(jwkString) : jwkString;
    return window.crypto.subtle.importKey("jwk", jwk, { name: "RSA-OAEP", hash: "SHA-256" }, false, ["decrypt"]);
  },

  // Encrypt a message with a recipient's RSA public key (returns base64)
  async encryptForRecipient(message, publicKey) {
    const encoded = new TextEncoder().encode(message);
    const encrypted = await window.crypto.subtle.encrypt({ name: "RSA-OAEP" }, publicKey, encoded);
    return btoa(String.fromCharCode(...new Uint8Array(encrypted)));
  },

  // Decrypt a message with own RSA private key
  async decryptWithPrivateKey(ciphertextB64, privateKey) {
    const ciphertext = Uint8Array.from(atob(ciphertextB64), c => c.charCodeAt(0));
    const decrypted = await window.crypto.subtle.decrypt({ name: "RSA-OAEP" }, privateKey, ciphertext);
    return new TextDecoder().decode(decrypted);
  },

  // === Group E2EE: AES-GCM + per-member RSA key wrapping ===

  // Generate a random AES-GCM key for a group message
  async generateAesKey() {
    return window.crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"]);
  },

  // Encrypt message with AES-GCM key, returns {ciphertext: base64, iv: base64}
  async encryptWithAes(message, aesKey) {
    const iv = window.crypto.getRandomValues(new Uint8Array(12));
    const encoded = new TextEncoder().encode(message);
    const encrypted = await window.crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, encoded);
    return {
      ciphertext: btoa(String.fromCharCode(...new Uint8Array(encrypted))),
      iv: btoa(String.fromCharCode(...iv))
    };
  },

  // Decrypt message with AES-GCM key
  async decryptWithAes(ciphertextB64, ivB64, aesKey) {
    const ciphertext = Uint8Array.from(atob(ciphertextB64), c => c.charCodeAt(0));
    const iv = Uint8Array.from(atob(ivB64), c => c.charCodeAt(0));
    const decrypted = await window.crypto.subtle.decrypt({ name: "AES-GCM", iv }, aesKey, ciphertext);
    return new TextDecoder().decode(decrypted);
  },

  // Wrap (encrypt) AES key with RSA public key for a member
  async wrapAesKey(aesKey, recipientPublicKey) {
    const exportedAes = await window.crypto.subtle.exportKey("raw", aesKey);
    const wrapped = await window.crypto.subtle.encrypt({ name: "RSA-OAEP" }, recipientPublicKey, exportedAes);
    return btoa(String.fromCharCode(...new Uint8Array(wrapped)));
  },

  // Unwrap (decrypt) AES key with own RSA private key
  async unwrapAesKey(wrappedB64, privateKey) {
    const wrapped = Uint8Array.from(atob(wrappedB64), c => c.charCodeAt(0));
    const rawAes = await window.crypto.subtle.decrypt({ name: "RSA-OAEP" }, privateKey, wrapped);
    return window.crypto.subtle.importKey("raw", rawAes, { name: "AES-GCM", length: 256 }, false, ["decrypt"]);
  },

  // Store private key in localStorage (encrypted with a password-derived key would be ideal; for now, store JWK)
  storePrivateKey(privateKeyJwk) {
    localStorage.setItem("sc_private_key", JSON.stringify(privateKeyJwk));
  },

  loadPrivateKey() {
    const stored = localStorage.getItem("sc_private_key");
    return stored ? JSON.parse(stored) : null;
  },

  clearPrivateKey() {
    localStorage.removeItem("sc_private_key");
  }
};