Repository navigation
v2.18.2
Patch release that fixes two bugs in the SRP authentication handshake:
- Crash on every attach (#438). On a server that tries Legacy_Auth before SRP, every
attach()crashed withTypeError: Cannot read properties of undefined (reading 'readUInt16LE'). - Random SRP login failures. About 1 attach in 16 failed, because the driver sometimes read the server's public key from the wrong position.
The API is unchanged, and no configuration changes are needed.
Summary of Changes
Firebird authenticates a connection by trying the plugins in the server's AuthServer list, in order. The client's first message proposes a plugin and sends initial data for it. The server then either accepts it or asks the client to switch to another plugin. The driver had two faults in this exchange:
-
The server switches to SRP after a failed plugin. With
AuthServer = Legacy_Auth, Srp256, Srp, the server offers Legacy_Auth first. If that account has no valid Legacy_Auth credentials, the server rejects it and moves to Srp256. It sends anop_cont_auththat namesSrp256and has no data, which means "start this plugin and send me your public key". The driver assumed every SRPop_cont_authcontained the salt and server key B. It read from an undefined buffer and threw inside the socketdatahandler, so the attach never returned. This happened on every attach. -
The server key B was read from the wrong offset. Firebird sends the SRP challenge as
[u16 saltLen][salt][u16 keyLen][B]with no padding. The driver looked for B at the 4-byte aligned offset(saltLen + 2 + 3) & ~3. That is only correct when the salt length is a multiple of 4. Firebird strips leading zeros from the hex salt, so the salt is sometimes 61–63 characters instead of 64. The driver then read a shifted, corrupted B:- with 61 or 63 characters, the proof was wrong and the server rejected the login after its failed-auth delay (about 8 s);
- with 62 characters, the key-length bytes ended up inside B,
BigInt()threw, and the attach hung.
Because the salt is random, this failed intermittently. It caused the flaky
should attach with srp pluginfailures on master CI.
Key Changes
1. Follow the server when it switches to an SRP plugin (#438, #441)
When op_cont_auth names an SRP plugin (Srp, Srp256, Srp384, Srp512) and carries no data, the driver now:
- creates SRP client keys if it doesn't have any yet (for example, when the first message used Legacy_Auth);
- sends its public key A for that plugin;
- takes the salt and B from the next
op_cont_authand completes the exchange with the existing proof (M1) and M2 steps.
The full handshake against a Legacy_Auth, Srp256, Srp server now looks like this:
client server
op_connect (Srp512, A) ──▶
◀── op_cond_accept (Legacy_Auth, no data)
op_cont_auth (Legacy_Auth hash) ──▶
◀── op_cont_auth (Srp256, no data) ← used to crash here
op_cont_auth (Srp256, A) ──▶
◀── op_cont_auth (Srp256, salt + B)
op_cont_auth (Srp256, M1) ──▶
◀── op_cont_auth (M2) + op_accept
The op_cond_accept path already handled an empty SRP challenge. The op_cont_auth path now does the same.
2. Read the SRP key B at the correct offset (#441)
B is now read from offset saltLen + 4, for exactly keyLen bytes. This is how Firebird writes the data, and how Jaybird and the native client read it. The fix applies to both the op_cond_accept and op_cont_auth paths.
3. Malformed challenges return an error instead of throwing
The buffer length is now checked before the salt and key lengths are read. A truncated or malformed SRP challenge rejects attach() with Invalid buffer size for <plugin> login. It no longer throws a TypeError or RangeError inside the socket handler, which left the caller waiting forever.
Behavior Matrix
| Scenario | 2.18.1 | 2.18.2 |
|---|---|---|
AuthServer = Srp256 (FB4/FB5 default) |
✅ works (fails about 1 in 16) | ✅ works |
AuthServer = Legacy_Auth, Srp256, Srp, user has Legacy_Auth credentials |
✅ works | ✅ works |
AuthServer = Legacy_Auth, Srp256, Srp, SRP-only user |
❌ TypeError … 'readUInt16LE' every time |
✅ Legacy_Auth → Srp256 switch completes |
| Salt is 61 or 63 hex chars (random) | ❌ auth rejected after about 8 s | ✅ works |
| Salt is 62 hex chars (random) | ❌ attach hangs (Cannot convert … to a BigInt) |
✅ works |
| Truncated SRP challenge | ❌ uncaught throw, attach never returns | ✅ attach() fails with Invalid buffer size for <plugin> login |
Upgrade Notes
-
No code or configuration changes are needed. Upgrading is enough.
-
Leave
pluginNameunset when the server lists several plugins. With an explicitpluginName, the driver still fails as soon as the server offers a different plugin (Server don't accept plugin : X, but support : Y). That is by design: an explicitpluginNamemeans "use only this plugin". -
If you changed
firebird.confto work around #438 (moving SRP first inAuthServer, or creating matching Legacy_UserManager users), you can keep that change. It is no longer required. PuttingSrp256first is still recommended for security. -
If you added retries to work around random SRP login failures, you can remove them.
-
To trace the handshake, set
FIREBIRD_DEBUG=1. It logs everyop_cond_accept/op_cont_authstep, and now includes a line when the server switches plugins:[fb-debug] auth: op_cond_accept received plugin=Legacy_Auth proto=32784 t=6ms [fb-debug] auth: op_cont_auth received plugin=Srp256 pendingAccept=Legacy_Auth t=7ms [fb-debug] auth: server switched to Srp256, sending client public key t=7ms
Testing
New offline wire-protocol tests in test/mock-server.js, which need no Firebird server:
- Legacy_Auth → Srp256 chain, the #438 handshake. Before the fix it fails with the same
readUInt16LETypeError reported in the issue. - Truncated SRP
op_cont_authchallenge.attach()rejects withInvalid buffer size for Srp256 logininstead of hanging. - Salts of 61, 62 and 63 hex characters. Each test checks that the parsed salt and B exactly match the server's values. All three fail before the fix.
test/mock-server.js: 37/37 passing. Typecheck and lint are clean. The CI matrix passes on Node.js 20, 22, 24 and 26 against Firebird 3, 4, 5 and the 6 snapshot: 32 of 32 jobs, including should attach with srp plugin, which failed intermittently on master before this release.
Pull Requests
Issues Fixed
- #438: Firebird 4.0 SuperServer:
op_cont_authcrashes withreadUInt16LE of undefined(100% reproducible), and plugin negotiation looks self-contradictory
Related
- 2.8.1 fixed a rare SRP handshake failure (#421, #347). The key-offset fix in this release addresses another source of intermittent SRP login failures, depending on the salt length.
- #254 added support for the server switching between SRP hash variants in the middle of a handshake. This release covers the related case where the server switches to SRP from a different plugin.
Installation
npm install node-firebird@2.18.2Full Changelog: v2.18.1...v2.18.2
🤖 Generated with Claude Code