session/sasl: send gs2 n on the browser's wss:// #3

Closed
asxpi wants to merge 0 commits from asxpi/libtacky:fix/browser-gs2-flag into develop
First-time contributor

The web client cannot sign in to another.im: every SCRAM login is answered with <failure><text>Invalid channel binding</text><not-authorized/></failure>.

Reported by qaios in Mattermost: https://wsg.rxtx.cx/anotherim/pl/7sp9jxegfjr47ywen15fg449sw

Cause

Since 4800b462 every encrypted stream counts as one that can bind, so a SCRAM login not offered -PLUS sends gs2 y. In the browser build the stream is the page's wss://. The browser runs that TLS, names no TLS version and gives the page no channel binding, so the client cannot bind at all. RFC 5802 §6 reserves y for a client that could bind. ejabberd behind a TLS proxy treats it as a downgrade and refuses the login.

From the HAR in the report: the server offers PLAIN SCRAM-SHA-1 X-OAUTH2 (no -PLUS), and the client-first message decodes to y,,n=….

Fix

  • sasl.tcl: new OwnTls. A stream counts as bindable only when this client runs its TLS itself, which the link shows by naming a TLS version. The browser's wss:// sends n, and native streams keep the downgrade protection of 4800b462.
  • fast.tcl: Request applies the same test, so the browser still asks for an HT-SHA-256-NONE token when -ENDP is offered instead of asking for none.

Testing

  • policy-gs2-flags and ht-token-requests-prefer-the-strongest-binding each gain a browser case (encrypted, empty tls_version).
  • Session suites in docker/run.sh (linux image): 311 passed, 0 failed, 9 skipped.
  • anotherim-js dist/web built against this branch on rc3 (2c6be166) signs in to another.im with gs2 n → <success/> in Chromium, Firefox, Firefox ESR and Epiphany.

Not in this PR

  • A server that sends XEP-0515's t= would still fail in the browser, because the browser link has no TLS version to compare. No server sends it today.
  • While testing, several devices (on two accounts) published OMEMO 2 bundles carrying a 33-byte OMEMO 0.3 identity key, which rc3 rejects. That's a publisher-side issue and unrelated to this fix.

🤖 Generated with Claude Code

The web client cannot sign in to another.im: every SCRAM login is answered with `<failure><text>Invalid channel binding</text><not-authorized/></failure>`. Reported by qaios in Mattermost: https://wsg.rxtx.cx/anotherim/pl/7sp9jxegfjr47ywen15fg449sw ## Cause Since 4800b462 every encrypted stream counts as one that can bind, so a SCRAM login not offered `-PLUS` sends gs2 `y`. In the browser build the stream is the page's `wss://`. The browser runs that TLS, names no TLS version and gives the page no channel binding, so the client cannot bind at all. RFC 5802 §6 reserves `y` for a client that *could* bind. ejabberd behind a TLS proxy treats it as a downgrade and refuses the login. From the HAR in the report: the server offers `PLAIN SCRAM-SHA-1 X-OAUTH2` (no `-PLUS`), and the client-first message decodes to `y,,n=…`. ## Fix - `sasl.tcl`: new `OwnTls`. A stream counts as bindable only when this client runs its TLS itself, which the link shows by naming a TLS version. The browser's `wss://` sends `n`, and native streams keep the downgrade protection of 4800b462. - `fast.tcl`: `Request` applies the same test, so the browser still asks for an `HT-SHA-256-NONE` token when `-ENDP` is offered instead of asking for none. ## Testing - `policy-gs2-flags` and `ht-token-requests-prefer-the-strongest-binding` each gain a browser case (encrypted, empty `tls_version`). - Session suites in `docker/run.sh` (linux image): 311 passed, 0 failed, 9 skipped. - anotherim-js `dist/web` built against this branch on rc3 (2c6be166) signs in to another.im with gs2 `n` → `<success/>` in Chromium, Firefox, Firefox ESR and Epiphany. ## Not in this PR - A server that sends XEP-0515's `t=` would still fail in the browser, because the browser link has no TLS version to compare. No server sends it today. - While testing, several devices (on two accounts) published OMEMO 2 bundles carrying a 33-byte OMEMO 0.3 identity key, which rc3 rejects. That's a publisher-side issue and unrelated to this fix. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Since 4800b462 every encrypted stream counts as one that can bind, so a
SCRAM login not offered -PLUS says gs2 `y`. In the browser build the
stream is the page's wss://: the browser runs that TLS, names no version
and gives the page no channel binding, so the client cannot bind at all.
RFC 5802 reserves `y` for a client that could bind; ejabberd behind a
TLS proxy treats it as a downgrade and answers every login with
"Invalid channel binding", so no one can sign in from the web client.

