Repository navigation
Expand file tree
/
Copy pathcrypto.js
More file actions
129 lines (116 loc) · 4.55 KB
/
Copy pathcrypto.js
File metadata and controls
129 lines (116 loc) · 4.55 KB
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/* crypto.js */
'use strict';
const CryptoUtils = {
async generateCEK() {
return crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']);
},
async wrapKey(keyToWrap, wrappingKey) {
const iv = crypto.getRandomValues(new Uint8Array(12));
const exported = await crypto.subtle.exportKey('raw', keyToWrap);
const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, wrappingKey, exported);
return {
iv: btoa(String.fromCharCode(...iv)),
ciphertext: btoa(String.fromCharCode(...new Uint8Array(ciphertext)))
};
},
async unwrapKey(wrapped, unwrappingKey) {
const iv = Uint8Array.from(atob(wrapped.iv), c => c.charCodeAt(0));
const ciphertext = Uint8Array.from(atob(wrapped.ciphertext), c => c.charCodeAt(0));
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, unwrappingKey, ciphertext);
return crypto.subtle.importKey('raw', decrypted, 'AES-GCM', true, ['encrypt', 'decrypt']);
},
async encryptWithKey(plaintext, key) {
const iv = crypto.getRandomValues(new Uint8Array(12));
const enc = new TextEncoder();
const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, enc.encode(plaintext));
return {
iv: btoa(String.fromCharCode(...iv)),
ciphertext: btoa(String.fromCharCode(...new Uint8Array(ciphertext)))
};
},
async decryptWithKey(payload, key) {
const dec = new TextDecoder();
const iv = Uint8Array.from(atob(payload.iv), c => c.charCodeAt(0));
const ciphertext = Uint8Array.from(atob(payload.ciphertext), c => c.charCodeAt(0));
const plain = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext);
return dec.decode(plain);
},
async deriveKeyFromPRF(prfResult) {
const hash = await crypto.subtle.digest('SHA-256', prfResult);
return crypto.subtle.importKey('raw', hash, 'AES-GCM', false, ['encrypt', 'decrypt']);
},
async deriveKeyFromPassword(password, salt) {
const enc = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey('raw', enc.encode(password), 'PBKDF2', false, ['deriveKey']);
return crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 600000, hash: 'SHA-256' },
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
},
generateRecoveryCode() {
const bytes = crypto.getRandomValues(new Uint8Array(24));
const b32 = base32Encode(bytes);
return b32.match(/.{1,4}/g).join('-');
}
};
function base32Encode(bytes) {
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
let bits = 0, value = 0, out = '';
for (const b of bytes) {
value = (value << 8) | b;
bits += 8;
while (bits >= 5) {
out += alphabet[(value >>> (bits - 5)) & 31];
bits -= 5;
}
}
if (bits > 0) out += alphabet[(value << (5 - bits)) & 31];
return out;
}
const TOTP = {
async generate(secret, algo = 'SHA-1', digits = 6, period = 30, offset = 0) {
try {
const b32 = secret.toUpperCase().replace(/[^A-Z2-7]/g, '');
if (!b32) return '------';
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
let bits = 0, value = 0, idx = 0;
const out = new Uint8Array(Math.floor(b32.length * 5 / 8));
for (const ch of b32) {
value = (value << 5) | alphabet.indexOf(ch);
bits += 5;
if (bits >= 8) {
out[idx++] = (value >>> (bits - 8)) & 0xFF;
bits -= 8;
}
}
const counter = Math.floor(Math.floor(Date.now() / 1000) / period) + offset;
const buf = new ArrayBuffer(8);
new DataView(buf).setBigUint64(0, BigInt(counter), false);
const key = await crypto.subtle.importKey('raw', out, { name: 'HMAC', hash: algo }, false, ['sign']);
const sig = new Uint8Array(await crypto.subtle.sign('HMAC', key, buf));
const off = sig[sig.length - 1] & 0xF;
const bin = ((sig[off] & 0x7F) << 24) | ((sig[off + 1] & 0xFF) << 16) | ((sig[off + 2] & 0xFF) << 8) | (sig[off + 3] & 0xFF);
return (bin % Math.pow(10, digits)).toString().padStart(digits, '0');
} catch {
return 'INVALID';
}
},
getTimeRemaining(period) {
return period - (Math.floor(Date.now() / 1000) % period);
},
getProgress(period) {
const rem = this.getTimeRemaining(period);
return ((period - rem) / period) * 100;
}
};
const StorageKeys = {
PASSKEYS: 'modcore_passkeys',
RECOVERY_SALT: 'modcore_recovery_salt',
WRAPPED_KEY_RECOVERY: 'modcore_wrapped_key_recovery',
VAULT: 'modcore_vault',
SETTINGS: 'modcore_settings',
SESSION_CEK: 'modcore_session_cek'
};