node-firebird 2.3.2 → 2.3.4
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 +163 -31
- package/lib/index.d.ts +12 -0
- package/lib/pool.js +10 -1
- package/lib/srp.js +12 -1
- package/lib/wire/connection.js +39 -7
- package/lib/wire/socket.js +13 -1
- package/lib/wire/xsqlvar.js +69 -6
- package/package.json +1 -1
- package/poc/node_modules/.package-lock.json +14 -0
- package/poc/node_modules/node-firebird/.eslintrc.json +12 -0
- package/poc/node_modules/node-firebird/.github/workflows/codeql.yml +76 -0
- package/poc/node_modules/node-firebird/.github/workflows/node.js.yml +95 -0
- package/poc/node_modules/node-firebird/BIGINT_MIGRATION.md +374 -0
- package/poc/node_modules/node-firebird/CI_DEBUGGING_GUIDE.md +148 -0
- package/poc/node_modules/node-firebird/ENCRYPTION_CALLBACK.md +152 -0
- package/poc/node_modules/node-firebird/FIREBIRD_LOG_FEATURE.md +145 -0
- package/poc/node_modules/node-firebird/LICENSE +373 -0
- package/poc/node_modules/node-firebird/MINIMAL_CHANGES_SUMMARY.md +136 -0
- package/poc/node_modules/node-firebird/PR_SUMMARY.md +96 -0
- package/poc/node_modules/node-firebird/README.md +794 -0
- package/poc/node_modules/node-firebird/ROADMAP.md +223 -0
- package/poc/node_modules/node-firebird/SRP_PROTOCOL.md +482 -0
- package/poc/node_modules/node-firebird/lib/callback.js +38 -0
- package/poc/node_modules/node-firebird/lib/firebird.msg +0 -0
- package/poc/node_modules/node-firebird/lib/firebird.msg.json +1371 -0
- package/poc/node_modules/node-firebird/lib/gdscodes.d.ts +1524 -0
- package/poc/node_modules/node-firebird/lib/gdscodes.js +1531 -0
- package/poc/node_modules/node-firebird/lib/ieee754-decimal.js +500 -0
- package/poc/node_modules/node-firebird/lib/index.d.ts +316 -0
- package/poc/node_modules/node-firebird/lib/index.js +128 -0
- package/poc/node_modules/node-firebird/lib/messages.js +162 -0
- package/poc/node_modules/node-firebird/lib/pool.js +108 -0
- package/poc/node_modules/node-firebird/lib/srp.js +299 -0
- package/poc/node_modules/node-firebird/lib/unix-crypt.js +343 -0
- package/poc/node_modules/node-firebird/lib/utils.js +164 -0
- package/poc/node_modules/node-firebird/lib/wire/connection.js +2510 -0
- package/poc/node_modules/node-firebird/lib/wire/const.js +807 -0
- package/poc/node_modules/node-firebird/lib/wire/database.js +378 -0
- package/poc/node_modules/node-firebird/lib/wire/eventConnection.js +118 -0
- package/poc/node_modules/node-firebird/lib/wire/fbEventManager.js +326 -0
- package/poc/node_modules/node-firebird/lib/wire/serialize.js +588 -0
- package/poc/node_modules/node-firebird/lib/wire/service.js +1058 -0
- package/poc/node_modules/node-firebird/lib/wire/socket.js +175 -0
- package/poc/node_modules/node-firebird/lib/wire/statement.js +48 -0
- package/poc/node_modules/node-firebird/lib/wire/transaction.js +206 -0
- package/poc/node_modules/node-firebird/lib/wire/xsqlvar.js +703 -0
- package/poc/node_modules/node-firebird/package.json +38 -0
- package/poc/node_modules/node-firebird/vitest.config.js +24 -0
- package/vitest.config.js +3 -1
|
@@ -0,0 +1,482 @@
|
|
|
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)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function doError(obj, callback) {
|
|
2
|
+
if (callback)
|
|
3
|
+
callback(obj)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function isError(obj) {
|
|
7
|
+
return Boolean(
|
|
8
|
+
obj != null && typeof obj === "object" && !Array.isArray(obj) && obj.status
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function doCallback(obj, callback) {
|
|
13
|
+
|
|
14
|
+
if (!callback)
|
|
15
|
+
return;
|
|
16
|
+
|
|
17
|
+
if (obj instanceof Error) {
|
|
18
|
+
callback(obj);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (isError(obj)) {
|
|
23
|
+
var error = new Error(obj.message);
|
|
24
|
+
var status = obj.status && obj.status.length && obj.status[0] || {};
|
|
25
|
+
error.gdscode = status.gdscode; // main error gds code
|
|
26
|
+
error.gdsparams = status.params; // parameters (constraint name, table, etc.)
|
|
27
|
+
callback(error);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
callback(undefined, obj);
|
|
32
|
+
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
doError,
|
|
37
|
+
doCallback
|
|
38
|
+
}
|
|
Binary file
|