Count a stream as bindable only when this client runs its TLS itself,
which the link shows by naming a TLS version (sasl::OwnTls). Native
streams keep the downgrade protection of 4800b462. FAST's Request uses
the same test, so the browser still asks for an HT-SHA-256-NONE token
when -ENDP is offered instead of asking for none.

Tested: policy-gs2-flags and ht-token-requests-prefer-the-strongest-
binding gain a browser case (encrypted, no TLS version); the session
suites pass, 311 passed, 0 failed, 9 skipped. The web client built with
this signs in to another.im with gs2 `n` in Chromium, Firefox, Firefox
ESR and Epiphany.

Fixes: 4800b462 ("A stream under TLS whose tls-server-end-point cannot be computed is no longer talked down to an unbound login")
Reported-by: qaios
Assisted-by: Claude:opus-5
Author
First-time contributor

The OMEMO 2 errors mentioned under "Not in this PR", from the same test session (2026-10-14). They are unrelated to this fix; I'm recording them here for a follow-up issue.

rc3 (2c6be166 + this branch), web client in Chromium and the 0.4.7 AppImage, 28 warnings, all for the same 7 devices of asx@another.im (1484, 2014, 2294, 5689, 9026, 12559, 14459):

[2026-10-14 14:05:12.208 warning] ::tacky::backend.client(asx@another.im).omemo: OMEMO bundle fetch asx@another.im/2294 (v2): its OMEMO 2 bundle carries an OMEMO 0.3 identity key (33 bytes beginning 0x05, where OMEMO 2 publishes a 32-byte Ed25519 key)

The AppImage logged the same lines while fetching those bundles from two other accounts on other servers. So the bundles as published are bad, whichever account reads them.

beta7 (8c3fa4d5), web client in Chromium: the same 7 asx@another.im devices plus 18 of ann@another.im. beta7 couldn't name the cause yet:

[2026-10-14 13:55:32.045 warning] ::tacky::backend.client(asx@another.im).omemo: OMEMO bundle fetch asx@another.im/2294 (v2): the signed pre-key's signature does not verify under the identity key
[2026-10-14 13:55:35.366 warning] ::tacky::backend.client(asx@another.im).omemo: OMEMO bundle fetch ann@another.im/14017 (legacy): ignoring 1 pre-key(s) with an unusable id (0); 99 left

ann's bundles weren't fetched in the rc3 session, so I don't know whether the same diagnosis covers them.

Some client is putting its 0.3 (Curve25519, 0x05-prefixed) identity key into the urn:xmpp:omemo:2:bundles node instead of the Ed25519 one. Those devices can't take part in OMEMO 2 sessions. I haven't checked which client published them; it might be an older anotherim/libtacky build.

