node-firebird 2.3.3 → 2.4.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/README.md +544 -6
- package/lib/callback.d.ts +20 -0
- package/lib/callback.js +6 -16
- package/lib/firebird.msg.json +1370 -1370
- package/lib/gdscodes.d.ts +1519 -1520
- package/lib/gdscodes.js +5 -5
- package/lib/ieee754-decimal.d.ts +38 -0
- package/lib/ieee754-decimal.js +71 -118
- package/lib/index.d.ts +37 -338
- package/lib/index.js +97 -86
- package/lib/messages.d.ts +6 -0
- package/lib/messages.js +135 -162
- package/lib/pool.d.ts +23 -0
- package/lib/pool.js +34 -47
- package/lib/srp.d.ts +51 -0
- package/lib/srp.js +62 -72
- package/lib/types.d.ts +375 -0
- package/lib/types.js +22 -0
- package/lib/unix-crypt.d.ts +1 -0
- package/lib/unix-crypt.js +126 -152
- package/lib/utils.d.ts +16 -0
- package/lib/utils.js +24 -48
- package/lib/wire/connection.d.ts +100 -0
- package/lib/wire/connection.js +1029 -1009
- package/lib/wire/const.d.ts +567 -0
- package/lib/wire/const.js +620 -599
- package/lib/wire/database.d.ts +57 -0
- package/lib/wire/database.js +111 -108
- package/lib/wire/eventConnection.d.ts +16 -0
- package/lib/wire/eventConnection.js +29 -31
- package/lib/wire/fbEventManager.d.ts +45 -0
- package/lib/wire/fbEventManager.js +38 -57
- package/lib/wire/serialize.d.ts +97 -0
- package/lib/wire/serialize.js +42 -141
- package/lib/wire/service.d.ts +66 -0
- package/lib/wire/service.js +374 -401
- package/lib/wire/socket.d.ts +59 -0
- package/lib/wire/socket.js +79 -40
- package/lib/wire/statement.d.ts +25 -0
- package/lib/wire/statement.js +13 -9
- package/lib/wire/transaction.d.ts +21 -0
- package/lib/wire/transaction.js +33 -61
- package/lib/wire/xsqlvar.d.ts +175 -0
- package/lib/wire/xsqlvar.js +169 -269
- package/package.json +12 -4
- package/src/callback.ts +55 -0
- package/{poc/node_modules/node-firebird/lib/gdscodes.js → src/gdscodes.ts} +3 -3
- package/{poc/node_modules/node-firebird/lib/ieee754-decimal.js → src/ieee754-decimal.ts} +7 -14
- package/src/index.ts +157 -0
- package/{poc/node_modules/node-firebird/lib/messages.js → src/messages.ts} +163 -162
- package/src/pool.ts +209 -0
- package/{poc/node_modules/node-firebird/lib/srp.js → src/srp.ts} +74 -33
- package/src/types.ts +419 -0
- package/{poc/node_modules/node-firebird/lib/unix-crypt.js → src/unix-crypt.ts} +6 -12
- package/{poc/node_modules/node-firebird/lib/utils.js → src/utils.ts} +11 -20
- package/{poc/node_modules/node-firebird/lib/wire/connection.js → src/wire/connection.ts} +611 -129
- package/{poc/node_modules/node-firebird/lib/wire/const.js → src/wire/const.ts} +96 -9
- package/{poc/node_modules/node-firebird/lib/wire/database.js → src/wire/database.ts} +110 -44
- package/{poc/node_modules/node-firebird/lib/wire/eventConnection.js → src/wire/eventConnection.ts} +29 -14
- package/{poc/node_modules/node-firebird/lib/wire/fbEventManager.js → src/wire/fbEventManager.ts} +39 -30
- package/{poc/node_modules/node-firebird/lib/wire/serialize.js → src/wire/serialize.ts} +78 -59
- package/{poc/node_modules/node-firebird/lib/wire/service.js → src/wire/service.ts} +90 -88
- package/src/wire/socket.ts +262 -0
- package/src/wire/statement.ts +71 -0
- package/{poc/node_modules/node-firebird/lib/wire/transaction.js → src/wire/transaction.ts} +25 -19
- package/{poc/node_modules/node-firebird/lib/wire/xsqlvar.js → src/wire/xsqlvar.ts} +244 -143
- package/.eslintrc.json +0 -12
- package/.github/workflows/codeql.yml +0 -76
- package/.github/workflows/node.js.yml +0 -95
- package/BIGINT_MIGRATION.md +0 -374
- package/CI_DEBUGGING_GUIDE.md +0 -148
- package/ENCRYPTION_CALLBACK.md +0 -152
- package/FIREBIRD_LOG_FEATURE.md +0 -145
- package/MINIMAL_CHANGES_SUMMARY.md +0 -136
- package/PR_SUMMARY.md +0 -96
- package/ROADMAP.md +0 -223
- package/SRP_PROTOCOL.md +0 -482
- package/poc/README.md +0 -160
- package/poc/helpers.js +0 -59
- package/poc/node_modules/.package-lock.json +0 -14
- package/poc/node_modules/node-firebird/.eslintrc.json +0 -12
- package/poc/node_modules/node-firebird/.github/workflows/codeql.yml +0 -76
- package/poc/node_modules/node-firebird/.github/workflows/node.js.yml +0 -95
- package/poc/node_modules/node-firebird/BIGINT_MIGRATION.md +0 -374
- package/poc/node_modules/node-firebird/CI_DEBUGGING_GUIDE.md +0 -148
- package/poc/node_modules/node-firebird/ENCRYPTION_CALLBACK.md +0 -152
- package/poc/node_modules/node-firebird/FIREBIRD_LOG_FEATURE.md +0 -145
- package/poc/node_modules/node-firebird/LICENSE +0 -373
- package/poc/node_modules/node-firebird/MINIMAL_CHANGES_SUMMARY.md +0 -136
- package/poc/node_modules/node-firebird/PR_SUMMARY.md +0 -96
- package/poc/node_modules/node-firebird/README.md +0 -794
- package/poc/node_modules/node-firebird/ROADMAP.md +0 -223
- package/poc/node_modules/node-firebird/SRP_PROTOCOL.md +0 -482
- package/poc/node_modules/node-firebird/lib/callback.js +0 -38
- package/poc/node_modules/node-firebird/lib/gdscodes.d.ts +0 -1524
- package/poc/node_modules/node-firebird/lib/index.d.ts +0 -316
- package/poc/node_modules/node-firebird/lib/index.js +0 -128
- package/poc/node_modules/node-firebird/lib/pool.js +0 -108
- package/poc/node_modules/node-firebird/lib/wire/socket.js +0 -175
- package/poc/node_modules/node-firebird/lib/wire/statement.js +0 -48
- package/poc/node_modules/node-firebird/package.json +0 -38
- package/poc/node_modules/node-firebird/vitest.config.js +0 -24
- package/poc/package-lock.json +0 -21
- package/poc/package.json +0 -12
- package/poc/reproduce-fixed.js +0 -150
- package/poc/reproduce.js +0 -133
- package/vitest.config.js +0 -27
- /package/{poc/node_modules/node-firebird/lib → src}/firebird.msg +0 -0
- /package/{poc/node_modules/node-firebird/lib → src}/firebird.msg.json +0 -0
package/SRP_PROTOCOL.md
DELETED
|
@@ -1,482 +0,0 @@
|
|
|
1
|
-
# Firebird SRP Authentication Protocol
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
Firebird uses **Secure Remote Password (SRP)** authentication for all connections that do not use the legacy wire-encryption scheme (`Legacy_Auth`). SRP provides password-authenticated key agreement: neither side ever sends the plaintext password over the wire, and both sides prove knowledge of the password without revealing it.
|
|
6
|
-
|
|
7
|
-
- **Firebird 3** introduced SRP (`Srp` plugin, SHA-1 HMAC, Protocol Version 14).
|
|
8
|
-
- **Firebird 4** added `Srp256` (SHA-256 HMAC, Protocol Version 16) and kept `Srp` as fallback.
|
|
9
|
-
- **Firebird 5** uses Protocol Version 17 with the same plugin set as FB4, plus optional wire compression.
|
|
10
|
-
- **Firebird 6** (in development) continues Protocol Version 17 and retains full backward compatibility.
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## SRP Concepts
|
|
15
|
-
|
|
16
|
-
| Term | Meaning |
|
|
17
|
-
|---|---|
|
|
18
|
-
| `N` | A large 1024-bit prime (the SRP group parameter, same for all Firebird versions) |
|
|
19
|
-
| `g` | Generator `2` |
|
|
20
|
-
| `k` | Multiplier parameter derived from `N` and `g` |
|
|
21
|
-
| `s` | Random salt stored in the security database |
|
|
22
|
-
| `v` | Verifier: `g^x mod N`, where `x = H(s, H(U:p))` |
|
|
23
|
-
| `a`, `A` | Client private/public ephemeral keys: `A = g^a mod N` |
|
|
24
|
-
| `b`, `B` | Server private/public ephemeral keys: `B = kv + g^b mod N` |
|
|
25
|
-
| `u` | Scrambler: `H(A, B)` |
|
|
26
|
-
| `K` | Session key derived by both sides |
|
|
27
|
-
| `M1` | Client proof: `H(H(N)⊕H(g), H(I), s, A, B, K)` |
|
|
28
|
-
| `M2` | Server proof: `H(A, M1, K)` (client may or may not verify this) |
|
|
29
|
-
|
|
30
|
-
The SRP group parameters are shared constants defined in `lib/srp.js`:
|
|
31
|
-
|
|
32
|
-
```
|
|
33
|
-
N = E67D2E994B2F900C3F41F08F5BB2627ED0D49EE1FE767A52EFCD565CD6E768812C3E1E9CE8F0A8BEA6CB13CD29DDEBF7A96D4A93B55D488DF099A15C89DCB0640738EB2CBDD9A8F7BAB561AB1B0DC1C6CDABF303264A08D1BCA932D1F1EE428B619D970F342ABA9A65793B8B2F041AE5364350C16F735F56ECBCA87BD57B29E7
|
|
34
|
-
g = 2
|
|
35
|
-
k = 1277432915985975349439481660349303019122249719989
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
> **Important exponent reduction note**: The Firebird engine reduces intermediate SRP exponents modulo N, which deviates from the SRP specification. `node-firebird` mirrors this behaviour in `lib/srp.js` so that client and server agree on the session key `K`.
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## Protocol Version Matrix
|
|
43
|
-
|
|
44
|
-
| Firebird Version | Wire Protocol | Auth Plugin | Hash | `op_accept_data` opcode | `op_cond_accept` opcode |
|
|
45
|
-
|---|---|---|---|---|---|
|
|
46
|
-
| 2.5 | 13 | `Legacy_Auth` | DES crypt | 94 | — |
|
|
47
|
-
| 3.x | 14 (`0x800E`) | `Srp` | SHA-1 | 94 | 98 |
|
|
48
|
-
| 4.x | 16 (`0x8010`) | `Srp`, `Srp256` | SHA-1 / SHA-256 | 94 | 98 |
|
|
49
|
-
| 5.x | 17 (`0x8011`) | `Srp`, `Srp256` | SHA-1 / SHA-256 | 94 | 98 |
|
|
50
|
-
|
|
51
|
-
The high bit of the protocol version number (`0x8000`) is the **Firebird private flag**: it prevents ambiguity with Borland InterBase protocol numbers (protocols 1–13).
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## Wire-Protocol Sequence Diagrams
|
|
56
|
-
|
|
57
|
-
### Legacy Auth (no SRP, `is_authenticated = 1`)
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
Client Server
|
|
61
|
-
────── ──────
|
|
62
|
-
op_connect (plugin=Legacy_Auth, A=hash(pw))
|
|
63
|
-
─────────────────────────────────────────────▶
|
|
64
|
-
op_accept_data (is_authenticated=1)
|
|
65
|
-
◀─────────────────────────────────────────────
|
|
66
|
-
op_attach (database, DPB)
|
|
67
|
-
─────────────────────────────────────────────▶
|
|
68
|
-
op_response (dbHandle)
|
|
69
|
-
◀─────────────────────────────────────────────
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### SRP Auth (FB3 Protocol 14 / FB4 Protocol 16 / FB5 Protocol 17)
|
|
73
|
-
|
|
74
|
-
```
|
|
75
|
-
Client Server
|
|
76
|
-
────── ──────
|
|
77
|
-
1. op_connect
|
|
78
|
-
plugin = "Srp" (or "Srp256")
|
|
79
|
-
A = clientPublicKey (hex)
|
|
80
|
-
─────────────────────────────────────────────▶
|
|
81
|
-
|
|
82
|
-
2. op_cond_accept (opcode 98)
|
|
83
|
-
BLR auth data:
|
|
84
|
-
[uint16LE saltLen][salt hex]
|
|
85
|
-
[uint16LE keyLen ][B hex ]
|
|
86
|
-
plugin = "Srp"
|
|
87
|
-
is_authenticated = 0
|
|
88
|
-
◀─────────────────────────────────────────────
|
|
89
|
-
|
|
90
|
-
3. Client computes:
|
|
91
|
-
u = SHA1(pad(A), pad(B))
|
|
92
|
-
x = SHA1(salt, SHA1(U + ':' + password))
|
|
93
|
-
S = (B - k·g^x)^(a + u·x) mod N
|
|
94
|
-
K = SHA1(S)
|
|
95
|
-
M1 = SHA1(SHA1(N)⊕SHA1(g), SHA1(user), salt, A, B, K)
|
|
96
|
-
|
|
97
|
-
4. op_cont_auth (opcode 92)
|
|
98
|
-
auth_data = M1 (hex)
|
|
99
|
-
plugin = "Srp"
|
|
100
|
-
─────────────────────────────────────────────▶
|
|
101
|
-
|
|
102
|
-
5. Server verifies M1, computes M2
|
|
103
|
-
op_cont_auth (opcode 92)
|
|
104
|
-
auth_data = M2 (server proof, may be empty)
|
|
105
|
-
plugin = "Srp"
|
|
106
|
-
◀─────────────────────────────────────────────
|
|
107
|
-
|
|
108
|
-
6. op_accept (opcode 3)
|
|
109
|
-
protocolVersion = 0x800E / 0x8010 / 0x8011
|
|
110
|
-
◀─────────────────────────────────────────────
|
|
111
|
-
|
|
112
|
-
[If wireCrypt ≠ DISABLE]:
|
|
113
|
-
7. op_crypt (opcode 96) "Arc4"
|
|
114
|
-
─────────────────────────────────────────────▶
|
|
115
|
-
op_response
|
|
116
|
-
◀─────────────────────────────────────────────
|
|
117
|
-
Both sides enable Arc4 stream cipher using
|
|
118
|
-
session key K (padded to 20 bytes / SHA-1 length)
|
|
119
|
-
|
|
120
|
-
8. op_attach (database, DPB)
|
|
121
|
-
─────────────────────────────────────────────▶
|
|
122
|
-
op_response (dbHandle)
|
|
123
|
-
◀─────────────────────────────────────────────
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### SRP256 (Srp256 plugin — FB4/FB5 only)
|
|
127
|
-
|
|
128
|
-
The wire sequence is identical to SRP (above). The only difference is:
|
|
129
|
-
|
|
130
|
-
- The hash algorithm for `M1` / `M2` computation switches from **SHA-1** to **SHA-256**.
|
|
131
|
-
- The plugin name in `op_cond_accept` is `"Srp256"` instead of `"Srp"`.
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## Changes Per Firebird Version
|
|
136
|
-
|
|
137
|
-
### Firebird 3 (Protocol 14, plugin `Srp`)
|
|
138
|
-
|
|
139
|
-
- Introduced the SRP authentication framework.
|
|
140
|
-
- Hash algorithm for `M1` and `M2`: **SHA-1**.
|
|
141
|
-
- Arc4 stream cipher for wire encryption.
|
|
142
|
-
- Auth data encoding: BLR byte array inside `op_cond_accept`.
|
|
143
|
-
- No wire compression support.
|
|
144
|
-
|
|
145
|
-
### Firebird 4 (Protocol 16, plugins `Srp256` and `Srp`)
|
|
146
|
-
|
|
147
|
-
- Added `Srp256` plugin with **SHA-256** hashing (preferred).
|
|
148
|
-
- Falls back to `Srp` (SHA-1) if the server does not offer `Srp256`.
|
|
149
|
-
- Wire compression support added (`pflag_compress` in protocol negotiation).
|
|
150
|
-
- `op_response_piggyback` (opcode 72) introduced — server sends this as an unsolicited completion notification; clients must silently discard it.
|
|
151
|
-
|
|
152
|
-
### Firebird 5 (Protocol 17, plugins `Srp256` and `Srp`)
|
|
153
|
-
|
|
154
|
-
- Protocol version bumped to 17 (`0x8011`).
|
|
155
|
-
- Wire protocol is otherwise compatible with Protocol 16.
|
|
156
|
-
- `op_response_piggyback` usage more prevalent during `EventConnection` teardown.
|
|
157
|
-
- BigInt arithmetic in SRP key generation can be significantly **slower** on resource-constrained CI runners (see [Timing Issue](#timing-issue-and-fix) below).
|
|
158
|
-
|
|
159
|
-
### Firebird 6 (Protocol 17, in development)
|
|
160
|
-
|
|
161
|
-
- Retains Protocol Version 17.
|
|
162
|
-
- Continues to support `Srp256` and `Srp` plugins.
|
|
163
|
-
- No client-visible protocol changes from Firebird 5 at this time.
|
|
164
|
-
|
|
165
|
-
---
|
|
166
|
-
|
|
167
|
-
## Timing Issue and Fix
|
|
168
|
-
|
|
169
|
-
### Root Cause
|
|
170
|
-
|
|
171
|
-
SRP key generation involves **modular exponentiation** over 1024-bit integers. In Node.js, this is performed by the `big-integer` library using pure-JavaScript arithmetic. On a developer machine the `clientSeed()` call completes in < 1 ms and `clientProof()` in < 5 ms. On a loaded CI runner (especially with Firebird 3 which uses SHA-1 requiring more steps), both calls can take **500–3000 ms** combined.
|
|
172
|
-
|
|
173
|
-
The original per-test timeout was 10 s → raised to 30 s → **raised to 60 s** (current) to handle the worst-case loaded runner scenario.
|
|
174
|
-
|
|
175
|
-
### How to Diagnose
|
|
176
|
-
|
|
177
|
-
Set the environment variable `FIREBIRD_DEBUG=1` before running your tests. You will see:
|
|
178
|
-
|
|
179
|
-
```
|
|
180
|
-
[fb-debug] srp.clientSeed: 843ms
|
|
181
|
-
[fb-debug] srp.clientProof(sha1): 1247ms
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
- `srp.clientSeed` measures the time to generate the client ephemeral key pair `(a, A = g^a mod N)`.
|
|
185
|
-
- `srp.clientProof(sha1|sha256)` measures the time to derive `K` and compute `M1`.
|
|
186
|
-
|
|
187
|
-
If these values exceed 4000 ms combined, the 60-second timeout may still be too tight on extremely loaded runners. In that case, increase the `it(…, { timeout: … })` value in `test/index.js` for the SRP tests.
|
|
188
|
-
|
|
189
|
-
### The Fix
|
|
190
|
-
|
|
191
|
-
`test/index.js`:
|
|
192
|
-
```js
|
|
193
|
-
it('should attach with srp plugin', { timeout: 60000 }, async function () { … });
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
### SRP Timing Debug Logs (in `lib/wire/connection.js`)
|
|
197
|
-
|
|
198
|
-
```js
|
|
199
|
-
// clientSeed — key pair generation
|
|
200
|
-
const _t0 = Date.now();
|
|
201
|
-
this.clientKeys = srp.clientSeed();
|
|
202
|
-
if (process.env.FIREBIRD_DEBUG) {
|
|
203
|
-
console.log('[fb-debug] srp.clientSeed: %dms', Date.now() - _t0);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// clientProof — session key + M1 computation
|
|
207
|
-
const _t1 = Date.now();
|
|
208
|
-
var proof = srp.clientProof(user, password, salt, A, B, a, hashAlgo);
|
|
209
|
-
if (process.env.FIREBIRD_DEBUG) {
|
|
210
|
-
console.log('[fb-debug] srp.clientProof(%s): %dms', accept.srpAlgo, Date.now() - _t1);
|
|
211
|
-
}
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
---
|
|
215
|
-
|
|
216
|
-
## Session Key Arc4 Encryption
|
|
217
|
-
|
|
218
|
-
After the SRP handshake, both the client and server share the session key `K = SHA1(S)`. This key is a 160-bit (20-byte) SHA-1 hash returned as a BigInt from `srp.clientProof()`.
|
|
219
|
-
|
|
220
|
-
The key is used to initialise an **Arc4 (RC4)** stream cipher for all subsequent bytes on the socket. A critical padding rule applies:
|
|
221
|
-
|
|
222
|
-
```js
|
|
223
|
-
// K is a BigInt; BigInt.toString(16) may omit leading zeros
|
|
224
|
-
// Arc4 requires exactly 20 bytes (SHA-1 output length)
|
|
225
|
-
var keyBuf = Buffer.from(ret.sessionKey.toString(16).padStart(40, '0'), 'hex');
|
|
226
|
-
// ^^^^^^^^^^^^^^^^^^^
|
|
227
|
-
// without this, a K with a leading 0x00 byte will be only
|
|
228
|
-
// 19 bytes, causing a key mismatch and a garbled connection
|
|
229
|
-
self._socket.enableEncryption(keyBuf);
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
> **Bug fixed (commit `fc0021d`)**: the `padStart(40, '0')` call was missing. A session key whose most-significant byte was `0x00` would produce a 19-byte (or shorter) key, while the Firebird server always uses the full 20 bytes. This caused connection corruption for ~1 in 256 SRP sessions.
|
|
233
|
-
|
|
234
|
-
---
|
|
235
|
-
|
|
236
|
-
## How to Run the Online SRP Test
|
|
237
|
-
|
|
238
|
-
Requires a real Firebird server with:
|
|
239
|
-
- `AuthServer = Srp` (or `Srp256`) configured in `firebird.conf`
|
|
240
|
-
- A user `SYSDBA` with password `masterkey` (or adjust `test/config.js`)
|
|
241
|
-
|
|
242
|
-
```bash
|
|
243
|
-
# Basic run
|
|
244
|
-
npm test
|
|
245
|
-
|
|
246
|
-
# With debug logging
|
|
247
|
-
FIREBIRD_DEBUG=1 npm test -- --grep "srp"
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
The test verifies:
|
|
251
|
-
1. `Firebird.attach()` succeeds using SRP.
|
|
252
|
-
2. The `'attach'` driver event fires exactly once.
|
|
253
|
-
3. `db.detach()` succeeds cleanly.
|
|
254
|
-
|
|
255
|
-
---
|
|
256
|
-
|
|
257
|
-
## How to Run the Offline SRP Mock-Server Tests
|
|
258
|
-
|
|
259
|
-
No Firebird installation required.
|
|
260
|
-
|
|
261
|
-
```bash
|
|
262
|
-
# Run only the offline tests
|
|
263
|
-
npx vitest run test/mock-server.js
|
|
264
|
-
|
|
265
|
-
# With debug timing output
|
|
266
|
-
FIREBIRD_DEBUG=1 npx vitest run test/mock-server.js
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
The offline tests use `test/mock-server.js`, which contains a minimal TCP server that speaks enough of the Firebird wire protocol to exercise the full SRP handshake.
|
|
270
|
-
|
|
271
|
-
### Covered Scenarios
|
|
272
|
-
|
|
273
|
-
| Test | Protocol | What is verified |
|
|
274
|
-
|---|---|---|
|
|
275
|
-
| Full SRP attach/detach (FB3) | 14 (`Srp`) | Complete op_connect → op_cond_accept → op_cont_auth → op_accept → op_attach cycle |
|
|
276
|
-
| Full SRP attach/detach (FB4) | 16 (`Srp`) | Same flow with Protocol 16 |
|
|
277
|
-
| Full SRP attach/detach (FB5) | 17 (`Srp`) | Same flow with Protocol 17 |
|
|
278
|
-
| `parseOpConnect` BLR | any | Extracts plugin name `"Srp"` and client key A from `CNCT_specific_data` multi-block |
|
|
279
|
-
| `parseOpContAuth` | any | Extracts M1 hex proof from client `op_cont_auth` message |
|
|
280
|
-
| `op_cond_accept` XDR round-trip | 14 | BLR format: `[u16LE saltLen][salt][u16LE keyLen][B]` |
|
|
281
|
-
| `op_cont_auth` XDR round-trip | any | Correct opcode, empty M2 array, plugin name |
|
|
282
|
-
| `op_accept` XDR round-trip | 16 | Correct opcode and protocol version field |
|
|
283
|
-
| Protocol version constants | 14/16/17 | `FB_PROTOCOL_FLAG`, `FB_PROTOCOL_MASK` |
|
|
284
|
-
|
|
285
|
-
### SRP Phase Timing (FIREBIRD_DEBUG)
|
|
286
|
-
|
|
287
|
-
When `FIREBIRD_DEBUG=1` is set, each SRP mock-server test emits a timing trace:
|
|
288
|
-
|
|
289
|
-
```
|
|
290
|
-
[srp-test fb3] opConnectRecv=2ms challengeSent=847ms m1Recv=2094ms acceptSent=2094ms opAttachRecv=2095ms
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
Fields:
|
|
294
|
-
- `opConnectRecv` — server received `op_connect` from client
|
|
295
|
-
- `challengeSent` — server sent `op_cond_accept` (salt + B)
|
|
296
|
-
- `m1Recv` — server received `op_cont_auth` (M1 proof)
|
|
297
|
-
- `acceptSent` — server sent `op_cont_auth` (M2) + `op_accept`
|
|
298
|
-
- `opAttachRecv` — server received `op_attach`
|
|
299
|
-
|
|
300
|
-
The gap between `opConnectRecv` and `challengeSent` is the server-side `srp.serverSeed()` time; the gap between `challengeSent` and `m1Recv` is the client-side `srp.clientSeed()` + `srp.clientProof()` time.
|
|
301
|
-
|
|
302
|
-
---
|
|
303
|
-
|
|
304
|
-
## BLR Auth Data Format in `op_cond_accept`
|
|
305
|
-
|
|
306
|
-
The `op_cond_accept` (opcode 98) frame carries the SRP challenge in a **BLR byte array** field. The format is:
|
|
307
|
-
|
|
308
|
-
```
|
|
309
|
-
Offset Size Field
|
|
310
|
-
────── ──── ─────
|
|
311
|
-
0 2 saltLen (uint16 little-endian) — length of the salt hex string
|
|
312
|
-
2 N salt (ASCII hex string, N = saltLen bytes)
|
|
313
|
-
2+N 2 keyLen (uint16 little-endian) — length of the server B hex string
|
|
314
|
-
4+N M B (ASCII hex string, M = keyLen bytes)
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
Example:
|
|
318
|
-
```
|
|
319
|
-
00 40 → saltLen = 64 (32 bytes of salt → 64 hex chars)
|
|
320
|
-
3031323334... → 64 ASCII hex characters of salt
|
|
321
|
-
00 80 → keyLen = 128 (64 bytes of B → 128 hex chars)
|
|
322
|
-
61626364... → 128 ASCII hex characters of B
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
`node-firebird` parses this in `lib/wire/connection.js`:
|
|
326
|
-
```js
|
|
327
|
-
var saltLen = d.buffer.readUInt16LE(0);
|
|
328
|
-
var keyLen = d.buffer.readUInt16LE(saltLen + 2);
|
|
329
|
-
var keyStart = saltLen + 4;
|
|
330
|
-
cnx.serverKeys = {
|
|
331
|
-
salt: d.buffer.slice(2, saltLen + 2).toString('utf8'),
|
|
332
|
-
public: BigInt(d.buffer.slice(keyStart).toString('utf8'), 16)
|
|
333
|
-
};
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
---
|
|
337
|
-
|
|
338
|
-
## `op_cont_auth` Client Message Format
|
|
339
|
-
|
|
340
|
-
The client sends `op_cont_auth` (opcode 92) with:
|
|
341
|
-
|
|
342
|
-
```
|
|
343
|
-
Field XDR type Content
|
|
344
|
-
────────────── ──────── ───────────────────────────────
|
|
345
|
-
opcode int32 92 (op_cont_auth)
|
|
346
|
-
auth_data array M1 proof as ASCII hex string
|
|
347
|
-
plugin_name string "Srp" or "Srp256"
|
|
348
|
-
plist string "" (empty)
|
|
349
|
-
pkey string "" (empty)
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
---
|
|
353
|
-
|
|
354
|
-
## `op_cont_auth` Server Response Format
|
|
355
|
-
|
|
356
|
-
The server replies with `op_cont_auth` (opcode 92) carrying M2:
|
|
357
|
-
|
|
358
|
-
```
|
|
359
|
-
Field XDR type Content
|
|
360
|
-
────────────── ──────── ────────────────────────────────
|
|
361
|
-
opcode int32 92 (op_cont_auth)
|
|
362
|
-
auth_data array M2 server proof (may be empty)
|
|
363
|
-
plugin_name string "Srp" or "Srp256"
|
|
364
|
-
plist string "" (empty)
|
|
365
|
-
pkey string "" (empty)
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
> **node-firebird does NOT validate M2**. After receiving the server `op_cont_auth`, it simply waits for the subsequent `op_accept`. This is safe for practical use but means a compromised server could send any M2 value.
|
|
369
|
-
|
|
370
|
-
---
|
|
371
|
-
|
|
372
|
-
## Offline Mock-Server Architecture
|
|
373
|
-
|
|
374
|
-
```
|
|
375
|
-
┌─────────────────────────────────────────────────────────┐
|
|
376
|
-
│ test/mock-server.js │
|
|
377
|
-
│ │
|
|
378
|
-
│ ┌──────────────────────────────────────────────────┐ │
|
|
379
|
-
│ │ Frame Builders (XdrWriter / BlrWriter) │ │
|
|
380
|
-
│ │ buildOpAcceptData(plugin) │ │
|
|
381
|
-
│ │ buildOpResponse(handle) │ │
|
|
382
|
-
│ │ buildOpEvent(dbHandle, eventRid) │ │
|
|
383
|
-
│ │ buildOpResponsePiggyback() │ │
|
|
384
|
-
│ │ buildOpCondAcceptSRP(proto, salt, B) │ │
|
|
385
|
-
│ │ buildOpContAuthServer(m2Data) │ │
|
|
386
|
-
│ │ buildOpAccept(proto) │ │
|
|
387
|
-
│ └──────────────────────────────────────────────────┘ │
|
|
388
|
-
│ │
|
|
389
|
-
│ ┌──────────────────────────────────────────────────┐ │
|
|
390
|
-
│ │ Frame Parsers │ │
|
|
391
|
-
│ │ parseOpConnect(buf) — BLR parser │ │
|
|
392
|
-
│ │ parseOpContAuth(buf) — M1 extractor │ │
|
|
393
|
-
│ └──────────────────────────────────────────────────┘ │
|
|
394
|
-
│ │
|
|
395
|
-
│ ┌──────────────────────────────────────────────────┐ │
|
|
396
|
-
│ │ Server Helpers │ │
|
|
397
|
-
│ │ startMockServer() / stopMockServer() │ │
|
|
398
|
-
│ │ makeDispatcher(port, handler) │ │
|
|
399
|
-
│ │ makeFullDispatcher(port, handler) │ │
|
|
400
|
-
│ │ withMockAttach(port) / withMockDetach(db) │ │
|
|
401
|
-
│ │ withMockSrpAttach(port, proto?) │ │
|
|
402
|
-
│ └──────────────────────────────────────────────────┘ │
|
|
403
|
-
└─────────────────────────────────────────────────────────┘
|
|
404
|
-
│ TCP loopback
|
|
405
|
-
▼
|
|
406
|
-
┌─────────────────────────────────────────────────────────┐
|
|
407
|
-
│ lib/wire/connection.js (Connection class) │
|
|
408
|
-
│ connect() → decodeResponse() → attach() → detach() │
|
|
409
|
-
└─────────────────────────────────────────────────────────┘
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
---
|
|
413
|
-
|
|
414
|
-
## Related Files
|
|
415
|
-
|
|
416
|
-
| File | Description |
|
|
417
|
-
|---|---|
|
|
418
|
-
| `lib/srp.js` | Pure-JS SRP implementation (BigInt arithmetic, SHA-1/SHA-256 hashing) |
|
|
419
|
-
| `lib/wire/connection.js` | Wire protocol encode/decode; SRP handshake; debug logging |
|
|
420
|
-
| `lib/wire/const.js` | Protocol version constants, opcode numbers, auth plugin names |
|
|
421
|
-
| `lib/wire/serialize.js` | `XdrWriter`, `XdrReader`, `BlrWriter`, `BlrReader` |
|
|
422
|
-
| `test/srp.js` | Unit tests for SRP arithmetic helpers and end-to-end handshake |
|
|
423
|
-
| `test/mock-server.js` | Offline wire-protocol tests (SRP auth + queue integrity) |
|
|
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) |
|
|
426
|
-
|
|
427
|
-
---
|
|
428
|
-
|
|
429
|
-
## Troubleshooting
|
|
430
|
-
|
|
431
|
-
### `should attach with srp plugin` times out in CI
|
|
432
|
-
|
|
433
|
-
1. Set `FIREBIRD_DEBUG=1` and check the timing logs:
|
|
434
|
-
```
|
|
435
|
-
[fb-debug] srp.clientSeed: Xms
|
|
436
|
-
[fb-debug] srp.clientProof(sha1): Yms
|
|
437
|
-
```
|
|
438
|
-
2. If `X + Y > 5000`, the runner is overloaded. Increase the timeout in `test/index.js`:
|
|
439
|
-
```js
|
|
440
|
-
it('should attach with srp plugin', { timeout: 120000 }, async function () { … });
|
|
441
|
-
```
|
|
442
|
-
3. Alternatively, run the tests at off-peak times or use a dedicated runner.
|
|
443
|
-
|
|
444
|
-
### Connection succeeds but data is garbled (after SRP)
|
|
445
|
-
|
|
446
|
-
Most likely cause: session key padding bug. Verify `lib/wire/connection.js` has:
|
|
447
|
-
```js
|
|
448
|
-
var keyBuf = Buffer.from(ret.sessionKey.toString(16).padStart(40, '0'), 'hex');
|
|
449
|
-
```
|
|
450
|
-
If the `padStart(40, '0')` is absent, keys with a leading zero byte will be truncated.
|
|
451
|
-
|
|
452
|
-
### `op_response_piggyback` causes queue corruption (Firebird 5)
|
|
453
|
-
|
|
454
|
-
Fixed in `lib/wire/connection.js`. Verify the `decodeResponse` switch statement has:
|
|
455
|
-
```js
|
|
456
|
-
case Const.op_response_piggyback:
|
|
457
|
-
parseOpResponse(data, {}, cb);
|
|
458
|
-
return { _isOpEvent: true }; // skip queue shift
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
### `op_event` on main connection causes hang
|
|
462
|
-
|
|
463
|
-
Fixed in `lib/wire/connection.js`. Verify:
|
|
464
|
-
```js
|
|
465
|
-
case Const.op_event:
|
|
466
|
-
data.readInt(); // db handle
|
|
467
|
-
data.readArray(); // EPB
|
|
468
|
-
data.readInt64(); // AST pointer
|
|
469
|
-
data.readInt(); // event RID
|
|
470
|
-
return { _isOpEvent: true }; // skip queue shift
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
---
|
|
474
|
-
|
|
475
|
-
## References
|
|
476
|
-
|
|
477
|
-
- [RFC 2945 — The SRP Authentication and Key Exchange System](https://www.ietf.org/rfc/rfc2945.txt)
|
|
478
|
-
- [Firebird source: `src/auth/SecureRemotePassword/`](https://github.com/FirebirdSQL/firebird/tree/master/src/auth/SecureRemotePassword)
|
|
479
|
-
- [Firebird Wire Protocol documentation](https://github.com/FirebirdSQL/firebird/blob/master/doc/WhatsNew)
|
|
480
|
-
- [node-firebird `lib/srp.js`](lib/srp.js)
|
|
481
|
-
- [node-firebird `lib/wire/connection.js`](lib/wire/connection.js)
|
|
482
|
-
- [node-firebird `test/mock-server.js`](test/mock-server.js)
|
package/poc/README.md
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
# node-firebird pool — Bug PoC & Fix
|
|
2
|
-
|
|
3
|
-
Proof of concept for three bugs in `lib/pool.js` (node-firebird ≥ 2.x).
|
|
4
|
-
No real Firebird server is needed — a tiny fake TCP server simulates the failure scenario.
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## The bugs
|
|
9
|
-
|
|
10
|
-
### Bug 1 — `_creating` stuck forever (critical)
|
|
11
|
-
|
|
12
|
-
**File:** `lib/pool.js` → `Pool.check()`
|
|
13
|
-
|
|
14
|
-
When no idle connection exists, `check()` calls `this.attach()` and increments `_creating`. The counter is only decremented **inside the `attach()` callback**. If the server accepts TCP but never responds to the Firebird wire protocol (e.g. during SRP authentication under load), the callback never fires. `_creating` stays elevated permanently, the slot is lost, and every `pool.get()` caller waits forever.
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
// original code
|
|
18
|
-
self._creating++;
|
|
19
|
-
this.attach(self.options, function (err, db) {
|
|
20
|
-
self._creating--; // ← never reached if attach() hangs
|
|
21
|
-
cb(err, db); // ← caller never notified
|
|
22
|
-
});
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
**Real-world log signature:**
|
|
26
|
-
```
|
|
27
|
-
*ERRO* Timeout (15000ms) ao aguardar conexão para tenant X.
|
|
28
|
-
Estado do pool: {"dbinuse":0,"_creating":1,"pending":0,"idle":4}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
**Fix:** `connectTimeout` option — a `setTimeout` wraps the `attach()` call. On expiry, `_creating` is decremented and the caller receives an error immediately. A late-arriving connection (if attach eventually completes) is discarded safely.
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
### Bug 2 — `destroy()` does not drain the `pending` queue
|
|
36
|
-
|
|
37
|
-
**File:** `lib/pool.js` → `Pool.destroy()`
|
|
38
|
-
|
|
39
|
-
Callbacks queued in `this.pending` (waiting for a free pool slot) are silently abandoned when `destroy()` is called. Their callers hang indefinitely — there is no error, no timeout, nothing. This is especially harmful when `destroy()` is used as a recovery strategy after a stuck pool.
|
|
40
|
-
|
|
41
|
-
**Fix:** `this.pending.splice(0)` at the start of `destroy()` calls every waiting callback with `Error('Pool is being destroyed')` before any connection is closed.
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
### Bug 3 — `pool.get()` after `pool.destroy()` silently accumulates
|
|
46
|
-
|
|
47
|
-
**File:** `lib/pool.js` → `Pool.get()` and `Pool.check()`
|
|
48
|
-
|
|
49
|
-
There is no `_destroyed` flag. Calling `pool.get()` on an already-destroyed pool pushes the callback into `this.pending` and calls `check()`. `check()` returns early because all slots are gone, but the callback is never served. The caller hangs forever.
|
|
50
|
-
|
|
51
|
-
**Fix:** `_destroyed = true` is set in `destroy()`. Both `get()` and `check()` check it and reject immediately with `Error('Pool has been destroyed')`.
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
### Bug 4 (minor) — `destroy()` callback never fires when connections are in use
|
|
56
|
-
|
|
57
|
-
**File:** `lib/pool.js` → `Pool.destroy()`
|
|
58
|
-
|
|
59
|
-
The original `destroy()` only calls `detachCallback()` for connections found in `pooldb` (idle). Connections currently in use (`dbinuse > 0`) fall through the `forEach` without decrementing `connectionCount`, so it never reaches zero and the `destroy()` completion callback is never invoked.
|
|
60
|
-
|
|
61
|
-
**Fix:** The `else` branch counts in-use connections down without forcing a detach — releasing them remains the caller's responsibility.
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## Files
|
|
66
|
-
|
|
67
|
-
| File | Purpose |
|
|
68
|
-
|------|---------|
|
|
69
|
-
| `helpers.js` | Shared logging and fake-server factory |
|
|
70
|
-
| `reproduce.js` | Demonstrates all bugs — exits with code **1** |
|
|
71
|
-
| `reproduce-fixed.js` | Validates all fixes — exits with code **0** |
|
|
72
|
-
| `pool-patched.js` | Drop-in replacement for `lib/pool.js` |
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
## How to run
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
npm install
|
|
80
|
-
|
|
81
|
-
# Show the bugs (process hangs → forced exit 1)
|
|
82
|
-
node reproduce.js
|
|
83
|
-
|
|
84
|
-
# Show the fixes (process exits cleanly → exit 0)
|
|
85
|
-
node reproduce-fixed.js
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## Expected output
|
|
91
|
-
|
|
92
|
-
### `node reproduce.js` (bugs present)
|
|
93
|
-
|
|
94
|
-
```
|
|
95
|
-
[fake-server] Listening on 127.0.0.1:13050 ...
|
|
96
|
-
[BUG-1] pool.get() → TCP connects, Firebird protocol hangs, callback NEVER fires
|
|
97
|
-
[pool-state] creating=1 idle=0 inuse=0 pending=0 destroyed=(no flag)
|
|
98
|
-
[pool-state] creating=1 idle=0 inuse=0 pending=0 destroyed=(no flag)
|
|
99
|
-
[BUG-2] Queuing a second get() in pending, then calling destroy()
|
|
100
|
-
[BUG-2] Before destroy — creating=1 idle=0 inuse=0 pending=1 ...
|
|
101
|
-
[BUG-2] After destroy — creating=1 idle=0 inuse=0 pending=1 ... ← pending NOT drained
|
|
102
|
-
[BUG-2] ↑ pending callback was NEVER called — Promise hangs forever
|
|
103
|
-
[BUG-3] pool.get() AFTER pool.destroy() — should be rejected immediately
|
|
104
|
-
[BUG-3] After post-destroy get — creating=1 idle=0 inuse=0 pending=2 ...
|
|
105
|
-
[SUMMARY] Bug 1 confirmed : _creating=1, permanently stuck (slot lost)
|
|
106
|
-
[SUMMARY] Bug 2 confirmed : pending callback from before destroy never fired
|
|
107
|
-
[SUMMARY] Bug 3 confirmed : post-destroy get silently accumulated in pending
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### `node reproduce-fixed.js` (fixes applied)
|
|
111
|
-
|
|
112
|
-
```
|
|
113
|
-
[fake-server] Listening on 127.0.0.1:13051 ...
|
|
114
|
-
[FIX-1] pool.get() — attach() will hang, timeout fires after 1500 ms
|
|
115
|
-
[pool-state] creating=1 idle=0 inuse=0 pending=0 destroyed=false
|
|
116
|
-
[pool-state] creating=1 idle=0 inuse=0 pending=0 destroyed=false
|
|
117
|
-
[FIX-1 ✓ OK] callback received error (expected): "Connection timeout after 1500ms"
|
|
118
|
-
[pool-state] creating=0 idle=0 inuse=0 pending=0 destroyed=false ← back to 0 ✓
|
|
119
|
-
[FIX-4] Queuing a get() in pending, then destroying pool immediately
|
|
120
|
-
[FIX-4 ✓ OK] pending callback received error: "Pool is being destroyed" ← immediate ✓
|
|
121
|
-
[FIX-4] After destroy — creating=0 idle=0 inuse=0 pending=0 destroyed=true
|
|
122
|
-
[FIX-2] pool.get() AFTER pool.destroy() — must be rejected immediately
|
|
123
|
-
[FIX-2 ✓ OK] immediately rejected: "Pool has been destroyed" ← no accumulation ✓
|
|
124
|
-
[SUMMARY] Fix 1 confirmed : _creating returned to 0 after 1500 ms timeout
|
|
125
|
-
[SUMMARY] Fix 2 confirmed : post-destroy get() rejected immediately (_destroyed flag)
|
|
126
|
-
[SUMMARY] Fix 4 confirmed : pending callback drained synchronously by destroy()
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
---
|
|
130
|
-
|
|
131
|
-
## Proposed change to `lib/index.d.ts`
|
|
132
|
-
|
|
133
|
-
Add `connectTimeout` to the `Options` interface:
|
|
134
|
-
|
|
135
|
-
```typescript
|
|
136
|
-
export interface Options {
|
|
137
|
-
// ... existing fields ...
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Timeout in milliseconds for a single pool.get() attach operation.
|
|
141
|
-
* If attach() does not complete within this time the slot is freed,
|
|
142
|
-
* the caller receives an error, and any late-arriving connection is
|
|
143
|
-
* safely discarded. Set to 0 or omit to disable (default: no timeout).
|
|
144
|
-
*
|
|
145
|
-
* Recommended value: 5000–10000 ms depending on network latency and
|
|
146
|
-
* expected Firebird server response time under load.
|
|
147
|
-
*/
|
|
148
|
-
connectTimeout?: number;
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
---
|
|
153
|
-
|
|
154
|
-
## Backward compatibility
|
|
155
|
-
|
|
156
|
-
All changes are **fully backward-compatible**:
|
|
157
|
-
|
|
158
|
-
- `connectTimeout` is optional and defaults to disabled (`undefined > 0` is `false`)
|
|
159
|
-
- `_destroyed` defaults to `false` — existing code paths are unchanged
|
|
160
|
-
- `destroy()` draining of `pending` is new behaviour but harmless for callers that did not add pending requests before calling `destroy()`
|