joopjs 2.0.0
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/CHANGELOG.md +678 -0
- package/README.md +583 -0
- package/dist/a11y.service-C-DQQfgO.d.mts +143 -0
- package/dist/a11y.service-CauEJrJe.d.ts +143 -0
- package/dist/adapters-B6slG6hQ.d.mts +84 -0
- package/dist/adapters-B6slG6hQ.d.ts +84 -0
- package/dist/aes.service-CkoupAww.d.mts +95 -0
- package/dist/aes.service-CkoupAww.d.ts +95 -0
- package/dist/ai/index.d.mts +99 -0
- package/dist/ai/index.d.ts +99 -0
- package/dist/ai/index.js +307 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/index.mjs +304 -0
- package/dist/ai/index.mjs.map +1 -0
- package/dist/analytics/index.d.mts +42 -0
- package/dist/analytics/index.d.ts +42 -0
- package/dist/analytics/index.js +139 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/analytics/index.mjs +136 -0
- package/dist/analytics/index.mjs.map +1 -0
- package/dist/angular/index.d.mts +148 -0
- package/dist/angular/index.d.ts +148 -0
- package/dist/angular/index.js +122 -0
- package/dist/angular/index.js.map +1 -0
- package/dist/angular/index.mjs +101 -0
- package/dist/angular/index.mjs.map +1 -0
- package/dist/api/index.d.mts +128 -0
- package/dist/api/index.d.ts +128 -0
- package/dist/api/index.js +1358 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/index.mjs +1332 -0
- package/dist/api/index.mjs.map +1 -0
- package/dist/auth/index.d.mts +105 -0
- package/dist/auth/index.d.ts +105 -0
- package/dist/auth/index.js +989 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/index.mjs +979 -0
- package/dist/auth/index.mjs.map +1 -0
- package/dist/auth.service-DNVB-L4U.d.mts +16 -0
- package/dist/auth.service-PjUUSUIt.d.ts +16 -0
- package/dist/banking/index.d.mts +1530 -0
- package/dist/banking/index.d.ts +1530 -0
- package/dist/banking/index.js +4739 -0
- package/dist/banking/index.js.map +1 -0
- package/dist/banking/index.mjs +4661 -0
- package/dist/banking/index.mjs.map +1 -0
- package/dist/cache/index.d.mts +40 -0
- package/dist/cache/index.d.ts +40 -0
- package/dist/cache/index.js +174 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/index.mjs +172 -0
- package/dist/cache/index.mjs.map +1 -0
- package/dist/client-profile.service-BuPeXVp5.d.mts +28 -0
- package/dist/client-profile.service-D5bRRYQp.d.ts +28 -0
- package/dist/config.models-Cqg04fAQ.d.mts +84 -0
- package/dist/config.models-Cqg04fAQ.d.ts +84 -0
- package/dist/config.service-CrCvI-JS.d.ts +31 -0
- package/dist/config.service-Cz4QQLlf.d.mts +31 -0
- package/dist/core/index.d.mts +4 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +631 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/index.mjs +619 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/crypto-utils-DriNhLdx.d.mts +30 -0
- package/dist/crypto-utils-DriNhLdx.d.ts +30 -0
- package/dist/data-storage.service-DT6xaTxE.d.ts +51 -0
- package/dist/data-storage.service-LvhGRCmw.d.mts +51 -0
- package/dist/deeplink/index.d.mts +39 -0
- package/dist/deeplink/index.d.ts +39 -0
- package/dist/deeplink/index.js +268 -0
- package/dist/deeplink/index.js.map +1 -0
- package/dist/deeplink/index.mjs +265 -0
- package/dist/deeplink/index.mjs.map +1 -0
- package/dist/deeplink.service-Ctd5u243.d.mts +35 -0
- package/dist/deeplink.service-uUuTnY9_.d.ts +35 -0
- package/dist/dev/index.d.mts +20 -0
- package/dist/dev/index.d.ts +20 -0
- package/dist/dev/index.js +51 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/dev/index.mjs +49 -0
- package/dist/dev/index.mjs.map +1 -0
- package/dist/device/index.d.mts +108 -0
- package/dist/device/index.d.ts +108 -0
- package/dist/device/index.js +960 -0
- package/dist/device/index.js.map +1 -0
- package/dist/device/index.mjs +951 -0
- package/dist/device/index.mjs.map +1 -0
- package/dist/differential-privacy-BcAv1G80.d.mts +210 -0
- package/dist/differential-privacy-C8mAUjZr.d.ts +210 -0
- package/dist/encryption/index.d.mts +75 -0
- package/dist/encryption/index.d.ts +75 -0
- package/dist/encryption/index.js +605 -0
- package/dist/encryption/index.js.map +1 -0
- package/dist/encryption/index.mjs +598 -0
- package/dist/encryption/index.mjs.map +1 -0
- package/dist/form-validator-3tkmzr_o.d.mts +72 -0
- package/dist/form-validator-3tkmzr_o.d.ts +72 -0
- package/dist/forms/index.d.mts +59 -0
- package/dist/forms/index.d.ts +59 -0
- package/dist/forms/index.js +446 -0
- package/dist/forms/index.js.map +1 -0
- package/dist/forms/index.mjs +442 -0
- package/dist/forms/index.mjs.map +1 -0
- package/dist/i18n/index.d.mts +37 -0
- package/dist/i18n/index.d.ts +37 -0
- package/dist/i18n/index.js +147 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/index.mjs +145 -0
- package/dist/i18n/index.mjs.map +1 -0
- package/dist/idempotency.service-_6LqhivP.d.mts +372 -0
- package/dist/idempotency.service-eOKoISRD.d.ts +372 -0
- package/dist/index-B_ksKpS1.d.mts +202 -0
- package/dist/index-CqDKWTUP.d.mts +28 -0
- package/dist/index-CqDKWTUP.d.ts +28 -0
- package/dist/index-DFqEoX_l.d.ts +202 -0
- package/dist/index-Dz0gOur2.d.mts +36 -0
- package/dist/index-Dz0gOur2.d.ts +36 -0
- package/dist/index.d.mts +1336 -0
- package/dist/index.d.ts +1336 -0
- package/dist/index.js +19464 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +19155 -0
- package/dist/index.mjs.map +1 -0
- package/dist/india/index.d.mts +75 -0
- package/dist/india/index.d.ts +75 -0
- package/dist/india/index.js +325 -0
- package/dist/india/index.js.map +1 -0
- package/dist/india/index.mjs +303 -0
- package/dist/india/index.mjs.map +1 -0
- package/dist/joop-Bx7Iwj5p.d.mts +155 -0
- package/dist/joop-CA3DMeOO.d.ts +155 -0
- package/dist/native-bridge/index.d.mts +27 -0
- package/dist/native-bridge/index.d.ts +27 -0
- package/dist/native-bridge/index.js +98 -0
- package/dist/native-bridge/index.js.map +1 -0
- package/dist/native-bridge/index.mjs +96 -0
- package/dist/native-bridge/index.mjs.map +1 -0
- package/dist/network/index.d.mts +85 -0
- package/dist/network/index.d.ts +85 -0
- package/dist/network/index.js +454 -0
- package/dist/network/index.js.map +1 -0
- package/dist/network/index.mjs +451 -0
- package/dist/network/index.mjs.map +1 -0
- package/dist/network-monitor-BIwPSXme.d.mts +179 -0
- package/dist/network-monitor-Bqp2hvZr.d.ts +179 -0
- package/dist/notification.service-Dm4fvfZf.d.mts +25 -0
- package/dist/notification.service-tEMKatWJ.d.ts +25 -0
- package/dist/observability/index.d.mts +179 -0
- package/dist/observability/index.d.ts +179 -0
- package/dist/observability/index.js +559 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/index.mjs +552 -0
- package/dist/observability/index.mjs.map +1 -0
- package/dist/oidc-client-DIJcClmB.d.mts +190 -0
- package/dist/oidc-client-DxhyE59t.d.ts +190 -0
- package/dist/platform/index.d.mts +73 -0
- package/dist/platform/index.d.ts +73 -0
- package/dist/platform/index.js +127 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/index.mjs +125 -0
- package/dist/platform/index.mjs.map +1 -0
- package/dist/pwa/index.d.mts +31 -0
- package/dist/pwa/index.d.ts +31 -0
- package/dist/pwa/index.js +247 -0
- package/dist/pwa/index.js.map +1 -0
- package/dist/pwa/index.mjs +244 -0
- package/dist/pwa/index.mjs.map +1 -0
- package/dist/react/index.d.mts +133 -0
- package/dist/react/index.d.ts +133 -0
- package/dist/react/index.js +632 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +630 -0
- package/dist/react/index.mjs.map +1 -0
- package/dist/router/index.d.mts +39 -0
- package/dist/router/index.d.ts +39 -0
- package/dist/router/index.js +168 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/index.mjs +166 -0
- package/dist/router/index.mjs.map +1 -0
- package/dist/security/index.d.mts +206 -0
- package/dist/security/index.d.ts +206 -0
- package/dist/security/index.js +1297 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/index.mjs +1285 -0
- package/dist/security/index.mjs.map +1 -0
- package/dist/session/index.d.mts +115 -0
- package/dist/session/index.d.ts +115 -0
- package/dist/session/index.js +297 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/index.mjs +292 -0
- package/dist/session/index.mjs.map +1 -0
- package/dist/state/index.d.mts +43 -0
- package/dist/state/index.d.ts +43 -0
- package/dist/state/index.js +156 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/index.mjs +152 -0
- package/dist/state/index.mjs.map +1 -0
- package/dist/statement-parser-BHQtXwCM.d.ts +260 -0
- package/dist/statement-parser-C2qNmb49.d.mts +260 -0
- package/dist/storage/index.d.mts +40 -0
- package/dist/storage/index.d.ts +40 -0
- package/dist/storage/index.js +256 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/index.mjs +252 -0
- package/dist/storage/index.mjs.map +1 -0
- package/dist/sync/index.d.mts +69 -0
- package/dist/sync/index.d.ts +69 -0
- package/dist/sync/index.js +330 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/index.mjs +323 -0
- package/dist/sync/index.mjs.map +1 -0
- package/dist/sync-engine-DCIMRG5s.d.ts +61 -0
- package/dist/sync-engine-DZqyKHkK.d.mts +61 -0
- package/dist/theme/index.d.mts +53 -0
- package/dist/theme/index.d.ts +53 -0
- package/dist/theme/index.js +169 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme/index.mjs +167 -0
- package/dist/theme/index.mjs.map +1 -0
- package/dist/ui/index.d.mts +66 -0
- package/dist/ui/index.d.ts +66 -0
- package/dist/ui/index.js +811 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/index.mjs +803 -0
- package/dist/ui/index.mjs.map +1 -0
- package/dist/utilities/index.d.mts +199 -0
- package/dist/utilities/index.d.ts +199 -0
- package/dist/utilities/index.js +1991 -0
- package/dist/utilities/index.js.map +1 -0
- package/dist/utilities/index.mjs +1923 -0
- package/dist/utilities/index.mjs.map +1 -0
- package/dist/validation/index.d.mts +60 -0
- package/dist/validation/index.d.ts +60 -0
- package/dist/validation/index.js +460 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/index.mjs +455 -0
- package/dist/validation/index.mjs.map +1 -0
- package/dist/vue/index.d.mts +135 -0
- package/dist/vue/index.d.ts +135 -0
- package/dist/vue/index.js +621 -0
- package/dist/vue/index.js.map +1 -0
- package/dist/vue/index.mjs +619 -0
- package/dist/vue/index.mjs.map +1 -0
- package/dist/watermark.service-Detur5tq.d.ts +235 -0
- package/dist/watermark.service-QNegMeQZ.d.mts +235 -0
- package/dist/workers/index.d.mts +42 -0
- package/dist/workers/index.d.ts +42 -0
- package/dist/workers/index.js +359 -0
- package/dist/workers/index.js.map +1 -0
- package/dist/workers/index.mjs +356 -0
- package/dist/workers/index.mjs.map +1 -0
- package/dist/workflow/index.d.mts +99 -0
- package/dist/workflow/index.d.ts +99 -0
- package/dist/workflow/index.js +282 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/index.mjs +279 -0
- package/dist/workflow/index.mjs.map +1 -0
- package/package.json +226 -0
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import { encode, decode } from 'base64-arraybuffer';
|
|
2
|
+
import { x25519 } from '@noble/curves/ed25519.js';
|
|
3
|
+
import { chacha20poly1305 } from '@noble/ciphers/chacha.js';
|
|
4
|
+
|
|
5
|
+
// src/utilities/common.utility.ts
|
|
6
|
+
function randomString(length, charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") {
|
|
7
|
+
let result = "";
|
|
8
|
+
for (let i = 0; i < length; i++) {
|
|
9
|
+
result += charset[Math.floor(Math.random() * charset.length)];
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
function stringToHex(input) {
|
|
14
|
+
let result = "";
|
|
15
|
+
for (let i = 0; i < input.length; i++) {
|
|
16
|
+
result += input.charCodeAt(i).toString(16).slice(-4);
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
function bufferToHex(buffer) {
|
|
21
|
+
return [...new Uint8Array(buffer)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/models/error.models.ts
|
|
25
|
+
var JoopEncryptionError = class extends Error {
|
|
26
|
+
constructor(message) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = "JoopEncryptionError";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// src/encryption/gcm.service.ts
|
|
33
|
+
var JoopGcmService = class {
|
|
34
|
+
async encrypt(data, aesKey) {
|
|
35
|
+
const keyBuffer = this._keyToBuffer(aesKey);
|
|
36
|
+
const iv = await this._deriveIV(aesKey, data);
|
|
37
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, ["encrypt"]);
|
|
38
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
39
|
+
{ name: "AES-GCM", iv: this._toSafeUint8Array(iv), tagLength: 128 },
|
|
40
|
+
cryptoKey,
|
|
41
|
+
new TextEncoder().encode(data)
|
|
42
|
+
);
|
|
43
|
+
const ivBytes = Array.from(iv);
|
|
44
|
+
const encBytes = Array.from(new Uint8Array(encrypted));
|
|
45
|
+
return bufferToHex(new Uint8Array([...ivBytes, ...encBytes]));
|
|
46
|
+
}
|
|
47
|
+
async decrypt(hexData, aesKey) {
|
|
48
|
+
const parsedIV = hexData.substring(0, 24);
|
|
49
|
+
const keyBuffer = this._keyToBuffer(aesKey);
|
|
50
|
+
const iv = this._toSafeUint8Array(this._hexToUint8(parsedIV));
|
|
51
|
+
const encrypted = this._toSafeUint8Array(this._hexToUint8(hexData.substring(24)));
|
|
52
|
+
const importedKey = await crypto.subtle.importKey("raw", keyBuffer, "AES-GCM", true, ["decrypt"]);
|
|
53
|
+
try {
|
|
54
|
+
const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, importedKey, encrypted);
|
|
55
|
+
return new TextDecoder().decode(new Uint8Array(plaintext));
|
|
56
|
+
} catch {
|
|
57
|
+
throw new JoopEncryptionError("AES-GCM decryption failed \u2014 wrong key or corrupted data");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async _deriveIV(key, data) {
|
|
61
|
+
const randomData = btoa(String.fromCharCode(...Array.from(crypto.getRandomValues(new Uint8Array(12)))));
|
|
62
|
+
return this._pbkdf2(key + randomData, data + Date.now().toString(), 1, 12, "SHA-256");
|
|
63
|
+
}
|
|
64
|
+
async _pbkdf2(message, salt, iterations, keyLen, algorithm) {
|
|
65
|
+
const enc = new TextEncoder();
|
|
66
|
+
const keyMaterial = await crypto.subtle.importKey("raw", enc.encode(message), { name: "PBKDF2" }, false, ["deriveBits"]);
|
|
67
|
+
const bits = await crypto.subtle.deriveBits(
|
|
68
|
+
{ name: "PBKDF2", salt: enc.encode(salt), iterations, hash: algorithm },
|
|
69
|
+
keyMaterial,
|
|
70
|
+
keyLen * 8
|
|
71
|
+
);
|
|
72
|
+
return new Uint8Array(bits);
|
|
73
|
+
}
|
|
74
|
+
_keyToBuffer(key) {
|
|
75
|
+
const enc = new TextEncoder();
|
|
76
|
+
const bytes = enc.encode(key);
|
|
77
|
+
const hex = bufferToHex(bytes);
|
|
78
|
+
return this._toSafeUint8Array(this._hexToUint8(hex)).buffer;
|
|
79
|
+
}
|
|
80
|
+
_hexToUint8(hex) {
|
|
81
|
+
hex = hex.replace(/^0x/, "");
|
|
82
|
+
const pairs = hex.match(/[\dA-F]{2}/gi) ?? [];
|
|
83
|
+
return new Uint8Array(pairs.map((s) => parseInt(s, 16)));
|
|
84
|
+
}
|
|
85
|
+
/** Copy Uint8Array into a fresh ArrayBuffer to satisfy TS5.3+ BufferSource constraints. */
|
|
86
|
+
_toSafeUint8Array(src) {
|
|
87
|
+
const buf = new ArrayBuffer(src.length);
|
|
88
|
+
new Uint8Array(buf).set(src);
|
|
89
|
+
return new Uint8Array(buf);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// src/encryption/rsa.service.ts
|
|
94
|
+
var JoopRsaService = class {
|
|
95
|
+
async encrypt(data, keys) {
|
|
96
|
+
let { modulus, exponent } = keys;
|
|
97
|
+
if (modulus.length === 514) {
|
|
98
|
+
modulus = modulus.slice(2);
|
|
99
|
+
}
|
|
100
|
+
let encBuffer = "";
|
|
101
|
+
try {
|
|
102
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
103
|
+
"jwk",
|
|
104
|
+
{
|
|
105
|
+
kty: "RSA",
|
|
106
|
+
e: this._b64ToB64Url(this._hexToBase64(exponent)),
|
|
107
|
+
n: this._b64ToB64Url(this._hexToBase64(modulus)),
|
|
108
|
+
alg: "RSA-OAEP-256",
|
|
109
|
+
ext: true
|
|
110
|
+
},
|
|
111
|
+
{ name: "RSA-OAEP", hash: { name: "SHA-256" } },
|
|
112
|
+
true,
|
|
113
|
+
["encrypt"]
|
|
114
|
+
);
|
|
115
|
+
const encoded = new TextEncoder().encode(data);
|
|
116
|
+
const encData = await crypto.subtle.encrypt({ name: "RSA-OAEP" }, cryptoKey, encoded);
|
|
117
|
+
encBuffer = bufferToHex(encData);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
throw new JoopEncryptionError(`RSA-OAEP encryption failed: ${err}`);
|
|
120
|
+
}
|
|
121
|
+
return encBuffer;
|
|
122
|
+
}
|
|
123
|
+
_hexToBase64(hex) {
|
|
124
|
+
return btoa(
|
|
125
|
+
(hex.match(/\w{2}/g) ?? []).map((a) => String.fromCodePoint(parseInt(a, 16) || 0)).join("")
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
_b64ToB64Url(b64) {
|
|
129
|
+
return b64.replaceAll("=", "").replaceAll("+", "-").replaceAll("/", "_");
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// src/encryption/aes.service.ts
|
|
134
|
+
var JoopAesService = class {
|
|
135
|
+
constructor(_gcm, _rsa) {
|
|
136
|
+
this._gcm = _gcm;
|
|
137
|
+
this._rsa = _rsa;
|
|
138
|
+
}
|
|
139
|
+
_gcm;
|
|
140
|
+
_rsa;
|
|
141
|
+
_e2eRsaStatus = "";
|
|
142
|
+
_e2eStatus = "";
|
|
143
|
+
_modulus = "";
|
|
144
|
+
_exponent = "";
|
|
145
|
+
_random = "";
|
|
146
|
+
_genKey = "";
|
|
147
|
+
_genKeyHex = "";
|
|
148
|
+
get e2eRsaStatus() {
|
|
149
|
+
return this._e2eRsaStatus;
|
|
150
|
+
}
|
|
151
|
+
set e2eRsaStatus(v) {
|
|
152
|
+
this._e2eRsaStatus = v;
|
|
153
|
+
}
|
|
154
|
+
get e2eStatus() {
|
|
155
|
+
return this._e2eStatus;
|
|
156
|
+
}
|
|
157
|
+
set e2eStatus(v) {
|
|
158
|
+
this._e2eStatus = v;
|
|
159
|
+
}
|
|
160
|
+
get modulus() {
|
|
161
|
+
return this._modulus;
|
|
162
|
+
}
|
|
163
|
+
set modulus(v) {
|
|
164
|
+
this._modulus = v;
|
|
165
|
+
}
|
|
166
|
+
get exponent() {
|
|
167
|
+
return this._exponent;
|
|
168
|
+
}
|
|
169
|
+
set exponent(v) {
|
|
170
|
+
this._exponent = v;
|
|
171
|
+
}
|
|
172
|
+
get random() {
|
|
173
|
+
return this._random;
|
|
174
|
+
}
|
|
175
|
+
set random(v) {
|
|
176
|
+
this._random = v;
|
|
177
|
+
}
|
|
178
|
+
get genKey() {
|
|
179
|
+
return this._genKey;
|
|
180
|
+
}
|
|
181
|
+
set genKey(v) {
|
|
182
|
+
this._genKey = v;
|
|
183
|
+
}
|
|
184
|
+
/** Returns true when E2E encryption is active (not on the initial request). */
|
|
185
|
+
isE2EEnabled(isInitialRequest) {
|
|
186
|
+
return !isInitialRequest && this._e2eStatus === "1";
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Encrypt serialised request data.
|
|
190
|
+
* Returns an object with `encryptedText` (and optionally `pageToken` already embedded in data).
|
|
191
|
+
*/
|
|
192
|
+
async processEncryption(serializedData, pageToken) {
|
|
193
|
+
if (serializedData && pageToken) {
|
|
194
|
+
serializedData = `${serializedData}&pageToken=${pageToken}`;
|
|
195
|
+
}
|
|
196
|
+
const isSessionMode = this._e2eRsaStatus === "1";
|
|
197
|
+
if (isSessionMode) {
|
|
198
|
+
if (!serializedData) return {};
|
|
199
|
+
const encText2 = await this._gcm.encrypt(serializedData, this._genKey);
|
|
200
|
+
return { encryptedText: encText2 };
|
|
201
|
+
}
|
|
202
|
+
this._generateKey();
|
|
203
|
+
const rsaEncryptedKey = await this._encryptAesKey();
|
|
204
|
+
if (!serializedData) return { encryptedText: rsaEncryptedKey };
|
|
205
|
+
const encText = await this._gcm.encrypt(serializedData, this._genKey);
|
|
206
|
+
return { encryptedText: rsaEncryptedKey + encText };
|
|
207
|
+
}
|
|
208
|
+
/** Decrypt an encrypted response payload. */
|
|
209
|
+
async decryptResponse(encryptedData) {
|
|
210
|
+
return this._gcm.decrypt(encryptedData, this._genKey);
|
|
211
|
+
}
|
|
212
|
+
clearKeys() {
|
|
213
|
+
this._e2eStatus = "";
|
|
214
|
+
this._e2eRsaStatus = "";
|
|
215
|
+
this._genKey = "";
|
|
216
|
+
this._genKeyHex = "";
|
|
217
|
+
this._modulus = "";
|
|
218
|
+
this._exponent = "";
|
|
219
|
+
this._random = "";
|
|
220
|
+
}
|
|
221
|
+
_generateKey() {
|
|
222
|
+
this._genKey = randomString(16, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
|
223
|
+
this._genKeyHex = stringToHex(this._genKey);
|
|
224
|
+
}
|
|
225
|
+
_encryptAesKey() {
|
|
226
|
+
const keys = {
|
|
227
|
+
modulus: this._modulus,
|
|
228
|
+
exponent: this._exponent,
|
|
229
|
+
randomNumber: this._random
|
|
230
|
+
};
|
|
231
|
+
return this._rsa.encrypt(this._genKeyHex, keys);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
var JoopX25519Service = class _JoopX25519Service {
|
|
235
|
+
static INFO_ENC_KEY = "enc-key";
|
|
236
|
+
static INFO_ENC_IV = "enc-iv";
|
|
237
|
+
static INFO_MAC_KEY = "mac-iv";
|
|
238
|
+
_privateKey = null;
|
|
239
|
+
_publicKey = null;
|
|
240
|
+
_encKey = null;
|
|
241
|
+
_nonce = null;
|
|
242
|
+
_macKey = null;
|
|
243
|
+
_ready = false;
|
|
244
|
+
/** Generate key pair; returns base64-encoded X.509 public key to send to the server. */
|
|
245
|
+
async init() {
|
|
246
|
+
try {
|
|
247
|
+
const priv = x25519.utils.randomSecretKey();
|
|
248
|
+
const pub = x25519.getPublicKey(priv);
|
|
249
|
+
this._privateKey = priv;
|
|
250
|
+
this._publicKey = pub;
|
|
251
|
+
const x509 = this._encodeX509(pub);
|
|
252
|
+
const buf = new ArrayBuffer(x509.byteLength);
|
|
253
|
+
new Uint8Array(buf).set(x509);
|
|
254
|
+
this._ready = true;
|
|
255
|
+
return encode(buf);
|
|
256
|
+
} catch (err) {
|
|
257
|
+
this._reset();
|
|
258
|
+
throw new JoopEncryptionError(`X25519 init failed: ${err}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/** Perform DH key exchange and derive ChaCha20-Poly1305 keys. */
|
|
262
|
+
async keyExchange(serverPublicKeyBase64, serverRandomBase64) {
|
|
263
|
+
this._assertReady();
|
|
264
|
+
try {
|
|
265
|
+
const serverPubRaw = this._base64UrlDecode(serverPublicKeyBase64);
|
|
266
|
+
const serverPub = this._decodeX509(serverPubRaw);
|
|
267
|
+
const salt = this._base64UrlDecode(serverRandomBase64);
|
|
268
|
+
const shared = x25519.getSharedSecret(this._privateKey, serverPub);
|
|
269
|
+
const prk = await this._hkdfExtract(salt, shared);
|
|
270
|
+
this._nonce = await this._hkdfExpand(prk, _JoopX25519Service.INFO_ENC_IV, 12);
|
|
271
|
+
this._encKey = await this._hkdfExpand(prk, _JoopX25519Service.INFO_ENC_KEY, 32);
|
|
272
|
+
this._macKey = await this._hkdfExpand(prk, _JoopX25519Service.INFO_MAC_KEY, 16);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
this._reset();
|
|
275
|
+
throw new JoopEncryptionError(`Key exchange failed: ${err}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
async encrypt(plainText) {
|
|
279
|
+
this._assertReady();
|
|
280
|
+
this._assertKeysReady();
|
|
281
|
+
try {
|
|
282
|
+
const data = new TextEncoder().encode(plainText);
|
|
283
|
+
const cipher = chacha20poly1305(this._encKey, this._nonce);
|
|
284
|
+
const ct = cipher.encrypt(data);
|
|
285
|
+
const buf = new ArrayBuffer(ct.byteLength);
|
|
286
|
+
new Uint8Array(buf).set(ct);
|
|
287
|
+
return encode(buf);
|
|
288
|
+
} catch (err) {
|
|
289
|
+
throw new JoopEncryptionError(`Encryption failed: ${err}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async decrypt(cipherBase64) {
|
|
293
|
+
this._assertReady();
|
|
294
|
+
this._assertKeysReady();
|
|
295
|
+
try {
|
|
296
|
+
const ct = new Uint8Array(decode(cipherBase64));
|
|
297
|
+
const cipher = chacha20poly1305(this._encKey, this._nonce);
|
|
298
|
+
return new TextDecoder().decode(cipher.decrypt(ct));
|
|
299
|
+
} catch (err) {
|
|
300
|
+
throw new JoopEncryptionError(`Decryption failed: ${err}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
reset() {
|
|
304
|
+
this._reset();
|
|
305
|
+
}
|
|
306
|
+
// ──────────────── private helpers ────────────────
|
|
307
|
+
_assertReady() {
|
|
308
|
+
if (!this._ready || !this._privateKey || !this._publicKey) {
|
|
309
|
+
throw new JoopEncryptionError("X25519Service not initialised \u2014 call init() first.");
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
_assertKeysReady() {
|
|
313
|
+
if (!this._encKey || !this._nonce || !this._macKey) {
|
|
314
|
+
throw new JoopEncryptionError("X25519Service keys not derived \u2014 call keyExchange() first.");
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
_reset() {
|
|
318
|
+
this._privateKey = this._publicKey = this._encKey = this._nonce = this._macKey = null;
|
|
319
|
+
this._ready = false;
|
|
320
|
+
}
|
|
321
|
+
_encodeX509(raw) {
|
|
322
|
+
if (raw.length !== 32) throw new JoopEncryptionError("Invalid X25519 public key length");
|
|
323
|
+
const prefix = new Uint8Array([48, 42, 48, 5, 6, 3, 43, 101, 110, 3, 33, 0]);
|
|
324
|
+
return new Uint8Array([...prefix, ...raw]);
|
|
325
|
+
}
|
|
326
|
+
_decodeX509(x509) {
|
|
327
|
+
if (x509.length < 44) throw new JoopEncryptionError("Invalid X.509 key format");
|
|
328
|
+
return x509.slice(12);
|
|
329
|
+
}
|
|
330
|
+
_base64UrlDecode(s) {
|
|
331
|
+
let b64 = s.replaceAll("-", "+").replaceAll("_", "/");
|
|
332
|
+
while (b64.length % 4 !== 0) b64 += "=";
|
|
333
|
+
return new Uint8Array(decode(b64));
|
|
334
|
+
}
|
|
335
|
+
async _hkdfExtract(salt, ikm) {
|
|
336
|
+
const saltBuf = new ArrayBuffer(salt.byteLength);
|
|
337
|
+
new Uint8Array(saltBuf).set(salt);
|
|
338
|
+
const key = await crypto.subtle.importKey("raw", saltBuf, { name: "HMAC", hash: "SHA-512" }, false, ["sign"]);
|
|
339
|
+
const ikmBuf = new ArrayBuffer(ikm.byteLength);
|
|
340
|
+
new Uint8Array(ikmBuf).set(ikm);
|
|
341
|
+
return new Uint8Array(await crypto.subtle.sign("HMAC", key, ikmBuf));
|
|
342
|
+
}
|
|
343
|
+
async _hkdfExpand(prk, info, length) {
|
|
344
|
+
const enc = new TextEncoder();
|
|
345
|
+
const infoBytes = enc.encode(info);
|
|
346
|
+
const prkBuf = new ArrayBuffer(prk.byteLength);
|
|
347
|
+
new Uint8Array(prkBuf).set(prk);
|
|
348
|
+
const key = await crypto.subtle.importKey("raw", prkBuf, { name: "HMAC", hash: "SHA-512" }, false, ["sign"]);
|
|
349
|
+
let T = new Uint8Array(0);
|
|
350
|
+
let output = new Uint8Array(0);
|
|
351
|
+
let counter = 0;
|
|
352
|
+
while (output.length < length) {
|
|
353
|
+
counter++;
|
|
354
|
+
const input = new Uint8Array([...T, ...infoBytes, counter]);
|
|
355
|
+
T = new Uint8Array(await crypto.subtle.sign("HMAC", key, input));
|
|
356
|
+
output = new Uint8Array([...output, ...T]);
|
|
357
|
+
}
|
|
358
|
+
return output.slice(0, length);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
// src/encryption/e2e.service.ts
|
|
363
|
+
var _oaepGen = (bits) => ({
|
|
364
|
+
name: "RSA-OAEP",
|
|
365
|
+
modulusLength: bits,
|
|
366
|
+
publicExponent: new Uint8Array([1, 0, 1]),
|
|
367
|
+
hash: "SHA-256"
|
|
368
|
+
});
|
|
369
|
+
var _pssGen = (bits) => ({
|
|
370
|
+
name: "RSA-PSS",
|
|
371
|
+
modulusLength: bits,
|
|
372
|
+
publicExponent: new Uint8Array([1, 0, 1]),
|
|
373
|
+
hash: "SHA-256"
|
|
374
|
+
});
|
|
375
|
+
var OAEP_IMP = { name: "RSA-OAEP", hash: { name: "SHA-256" } };
|
|
376
|
+
var PSS_IMP = { name: "RSA-PSS", hash: { name: "SHA-256" } };
|
|
377
|
+
function _b64(buf) {
|
|
378
|
+
const bytes = ArrayBuffer.isView(buf) ? new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength) : new Uint8Array(buf);
|
|
379
|
+
return btoa(String.fromCharCode(...bytes));
|
|
380
|
+
}
|
|
381
|
+
function _unb64(s) {
|
|
382
|
+
return new Uint8Array(Array.from(atob(s), (c) => c.charCodeAt(0)));
|
|
383
|
+
}
|
|
384
|
+
function _pemToBytes(pem) {
|
|
385
|
+
return _unb64(pem.replace(/-----[^-]+-----/g, "").replace(/\s/g, ""));
|
|
386
|
+
}
|
|
387
|
+
function _bytesToPem(bytes, type) {
|
|
388
|
+
const b64 = _b64(bytes);
|
|
389
|
+
const lines = b64.match(/.{1,64}/g)?.join("\n") ?? b64;
|
|
390
|
+
return `-----BEGIN ${type}-----
|
|
391
|
+
${lines}
|
|
392
|
+
-----END ${type}-----`;
|
|
393
|
+
}
|
|
394
|
+
var JoopE2EService = class {
|
|
395
|
+
// ── Key generation ─────────────────────────────────────────────────────────
|
|
396
|
+
async generateKeyPair(bits = 2048) {
|
|
397
|
+
const pair = await crypto.subtle.generateKey(_oaepGen(bits), true, ["encrypt", "decrypt"]);
|
|
398
|
+
return pair;
|
|
399
|
+
}
|
|
400
|
+
async generateSigningKeyPair(bits = 2048) {
|
|
401
|
+
const pair = await crypto.subtle.generateKey(_pssGen(bits), true, ["sign", "verify"]);
|
|
402
|
+
return pair;
|
|
403
|
+
}
|
|
404
|
+
// ── PEM export/import ──────────────────────────────────────────────────────
|
|
405
|
+
async exportPublicKeyPem(key) {
|
|
406
|
+
return _bytesToPem(await crypto.subtle.exportKey("spki", key), "PUBLIC KEY");
|
|
407
|
+
}
|
|
408
|
+
async exportPrivateKeyPem(key) {
|
|
409
|
+
return _bytesToPem(await crypto.subtle.exportKey("pkcs8", key), "PRIVATE KEY");
|
|
410
|
+
}
|
|
411
|
+
async importPublicKeyPem(pem, usage = "encrypt") {
|
|
412
|
+
const algo = usage === "encrypt" ? OAEP_IMP : PSS_IMP;
|
|
413
|
+
return crypto.subtle.importKey("spki", _pemToBytes(pem), algo, true, [usage]);
|
|
414
|
+
}
|
|
415
|
+
async importPrivateKeyPem(pem, usage = "decrypt") {
|
|
416
|
+
const algo = usage === "decrypt" ? OAEP_IMP : PSS_IMP;
|
|
417
|
+
return crypto.subtle.importKey("pkcs8", _pemToBytes(pem), algo, true, [usage]);
|
|
418
|
+
}
|
|
419
|
+
// ── JWK export/import ──────────────────────────────────────────────────────
|
|
420
|
+
async exportPublicKeyJwk(key) {
|
|
421
|
+
return crypto.subtle.exportKey("jwk", key);
|
|
422
|
+
}
|
|
423
|
+
async importPublicKeyJwk(jwk, usage = "encrypt") {
|
|
424
|
+
const algo = usage === "encrypt" ? OAEP_IMP : PSS_IMP;
|
|
425
|
+
return crypto.subtle.importKey("jwk", jwk, algo, true, [usage]);
|
|
426
|
+
}
|
|
427
|
+
async exportPrivateKeyJwk(key) {
|
|
428
|
+
return crypto.subtle.exportKey("jwk", key);
|
|
429
|
+
}
|
|
430
|
+
// ── Hybrid encryption (RSA wraps AES key) ─────────────────────────────────
|
|
431
|
+
async encryptForRecipient(plaintext, publicKey) {
|
|
432
|
+
const pk = typeof publicKey === "string" ? await this.importPublicKeyPem(publicKey, "encrypt") : publicKey;
|
|
433
|
+
const aesKey = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt"]);
|
|
434
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
435
|
+
const cipherBuf = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, new TextEncoder().encode(plaintext));
|
|
436
|
+
const rawAes = await crypto.subtle.exportKey("raw", aesKey);
|
|
437
|
+
const wrappedBuf = await crypto.subtle.encrypt({ name: "RSA-OAEP" }, pk, rawAes);
|
|
438
|
+
return { wrappedKey: _b64(wrappedBuf), iv: _b64(iv), ciphertext: _b64(cipherBuf), algorithm: "RSA-OAEP+AES-GCM" };
|
|
439
|
+
}
|
|
440
|
+
async decryptFromSender(payload, privateKey) {
|
|
441
|
+
const sk = typeof privateKey === "string" ? await this.importPrivateKeyPem(privateKey, "decrypt") : privateKey;
|
|
442
|
+
const rawAes = await crypto.subtle.decrypt({ name: "RSA-OAEP" }, sk, _unb64(payload.wrappedKey));
|
|
443
|
+
const aesKey = await crypto.subtle.importKey("raw", rawAes, "AES-GCM", false, ["decrypt"]);
|
|
444
|
+
const plainBuf = await crypto.subtle.decrypt({ name: "AES-GCM", iv: _unb64(payload.iv) }, aesKey, _unb64(payload.ciphertext));
|
|
445
|
+
return new TextDecoder().decode(plainBuf);
|
|
446
|
+
}
|
|
447
|
+
// ── Digital signatures (RSA-PSS) ──────────────────────────────────────────
|
|
448
|
+
async sign(data, privateKey) {
|
|
449
|
+
const sk = typeof privateKey === "string" ? await this.importPrivateKeyPem(privateKey, "sign") : privateKey;
|
|
450
|
+
const buf = await crypto.subtle.sign({ name: "RSA-PSS", saltLength: 32 }, sk, new TextEncoder().encode(data));
|
|
451
|
+
return _b64(buf);
|
|
452
|
+
}
|
|
453
|
+
async verify(data, signature, publicKey) {
|
|
454
|
+
try {
|
|
455
|
+
const pk = typeof publicKey === "string" ? await this.importPublicKeyPem(publicKey, "verify") : publicKey;
|
|
456
|
+
return crypto.subtle.verify({ name: "RSA-PSS", saltLength: 32 }, pk, _unb64(signature), new TextEncoder().encode(data));
|
|
457
|
+
} catch {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// ── Session key utilities ─────────────────────────────────────────────────
|
|
462
|
+
async generateSessionKey() {
|
|
463
|
+
const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"]);
|
|
464
|
+
return { key, exported: _b64(await crypto.subtle.exportKey("raw", key)) };
|
|
465
|
+
}
|
|
466
|
+
async wrapSessionKey(sessionKey, recipientPublicKey) {
|
|
467
|
+
const raw = await crypto.subtle.exportKey("raw", sessionKey);
|
|
468
|
+
return _b64(await crypto.subtle.encrypt({ name: "RSA-OAEP" }, recipientPublicKey, raw));
|
|
469
|
+
}
|
|
470
|
+
async unwrapSessionKey(wrappedKey, privateKey) {
|
|
471
|
+
const raw = await crypto.subtle.decrypt({ name: "RSA-OAEP" }, privateKey, _unb64(wrappedKey));
|
|
472
|
+
return crypto.subtle.importKey("raw", raw, "AES-GCM", false, ["encrypt", "decrypt"]);
|
|
473
|
+
}
|
|
474
|
+
// ── Direct RSA encrypt/decrypt (small payloads only, <190 bytes for 2048-bit key) ──
|
|
475
|
+
async encryptRaw(data, publicKey) {
|
|
476
|
+
const pk = typeof publicKey === "string" ? await this.importPublicKeyPem(publicKey, "encrypt") : publicKey;
|
|
477
|
+
return _b64(await crypto.subtle.encrypt({ name: "RSA-OAEP" }, pk, new TextEncoder().encode(data)));
|
|
478
|
+
}
|
|
479
|
+
async decryptRaw(ciphertext, privateKey) {
|
|
480
|
+
const sk = typeof privateKey === "string" ? await this.importPrivateKeyPem(privateKey, "decrypt") : privateKey;
|
|
481
|
+
return new TextDecoder().decode(await crypto.subtle.decrypt({ name: "RSA-OAEP" }, sk, _unb64(ciphertext)));
|
|
482
|
+
}
|
|
483
|
+
// ── Key fingerprint ───────────────────────────────────────────────────────
|
|
484
|
+
async fingerprint(publicKey) {
|
|
485
|
+
const spki = await crypto.subtle.exportKey("spki", publicKey);
|
|
486
|
+
const hash = await crypto.subtle.digest("SHA-256", spki);
|
|
487
|
+
return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join(":");
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// src/encryption/crypto-utils.ts
|
|
492
|
+
function _hex(buf) {
|
|
493
|
+
return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
494
|
+
}
|
|
495
|
+
function _hexToBytes(hex) {
|
|
496
|
+
return new Uint8Array(hex.match(/.{2}/g).map((h) => parseInt(h, 16)));
|
|
497
|
+
}
|
|
498
|
+
var JoopCryptoUtils = class {
|
|
499
|
+
// ── Hashing ──────────────────────────────────────────────────────────────
|
|
500
|
+
async sha256(data) {
|
|
501
|
+
const buf = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
502
|
+
return _hex(await crypto.subtle.digest("SHA-256", buf));
|
|
503
|
+
}
|
|
504
|
+
async sha512(data) {
|
|
505
|
+
const buf = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
506
|
+
return _hex(await crypto.subtle.digest("SHA-512", buf));
|
|
507
|
+
}
|
|
508
|
+
async sha1(data) {
|
|
509
|
+
const buf = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
510
|
+
return _hex(await crypto.subtle.digest("SHA-1", buf));
|
|
511
|
+
}
|
|
512
|
+
// ── HMAC ─────────────────────────────────────────────────────────────────
|
|
513
|
+
async hmac(data, secret, algo = "SHA-256") {
|
|
514
|
+
const key = await crypto.subtle.importKey(
|
|
515
|
+
"raw",
|
|
516
|
+
new TextEncoder().encode(secret),
|
|
517
|
+
{ name: "HMAC", hash: algo },
|
|
518
|
+
false,
|
|
519
|
+
["sign"]
|
|
520
|
+
);
|
|
521
|
+
return _hex(await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(data)));
|
|
522
|
+
}
|
|
523
|
+
async hmacVerify(data, secret, signature, algo = "SHA-256") {
|
|
524
|
+
const expected = await this.hmac(data, secret, algo);
|
|
525
|
+
return this.timingSafeEqual(expected, signature);
|
|
526
|
+
}
|
|
527
|
+
// ── PBKDF2 ───────────────────────────────────────────────────────────────
|
|
528
|
+
async pbkdf2(password, salt, iterations = 1e5, keyLen = 32, hash = "SHA-256") {
|
|
529
|
+
const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveBits"]);
|
|
530
|
+
const bits = await crypto.subtle.deriveBits(
|
|
531
|
+
{ name: "PBKDF2", salt: new TextEncoder().encode(salt), iterations, hash },
|
|
532
|
+
key,
|
|
533
|
+
keyLen * 8
|
|
534
|
+
);
|
|
535
|
+
return _hex(bits);
|
|
536
|
+
}
|
|
537
|
+
// ── Password hashing ──────────────────────────────────────────────────────
|
|
538
|
+
async hashPassword(password, salt, iterations = 1e5) {
|
|
539
|
+
const s = salt ?? this.generateSalt();
|
|
540
|
+
const hash = await this.pbkdf2(password, s, iterations);
|
|
541
|
+
return { hash, salt: s, iterations, keyLen: 32 };
|
|
542
|
+
}
|
|
543
|
+
async verifyPassword(password, record) {
|
|
544
|
+
const { hash } = await this.hashPassword(password, record.salt, record.iterations);
|
|
545
|
+
return this.timingSafeEqual(hash, record.hash);
|
|
546
|
+
}
|
|
547
|
+
// ── Utilities ──────────────────────────────────────────────────────────────
|
|
548
|
+
generateSalt(bytes = 32) {
|
|
549
|
+
return _hex(crypto.getRandomValues(new Uint8Array(bytes)).buffer);
|
|
550
|
+
}
|
|
551
|
+
randomBytes(length) {
|
|
552
|
+
return new Uint8Array(crypto.getRandomValues(new Uint8Array(length)));
|
|
553
|
+
}
|
|
554
|
+
randomHex(bytes = 16) {
|
|
555
|
+
return _hex(this.randomBytes(bytes).buffer);
|
|
556
|
+
}
|
|
557
|
+
uuid() {
|
|
558
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
559
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
560
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
561
|
+
const hex = _hex(bytes.buffer);
|
|
562
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
563
|
+
}
|
|
564
|
+
/** Constant-time string comparison to prevent timing attacks */
|
|
565
|
+
timingSafeEqual(a, b) {
|
|
566
|
+
if (a.length !== b.length) return false;
|
|
567
|
+
let diff = 0;
|
|
568
|
+
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
569
|
+
return diff === 0;
|
|
570
|
+
}
|
|
571
|
+
hexToBytes(hex) {
|
|
572
|
+
return _hexToBytes(hex);
|
|
573
|
+
}
|
|
574
|
+
bytesToHex(bytes) {
|
|
575
|
+
return _hex(bytes.buffer);
|
|
576
|
+
}
|
|
577
|
+
base64ToHex(b64) {
|
|
578
|
+
return this.bytesToHex(new Uint8Array(Array.from(atob(b64), (c) => c.charCodeAt(0))));
|
|
579
|
+
}
|
|
580
|
+
hexToBase64(hex) {
|
|
581
|
+
return btoa(String.fromCharCode(..._hexToBytes(hex)));
|
|
582
|
+
}
|
|
583
|
+
/** Derive an AES-GCM key from a password via PBKDF2 */
|
|
584
|
+
async deriveKey(password, salt, iterations = 1e5) {
|
|
585
|
+
const base = await crypto.subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveKey"]);
|
|
586
|
+
return crypto.subtle.deriveKey(
|
|
587
|
+
{ name: "PBKDF2", salt: new TextEncoder().encode(salt), iterations, hash: "SHA-256" },
|
|
588
|
+
base,
|
|
589
|
+
{ name: "AES-GCM", length: 256 },
|
|
590
|
+
false,
|
|
591
|
+
["encrypt", "decrypt"]
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
export { JoopAesService, JoopCryptoUtils, JoopE2EService, JoopGcmService, JoopRsaService, JoopX25519Service };
|
|
597
|
+
//# sourceMappingURL=index.mjs.map
|
|
598
|
+
//# sourceMappingURL=index.mjs.map
|