@omnituum/pqc-shared 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +543 -0
  3. package/dist/crypto/index.cjs +807 -0
  4. package/dist/crypto/index.d.cts +641 -0
  5. package/dist/crypto/index.d.ts +641 -0
  6. package/dist/crypto/index.js +716 -0
  7. package/dist/decrypt-eSHlbh1j.d.cts +321 -0
  8. package/dist/decrypt-eSHlbh1j.d.ts +321 -0
  9. package/dist/fs/index.cjs +1168 -0
  10. package/dist/fs/index.d.cts +400 -0
  11. package/dist/fs/index.d.ts +400 -0
  12. package/dist/fs/index.js +1091 -0
  13. package/dist/index.cjs +2160 -0
  14. package/dist/index.d.cts +282 -0
  15. package/dist/index.d.ts +282 -0
  16. package/dist/index.js +2031 -0
  17. package/dist/integrity-CCYjrap3.d.ts +31 -0
  18. package/dist/integrity-Dx9jukMH.d.cts +31 -0
  19. package/dist/types-61c7Q9ri.d.ts +134 -0
  20. package/dist/types-Ch0y-n7K.d.cts +134 -0
  21. package/dist/utils/index.cjs +129 -0
  22. package/dist/utils/index.d.cts +49 -0
  23. package/dist/utils/index.d.ts +49 -0
  24. package/dist/utils/index.js +114 -0
  25. package/dist/vault/index.cjs +713 -0
  26. package/dist/vault/index.d.cts +237 -0
  27. package/dist/vault/index.d.ts +237 -0
  28. package/dist/vault/index.js +677 -0
  29. package/dist/version-BygzPVGs.d.cts +55 -0
  30. package/dist/version-BygzPVGs.d.ts +55 -0
  31. package/package.json +86 -0
  32. package/src/crypto/dilithium.ts +233 -0
  33. package/src/crypto/hybrid.ts +358 -0
  34. package/src/crypto/index.ts +181 -0
  35. package/src/crypto/kyber.ts +199 -0
  36. package/src/crypto/nacl.ts +204 -0
  37. package/src/crypto/primitives/blake3.ts +141 -0
  38. package/src/crypto/primitives/chacha.ts +211 -0
  39. package/src/crypto/primitives/hkdf.ts +192 -0
  40. package/src/crypto/primitives/index.ts +54 -0
  41. package/src/crypto/primitives.ts +144 -0
  42. package/src/crypto/x25519.ts +134 -0
  43. package/src/fs/aes.ts +343 -0
  44. package/src/fs/argon2.ts +184 -0
  45. package/src/fs/browser.ts +408 -0
  46. package/src/fs/decrypt.ts +320 -0
  47. package/src/fs/encrypt.ts +324 -0
  48. package/src/fs/format.ts +425 -0
  49. package/src/fs/index.ts +144 -0
  50. package/src/fs/types.ts +304 -0
  51. package/src/index.ts +414 -0
  52. package/src/kdf/index.ts +311 -0
  53. package/src/runtime/crypto.ts +16 -0
  54. package/src/security/index.ts +345 -0
  55. package/src/tunnel/index.ts +39 -0
  56. package/src/tunnel/session.ts +229 -0
  57. package/src/tunnel/types.ts +115 -0
  58. package/src/utils/entropy.ts +128 -0
  59. package/src/utils/index.ts +25 -0
  60. package/src/utils/integrity.ts +95 -0
  61. package/src/vault/decrypt.ts +167 -0
  62. package/src/vault/encrypt.ts +207 -0
  63. package/src/vault/index.ts +71 -0
  64. package/src/vault/manager.ts +327 -0
  65. package/src/vault/migrate.ts +190 -0
  66. package/src/vault/types.ts +177 -0
  67. package/src/version.ts +304 -0
