Skip to content

Commit 24cd9d5

Browse files
committed
Post Quantum Encryption
1 parent 03a02c3 commit 24cd9d5

File tree

15 files changed

+911
-93
lines changed

15 files changed

+911
-93
lines changed

browser-extension/background.js

Lines changed: 78 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,26 @@
1-
class CryptoService {
2-
constructor(key) {
3-
this.key = key;
4-
}
5-
6-
static async new(seedPhrase) {
7-
const encoder = new TextEncoder();
8-
const data = encoder.encode(seedPhrase);
9-
const hash = await crypto.subtle.digest('SHA-256', data);
10-
return new CryptoService(new Uint8Array(hash));
11-
}
12-
13-
async encrypt(data) {
14-
const iv = crypto.getRandomValues(new Uint8Array(12));
15-
const key = await crypto.subtle.importKey(
16-
'raw',
17-
this.key,
18-
{ name: 'AES-GCM' },
19-
false,
20-
['encrypt']
21-
);
22-
23-
const encoded = new TextEncoder().encode(JSON.stringify(data));
24-
const encrypted = await crypto.subtle.encrypt(
25-
{ name: 'AES-GCM', iv },
26-
key,
27-
encoded
28-
);
29-
30-
return {
31-
iv: Array.from(iv),
32-
data: Array.from(new Uint8Array(encrypted))
33-
};
34-
}
35-
36-
async decrypt(encrypted) {
37-
const key = await crypto.subtle.importKey(
38-
'raw',
39-
this.key,
40-
{ name: 'AES-GCM' },
41-
false,
42-
['decrypt']
43-
);
44-
45-
const decrypted = await crypto.subtle.decrypt(
46-
{ name: 'AES-GCM', iv: new Uint8Array(encrypted.iv) },
47-
key,
48-
new Uint8Array(encrypted.data)
49-
);
50-
51-
return JSON.parse(new TextDecoder().decode(decrypted));
52-
}
53-
}
1+
import { CryptoService } from './services/cryptoService.js';
542

553
let cryptoService = null;
564
let webappTabCheckInterval = null;
575
let lastSyncTime = 0;
586
const SYNC_COOLDOWN = 2000;
597

