favalib 0.0.1
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.
- package/build/Command/BaseCommand.d.mts +65 -0
- package/build/Command/BaseCommand.mjs +54 -0
- package/build/Command/CommandQueue.d.mts +28 -0
- package/build/Command/CommandQueue.mjs +43 -0
- package/build/Command/commandConstructors.d.mts +11 -0
- package/build/Command/commandConstructors.mjs +11 -0
- package/build/Command/commands/AddEntryCommand.d.mts +31 -0
- package/build/Command/commands/AddEntryCommand.mjs +43 -0
- package/build/Command/commands/AddSyncDeviceCommand.d.mts +36 -0
- package/build/Command/commands/AddSyncDeviceCommand.mjs +42 -0
- package/build/Command/commands/DeleteEntryCommand.d.mts +35 -0
- package/build/Command/commands/DeleteEntryCommand.mjs +50 -0
- package/build/Command/commands/UpdateEntryCommand.d.mts +38 -0
- package/build/Command/commands/UpdateEntryCommand.mjs +51 -0
- package/build/CryptoProviders/browser/index.d.mts +73 -0
- package/build/CryptoProviders/browser/index.mjs +209 -0
- package/build/CryptoProviders/node/index.d.mts +62 -0
- package/build/CryptoProviders/node/index.mjs +189 -0
- package/build/TwoFALibError.d.mts +77 -0
- package/build/TwoFALibError.mjs +91 -0
- package/build/TwoFaLib.d.mts +95 -0
- package/build/TwoFaLib.mjs +180 -0
- package/build/TwoFaLibEvent.d.mts +8 -0
- package/build/TwoFaLibEvent.mjs +9 -0
- package/build/TwoFaLibMediator.d.mts +37 -0
- package/build/TwoFaLibMediator.mjs +58 -0
- package/build/interfaces/CommandTypes.d.mts +20 -0
- package/build/interfaces/CommandTypes.mjs +1 -0
- package/build/interfaces/CryptoLib.d.mts +113 -0
- package/build/interfaces/CryptoLib.mjs +1 -0
- package/build/interfaces/Entry.d.mts +33 -0
- package/build/interfaces/Entry.mjs +1 -0
- package/build/interfaces/Events.d.mts +22 -0
- package/build/interfaces/Events.mjs +1 -0
- package/build/interfaces/PassphraseExtraDict.d.ts +2 -0
- package/build/interfaces/PassphraseExtraDict.js +1 -0
- package/build/interfaces/SyncTypes.d.mts +45 -0
- package/build/interfaces/SyncTypes.mjs +1 -0
- package/build/interfaces/Vault.d.mts +30 -0
- package/build/interfaces/Vault.mjs +1 -0
- package/build/main.d.mts +12 -0
- package/build/main.mjs +5 -0
- package/build/subclasses/CommandManager.d.mts +46 -0
- package/build/subclasses/CommandManager.mjs +117 -0
- package/build/subclasses/ExportImportManager.d.mts +58 -0
- package/build/subclasses/ExportImportManager.mjs +105 -0
- package/build/subclasses/LibraryLoader.d.mts +56 -0
- package/build/subclasses/LibraryLoader.mjs +108 -0
- package/build/subclasses/PersistentStorageManager.d.mts +71 -0
- package/build/subclasses/PersistentStorageManager.mjs +127 -0
- package/build/subclasses/SyncManager.d.mts +161 -0
- package/build/subclasses/SyncManager.mjs +567 -0
- package/build/subclasses/VaultDataManager.d.mts +68 -0
- package/build/subclasses/VaultDataManager.mjs +114 -0
- package/build/subclasses/VaultOperationsManager.d.mts +91 -0
- package/build/subclasses/VaultOperationsManager.mjs +163 -0
- package/build/utils/constants.d.mts +2 -0
- package/build/utils/constants.mjs +1 -0
- package/build/utils/creationUtils.d.mts +43 -0
- package/build/utils/creationUtils.mjs +125 -0
- package/build/utils/exportImportUtils.d.mts +53 -0
- package/build/utils/exportImportUtils.mjs +185 -0
- package/build/utils/qrUtils.d.mts +25 -0
- package/build/utils/qrUtils.mjs +84 -0
- package/build/utils/syncUtils.d.mts +26 -0
- package/build/utils/syncUtils.mjs +78 -0
- package/package.json +56 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import forge from 'node-forge';
|
|
2
|
+
import { base64ToUint8Array, stringToUint8Array, uint8ArrayToBase64, uint8ArrayToString, } from 'uint8array-extras';
|
|
3
|
+
import { argon2id } from 'hash-wasm';
|
|
4
|
+
import { CryptoError } from '../../TwoFALibError.mjs';
|
|
5
|
+
/**
|
|
6
|
+
* Normalizes line endings in a string so they match the
|
|
7
|
+
* node cryptoprovider format
|
|
8
|
+
* @param str - The input string to normalize.
|
|
9
|
+
* @returns The normalized string with consistent line endings.
|
|
10
|
+
*/
|
|
11
|
+
const normalizeLineEndings = (str) => {
|
|
12
|
+
return str.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Create a password hash
|
|
16
|
+
* @param salt - The salt to use
|
|
17
|
+
* @param passphrase - The passphrase to hash
|
|
18
|
+
* @returns The calculated password hash
|
|
19
|
+
*/
|
|
20
|
+
export const generatePassphraseHash = (salt, passphrase) => {
|
|
21
|
+
return argon2id({
|
|
22
|
+
password: passphrase,
|
|
23
|
+
salt,
|
|
24
|
+
parallelism: 1,
|
|
25
|
+
iterations: 256,
|
|
26
|
+
memorySize: 512,
|
|
27
|
+
hashLength: 64,
|
|
28
|
+
outputType: 'hex',
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* @inheritdoc
|
|
33
|
+
*/
|
|
34
|
+
class BrowserCryptoLib {
|
|
35
|
+
/**
|
|
36
|
+
* @inheritdoc
|
|
37
|
+
*/
|
|
38
|
+
async getRandomBytes(count) {
|
|
39
|
+
return Promise.resolve(window.crypto.getRandomValues(new Uint8Array(count)));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* @inheritdoc
|
|
43
|
+
*/
|
|
44
|
+
async createKeys(passphrase) {
|
|
45
|
+
// create random salt
|
|
46
|
+
const salt = uint8ArrayToBase64(window.crypto.getRandomValues(new Uint8Array(16)));
|
|
47
|
+
// create passwordHash
|
|
48
|
+
const passphraseHash = await generatePassphraseHash(salt, passphrase);
|
|
49
|
+
const { privateKey, encryptedPrivateKey, publicKey } = await this.createKeyPair(passphraseHash);
|
|
50
|
+
const symmetricKey = await this.createSymmetricKey();
|
|
51
|
+
const encryptedSymmetricKey = await this.encrypt(publicKey, symmetricKey);
|
|
52
|
+
return {
|
|
53
|
+
privateKey,
|
|
54
|
+
symmetricKey,
|
|
55
|
+
encryptedPrivateKey,
|
|
56
|
+
encryptedSymmetricKey: encryptedSymmetricKey,
|
|
57
|
+
salt,
|
|
58
|
+
publicKey,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* @inheritdoc
|
|
63
|
+
*/
|
|
64
|
+
async encryptKeys(privateKey, symmetricKey, salt, passphrase) {
|
|
65
|
+
// recreate passwordHash
|
|
66
|
+
const passphraseHash = await generatePassphraseHash(salt, passphrase);
|
|
67
|
+
const encryptedPrivateKey = await this.encryptPrivateKey(privateKey, passphraseHash);
|
|
68
|
+
const publicKey = await this.getPublicKeyFromPrivateKey(privateKey);
|
|
69
|
+
const encryptedSymmetricKey = await this.encrypt(publicKey, symmetricKey);
|
|
70
|
+
return {
|
|
71
|
+
encryptedPrivateKey,
|
|
72
|
+
encryptedSymmetricKey: encryptedSymmetricKey,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* @inheritdoc
|
|
77
|
+
*/
|
|
78
|
+
async decryptKeys(encryptedPrivateKey, encryptedSymmetricKey, salt, passphrase) {
|
|
79
|
+
// recreate passwordHash
|
|
80
|
+
const passphraseHash = await generatePassphraseHash(salt, passphrase);
|
|
81
|
+
const { privateKey, publicKey } = await this.decryptPrivateKey(encryptedPrivateKey, passphraseHash);
|
|
82
|
+
const symmetricKey = (await this.decrypt(privateKey, encryptedSymmetricKey));
|
|
83
|
+
return { privateKey, publicKey, symmetricKey };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* @inheritdoc
|
|
87
|
+
*/
|
|
88
|
+
async encrypt(publicKey, plainText) {
|
|
89
|
+
const publicKeyObj = forge.pki.publicKeyFromPem(publicKey);
|
|
90
|
+
const encrypted = publicKeyObj.encrypt(plainText, 'RSA-OAEP');
|
|
91
|
+
return Promise.resolve(btoa(encrypted));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* @inheritdoc
|
|
95
|
+
*/
|
|
96
|
+
async decrypt(privateKey, encryptedText) {
|
|
97
|
+
const privateKeyObj = forge.pki.privateKeyFromPem(privateKey);
|
|
98
|
+
const decrypted = privateKeyObj.decrypt(atob(encryptedText), 'RSA-OAEP');
|
|
99
|
+
return Promise.resolve(decrypted);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* @inheritdoc
|
|
103
|
+
*/
|
|
104
|
+
async encryptSymmetric(symmetricKey, plainText) {
|
|
105
|
+
const key = await window.crypto.subtle.importKey('raw', base64ToUint8Array(symmetricKey), { name: 'AES-CBC', length: 256 }, false, ['encrypt']);
|
|
106
|
+
const iv = window.crypto.getRandomValues(new Uint8Array(16));
|
|
107
|
+
const encrypted = await window.crypto.subtle.encrypt({ name: 'AES-CBC', iv }, key, stringToUint8Array(plainText));
|
|
108
|
+
const result = [
|
|
109
|
+
uint8ArrayToBase64(iv),
|
|
110
|
+
uint8ArrayToBase64(new Uint8Array(encrypted)),
|
|
111
|
+
];
|
|
112
|
+
return result.join(':');
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* @inheritdoc
|
|
116
|
+
*/
|
|
117
|
+
async decryptSymmetric(symmetricKey, encryptedText) {
|
|
118
|
+
const [ivString, encryptedData] = encryptedText.split(':');
|
|
119
|
+
const iv = base64ToUint8Array(ivString);
|
|
120
|
+
const keyUint8Array = base64ToUint8Array(symmetricKey);
|
|
121
|
+
const key = await window.crypto.subtle.importKey('raw', keyUint8Array, { name: 'AES-CBC', length: 256 }, false, ['decrypt']);
|
|
122
|
+
const encrypted = base64ToUint8Array(encryptedData);
|
|
123
|
+
const decrypted = await window.crypto.subtle.decrypt({ name: 'AES-CBC', iv }, key, encrypted);
|
|
124
|
+
return uint8ArrayToString(decrypted);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* @inheritdoc
|
|
128
|
+
*/
|
|
129
|
+
async createSymmetricKey() {
|
|
130
|
+
const key = await window.crypto.subtle.generateKey({ name: 'AES-CBC', length: 256 }, true, ['encrypt', 'decrypt']);
|
|
131
|
+
const exportedKey = await window.crypto.subtle.exportKey('raw', key);
|
|
132
|
+
return uint8ArrayToBase64(new Uint8Array(exportedKey));
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* @inheritdoc
|
|
136
|
+
*/
|
|
137
|
+
async createSyncKey(sharedKey, salt) {
|
|
138
|
+
const key = await argon2id({
|
|
139
|
+
password: sharedKey,
|
|
140
|
+
salt,
|
|
141
|
+
parallelism: 1,
|
|
142
|
+
iterations: 256,
|
|
143
|
+
memorySize: 512,
|
|
144
|
+
hashLength: 32,
|
|
145
|
+
outputType: 'binary',
|
|
146
|
+
});
|
|
147
|
+
return uint8ArrayToBase64(key);
|
|
148
|
+
}
|
|
149
|
+
async encryptPrivateKey(privateKey, passphraseHash) {
|
|
150
|
+
const privateKeyObj = forge.pki.privateKeyFromPem(privateKey);
|
|
151
|
+
const encryptedPrivateKey = forge.pki.encryptRsaPrivateKey(privateKeyObj, passphraseHash, {
|
|
152
|
+
algorithm: 'aes256',
|
|
153
|
+
});
|
|
154
|
+
return Promise.resolve(encryptedPrivateKey);
|
|
155
|
+
}
|
|
156
|
+
async decryptPrivateKey(encryptedPrivateKey, passphraseHash) {
|
|
157
|
+
try {
|
|
158
|
+
const privateKeyPem = forge.pki.decryptRsaPrivateKey(encryptedPrivateKey, passphraseHash);
|
|
159
|
+
if (!privateKeyPem) {
|
|
160
|
+
throw new CryptoError('Invalid passphrase');
|
|
161
|
+
}
|
|
162
|
+
const privateKey = forge.pki.privateKeyToPem(privateKeyPem);
|
|
163
|
+
const publicKey = forge.pki.publicKeyToPem(forge.pki.setRsaPublicKey(privateKeyPem.n, privateKeyPem.e));
|
|
164
|
+
return Promise.resolve({
|
|
165
|
+
privateKey: normalizeLineEndings(privateKey),
|
|
166
|
+
publicKey: normalizeLineEndings(publicKey),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
// eslint-disable-next-line no-restricted-globals
|
|
171
|
+
if (err instanceof Error) {
|
|
172
|
+
if (err.message === 'Invalid passphrase') {
|
|
173
|
+
throw new CryptoError('Invalid passphrase');
|
|
174
|
+
}
|
|
175
|
+
if (err.message.includes('Unsupported private key')) {
|
|
176
|
+
throw new CryptoError('Invalid private key');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
throw err;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async createKeyPair(passphrase) {
|
|
183
|
+
return new Promise((resolve, reject) => {
|
|
184
|
+
forge.pki.rsa.generateKeyPair({ bits: 4096 }, (err, keyPair) => {
|
|
185
|
+
if (err) {
|
|
186
|
+
reject(err);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
const publicKey = forge.pki.publicKeyToPem(keyPair.publicKey);
|
|
190
|
+
const privateKey = forge.pki.privateKeyToPem(keyPair.privateKey);
|
|
191
|
+
const encryptedPrivateKey = forge.pki.encryptRsaPrivateKey(keyPair.privateKey, passphrase, {
|
|
192
|
+
algorithm: 'aes256',
|
|
193
|
+
});
|
|
194
|
+
resolve({
|
|
195
|
+
privateKey: normalizeLineEndings(privateKey),
|
|
196
|
+
publicKey: normalizeLineEndings(publicKey),
|
|
197
|
+
encryptedPrivateKey,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
async getPublicKeyFromPrivateKey(privateKey) {
|
|
204
|
+
const privateKeyObj = forge.pki.privateKeyFromPem(privateKey);
|
|
205
|
+
const publicKey = forge.pki.publicKeyToPem(forge.pki.setRsaPublicKey(privateKeyObj.n, privateKeyObj.e));
|
|
206
|
+
return Promise.resolve(normalizeLineEndings(publicKey));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
export default BrowserCryptoLib;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type CryptoLib from '../../interfaces/CryptoLib.mjs';
|
|
2
|
+
import type { Encrypted, EncryptedPrivateKey, EncryptedSymmetricKey, Passphrase, PrivateKey, PublicKey, Salt, SymmetricKey, SyncKey } from '../../interfaces/CryptoLib.mjs';
|
|
3
|
+
/**
|
|
4
|
+
* @inheritdoc
|
|
5
|
+
*/
|
|
6
|
+
declare class NodeCryptoLib implements CryptoLib {
|
|
7
|
+
/**
|
|
8
|
+
* @inheritdoc
|
|
9
|
+
*/
|
|
10
|
+
getRandomBytes(count: number): Promise<Uint8Array>;
|
|
11
|
+
/**
|
|
12
|
+
* @inheritdoc
|
|
13
|
+
*/
|
|
14
|
+
createKeys(passphrase: Passphrase): Promise<{
|
|
15
|
+
privateKey: PrivateKey;
|
|
16
|
+
symmetricKey: SymmetricKey;
|
|
17
|
+
publicKey: PublicKey;
|
|
18
|
+
salt: Salt;
|
|
19
|
+
encryptedPrivateKey: EncryptedPrivateKey;
|
|
20
|
+
encryptedSymmetricKey: EncryptedSymmetricKey;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* @inheritdoc
|
|
24
|
+
*/
|
|
25
|
+
encryptKeys(privateKey: PrivateKey, symmetricKey: SymmetricKey, salt: Salt, passphrase: Passphrase): Promise<{
|
|
26
|
+
encryptedPrivateKey: EncryptedPrivateKey;
|
|
27
|
+
encryptedSymmetricKey: EncryptedSymmetricKey;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* @inheritdoc
|
|
31
|
+
*/
|
|
32
|
+
decryptKeys(encryptedPrivateKey: EncryptedPrivateKey, encryptedSymmetricKey: EncryptedSymmetricKey, salt: Salt, passphrase: Passphrase): Promise<{
|
|
33
|
+
privateKey: PrivateKey;
|
|
34
|
+
publicKey: PublicKey;
|
|
35
|
+
symmetricKey: SymmetricKey;
|
|
36
|
+
}>;
|
|
37
|
+
/**
|
|
38
|
+
* @inheritdoc
|
|
39
|
+
*/
|
|
40
|
+
encrypt<T extends string>(publicKey: PublicKey, plainText: T): Promise<Encrypted<T>>;
|
|
41
|
+
/**
|
|
42
|
+
* @inheritdoc
|
|
43
|
+
*/
|
|
44
|
+
decrypt<T extends string>(privateKey: PrivateKey, encryptedText: Encrypted<T>): Promise<T>;
|
|
45
|
+
/**
|
|
46
|
+
* @inheritdoc
|
|
47
|
+
*/
|
|
48
|
+
encryptSymmetric<T extends string>(symmetricKey: SymmetricKey, plainText: T): Promise<Encrypted<T>>;
|
|
49
|
+
/**
|
|
50
|
+
* @inheritdoc
|
|
51
|
+
*/
|
|
52
|
+
decryptSymmetric<T extends string>(symmetricKey: SymmetricKey, encryptedText: Encrypted<T>): Promise<T>;
|
|
53
|
+
/**
|
|
54
|
+
* @inheritdoc
|
|
55
|
+
*/
|
|
56
|
+
createSymmetricKey(): Promise<SymmetricKey>;
|
|
57
|
+
/**
|
|
58
|
+
* @inheritdoc
|
|
59
|
+
*/
|
|
60
|
+
createSyncKey(sharedKey: Uint8Array, salt: string): Promise<SyncKey>;
|
|
61
|
+
}
|
|
62
|
+
export default NodeCryptoLib;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/* eslint no-restricted-globals: ["error", "Error"] */
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { generateKeyPair as generateKeyPairCb, generateKey as generateKeyCb, publicEncrypt, privateDecrypt, createPrivateKey, createPublicKey, randomBytes, createCipheriv, createDecipheriv, } from 'node:crypto';
|
|
4
|
+
import { argon2id } from 'hash-wasm';
|
|
5
|
+
import { toUint8Array } from 'uint8array-extras';
|
|
6
|
+
import { CryptoError } from '../../TwoFALibError.mjs';
|
|
7
|
+
import { generatePassphraseHash } from '../browser/index.mjs';
|
|
8
|
+
const generateKeyPair = promisify(generateKeyPairCb);
|
|
9
|
+
const generateKey = promisify(generateKeyCb);
|
|
10
|
+
/**
|
|
11
|
+
* @inheritdoc
|
|
12
|
+
*/
|
|
13
|
+
class NodeCryptoLib {
|
|
14
|
+
/**
|
|
15
|
+
* @inheritdoc
|
|
16
|
+
*/
|
|
17
|
+
async getRandomBytes(count) {
|
|
18
|
+
return Promise.resolve(toUint8Array(randomBytes(count)));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* @inheritdoc
|
|
22
|
+
*/
|
|
23
|
+
async createKeys(passphrase) {
|
|
24
|
+
// create random salt
|
|
25
|
+
const salt = randomBytes(16).toString('base64');
|
|
26
|
+
// create passwordHash
|
|
27
|
+
const passphraseHash = await generatePassphraseHash(salt, passphrase);
|
|
28
|
+
// Generate public/private key pair
|
|
29
|
+
const { publicKey, privateKey: encryptedPrivateKey } = await generateKeyPair('rsa', {
|
|
30
|
+
modulusLength: 4096,
|
|
31
|
+
publicKeyEncoding: {
|
|
32
|
+
type: 'spki',
|
|
33
|
+
format: 'pem',
|
|
34
|
+
},
|
|
35
|
+
privateKeyEncoding: {
|
|
36
|
+
type: 'pkcs8',
|
|
37
|
+
format: 'pem',
|
|
38
|
+
cipher: 'aes-256-cbc',
|
|
39
|
+
passphrase: passphraseHash,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
// Create and encrypt symmetric key with public key
|
|
43
|
+
const symmetricKey = await this.createSymmetricKey();
|
|
44
|
+
const encryptedSymmetricKey = await this.encrypt(publicKey, symmetricKey);
|
|
45
|
+
const { privateKey } = await this.decryptKeys(encryptedPrivateKey, encryptedSymmetricKey, salt, passphrase);
|
|
46
|
+
return {
|
|
47
|
+
privateKey: privateKey,
|
|
48
|
+
symmetricKey,
|
|
49
|
+
publicKey: publicKey,
|
|
50
|
+
salt: salt,
|
|
51
|
+
encryptedPrivateKey: encryptedPrivateKey,
|
|
52
|
+
encryptedSymmetricKey: encryptedSymmetricKey,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* @inheritdoc
|
|
57
|
+
*/
|
|
58
|
+
async encryptKeys(privateKey, symmetricKey, salt, passphrase) {
|
|
59
|
+
// recreate passwordHash
|
|
60
|
+
const passphraseHash = await generatePassphraseHash(salt, passphrase);
|
|
61
|
+
// Encrypt private key
|
|
62
|
+
const privateKeyObject = createPrivateKey({
|
|
63
|
+
key: privateKey,
|
|
64
|
+
format: 'pem',
|
|
65
|
+
});
|
|
66
|
+
const encryptedPrivateKey = privateKeyObject.export({
|
|
67
|
+
type: 'pkcs8',
|
|
68
|
+
format: 'pem',
|
|
69
|
+
cipher: 'aes-256-cbc',
|
|
70
|
+
passphrase: passphraseHash,
|
|
71
|
+
});
|
|
72
|
+
// Encrypt symmetric key with public key
|
|
73
|
+
const publicKeyObject = createPublicKey(privateKeyObject);
|
|
74
|
+
const publicKey = publicKeyObject.export({
|
|
75
|
+
type: 'spki',
|
|
76
|
+
format: 'pem',
|
|
77
|
+
});
|
|
78
|
+
const encryptedSymmetricKey = await this.encrypt(publicKey, symmetricKey);
|
|
79
|
+
return {
|
|
80
|
+
encryptedPrivateKey,
|
|
81
|
+
encryptedSymmetricKey: encryptedSymmetricKey,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* @inheritdoc
|
|
86
|
+
*/
|
|
87
|
+
async decryptKeys(encryptedPrivateKey, encryptedSymmetricKey, salt, passphrase) {
|
|
88
|
+
// recreate passwordHash
|
|
89
|
+
const passphraseHash = await generatePassphraseHash(salt, passphrase);
|
|
90
|
+
let privateKeyObject;
|
|
91
|
+
let privateKey;
|
|
92
|
+
try {
|
|
93
|
+
privateKeyObject = createPrivateKey({
|
|
94
|
+
key: encryptedPrivateKey,
|
|
95
|
+
type: 'pkcs8',
|
|
96
|
+
format: 'pem',
|
|
97
|
+
passphrase: passphraseHash,
|
|
98
|
+
});
|
|
99
|
+
privateKey = privateKeyObject.export({
|
|
100
|
+
type: 'pkcs8',
|
|
101
|
+
format: 'pem',
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
// eslint-disable-next-line no-restricted-globals
|
|
106
|
+
if (err instanceof Error && 'code' in err) {
|
|
107
|
+
if (err.code === 'ERR_OSSL_BAD_DECRYPT') {
|
|
108
|
+
throw new CryptoError('Invalid passphrase');
|
|
109
|
+
}
|
|
110
|
+
if (err.code === 'ERR_OSSL_UNSUPPORTED') {
|
|
111
|
+
throw new CryptoError('Invalid private key');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
const publicKeyObject = createPublicKey(privateKeyObject);
|
|
117
|
+
const publicKey = publicKeyObject.export({
|
|
118
|
+
type: 'spki',
|
|
119
|
+
format: 'pem',
|
|
120
|
+
});
|
|
121
|
+
// Decrypt the symmetric key
|
|
122
|
+
const symmetricKey = (await this.decrypt(privateKey, encryptedSymmetricKey));
|
|
123
|
+
return { privateKey, publicKey, symmetricKey };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* @inheritdoc
|
|
127
|
+
*/
|
|
128
|
+
async encrypt(publicKey, plainText) {
|
|
129
|
+
const buffer = Buffer.from(plainText, 'utf8');
|
|
130
|
+
const encrypted = publicEncrypt(publicKey, buffer);
|
|
131
|
+
return Promise.resolve(encrypted.toString('base64'));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* @inheritdoc
|
|
135
|
+
*/
|
|
136
|
+
async decrypt(privateKey, encryptedText) {
|
|
137
|
+
const buffer = Buffer.from(encryptedText, 'base64');
|
|
138
|
+
const decrypted = privateDecrypt({
|
|
139
|
+
key: privateKey,
|
|
140
|
+
}, buffer);
|
|
141
|
+
return Promise.resolve(decrypted.toString('utf8'));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* @inheritdoc
|
|
145
|
+
*/
|
|
146
|
+
async encryptSymmetric(symmetricKey, plainText) {
|
|
147
|
+
const iv = randomBytes(16);
|
|
148
|
+
const keyBuffer = Buffer.from(symmetricKey, 'base64');
|
|
149
|
+
const cipher = createCipheriv('aes-256-cbc', keyBuffer, iv);
|
|
150
|
+
let encrypted = cipher.update(plainText, 'utf8', 'base64');
|
|
151
|
+
encrypted += cipher.final('base64');
|
|
152
|
+
return Promise.resolve((iv.toString('base64') + ':' + encrypted));
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* @inheritdoc
|
|
156
|
+
*/
|
|
157
|
+
async decryptSymmetric(symmetricKey, encryptedText) {
|
|
158
|
+
const [ivString, encryptedData] = encryptedText.split(':');
|
|
159
|
+
const iv = Buffer.from(ivString, 'base64');
|
|
160
|
+
const keyBuffer = Buffer.from(symmetricKey, 'base64');
|
|
161
|
+
const decipher = createDecipheriv('aes-256-cbc', keyBuffer, iv);
|
|
162
|
+
let decrypted = decipher.update(encryptedData, 'base64', 'utf8');
|
|
163
|
+
decrypted += decipher.final('utf8');
|
|
164
|
+
return Promise.resolve(decrypted);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* @inheritdoc
|
|
168
|
+
*/
|
|
169
|
+
async createSymmetricKey() {
|
|
170
|
+
const key = await generateKey('aes', { length: 256 });
|
|
171
|
+
return key.export().toString('base64');
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* @inheritdoc
|
|
175
|
+
*/
|
|
176
|
+
async createSyncKey(sharedKey, salt) {
|
|
177
|
+
const keyBuffer = await argon2id({
|
|
178
|
+
password: sharedKey,
|
|
179
|
+
salt,
|
|
180
|
+
parallelism: 1,
|
|
181
|
+
iterations: 256,
|
|
182
|
+
memorySize: 512,
|
|
183
|
+
hashLength: 32,
|
|
184
|
+
outputType: 'binary',
|
|
185
|
+
});
|
|
186
|
+
return Buffer.from(keyBuffer).toString('base64');
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
export default NodeCryptoLib;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error class for the libs errors.
|
|
3
|
+
*/
|
|
4
|
+
export declare class TwoFALibError extends Error {
|
|
5
|
+
/**
|
|
6
|
+
* Creates a new Error.
|
|
7
|
+
* @param message - The error message.
|
|
8
|
+
*/
|
|
9
|
+
constructor(message: string);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Error thrown during lib initialization failures.
|
|
13
|
+
*/
|
|
14
|
+
export declare class InitializationError extends TwoFALibError {
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Error thrown during authentication, e.g. wrong passphrase for locked vault.
|
|
18
|
+
*/
|
|
19
|
+
export declare class AuthenticationError extends TwoFALibError {
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Error thrown when an entry is requested but not found.
|
|
23
|
+
*/
|
|
24
|
+
export declare class EntryNotFoundError extends TwoFALibError {
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Error thrown when token generation fails.
|
|
28
|
+
*/
|
|
29
|
+
export declare class TokenGenerationError extends TwoFALibError {
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Error thrown when an unexpected error occurs during cryptographic operations.
|
|
33
|
+
*/
|
|
34
|
+
export declare class CryptoError extends TwoFALibError {
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Error thrown when an unexpected error occurs during export/import operations.
|
|
38
|
+
*/
|
|
39
|
+
export declare class ExportImportError extends TwoFALibError {
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Error thrown when an unexpected error occurs during synchronization.
|
|
43
|
+
*/
|
|
44
|
+
export declare class SyncError extends TwoFALibError {
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Error thrown when synchronization is in the wrong state for the operation.
|
|
48
|
+
*/
|
|
49
|
+
export declare class SyncInWrongStateError extends SyncError {
|
|
50
|
+
/**
|
|
51
|
+
* @inheritdoc
|
|
52
|
+
*/
|
|
53
|
+
constructor(message?: string);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Error thrown when starting an add device flow while a previous one is still active.
|
|
57
|
+
*/
|
|
58
|
+
export declare class SyncAddDeviceFlowConflictError extends SyncError {
|
|
59
|
+
/**
|
|
60
|
+
* @inheritdoc
|
|
61
|
+
*/
|
|
62
|
+
constructor(message?: string);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Error thrown when attempting a sync operation when there is no server connection.
|
|
66
|
+
*/
|
|
67
|
+
export declare class SyncNoServerConnectionError extends SyncError {
|
|
68
|
+
/**
|
|
69
|
+
* @inheritdoc
|
|
70
|
+
*/
|
|
71
|
+
constructor(message?: string);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Error thrown when an invalid command is being executed.
|
|
75
|
+
*/
|
|
76
|
+
export declare class InvalidCommandError extends TwoFALibError {
|
|
77
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* eslint-disable no-restricted-globals */
|
|
2
|
+
/**
|
|
3
|
+
* Custom error class for the libs errors.
|
|
4
|
+
*/
|
|
5
|
+
export class TwoFALibError extends Error {
|
|
6
|
+
/**
|
|
7
|
+
* Creates a new Error.
|
|
8
|
+
* @param message - The error message.
|
|
9
|
+
*/
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'TwoFALibError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Error thrown during lib initialization failures.
|
|
17
|
+
*/
|
|
18
|
+
export class InitializationError extends TwoFALibError {
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Error thrown during authentication, e.g. wrong passphrase for locked vault.
|
|
22
|
+
*/
|
|
23
|
+
export class AuthenticationError extends TwoFALibError {
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Error thrown when an entry is requested but not found.
|
|
27
|
+
*/
|
|
28
|
+
export class EntryNotFoundError extends TwoFALibError {
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Error thrown when token generation fails.
|
|
32
|
+
*/
|
|
33
|
+
export class TokenGenerationError extends TwoFALibError {
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Error thrown when an unexpected error occurs during cryptographic operations.
|
|
37
|
+
*/
|
|
38
|
+
export class CryptoError extends TwoFALibError {
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Error thrown when an unexpected error occurs during export/import operations.
|
|
42
|
+
*/
|
|
43
|
+
export class ExportImportError extends TwoFALibError {
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Error thrown when an unexpected error occurs during synchronization.
|
|
47
|
+
*/
|
|
48
|
+
export class SyncError extends TwoFALibError {
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Error thrown when synchronization is in the wrong state for the operation.
|
|
52
|
+
*/
|
|
53
|
+
export class SyncInWrongStateError extends SyncError {
|
|
54
|
+
/**
|
|
55
|
+
* @inheritdoc
|
|
56
|
+
*/
|
|
57
|
+
constructor(message) {
|
|
58
|
+
super(message ?? 'Unexpected state while syncing');
|
|
59
|
+
this.name = 'TwoFALibError';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Error thrown when starting an add device flow while a previous one is still active.
|
|
64
|
+
*/
|
|
65
|
+
export class SyncAddDeviceFlowConflictError extends SyncError {
|
|
66
|
+
/**
|
|
67
|
+
* @inheritdoc
|
|
68
|
+
*/
|
|
69
|
+
constructor(message) {
|
|
70
|
+
super(message ??
|
|
71
|
+
"Can't start an add device flow while a previous one is still active");
|
|
72
|
+
this.name = 'TwoFALibError';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Error thrown when attempting a sync operation when there is no server connection.
|
|
77
|
+
*/
|
|
78
|
+
export class SyncNoServerConnectionError extends SyncError {
|
|
79
|
+
/**
|
|
80
|
+
* @inheritdoc
|
|
81
|
+
*/
|
|
82
|
+
constructor(message) {
|
|
83
|
+
super(message ?? 'No server connection available');
|
|
84
|
+
this.name = 'TwoFALibError';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Error thrown when an invalid command is being executed.
|
|
89
|
+
*/
|
|
90
|
+
export class InvalidCommandError extends TwoFALibError {
|
|
91
|
+
}
|