Skip to content

v2.18.2

Choose a tag to compare

@mariuz mariuz released this 23 Sep 22:56
· 12 commits to master since this release

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 with TypeError: 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:

  1. 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 an op_cont_auth that names Srp256 and has no data, which means "start this plugin and send me your public key". The driver assumed every SRP op_cont_auth contained the salt and server key B. It read from an undefined buffer and threw inside the socket data handler, so the attach never returned. This happened on every attach.

  2. 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 plugin failures 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_auth and 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 pluginName unset when the server lists several plugins. With an explicit pluginName, 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 explicit pluginName means "use only this plugin".

  • If you changed firebird.conf to work around #438 (moving SRP first in AuthServer, or creating matching Legacy_UserManager users), you can keep that change. It is no longer required. Putting Srp256 first 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 every op_cond_accept/op_cont_auth step, 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 readUInt16LE TypeError reported in the issue.
  • Truncated SRP op_cont_auth challenge. attach() rejects with Invalid buffer size for Srp256 login instead 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

  • fix(auth): SRP plugin switch via empty op_cont_auth (#438) and key B offset by @mariuz in #441

Issues Fixed

  • #438: Firebird 4.0 SuperServer: op_cont_auth crashes with readUInt16LE 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.2

Full Changelog: v2.18.1...v2.18.2

🤖 Generated with Claude Code