8+
// Initialize the crypto service when the extension starts
9+
async function initializeCrypto() {
10+
try {
11+
// Get seed phrase from storage
12+
const result = await chrome.storage.local.get('seedPhrase');
13+
if (result.seedPhrase) {
14+
cryptoService = await CryptoService.new(result.seedPhrase);
15+
console.log('Crypto service initialized with PQ support:', cryptoService.isPQAvailable());
16+
} else {
17+
console.log('No seed phrase found, crypto not initialized');
18+
}
19+
} catch (error) {
20+
console.error('Failed to initialize crypto:', error);
21+
}
22+
}
23+
6024
async function encryptAndStoreNotes(notes) {
6125
try {
6226
if (!cryptoService) {
@@ -237,7 +201,64 @@ function startPeriodicCheck() {
237201

238202
startPeriodicCheck();
239203

204+
// Handle messages from content scripts or popup
240205
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
206+
if (message.action === 'encrypt') {
207+
if (!cryptoService) {
208+
sendResponse({ error: 'Crypto service not initialized' });
209+
return;
210+
}
211+
212+
// Encrypt data asynchronously
213+
(async () => {
214+
try {
215+
const encrypted = await cryptoService.encrypt(message.data);
216+
sendResponse({ success: true, encrypted });
217+
} catch (error) {
218+
sendResponse({ error: error.message });
219+
}
220+
})();
221+
222+
return true; // Indicate asynchronous response
223+
}
224+
225+
if (message.action === 'decrypt') {
226+
if (!cryptoService) {
227+
sendResponse({ error: 'Crypto service not initialized' });
228+
return;
229+
}
230+
231+
// Decrypt data asynchronously
232+
(async () => {
233+
try {
234+
const decrypted = await cryptoService.decrypt(message.encrypted);
235+
sendResponse({ success: true, decrypted });
236+
} catch (error) {
237+
sendResponse({ error: error.message });
238+
}
239+
})();
240+
241+
return true; // Indicate asynchronous response
242+
}
243+
244+
if (message.action === 'setupCrypto') {
245+
// Initialize with new seed phrase
246+
(async () => {
247+
try {
248+
await chrome.storage.local.set({ seedPhrase: message.seedPhrase });
249+
cryptoService = await CryptoService.new(message.seedPhrase);
250+
sendResponse({
251+
success: true,
252+
pqAvailable: cryptoService.isPQAvailable()
253+
});
254+
} catch (error) {
255+
sendResponse({ error: error.message });
256+
}
257+
})();
258+
259+
return true; // Indicate asynchronous response
260+
}
261+
241262
if (message.type === 'NOTES_UPDATED' && message.notes) {
242263
lastSyncTime = Date.now();
243264
encryptAndStoreNotes(message.notes);
@@ -257,4 +278,7 @@ chrome.runtime.onConnect.addListener(function(port) {
257278
port.onDisconnect.addListener(function() {
258279
startPeriodicCheck();
259280
});
260-
});
281+
});
282+
283+
// Initialize when extension loads
284+
initializeCrypto();

browser-extension/popup/sync.js

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Sync notes with the server
2+
async function syncNotes() {
3+
try {
4+
const statusElement = document.getElementById('sync-status');
5+
statusElement.textContent = 'Syncing...';
6+
7+
// Get crypto information
8+
const cryptoResponse = await chrome.runtime.sendMessage({ action: 'getCryptoStatus' });
9+
if (cryptoResponse.error) {
10+
throw new Error(cryptoResponse.error);
11+
}
12+
13+
// Get notes from storage
14+
const storedNotes = await chrome.storage.local.get('notes');
15+
const notes = storedNotes.notes || [];
16+
17+
// Encrypt notes for sync
18+
const encryptedNotes = [];
19+
for (const note of notes) {
20+
if (note.pending_sync) {
21+
const encrypted = await chrome.runtime.sendMessage({
22+
action: 'encrypt',
23+
data: {
24+
title: note.title,
25+
content: note.content,
26+
created_at: note.created_at,
27+
updated_at: note.updated_at
28+
}
29+
});
30+
31+
if (encrypted.error) throw new Error(encrypted.error);
32+
33+
encryptedNotes.push({
34+
id: note.id.toString(16).padStart(16, '0'),
35+
data: encrypted.encrypted.data,
36+
nonce: encrypted.encrypted.iv,
37+
timestamp: note.updated_at,
38+
version: encrypted.encrypted.version
39+
});
40+
41+
// Mark as synced
42+
note.pending_sync = false;
43+
}
44+
}
45+
46+
// Get MLKEM public key if available
47+
let pqPublicKey = null;
48+
if (cryptoResponse.isPQAvailable) {
49+
const keyResponse = await chrome.runtime.sendMessage({ action: 'getMlkemPublicKey' });
50+
if (!keyResponse.error) {
51+
pqPublicKey = keyResponse.publicKey;
52+
}
53+
}
54+
55+
// Send to server
56+
const serverUrl = 'https://sync.trustynotes.app'; // Get from settings
57+
const publicKey = cryptoResponse.publicKey;
58+
59+
const response = await fetch(`${serverUrl}/api/sync`, {
60+
method: 'POST',
61+
headers: {
62+
'Content-Type': 'application/json',
63+
},
64+
body: JSON.stringify({
65+
public_key: publicKey,
66+
notes: encryptedNotes,
67+
client_version: '0.3.0',
68+
sync_type: encryptedNotes.length > 0 ? 'full' : 'check',
69+
pq_public_key: pqPublicKey
70+
}),
71+
});
72+
73+
if (!response.ok) {
74+
throw new Error(`Server returned ${response.status}`);
75+
}
76+
77+
const syncResult = await response.json();
78+
79+
// Process server notes (decrypt and store)
80+
if (syncResult.notes && syncResult.notes.length > 0) {
81+
for (const serverNote of syncResult.notes) {
82+
const decrypted = await chrome.runtime.sendMessage({
83+
action: 'decrypt',
84+
encrypted: {
85+
data: serverNote.data,
86+
iv: serverNote.nonce,
87+
version: serverNote.version || 1
88+
}
89+
});
90+
91+
if (decrypted.error) {
92+
console.error('Failed to decrypt note:', decrypted.error);
93+
continue;
94+
}
95+
96+
// Update or add note
97+
const noteId = parseInt(serverNote.id, 16);
98+
const existingIndex = notes.findIndex(n => n.id === noteId);
99+
100+
const decryptedNote = {
101+
id: noteId,
102+
...decrypted.decrypted,
103+
pending_sync: false,
104+
encryptionType: serverNote.version || 1
105+
};
106+
107+
if (existingIndex >= 0) {
108+
notes[existingIndex] = decryptedNote;
109+
} else {
110+
notes.push(decryptedNote);
111+
}
112+
}
113+
114+
// Save updated notes
115+
await chrome.storage.local.set({ notes });
116+
}
117+
118+
statusElement.textContent = 'Sync complete!';
119+
setTimeout(() => {
120+
statusElement.textContent = '';
121+
}, 3000);
122+
123+
} catch (error) {
124+
console.error('Sync failed:', error);
125+
const statusElement = document.getElementById('sync-status');
126+
statusElement.textContent = `Sync failed: ${error.message}`;
127+
}
128+
}

0 commit comments

Comments
 (0)