@@ -0,0 +1,408 @@
1
+ /**
2
+ * Omnituum FS - Browser File Utilities
3
+ *
4
+ * Utilities for handling files in the browser environment:
5
+ * - Drag and drop support
6
+ * - File download/upload
7
+ * - Blob/URL handling
8
+ */
9
+
10
+ import { OQE_MIME_TYPE, OQE_EXTENSION, isOQEFile } from './format';
11
+ import { OQEEncryptResult, OQEDecryptResult } from './types';
12
+
13
+ // Helper to convert Uint8Array to a format compatible with Blob constructor
14
+ function toArrayBuffer(data: Uint8Array): ArrayBuffer {
15
+ // Handle SharedArrayBuffer or offset views by copying
16
+ if (data.buffer instanceof SharedArrayBuffer || data.byteOffset !== 0 || data.byteLength !== data.buffer.byteLength) {
17
+ const copy = new ArrayBuffer(data.byteLength);
18
+ new Uint8Array(copy).set(data);
19
+ return copy;
20
+ }
21
+ return data.buffer as ArrayBuffer;
22
+ }
23
+
24
+ // ═══════════════════════════════════════════════════════════════════════════
25
+ // FILE DOWNLOAD
26
+ // ═══════════════════════════════════════════════════════════════════════════
27
+
28
+ /**
29
+ * Download encrypted file to user's device.
30
+ *
31
+ * @param result - Encryption result from encryptFile()
32
+ */
33
+ export function downloadEncryptedFile(result: OQEEncryptResult): void {
34
+ const blob = new Blob([toArrayBuffer(result.data)], { type: OQE_MIME_TYPE });
35
+ downloadBlob(blob, result.filename);
36
+ }
37
+
38
+ /**
39
+ * Download decrypted file to user's device.
40
+ *
41
+ * @param result - Decryption result from decryptFile()
42
+ */
43
+ export function downloadDecryptedFile(result: OQEDecryptResult): void {
44
+ const mimeType = result.mimeType || 'application/octet-stream';
45
+ const blob = new Blob([toArrayBuffer(result.data)], { type: mimeType });
46
+ downloadBlob(blob, result.filename);
47
+ }
48
+
49
+ /**
50
+ * Download a Blob as a file.
51
+ */
52
+ export function downloadBlob(blob: Blob, filename: string): void {
53
+ const url = URL.createObjectURL(blob);
54
+
55
+ const link = document.createElement('a');
56
+ link.href = url;
57
+ link.download = filename;
58
+ link.style.display = 'none';
59
+
60
+ document.body.appendChild(link);
61
+ link.click();
62
+ document.body.removeChild(link);
63
+
64
+ // Clean up URL after download starts
65
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
66
+ }
67
+
68
+ /**
69
+ * Download bytes as a file.
70
+ */
71
+ export function downloadBytes(data: Uint8Array, filename: string, mimeType?: string): void {
72
+ const blob = new Blob([toArrayBuffer(data)], { type: mimeType || 'application/octet-stream' });
73
+ downloadBlob(blob, filename);
74
+ }
75
+
76
+ // ═══════════════════════════════════════════════════════════════════════════
77
+ // FILE READING
78
+ // ═══════════════════════════════════════════════════════════════════════════
79
+
80
+ /**
81
+ * Read a File object as Uint8Array.
82
+ */
83
+ export async function readFile(file: File): Promise<Uint8Array> {
84
+ const buffer = await file.arrayBuffer();
85
+ return new Uint8Array(buffer);
86
+ }
87
+
88
+ /**
89
+ * Read a File object as text.
90
+ */
91
+ export async function readFileAsText(file: File): Promise<string> {
92
+ return new Promise((resolve, reject) => {
93
+ const reader = new FileReader();
94
+ reader.onload = () => resolve(reader.result as string);
95
+ reader.onerror = () => reject(reader.error);
96
+ reader.readAsText(file);
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Read a File object as Data URL.
102
+ */
103
+ export async function readFileAsDataURL(file: File): Promise<string> {
104
+ return new Promise((resolve, reject) => {
105
+ const reader = new FileReader();
106
+ reader.onload = () => resolve(reader.result as string);
107
+ reader.onerror = () => reject(reader.error);
108
+ reader.readAsDataURL(file);
109
+ });
110
+ }
111
+
112
+ // ═══════════════════════════════════════════════════════════════════════════
113
+ // DRAG AND DROP
114
+ // ═══════════════════════════════════════════════════════════════════════════
115
+
116
+ export interface DropZoneOptions {
117
+ /** Element to attach drop zone to */
118
+ element: HTMLElement;
119
+ /** Called when valid files are dropped */
120
+ onDrop: (files: File[]) => void;
121
+ /** Called when drag enters */
122
+ onDragEnter?: () => void;
123
+ /** Called when drag leaves */
124
+ onDragLeave?: () => void;
125
+ /** Filter for accepted file types (e.g., ['image/*', '.pdf']) */
126
+ accept?: string[];
127
+ /** Allow multiple files */
128
+ multiple?: boolean;
129
+ }
130
+
131
+ /**
132
+ * Create a drop zone for file drag and drop.
133
+ * Returns a cleanup function to remove listeners.
134
+ */
135
+ export function createDropZone(options: DropZoneOptions): () => void {
136
+ const { element, onDrop, onDragEnter, onDragLeave, accept, multiple = true } = options;
137
+
138
+ let dragCounter = 0;
139
+
140
+ const handleDragEnter = (e: DragEvent) => {
141
+ e.preventDefault();
142
+ e.stopPropagation();
143
+ dragCounter++;
144
+ if (dragCounter === 1) {
145
+ onDragEnter?.();
146
+ }
147
+ };
148
+
149
+ const handleDragLeave = (e: DragEvent) => {
150
+ e.preventDefault();
151
+ e.stopPropagation();
152
+ dragCounter--;
153
+ if (dragCounter === 0) {
154
+ onDragLeave?.();
155
+ }
156
+ };
157
+
158
+ const handleDragOver = (e: DragEvent) => {
159
+ e.preventDefault();
160
+ e.stopPropagation();
161
+ };
162
+
163
+ const handleDrop = (e: DragEvent) => {
164
+ e.preventDefault();
165
+ e.stopPropagation();
166
+ dragCounter = 0;
167
+ onDragLeave?.();
168
+
169
+ const files = Array.from(e.dataTransfer?.files || []);
170
+
171
+ // Filter by accept types if specified
172
+ let filteredFiles = files;
173
+ if (accept && accept.length > 0) {
174
+ filteredFiles = files.filter((file) => {
175
+ return accept.some((pattern) => {
176
+ if (pattern.startsWith('.')) {
177
+ return file.name.toLowerCase().endsWith(pattern.toLowerCase());
178
+ }
179
+ if (pattern.endsWith('/*')) {
180
+ const type = pattern.slice(0, -2);
181
+ return file.type.startsWith(type);
182
+ }
183
+ return file.type === pattern;
184
+ });
185
+ });
186
+ }
187
+
188
+ // Limit to single file if not multiple
189
+ if (!multiple && filteredFiles.length > 1) {
190
+ filteredFiles = [filteredFiles[0]];
191
+ }
192
+
193
+ if (filteredFiles.length > 0) {
194
+ onDrop(filteredFiles);
195
+ }
196
+ };
197
+
198
+ element.addEventListener('dragenter', handleDragEnter);
199
+ element.addEventListener('dragleave', handleDragLeave);
200
+ element.addEventListener('dragover', handleDragOver);
201
+ element.addEventListener('drop', handleDrop);
202
+
203
+ // Return cleanup function
204
+ return () => {
205
+ element.removeEventListener('dragenter', handleDragEnter);
206
+ element.removeEventListener('dragleave', handleDragLeave);
207
+ element.removeEventListener('dragover', handleDragOver);
208
+ element.removeEventListener('drop', handleDrop);
209
+ };
210
+ }
211
+
212
+ // ═══════════════════════════════════════════════════════════════════════════
213
+ // FILE INPUT
214
+ // ═══════════════════════════════════════════════════════════════════════════
215
+
216
+ export interface FileInputOptions {
217
+ /** Filter for accepted file types */
218
+ accept?: string[];
219
+ /** Allow multiple files */
220
+ multiple?: boolean;
221
+ }
222
+
223
+ /**
224
+ * Open file picker dialog and return selected files.
225
+ */
226
+ export function openFilePicker(options: FileInputOptions = {}): Promise<File[]> {
227
+ return new Promise((resolve) => {
228
+ const input = document.createElement('input');
229
+ input.type = 'file';
230
+ input.multiple = options.multiple ?? false;
231
+
232
+ if (options.accept && options.accept.length > 0) {
233
+ input.accept = options.accept.join(',');
234
+ }
235
+
236
+ input.onchange = () => {
237
+ const files = Array.from(input.files || []);
238
+ resolve(files);
239
+ };
240
+
241
+ input.oncancel = () => {
242
+ resolve([]);
243
+ };
244
+
245
+ input.click();
246
+ });
247
+ }
248
+
249
+ /**
250
+ * Open file picker for OQE files specifically.
251
+ */
252
+ export function openOQEFilePicker(multiple = false): Promise<File[]> {
253
+ return openFilePicker({
254
+ accept: [OQE_EXTENSION, OQE_MIME_TYPE],
255
+ multiple,
256
+ });
257
+ }
258
+
259
+ /**
260
+ * Open file picker for any file to encrypt.
261
+ */
262
+ export function openFileToEncrypt(multiple = false): Promise<File[]> {
263
+ return openFilePicker({ multiple });
264
+ }
265
+
266
+ // ═══════════════════════════════════════════════════════════════════════════
267
+ // BLOB UTILITIES
268
+ // ═══════════════════════════════════════════════════════════════════════════
269
+
270
+ /**
271
+ * Create a Blob from encryption result.
272
+ */
273
+ export function encryptResultToBlob(result: OQEEncryptResult): Blob {
274
+ return new Blob([toArrayBuffer(result.data)], { type: OQE_MIME_TYPE });
275
+ }
276
+
277
+ /**
278
+ * Create a Blob from decryption result.
279
+ */
280
+ export function decryptResultToBlob(result: OQEDecryptResult): Blob {
281
+ const mimeType = result.mimeType || 'application/octet-stream';
282
+ return new Blob([toArrayBuffer(result.data)], { type: mimeType });
283
+ }
284
+
285
+ /**
286
+ * Create an object URL for a Blob.
287
+ * Remember to call URL.revokeObjectURL() when done.
288
+ */
289
+ export function createObjectURL(blob: Blob): string {
290
+ return URL.createObjectURL(blob);
291
+ }
292
+
293
+ /**
294
+ * Create a Data URL from bytes.
295
+ */
296
+ export async function bytesToDataURL(data: Uint8Array, mimeType: string): Promise<string> {
297
+ const blob = new Blob([toArrayBuffer(data)], { type: mimeType });
298
+ return new Promise((resolve, reject) => {
299
+ const reader = new FileReader();
300
+ reader.onload = () => resolve(reader.result as string);
301
+ reader.onerror = () => reject(reader.error);
302
+ reader.readAsDataURL(blob);
303
+ });
304
+ }
305
+
306
+ // ═══════════════════════════════════════════════════════════════════════════
307
+ // FILE INFO
308
+ // ═══════════════════════════════════════════════════════════════════════════
309
+
310
+ export interface FileInfo {
311
+ /** Filename */
312
+ name: string;
313
+ /** File size in bytes */
314
+ size: number;
315
+ /** MIME type */
316
+ type: string;
317
+ /** Last modified timestamp */
318
+ lastModified: number;
319
+ /** Is this an OQE file? */
320
+ isOQE: boolean;
321
+ /** Human-readable size */
322
+ sizeFormatted: string;
323
+ }
324
+
325
+ /**
326
+ * Get information about a file.
327
+ */
328
+ export function getFileInfo(file: File): FileInfo {
329
+ return {
330
+ name: file.name,
331
+ size: file.size,
332
+ type: file.type || 'application/octet-stream',
333
+ lastModified: file.lastModified,
334
+ isOQE: isOQEFile(file.name),
335
+ sizeFormatted: formatFileSize(file.size),
336
+ };
337
+ }
338
+
339
+ /**
340
+ * Format file size for display.
341
+ */
342
+ export function formatFileSize(bytes: number): string {
343
+ if (bytes === 0) return '0 B';
344
+
345
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
346
+ const k = 1024;
347
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
348
+
349
+ return `${(bytes / Math.pow(k, i)).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
350
+ }
351
+
352
+ // ═══════════════════════════════════════════════════════════════════════════
353
+ // CLIPBOARD
354
+ // ═══════════════════════════════════════════════════════════════════════════
355
+
356
+ /**
357
+ * Copy text to clipboard.
358
+ */
359
+ export async function copyToClipboard(text: string): Promise<boolean> {
360
+ try {
361
+ await navigator.clipboard.writeText(text);
362
+ return true;
363
+ } catch {
364
+ // Fallback for older browsers
365
+ const textarea = document.createElement('textarea');
366
+ textarea.value = text;
367
+ textarea.style.position = 'fixed';
368
+ textarea.style.left = '-9999px';
369
+ document.body.appendChild(textarea);
370
+ textarea.select();
371
+ const success = document.execCommand('copy');
372
+ document.body.removeChild(textarea);
373
+ return success;
374
+ }
375
+ }
376
+
377
+ // ═══════════════════════════════════════════════════════════════════════════
378
+ // ENVIRONMENT DETECTION
379
+ // ═══════════════════════════════════════════════════════════════════════════
380
+
381
+ /**
382
+ * Check if running in a browser environment.
383
+ */
384
+ export function isBrowser(): boolean {
385
+ return typeof window !== 'undefined' && typeof document !== 'undefined';
386
+ }
387
+
388
+ /**
389
+ * Check if Web Crypto API is available.
390
+ */
391
+ export function isWebCryptoAvailable(): boolean {
392
+ return typeof globalThis.crypto !== 'undefined' && typeof globalThis.crypto.subtle !== 'undefined';
393
+ }
394
+
395
+ /**
396
+ * Check if File API is available.
397
+ */
398
+ export function isFileAPIAvailable(): boolean {
399
+ return typeof File !== 'undefined' && typeof FileReader !== 'undefined';
400
+ }
401
+
402
+ /**
403
+ * Check if drag and drop is supported.
404
+ */
405
+ export function isDragDropSupported(): boolean {
406
+ const div = document.createElement('div');
407
+ return 'draggable' in div || ('ondragstart' in div && 'ondrop' in div);
408
+ }