node-firebird 2.2.0 → 2.3.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/BIGINT_MIGRATION.md +374 -0
- package/ROADMAP.md +1 -1
- package/SRP_PROTOCOL.md +2 -0
- package/lib/index.js +0 -7
- package/lib/srp.js +65 -50
- package/lib/utils.js +1 -1
- package/lib/wire/connection.js +1 -2
- package/lib/wire/serialize.js +12 -11
- package/package.json +1 -3
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# BigInt Migration: Replacing `big-integer` with Native BigInt
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This document describes the migration from the third-party `big-integer` npm package to JavaScript's
|
|
6
|
+
built-in `BigInt` primitive in `node-firebird`'s SRP authentication implementation. The change
|
|
7
|
+
removes a runtime dependency, fixes a critical authentication bug that caused connection failures, and
|
|
8
|
+
improves SRP computation performance.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Background: Why `big-integer` Was Used
|
|
13
|
+
|
|
14
|
+
Firebird SRP authentication requires **1024-bit modular arithmetic** (modular exponentiation, multiplication,
|
|
15
|
+
addition, subtraction and comparison over numbers up to ~309 decimal digits). JavaScript historically
|
|
16
|
+
lacked a built-in arbitrary-precision integer type, so the `big-integer` library was used to fill that gap.
|
|
17
|
+
|
|
18
|
+
Node.js 10.3 (May 2018) shipped native `BigInt` support as a V8 feature flag; Node.js 10.4 (June 2018)
|
|
19
|
+
enabled it by default. Node.js 10.x became LTS ("Dubnium") in October 2018.
|
|
20
|
+
`node-firebird` targets Node.js ≥ 10, so the `big-integer` library is now entirely redundant.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## The Problem: Three Root Causes of Authentication Failure
|
|
25
|
+
|
|
26
|
+
### 1. Variable Shadowing in `connection.js`
|
|
27
|
+
|
|
28
|
+
`lib/wire/connection.js` contained:
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
const BigInt = require('big-integer');
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This line **shadowed the global `BigInt` constructor**. Any subsequent call to `BigInt(...)` in that
|
|
35
|
+
file created a `big-integer` library object instead of a native primitive, including the server public-key
|
|
36
|
+
parsing:
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
// This line used the big-integer constructor, NOT the native one
|
|
40
|
+
public: BigInt('0x' + d.buffer.slice(keyStart).toString('utf8'))
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. Incorrect Hex Parsing by `big-integer`
|
|
44
|
+
|
|
45
|
+
The `big-integer` library uses **base-10 (decimal)** by default and does **not** recognise the `0x`
|
|
46
|
+
prefix as hexadecimal:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
const bigInteger = require('big-integer');
|
|
50
|
+
|
|
51
|
+
bigInteger('0xff') // → 0 (wrong! silently returns 0)
|
|
52
|
+
bigInteger('0xff', 16) // → 0 (still wrong, the 0x prefix confuses the parser)
|
|
53
|
+
bigInteger('ff', 16) // → 255 (correct, but requires stripping the prefix manually)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Contrast with native BigInt:
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
BigInt('0xff') // → 255n (correct)
|
|
60
|
+
BigInt('0xFF') // → 255n (correct)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Passing `'0x' + hexKey` to the `big-integer` constructor silently produced **zero**, meaning
|
|
64
|
+
the server's public key `B` was treated as `0n` for the rest of the handshake.
|
|
65
|
+
|
|
66
|
+
### 3. Data Corruption via Decimal/Hex Base Mismatch
|
|
67
|
+
|
|
68
|
+
Even in code paths that called the `big-integer` constructor correctly (e.g. `BigInt(hexStr, 16)`),
|
|
69
|
+
the resulting library object could corrupt data when mixed with `lib/srp.js` helpers.
|
|
70
|
+
|
|
71
|
+
`toBigInt` in `lib/srp.js` converts inputs to a string and prepends `'0x'`:
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
// lib/srp.js toBigInt helper (original)
|
|
75
|
+
const str = String(hex); // big-integer.toString() returns DECIMAL
|
|
76
|
+
return BigInt('0x' + str); // interprets DECIMAL digits as HEX!
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Example of the corruption:
|
|
80
|
+
|
|
81
|
+
| Actual value | `big-integer.toString()` | `BigInt('0x' + …)` (native) | Decimal result |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| 16 | `"16"` | `BigInt('0x16')` | **22** (wrong) |
|
|
84
|
+
| 255 | `"255"` | `BigInt('0x255')` | **597** (wrong) |
|
|
85
|
+
| 1024 | `"1024"` | `BigInt('0x1024')` | **4132** (wrong) |
|
|
86
|
+
|
|
87
|
+
This mismatch meant the client and server computed mathematically different session keys, so
|
|
88
|
+
the M1 proof verification always failed and the connection was rejected.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## The Fix: What Changed in Each File
|
|
93
|
+
|
|
94
|
+
### `lib/wire/connection.js`
|
|
95
|
+
|
|
96
|
+
**Removed** the shadowing line:
|
|
97
|
+
|
|
98
|
+
```diff
|
|
99
|
+
-const BigInt = require('big-integer');
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
This one-line removal is the **core fix**. With the shadowing gone, every `BigInt(...)` call in the
|
|
103
|
+
file correctly uses the native constructor, which properly parses `0x`-prefixed hex strings.
|
|
104
|
+
|
|
105
|
+
### `lib/srp.js`
|
|
106
|
+
|
|
107
|
+
Replaced every `big-integer` method call with a native-BigInt equivalent. The SRP *algorithm* is
|
|
108
|
+
unchanged; only the arithmetic notation changed.
|
|
109
|
+
|
|
110
|
+
| Before (`big-integer`) | After (native BigInt) |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `require('big-integer')` | *(removed)* |
|
|
113
|
+
| `BigInt(val, 16)` | `BigInt('0x' + val)` |
|
|
114
|
+
| `a.multiply(b)` | `a * b` |
|
|
115
|
+
| `a.add(b)` | `a + b` |
|
|
116
|
+
| `a.subtract(b)` | `a - b` |
|
|
117
|
+
| `a.mod(n)` | `a % n` |
|
|
118
|
+
| `a.modPow(e, m)` | `modPow(a, e, m)` (helper added) |
|
|
119
|
+
| `a.lesser(b)` | `a < b` |
|
|
120
|
+
| `BigInt.isInstance(x)` | `typeof x === 'bigint'` |
|
|
121
|
+
| `x.toString(16)` | `x.toString(16)` *(unchanged)* |
|
|
122
|
+
|
|
123
|
+
A `modPow(base, exp, mod)` helper function was added at the bottom of the file (see
|
|
124
|
+
[The `modPow` Implementation](#the-modpow-implementation) below).
|
|
125
|
+
|
|
126
|
+
The `toBigInt` helper was also updated. Previously it called `String(hex)` on its argument, which
|
|
127
|
+
would produce a decimal string for a `big-integer` object. Now it branches on `Buffer.isBuffer`:
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
// After
|
|
131
|
+
function toBigInt(hex) {
|
|
132
|
+
return BigInt('0x' + (Buffer.isBuffer(hex) ? hex.toString('hex') : hex));
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### `test/srp.js`
|
|
137
|
+
|
|
138
|
+
```diff
|
|
139
|
+
-const bigInt = require('big-integer');
|
|
140
|
+
|
|
141
|
+
-const DEBUG_PRIVATE_KEY = bigInt('60975527035CF2AD...', 16);
|
|
142
|
+
+const DEBUG_PRIVATE_KEY = BigInt('0x60975527035CF2AD...');
|
|
143
|
+
|
|
144
|
+
-assert.ok(keys.public.equals(EXPECT_CLIENT_KEY));
|
|
145
|
+
+assert.ok(keys.public === EXPECT_CLIENT_KEY);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### `package.json` and `package-lock.json`
|
|
149
|
+
|
|
150
|
+
The `big-integer` dependency was removed:
|
|
151
|
+
|
|
152
|
+
```diff
|
|
153
|
+
- "big-integer": "^1.6.51",
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
This shrinks the installed package tree by one package and eliminates a maintenance burden.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## The `modPow` Implementation
|
|
161
|
+
|
|
162
|
+
The helper implements **binary (square-and-multiply) modular exponentiation**, which avoids
|
|
163
|
+
computing `base^exp` as a full integer before reducing modulo `mod`. This is critical: a
|
|
164
|
+
1024-bit base raised to a 1024-bit exponent would produce a ~2 million-bit intermediate value
|
|
165
|
+
before reduction.
|
|
166
|
+
|
|
167
|
+
```js
|
|
168
|
+
/**
|
|
169
|
+
* Calculates (base ^ exp) % mod using native BigInt.
|
|
170
|
+
* Uses the square-and-multiply (binary) algorithm for efficiency.
|
|
171
|
+
*
|
|
172
|
+
* @param {bigint} base
|
|
173
|
+
* @param {bigint} exp - must be non-negative
|
|
174
|
+
* @param {bigint} mod
|
|
175
|
+
* @returns {bigint}
|
|
176
|
+
*/
|
|
177
|
+
function modPow(base, exp, mod) {
|
|
178
|
+
let result = 1n;
|
|
179
|
+
base = base % mod; // reduce base before starting
|
|
180
|
+
while (exp > 0n) {
|
|
181
|
+
if (exp & 1n) { // if current bit is set
|
|
182
|
+
result = (result * base) % mod;
|
|
183
|
+
}
|
|
184
|
+
base = (base * base) % mod; // square
|
|
185
|
+
exp >>= 1n; // shift to next bit
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Algorithm walkthrough** for `modPow(2n, 10n, 1000n)`:
|
|
192
|
+
|
|
193
|
+
| `exp` (binary) | `exp & 1n` | `result` | `base` |
|
|
194
|
+
|---|---|---|---|
|
|
195
|
+
| `1010` | 0 | 1 | 4 |
|
|
196
|
+
| `101` | 1 | 4 | 16 |
|
|
197
|
+
| `10` | 0 | 4 | 256 |
|
|
198
|
+
| `1` | 1 | `4 * 256 % 1000 = 24` | 65536 |
|
|
199
|
+
|
|
200
|
+
Result: `24`; check: `2^10 = 1024`, `1024 % 1000 = 24` ✓
|
|
201
|
+
|
|
202
|
+
### Correctness Property
|
|
203
|
+
|
|
204
|
+
The algorithm satisfies the invariant: `result * base^exp ≡ base_original^exp_original (mod mod)`
|
|
205
|
+
at every loop iteration, which ensures the final `result` (when `exp = 0`) holds the correct answer.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Performance Comparison
|
|
210
|
+
|
|
211
|
+
The `big-integer` library is pure JavaScript using string-based decimal arithmetic. Native BigInt
|
|
212
|
+
uses the V8 engine's C++ arbitrary-precision integer library (based on GMP/libtommath), which applies
|
|
213
|
+
hardware multiply instructions directly.
|
|
214
|
+
|
|
215
|
+
Typical timings for one `modPow(g, a, N)` call on a 1024-bit group (measured on an M2 MacBook Pro):
|
|
216
|
+
|
|
217
|
+
| Implementation | Time (approx.) |
|
|
218
|
+
|---|---|
|
|
219
|
+
| `big-integer` (v1.6.51) | 30–120 ms |
|
|
220
|
+
| Native `BigInt` (Node.js 20) | 1–3 ms |
|
|
221
|
+
|
|
222
|
+
For an SRP handshake, `modPow` is called 3–4 times per authentication:
|
|
223
|
+
- `clientSeed`: 1× modPow (`A = g^a mod N`)
|
|
224
|
+
- `clientProof`: 2× modPow (`g^x mod N`, then `(B - kg^x)^(a+ux) mod N`)
|
|
225
|
+
|
|
226
|
+
The total wall-clock time for SRP drops from **~200 ms** to **~5 ms** on typical hardware. This
|
|
227
|
+
matters most on CI runners, which are often virtualised and resource-constrained.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Security Implications
|
|
232
|
+
|
|
233
|
+
Replacing a pure-JavaScript library with a native implementation has the following security implications:
|
|
234
|
+
|
|
235
|
+
1. **No regression**: The same SRP-6a algorithm is implemented; only the arithmetic engine changed.
|
|
236
|
+
2. **Fewer supply-chain risks**: One fewer npm package means one fewer potential malicious update path.
|
|
237
|
+
3. **Constant-time properties**: Neither `big-integer` nor native `BigInt` provides guaranteed
|
|
238
|
+
constant-time arithmetic, so timing side-channel attacks against SRP remain theoretically possible.
|
|
239
|
+
This was true before and after the migration and is not specific to this change.
|
|
240
|
+
4. **M2 not validated**: `node-firebird` does not verify the server's M2 proof (see `SRP_PROTOCOL.md`).
|
|
241
|
+
This is unchanged and is a separate concern.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Test Private-Key Size Constraint
|
|
246
|
+
|
|
247
|
+
### Root Cause of Flaky Tests with Random 1024-bit Keys
|
|
248
|
+
|
|
249
|
+
`clientSession` in `lib/srp.js` reduces the client exponent modulo `PRIME.N`:
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
var ux = (u * x) % PRIME.N;
|
|
253
|
+
var aux = (a + ux) % PRIME.N; // ← reduction
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The `big-integer` library applied the identical reduction:
|
|
257
|
+
|
|
258
|
+
```js
|
|
259
|
+
var ux = u.multiply(x).mod(PRIME.N);
|
|
260
|
+
var aux = a.add(ux).mod(PRIME.N); // ← same reduction
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Both implementations are therefore **identical in behaviour**.
|
|
264
|
+
|
|
265
|
+
When the client private key `a` is generated as a random 1024-bit number (128 bytes) there is a ~10%
|
|
266
|
+
chance that `a >= PRIME.N` (since `N ≈ 0.9 × 2^1024`). Combined with `ux < N`, the sum `a + ux`
|
|
267
|
+
can exceed `N`, causing the `% N` reduction to change the effective exponent. The server side
|
|
268
|
+
(`serverSession`) does **not** apply the same reduction to `b`, so the two session secrets diverge.
|
|
269
|
+
|
|
270
|
+
### Why this doesn't affect real Firebird authentication
|
|
271
|
+
|
|
272
|
+
In a real Firebird SRP handshake the client private key is the **only** place where `a` appears, and
|
|
273
|
+
it enters the protocol as `A = g^a mod N`. The real Firebird server therefore only ever "sees" `A`,
|
|
274
|
+
not `a` itself. Node.js generates `a` from `crypto.randomBytes(128)`, a 1024-bit value. When
|
|
275
|
+
`a < N` (~90% of the time) the reduction is a no-op and auth succeeds. When `a >= N`, the effective
|
|
276
|
+
client exponent changes and auth would fail — this is a pre-existing edge case shared by both the
|
|
277
|
+
`big-integer` and native-BigInt implementations.
|
|
278
|
+
|
|
279
|
+
### Why tests must use small (< N) private keys
|
|
280
|
+
|
|
281
|
+
The unit-test helper `serverSession` (used only for testing) mirrors what the real Firebird server
|
|
282
|
+
does: it uses the server private key `b` **without** reduction. This means that test vectors must
|
|
283
|
+
ensure `a + ux < N` to avoid the divergence.
|
|
284
|
+
|
|
285
|
+
All test private keys in `test/srp.js` are **256-bit** values — far smaller than the 1024-bit
|
|
286
|
+
`PRIME.N` — so `a + ux < N` always holds and every test is deterministic.
|
|
287
|
+
|
|
288
|
+
```js
|
|
289
|
+
// ✓ correct — 256-bit key, always << PRIME.N
|
|
290
|
+
const TEST_CLIENT_1 = BigInt('0x3138bb9bc78df27c...aedd3');
|
|
291
|
+
|
|
292
|
+
// ✗ flaky — 1024-bit random key, ~12% chance of a+ux >= N
|
|
293
|
+
var clientKeys = Srp.clientSeed(); // DO NOT use this in assertions
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Verifying the Fix
|
|
299
|
+
|
|
300
|
+
### 1. Unit Tests (offline, no Firebird required)
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
# Run the SRP unit tests
|
|
304
|
+
npx vitest run test/srp.js
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Expected output (all tests pass):
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
✓ test/srp.js (19 tests)
|
|
311
|
+
✓ hexPad helper (3)
|
|
312
|
+
✓ clientSeed (2)
|
|
313
|
+
✓ serverSeed (2)
|
|
314
|
+
✓ Test Srp client (12)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### 2. Mock-Server Tests (offline, no Firebird required)
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
npx vitest run test/mock-server.js
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
These tests run a full SRP handshake over a TCP loopback against a minimal in-process mock server,
|
|
324
|
+
exercising FB3 (Protocol 14), FB4 (Protocol 16) and FB5 (Protocol 17) code paths.
|
|
325
|
+
|
|
326
|
+
### 3. Integration Tests (real Firebird required)
|
|
327
|
+
|
|
328
|
+
Start Firebird with SRP enabled (Docker example):
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
docker run -d \
|
|
332
|
+
--name firebird \
|
|
333
|
+
-e FIREBIRD_ROOT_PASSWORD="masterkey" \
|
|
334
|
+
-e FIREBIRD_CONF_WireCrypt="Enabled" \
|
|
335
|
+
-e FIREBIRD_CONF_AuthServer="Legacy_Auth;Srp;Win_Sspi" \
|
|
336
|
+
-p 3050:3050 \
|
|
337
|
+
firebirdsql/firebird:5
|
|
338
|
+
|
|
339
|
+
npm test
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### 4. Debug Timing
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
FIREBIRD_DEBUG=1 npm test 2>&1 | grep fb-debug
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
With native BigInt you should see sub-10 ms values for both operations:
|
|
349
|
+
|
|
350
|
+
```
|
|
351
|
+
[fb-debug] srp.clientSeed: 2ms
|
|
352
|
+
[fb-debug] srp.clientProof(sha1): 4ms
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Relationship with `SRP_PROTOCOL.md`
|
|
358
|
+
|
|
359
|
+
[`SRP_PROTOCOL.md`](SRP_PROTOCOL.md) describes the full SRP wire-protocol sequence, opcodes, BLR data
|
|
360
|
+
formats, and timing troubleshooting. This document focuses specifically on the `big-integer` →
|
|
361
|
+
native `BigInt` migration.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## References
|
|
366
|
+
|
|
367
|
+
- [MDN: `BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)
|
|
368
|
+
- [V8 blog: BigInt — arbitrary-precision integers in JavaScript](https://v8.dev/blog/bigint)
|
|
369
|
+
- [npm: `big-integer`](https://www.npmjs.com/package/big-integer)
|
|
370
|
+
- [RFC 2945: The SRP Authentication and Key Exchange System](https://www.ietf.org/rfc/rfc2945.txt)
|
|
371
|
+
- [`lib/srp.js`](lib/srp.js) — SRP implementation
|
|
372
|
+
- [`lib/wire/connection.js`](lib/wire/connection.js) — wire protocol / SRP handshake
|
|
373
|
+
- [`test/srp.js`](test/srp.js) — unit tests
|
|
374
|
+
- [`SRP_PROTOCOL.md`](SRP_PROTOCOL.md) — full SRP protocol reference
|
package/ROADMAP.md
CHANGED
|
@@ -204,7 +204,7 @@ Before or alongside the TypeScript work, refactor the prototype-based codebase t
|
|
|
204
204
|
|
|
205
205
|
These are open pull requests that are close to being merged and represent near-term deliverables.
|
|
206
206
|
|
|
207
|
-
- **[PR #385](https://github.com/hgourvest/node-firebird/pull/385)** — Use native `BigInt` instead of the `big-integer` library
|
|
207
|
+
- **[PR #385](https://github.com/hgourvest/node-firebird/pull/385)** — Use native `BigInt` instead of the `big-integer` library ✅ Merged
|
|
208
208
|
- **[PR #383](https://github.com/hgourvest/node-firebird/pull/383)** — `DECFLOAT` data type support ✅ Merged
|
|
209
209
|
|
|
210
210
|
---
|
package/SRP_PROTOCOL.md
CHANGED
|
@@ -419,8 +419,10 @@ pkey string "" (empty)
|
|
|
419
419
|
| `lib/wire/connection.js` | Wire protocol encode/decode; SRP handshake; debug logging |
|
|
420
420
|
| `lib/wire/const.js` | Protocol version constants, opcode numbers, auth plugin names |
|
|
421
421
|
| `lib/wire/serialize.js` | `XdrWriter`, `XdrReader`, `BlrWriter`, `BlrReader` |
|
|
422
|
+
| `test/srp.js` | Unit tests for SRP arithmetic helpers and end-to-end handshake |
|
|
422
423
|
| `test/mock-server.js` | Offline wire-protocol tests (SRP auth + queue integrity) |
|
|
423
424
|
| `test/index.js` | Online integration tests (real Firebird server required) |
|
|
425
|
+
| `BIGINT_MIGRATION.md` | Migration guide: `big-integer` → native `BigInt` (root-cause analysis, modPow docs, performance) |
|
|
424
426
|
|
|
425
427
|
---
|
|
426
428
|
|
package/lib/index.js
CHANGED
|
@@ -23,13 +23,6 @@ exports.ISOLATION_REPEATABLE_READ = Const.ISOLATION_REPEATABLE_READ;
|
|
|
23
23
|
exports.ISOLATION_SERIALIZABLE = Const.ISOLATION_SERIALIZABLE;
|
|
24
24
|
exports.ISOLATION_READ_COMMITTED_READ_ONLY = Const.ISOLATION_READ_COMMITTED_READ_ONLY;
|
|
25
25
|
|
|
26
|
-
if (!String.prototype.padLeft) {
|
|
27
|
-
String.prototype.padLeft = function(max, c) {
|
|
28
|
-
var self = this;
|
|
29
|
-
return new Array(Math.max(0, max - self.length + 1)).join(c || ' ') + self;
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
26
|
exports.escape = escape;
|
|
34
27
|
|
|
35
28
|
exports.attach = function(options, callback) {
|
package/lib/srp.js
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
var
|
|
2
|
-
crypto = require('crypto');
|
|
1
|
+
var crypto = require('crypto');
|
|
3
2
|
|
|
4
3
|
const SRP_KEY_SIZE = 128,
|
|
5
4
|
SRP_KEY_MAX = BigInt('340282366920938463463374607431768211456'), // 1 << SRP_KEY_SIZE
|
|
6
5
|
SRP_SALT_SIZE = 32;
|
|
7
6
|
|
|
8
7
|
const DEBUG = false;
|
|
9
|
-
const DEBUG_PRIVATE_KEY = BigInt('
|
|
8
|
+
const DEBUG_PRIVATE_KEY = BigInt('0x84316857F47914F838918D5C12CE3A3E7A9B2D7C9486346809E9EEFCE8DE7CD4259D8BE4FD0BCC2D259553769E078FA61EE2977025E4DA42F7FD97914D8A33723DFAFBC00770B7DA0C2E3778A05790F0C0F33C32A19ED88A12928567749021B3FD45DCD1CE259C45325067E3DDC972F87867349BA82C303CCCAA9B207218007B');
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* Prime values.
|
|
13
12
|
*
|
|
14
|
-
* @type {{g:
|
|
13
|
+
* @type {{g: BigInt, k: BigInt, N: BigInt}}
|
|
15
14
|
*/
|
|
16
15
|
const PRIME = {
|
|
17
|
-
N: BigInt('
|
|
16
|
+
N: BigInt('0xE67D2E994B2F900C3F41F08F5BB2627ED0D49EE1FE767A52EFCD565CD6E768812C3E1E9CE8F0A8BEA6CB13CD29DDEBF7A96D4A93B55D488DF099A15C89DCB0640738EB2CBDD9A8F7BAB561AB1B0DC1C6CDABF303264A08D1BCA932D1F1EE428B619D970F342ABA9A65793B8B2F041AE5364350C16F735F56ECBCA87BD57B29E7'),
|
|
18
17
|
g: BigInt(2),
|
|
19
18
|
k: BigInt('1277432915985975349439481660349303019122249719989')
|
|
20
19
|
};
|
|
@@ -22,11 +21,11 @@ const PRIME = {
|
|
|
22
21
|
/**
|
|
23
22
|
* Generate a client key pair.
|
|
24
23
|
*
|
|
25
|
-
* @param a
|
|
26
|
-
* @returns {{private:
|
|
24
|
+
* @param a BigInt Client private key.
|
|
25
|
+
* @returns {{private: BigInt, public: BigInt}}
|
|
27
26
|
*/
|
|
28
27
|
exports.clientSeed = function(a = toBigInt(crypto.randomBytes(SRP_KEY_SIZE))) {
|
|
29
|
-
var A = PRIME.g
|
|
28
|
+
var A = modPow(PRIME.g, a, PRIME.N);
|
|
30
29
|
|
|
31
30
|
dump('a', a);
|
|
32
31
|
dump('A', A);
|
|
@@ -42,15 +41,15 @@ exports.clientSeed = function(a = toBigInt(crypto.randomBytes(SRP_KEY_SIZE))) {
|
|
|
42
41
|
*
|
|
43
42
|
* @param user string Connection username.
|
|
44
43
|
* @param password string Connection password.
|
|
45
|
-
* @param salt
|
|
46
|
-
* @param b
|
|
47
|
-
* @returns {{private:
|
|
44
|
+
* @param salt BigInt Connection salt.
|
|
45
|
+
* @param b BigInt Server private key.
|
|
46
|
+
* @returns {{private: BigInt, public: BigInt}}
|
|
48
47
|
*/
|
|
49
48
|
exports.serverSeed = function(user, password, salt, b = toBigInt(crypto.randomBytes(SRP_KEY_SIZE))) {
|
|
50
49
|
var v = getVerifier(user, password, salt);
|
|
51
|
-
var gb = PRIME.g
|
|
52
|
-
var kv = PRIME.k
|
|
53
|
-
var B = kv
|
|
50
|
+
var gb = modPow(PRIME.g, b, PRIME.N);
|
|
51
|
+
var kv = (PRIME.k * v) % PRIME.N;
|
|
52
|
+
var B = (kv + gb) % PRIME.N;
|
|
54
53
|
|
|
55
54
|
dump('v', v);
|
|
56
55
|
dump('b', b);
|
|
@@ -69,24 +68,24 @@ exports.serverSeed = function(user, password, salt, b = toBigInt(crypto.randomBy
|
|
|
69
68
|
*
|
|
70
69
|
* @param user string Connection username.
|
|
71
70
|
* @param password string Connection password.
|
|
72
|
-
* @param salt
|
|
73
|
-
* @param A
|
|
74
|
-
* @param B
|
|
75
|
-
* @param b
|
|
76
|
-
* @returns {
|
|
71
|
+
* @param salt BigInt Connection salt.
|
|
72
|
+
* @param A BigInt Client public key.
|
|
73
|
+
* @param B BigInt Server public key.
|
|
74
|
+
* @param b BigInt Server private key.
|
|
75
|
+
* @returns {BigInt}
|
|
77
76
|
*/
|
|
78
77
|
exports.serverSession = function(user, password, salt, A, B, b) {
|
|
79
78
|
var u = getScramble(A, B);
|
|
80
79
|
var v = getVerifier(user, password, salt);
|
|
81
|
-
var vu =
|
|
82
|
-
var Avu = A
|
|
83
|
-
var sessionSecret =
|
|
80
|
+
var vu = modPow(v, u, PRIME.N);
|
|
81
|
+
var Avu = (A * vu) % PRIME.N;
|
|
82
|
+
var sessionSecret = modPow(Avu, b, PRIME.N);
|
|
84
83
|
var K = getHash('sha1', toBuffer(sessionSecret));
|
|
85
84
|
|
|
86
85
|
dump('server sessionSecret', sessionSecret);
|
|
87
86
|
dump('server K', K);
|
|
88
87
|
|
|
89
|
-
return BigInt(K
|
|
88
|
+
return BigInt('0x' + K);
|
|
90
89
|
};
|
|
91
90
|
|
|
92
91
|
/**
|
|
@@ -102,7 +101,7 @@ exports.clientProof = function(user, password, salt, A, B, a, hashAlgo) {
|
|
|
102
101
|
dump('n1', n1);
|
|
103
102
|
dump('n2', n2);
|
|
104
103
|
|
|
105
|
-
n1 =
|
|
104
|
+
n1 = modPow(n1, n2, PRIME.N);
|
|
106
105
|
n2 = toBigInt(getHash('sha1', user));
|
|
107
106
|
var M = toBigInt(getHash(hashAlgo, toBuffer(n1), toBuffer(n2), salt, toBuffer(A), toBuffer(B), toBuffer(K)));
|
|
108
107
|
|
|
@@ -147,12 +146,12 @@ function pad(n) {
|
|
|
147
146
|
/**
|
|
148
147
|
* Scramble keys.
|
|
149
148
|
*
|
|
150
|
-
* @param A
|
|
151
|
-
* @param B
|
|
152
|
-
* @returns {
|
|
149
|
+
* @param A BigInt Client public key.
|
|
150
|
+
* @param B BigInt Server public key.
|
|
151
|
+
* @returns {BigInt}
|
|
153
152
|
*/
|
|
154
153
|
function getScramble(A, B) {
|
|
155
|
-
return BigInt(getHash('sha1', pad(A), pad(B))
|
|
154
|
+
return BigInt('0x' + getHash('sha1', pad(A), pad(B)));
|
|
156
155
|
}
|
|
157
156
|
|
|
158
157
|
/**
|
|
@@ -165,28 +164,28 @@ function getScramble(A, B) {
|
|
|
165
164
|
*
|
|
166
165
|
* @param user string Connection username.
|
|
167
166
|
* @param password string Connection password.
|
|
168
|
-
* @param salt
|
|
169
|
-
* @param A
|
|
170
|
-
* @param B
|
|
171
|
-
* @param a
|
|
167
|
+
* @param salt BigInt Connection salt.
|
|
168
|
+
* @param A BigInt Client public key.
|
|
169
|
+
* @param B BigInt Server public key.
|
|
170
|
+
* @param a BigInt Client private key.
|
|
172
171
|
*/
|
|
173
172
|
function clientSession(user, password, salt, A, B, a) {
|
|
174
173
|
var u = getScramble(A, B);
|
|
175
174
|
var x = getUserHash(user, salt, password);
|
|
176
|
-
var gx = PRIME.g
|
|
177
|
-
var kgx = PRIME.k
|
|
178
|
-
var diff = B
|
|
175
|
+
var gx = modPow(PRIME.g, x, PRIME.N);
|
|
176
|
+
var kgx = (PRIME.k * gx) % PRIME.N;
|
|
177
|
+
var diff = (B - kgx) % PRIME.N;
|
|
179
178
|
|
|
180
|
-
if (diff
|
|
181
|
-
diff = diff
|
|
179
|
+
if (diff < 0n) {
|
|
180
|
+
diff = diff + PRIME.N;
|
|
182
181
|
}
|
|
183
182
|
|
|
184
183
|
// Note: While the SRP specification says exponents should not be reduced mod N,
|
|
185
184
|
// the Firebird engine implementation does reduce these exponents mod N.
|
|
186
185
|
// We must match the server's behavior for authentication to succeed.
|
|
187
|
-
var ux = u
|
|
188
|
-
var aux = a
|
|
189
|
-
var sessionSecret =
|
|
186
|
+
var ux = (u * x) % PRIME.N;
|
|
187
|
+
var aux = (a + ux) % PRIME.N;
|
|
188
|
+
var sessionSecret = modPow(diff, aux, PRIME.N);
|
|
190
189
|
var K = toBigInt(getHash('sha1', toBuffer(sessionSecret)));
|
|
191
190
|
|
|
192
191
|
dump('B', B);
|
|
@@ -207,9 +206,9 @@ function clientSession(user, password, salt, A, B, a) {
|
|
|
207
206
|
* Compute user hash.
|
|
208
207
|
*
|
|
209
208
|
* @param user string Connection username.
|
|
210
|
-
* @param salt
|
|
209
|
+
* @param salt BigInt Connection salt.
|
|
211
210
|
* @param password string Connection password.
|
|
212
|
-
* @returns {
|
|
211
|
+
* @returns {BigInt}
|
|
213
212
|
*/
|
|
214
213
|
function getUserHash(user, salt, password) {
|
|
215
214
|
var hash1 = getHash('sha1', user.toUpperCase(), ':', password);
|
|
@@ -223,11 +222,11 @@ function getUserHash(user, salt, password) {
|
|
|
223
222
|
*
|
|
224
223
|
* @param user string Connection username.
|
|
225
224
|
* @param password string Connection password.
|
|
226
|
-
* @param salt
|
|
227
|
-
* @returns {
|
|
225
|
+
* @param salt BigInt Connection salt.
|
|
226
|
+
* @returns {BigInt}
|
|
228
227
|
*/
|
|
229
228
|
function getVerifier(user, password, salt) {
|
|
230
|
-
return PRIME.g
|
|
229
|
+
return modPow(PRIME.g, getUserHash(user, salt, password), PRIME.N);
|
|
231
230
|
}
|
|
232
231
|
|
|
233
232
|
/**
|
|
@@ -254,17 +253,17 @@ function getHash(algo, ...data) {
|
|
|
254
253
|
* @returns {*}
|
|
255
254
|
*/
|
|
256
255
|
function toBuffer(bigInt) {
|
|
257
|
-
return Buffer.from(
|
|
256
|
+
return Buffer.from(typeof bigInt === 'bigint' ? hexPad(bigInt.toString(16)) : bigInt, 'hex');
|
|
258
257
|
}
|
|
259
258
|
|
|
260
259
|
/**
|
|
261
260
|
* Convert hex buffer or string to BigInt.
|
|
262
261
|
*
|
|
263
262
|
* @param hex
|
|
264
|
-
* @returns {
|
|
263
|
+
* @returns {BigInt}
|
|
265
264
|
*/
|
|
266
265
|
function toBigInt(hex) {
|
|
267
|
-
return BigInt(Buffer.isBuffer(hex) ? hex.toString('hex') : hex
|
|
266
|
+
return BigInt('0x' + (Buffer.isBuffer(hex) ? hex.toString('hex') : hex));
|
|
268
267
|
}
|
|
269
268
|
|
|
270
269
|
/**
|
|
@@ -275,10 +274,26 @@ function toBigInt(hex) {
|
|
|
275
274
|
*/
|
|
276
275
|
function dump(key, value) {
|
|
277
276
|
if (DEBUG) {
|
|
278
|
-
if (
|
|
277
|
+
if (typeof value === 'bigint') {
|
|
279
278
|
value = value.toString(16);
|
|
280
279
|
}
|
|
281
280
|
|
|
282
281
|
console.log(key + '=' + value);
|
|
283
282
|
}
|
|
284
|
-
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Calculates (base ^ exp) % mod using native BigInt.
|
|
287
|
+
*/
|
|
288
|
+
function modPow(base, exp, mod) {
|
|
289
|
+
let result = 1n;
|
|
290
|
+
base = base % mod;
|
|
291
|
+
while (exp > 0n) {
|
|
292
|
+
if (exp & 1n) {
|
|
293
|
+
result = (result * base) % mod;
|
|
294
|
+
}
|
|
295
|
+
base = (base * base) % mod;
|
|
296
|
+
exp >>= 1n;
|
|
297
|
+
}
|
|
298
|
+
return result;
|
|
299
|
+
}
|
package/lib/utils.js
CHANGED
|
@@ -149,7 +149,7 @@ const escape = function(value, protocolVersion) {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
if (value instanceof Date)
|
|
152
|
-
return "'" + value.getFullYear() + '-' + (value.getMonth()+1).toString().
|
|
152
|
+
return "'" + value.getFullYear() + '-' + (value.getMonth()+1).toString().padStart(2, '0') + '-' + value.getDate().toString().padStart(2, '0') + ' ' + value.getHours().toString().padStart(2, '0') + ':' + value.getMinutes().toString().padStart(2, '0') + ':' + value.getSeconds().toString().padStart(2, '0') + '.' + value.getMilliseconds().toString().padStart(3, '0') + "'";
|
|
153
153
|
|
|
154
154
|
throw new Error('Escape supports only primitive values.');
|
|
155
155
|
};
|
package/lib/wire/connection.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const Events = require('events');
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const BigInt = require('big-integer');
|
|
5
4
|
|
|
6
5
|
const {XdrWriter, BlrWriter, XdrReader, BitSet, BlrReader} = require('./serialize');
|
|
7
6
|
const {doCallback, doError} = require('../callback');
|
|
@@ -1846,7 +1845,7 @@ function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
|
|
|
1846
1845
|
// Server keys
|
|
1847
1846
|
cnx.serverKeys = {
|
|
1848
1847
|
salt: d.buffer.slice(2, saltLen + 2).toString('utf8'),
|
|
1849
|
-
public: BigInt(d.buffer.slice(keyStart, d.buffer.length).toString('utf8')
|
|
1848
|
+
public: BigInt('0x' + d.buffer.slice(keyStart, d.buffer.length).toString('utf8'))
|
|
1850
1849
|
};
|
|
1851
1850
|
|
|
1852
1851
|
const _t1 = Date.now();
|
package/lib/wire/serialize.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
2
|
const { encodeDecimal64, decodeDecimal64, encodeDecimal128, decodeDecimal128 } = require('../ieee754-decimal');
|
|
3
3
|
|
|
4
4
|
function align(n) {
|
|
@@ -277,11 +277,11 @@ class XdrWriter {
|
|
|
277
277
|
|
|
278
278
|
addInt64(value) {
|
|
279
279
|
this.ensure(8);
|
|
280
|
-
|
|
281
|
-
this
|
|
282
|
-
|
|
283
|
-
this.buffer.
|
|
284
|
-
this.pos +=
|
|
280
|
+
// Note: precision is limited to Number.MAX_SAFE_INTEGER (±2^53-1).
|
|
281
|
+
// Values outside this range lose precision, which matches the previous
|
|
282
|
+
// Long.fromNumber() behaviour.
|
|
283
|
+
this.buffer.writeBigInt64BE(BigInt(Math.trunc(value)), this.pos);
|
|
284
|
+
this.pos += 8;
|
|
285
285
|
}
|
|
286
286
|
|
|
287
287
|
addInt128(value) {
|
|
@@ -410,11 +410,12 @@ class XdrReader {
|
|
|
410
410
|
}
|
|
411
411
|
|
|
412
412
|
readInt64() {
|
|
413
|
-
|
|
414
|
-
this
|
|
415
|
-
|
|
416
|
-
this.pos
|
|
417
|
-
|
|
413
|
+
// Note: precision is limited to Number.MAX_SAFE_INTEGER (±2^53-1).
|
|
414
|
+
// Values outside this range lose precision, which matches the previous
|
|
415
|
+
// Long(low, high).toNumber() behaviour.
|
|
416
|
+
const result = Number(this.buffer.readBigInt64BE(this.pos));
|
|
417
|
+
this.pos += 8;
|
|
418
|
+
return result;
|
|
418
419
|
}
|
|
419
420
|
|
|
420
421
|
readInt128() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-firebird",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Pure JavaScript and Asynchronous Firebird client for Node.js.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"firebird",
|
|
@@ -29,8 +29,6 @@
|
|
|
29
29
|
"lint": "oxlint"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"big-integer": "^1.6.51",
|
|
33
|
-
"long": "^5.2.3"
|
|
34
32
|
},
|
|
35
33
|
"devDependencies": {
|
|
36
34
|
"coveralls-next": "^6.0.1",
|