The OMEMO 2 errors mentioned under "Not in this PR", from the same test session (2026-10-14). They are unrelated to this fix; I'm recording them here for a follow-up issue. **rc3 (2c6be166 + this branch), web client in Chromium and the 0.4.7 AppImage**, 28 warnings, all for the same 7 devices of `asx@another.im` (1484, 2014, 2294, 5689, 9026, 12559, 14459): ``` [2026-10-14 14:05:12.208 warning] ::tacky::backend.client(asx@another.im).omemo: OMEMO bundle fetch asx@another.im/2294 (v2): its OMEMO 2 bundle carries an OMEMO 0.3 identity key (33 bytes beginning 0x05, where OMEMO 2 publishes a 32-byte Ed25519 key) ``` The AppImage logged the same lines while fetching those bundles from two other accounts on other servers. So the bundles as published are bad, whichever account reads them. **beta7 (8c3fa4d5), web client in Chromium**: the same 7 `asx@another.im` devices plus 18 of `ann@another.im`. beta7 couldn't name the cause yet: ``` [2026-10-14 13:55:32.045 warning] ::tacky::backend.client(asx@another.im).omemo: OMEMO bundle fetch asx@another.im/2294 (v2): the signed pre-key's signature does not verify under the identity key [2026-10-14 13:55:35.366 warning] ::tacky::backend.client(asx@another.im).omemo: OMEMO bundle fetch ann@another.im/14017 (legacy): ignoring 1 pre-key(s) with an unusable id (0); 99 left ``` ann's bundles weren't fetched in the rc3 session, so I don't know whether the same diagnosis covers them. Some client is putting its 0.3 (Curve25519, `0x05`-prefixed) identity key into the `urn:xmpp:omemo:2:bundles` node instead of the Ed25519 one. Those devices can't take part in OMEMO 2 sessions. I haven't checked which client published them; it might be an older anotherim/libtacky build.
Owner

Merged into develop by hand as 66283e0a (release/rc5), in v1.0.0-rc5 (d1cca8e5). Reviewed: the fix is right, a stream counts as bindable only when this client runs its TLS; both tests kept.

Merged into develop by hand as 66283e0a (release/rc5), in v1.0.0-rc5 (d1cca8e5). Reviewed: the fix is right, a stream counts as bindable only when this client runs its TLS; both tests kept.
ann closed this pull request 2026-10-15 17:35:50 +00:00
ann referenced this pull request from a commit 2026-10-27 00:26:42 +00:00
schedule: the scheduler is our own server or a gateway we are registered with, never any domain that says it schedules, and resubmits, releases and cancels lose nothing, from the audit of 2026-10-26 (/data/another-im/audit-2026-10-26-libtacky.md #1 Critical, #3, #4, #5; S5 of the schedule sub-report): a chat's domain is its scheduler only when the account is registered with it (in the roster at to or both, roster transportUser's rule) and its disco#info names a gateway identity and the feature (Registered, Trusted); any other domain, a plain server running mod_schedule included, is never asked, and a <changed/> from anyone but our server or a registered gateway about a chat at itself is ignored before anything is asked or read, so a federated domain can no longer hold, drop or plant our scheduled messages or make us send disco#info to domains it names; a redirect is followed only to a registered gateway; a listed row is read with omemo decryptListed, side-effect free. Resubmit writes the msg column (it wrote row, which the table has not, so send_now of a refused submit and an edit of one never taken always threw). A submit left waiting by a lost answer goes again at the next sign-in or resumption only after its chat's list answered (SubmitWaiting, RecheckThenSubmit), which takes the item back by origin-id, and an edit made meanwhile is put to that item by <replace/> (PutChange) instead of a second submit. An item gone from a list, or a cancel answered item-not-found, keeps its row hidden as released for its copy (30 days) instead of deleting it when its time was ahead, which lost a send_now from another device or a release racing a cancel from this device's history; a <changed what='cancelled'/> naming the item drops it at once. docs/protocol/schedule-0.md §3, §4.4, §5, §6, §7 and §8 say so. Unit tests in test_schedule.tcl, each failing without the change but the cancelled-headline guard; test_schedule 37 of 37 in the container. Nothing changes for tackygram, which is a registered gateway with identity gateway/telegram; anotherim-server mod_schedule could also answer a submit repeating one of the user's origin-ids with that item.

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
another-im/libtacky!3
No description provided.