Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement SASL2, BIND2, and FAST #1006

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4c3a481
Include stream from when possible
singpolyma Nov 21, 2023
3fb0a08
Implement SASL HT-SHA-256-NONE mechanism
singpolyma Nov 21, 2023
36f543f
Implement SASL2 (with optional BIND2 and FAST)
singpolyma Nov 21, 2023
2d3bde9
Use SASL2 from client
singpolyma Nov 21, 2023
46f3ee6
Allow other modules to inline into sasl2/bind2
singpolyma Nov 22, 2023
917f30b
Inline stream management into sasl2/bind2
singpolyma Nov 22, 2023
d9b0711
Add SASL2/BIND2/FAST tests
singpolyma Jul 4, 2024
3477d68
Merge branch 'main' into sasl2
sonnyp Dec 12, 2024
42cccdb
fix eslint
sonnyp Dec 12, 2024
296000d
Remove sasl-ht-sha-256-none from @xmpp/client browser
sonnyp Dec 12, 2024
b8e0fac
Add doc comment to sasl-h2-sha-256-none
singpolyma Dec 18, 2024
9f6ad2f
Update sasl2 doc link comment
singpolyma Dec 18, 2024
675aab9
Add explanatory comment about bind2 inline setting to online
singpolyma Dec 18, 2024
30ca0a7
Explanation of setting stream from
singpolyma Dec 18, 2024
f2e29fa
Workaround for bug in babel-plugin-transform-async-to-promises
singpolyma Dec 18, 2024
2fd7003
Enable SASL2 for test server
singpolyma Dec 18, 2024
96d50c8
e2e tests for bind2 and fast
singpolyma Dec 18, 2024
faf34ef
Fix for 0.12+
singpolyma Dec 18, 2024
07f4250
SASL2 et al need prosody trunk
singpolyma Dec 18, 2024
069d0f6
Enable sasl2 in react-native
sonnyp Dec 20, 2024
54db23a
Merge branch 'main' into sasl2
sonnyp Dec 21, 2024
7ce1b2f
fixes
sonnyp Dec 21, 2024
21bb7cb
Merge branch 'main' into sasl2
sonnyp Dec 21, 2024
441efe0
Stop vendoring prosody modules
sonnyp Dec 21, 2024
ca6b6f5
Merge branch 'main' into sasl2
sonnyp Dec 22, 2024
e37dfbf
Address some review comments
sonnyp Dec 22, 2024
c5645bc
f
sonnyp Dec 22, 2024
195cda5
Fix CI install modules
sonnyp Dec 22, 2024
2e1782f
Remove sasl2 SASLError
sonnyp Dec 22, 2024
c7e17c9
Move sasl factory to client
sonnyp Dec 22, 2024
9973b6f
add make e2e with prosody modules install
sonnyp Dec 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"bundlesize": [
{
"path": "./packages/client/dist/xmpp.min.js",
"maxSize": "16 KB"
"maxSize": "17 KB"
}
],
"lint-staged": {
Expand Down
16 changes: 13 additions & 3 deletions packages/client/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const _iqCallee = require("@xmpp/iq/callee");
const _resolve = require("@xmpp/resolve");

// Stream features - order matters and define priority
const _sasl2 = require("@xmpp/sasl2");
const _sasl = require("@xmpp/sasl");
const _resourceBinding = require("@xmpp/resource-binding");
const _sessionEstablishment = require("@xmpp/session-establishment");
Expand All @@ -23,6 +24,7 @@ const plain = require("@xmpp/sasl-plain");

function client(options = {}) {
const { resource, credentials, username, password, ...params } = options;
const { clientId, software, device } = params;

const { domain, service } = params;
if (!domain && service) {
Expand All @@ -40,11 +42,17 @@ function client(options = {}) {
const iqCallee = _iqCallee({ middleware, entity });
const resolve = _resolve({ entity });
// Stream features - order matters and define priority
const sasl2 = _sasl2(
{ streamFeatures },
credentials || { username, password },
{ clientId, software, device },
);
const sasl = _sasl({ streamFeatures }, credentials || { username, password });
const streamManagement = _streamManagement({
streamFeatures,
entity,
middleware,
sasl2,
});
const resourceBinding = _resourceBinding(
{ iqCaller, streamFeatures },
Expand All @@ -55,9 +63,10 @@ function client(options = {}) {
streamFeatures,
});
// SASL mechanisms - order matters and define priority
const mechanisms = Object.entries({ plain, anonymous }).map(([k, v]) => ({
[k]: v(sasl),
}));
const mechanisms = Object.entries({
plain,
anonymous,
}).map(([k, v]) => ({ [k]: [v(sasl2), v(sasl)] }));

return Object.assign(entity, {
entity,
Expand All @@ -68,6 +77,7 @@ function client(options = {}) {
iqCaller,
iqCallee,
resolve,
sasl2,
sasl,
resourceBinding,
sessionEstablishment,
Expand Down
13 changes: 12 additions & 1 deletion packages/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,21 @@ const _resolve = require("@xmpp/resolve");

// Stream features - order matters and define priority
const _starttls = require("@xmpp/starttls/client");
const _sasl2 = require("@xmpp/sasl2");
const _sasl = require("@xmpp/sasl");
const _resourceBinding = require("@xmpp/resource-binding");
const _sessionEstablishment = require("@xmpp/session-establishment");
const _streamManagement = require("@xmpp/stream-management");

// SASL mechanisms - order matters and define priority
const scramsha1 = require("@xmpp/sasl-scram-sha-1");
const htsha256 = require("@xmpp/sasl-ht-sha-256-none");
sonnyp marked this conversation as resolved.
Show resolved Hide resolved
const plain = require("@xmpp/sasl-plain");
const anonymous = require("@xmpp/sasl-anonymous");

function client(options = {}) {
const { resource, credentials, username, password, ...params } = options;
const { clientId, software, device } = params;

const { domain, service } = params;
if (!domain && service) {
Expand All @@ -47,11 +50,17 @@ function client(options = {}) {
const resolve = _resolve({ entity });
// Stream features - order matters and define priority
const starttls = _starttls({ streamFeatures });
const sasl2 = _sasl2(
{ streamFeatures },
credentials || { username, password },
{ clientId, software, device },
);
const sasl = _sasl({ streamFeatures }, credentials || { username, password });
const streamManagement = _streamManagement({
streamFeatures,
entity,
middleware,
sasl2,
});
const resourceBinding = _resourceBinding(
{ iqCaller, streamFeatures },
Expand All @@ -64,9 +73,10 @@ function client(options = {}) {
// SASL mechanisms - order matters and define priority
const mechanisms = Object.entries({
scramsha1,
htsha256,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

plain,
anonymous,
}).map(([k, v]) => ({ [k]: v(sasl) }));
}).map(([k, v]) => ({ [k]: [v(sasl2), v(sasl)] }));

return Object.assign(entity, {
entity,
Expand All @@ -80,6 +90,7 @@ function client(options = {}) {
iqCallee,
resolve,
starttls,
sasl2,
sasl,
resourceBinding,
sessionEstablishment,
Expand Down
2 changes: 2 additions & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
"@xmpp/reconnect": "^0.13.2",
"@xmpp/resolve": "^0.13.2",
"@xmpp/resource-binding": "^0.13.2",
"@xmpp/sasl2": "^0.13.0",
"@xmpp/sasl": "^0.13.2",
"@xmpp/sasl-anonymous": "^0.13.2",
"@xmpp/sasl-plain": "^0.13.2",
"@xmpp/sasl-scram-sha-1": "^0.13.2",
"@xmpp/sasl-ht-sha-256-none": "^0.13.0",
"@xmpp/session-establishment": "^0.13.2",
"@xmpp/starttls": "^0.13.2",
"@xmpp/stream-features": "^0.13.2",
Expand Down
7 changes: 7 additions & 0 deletions packages/connection/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,13 @@ class Connection extends EventEmitter {

const headerElement = this.headerElement();
headerElement.attrs.to = domain;
if (
this.socket.secure &&
this.socket.secure() &&
(this.streamFrom || this.jid)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need a new streamFrom param?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we want to set the from on initial connection, before the jid is otherwise known in a c2s case, so it must be provided by the caller. Setting the jid too early causes various parts of the library to assume we have done binding already since that is where that value comes from curretly.

) {
headerElement.attrs.from = (this.streamFrom || this.jid).toString();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added comment to explain:
When the stream is secure there is no leak to setting the stream from
This is suggested in general and in required for FAST implementations
in particular

headerElement.attrs["xml:lang"] = lang;
this.root = headerElement;

Expand Down
28 changes: 28 additions & 0 deletions packages/sasl-ht-sha-256-none/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use strict";

var createHmac = require("create-hmac");
singpolyma marked this conversation as resolved.
Show resolved Hide resolved

function Mechanism() {}

Mechanism.prototype.Mechanism = Mechanism;
Mechanism.prototype.name = "HT-SHA-256-NONE";
Mechanism.prototype.clientFirst = true;

Mechanism.prototype.response = (cred) => {
this.password = cred.password;
const hmac = createHmac("sha256", this.password);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't password and token be 2 different concepts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the point of view of a sasl mechanism it's the same thing. It doesn't know if you're using a token or a password or what, it's just some shared secret string.

hmac.update("Initiator");
return cred.username + "\0" + hmac.digest("latin1");
};

Mechanism.prototype.final = (data) => {
const hmac = createHmac("sha256", this.password);
hmac.update("Responder");
if (hmac.digest("latin1") !== data) {
throw "Responder message from server was wrong";
sonnyp marked this conversation as resolved.
Show resolved Hide resolved
}
};

module.exports = function sasl2(sasl) {
sonnyp marked this conversation as resolved.
Show resolved Hide resolved
sasl.use(Mechanism);
};
23 changes: 23 additions & 0 deletions packages/sasl-ht-sha-256-none/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "@xmpp/sasl-ht-sha-256-none",
"description": "XMPP SASL HT-SHA-256-NONE for JavaScript",
"repository": "github:xmppjs/xmpp.js",
"homepage": "https://github.com/xmppjs/xmpp.js/tree/main/packages/sasl-ht-sha-256-none",
"bugs": "http://github.com/xmppjs/xmpp.js/issues",
"version": "0.13.0",
"license": "ISC",
"keywords": [
"XMPP",
"sasl",
"plain"
],
"dependencies": {
"create-hmac": "^1.1.7"
},
"engines": {
"node": ">= 14"
},
"publishConfig": {
"access": "public"
}
}
2 changes: 0 additions & 2 deletions packages/sasl/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,6 @@ test("use ANONYMOUS if username and password are not provided", async (t) => {
});

test("with whitespaces", async (t) => {
console.log("truc");

const { entity } = mockClient();

entity.mockInput(
Expand Down
79 changes: 79 additions & 0 deletions packages/sasl2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# SASL
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# SASL
# SASL2


SASL2 Negotiation for `@xmpp/client` (including optional BIND2 and FAST).

Note that if you set clientId then BIND2 will be used so you will not get offline messages (and are expected to do a MAM sync instead if you want that).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user-agent id isn't required to use bind2 from what I can tell
so I'm not sur why that should be the case

2 options:

We don't include sasl2 for now in @xmpp/client

or

We move the configuration to {user_agent: {id, software, device}} and only use bind2 if user_agent is present.


Included and enabled in `@xmpp/client`.

## Usage

### object

```js
const {xmpp} = require('@xmpp/client')
const client = xmpp({credentials: {
username: 'foo',
password: 'bar',
clientId: "Some UUID for this client/server pair (optional)",
software: "Name of this software (optional)",
device: "Description of this device (optional)",
})
```

### function

Instead, you can provide a function that will be called every time authentication occurs (every (re)connect).

Uses cases:

- Have the user enter the password every time
- Do not ask for password before connection is made
- Debug authentication
- Using a SASL mechanism with specific requirements (such as FAST)
- Perform an asynchronous operation to get credentials

```js
const { xmpp } = require("@xmpp/client");
const client = xmpp({
credentials: authenticate,
clientId: "Some UUID for this client/server pair (optional)",
software: "Name of this software (optional)",
device: "Description of this device (optional)",
});

async function authenticate(callback, mechanisms) {
const fast = mechanisms.find((mech) => mech.canFast)?.name;
const mech = mechanisms.find((mech) => mech.canOther)?.name;

if (fast) {
const [token, count] = await db.lookupFast(clientId);
if (token) {
await db.incrementFastCount(clientId);
return callback(
{
username: await prompt("enter username"),
password: token,
fastCount: count,
},
fast,
);
}
}

return callback(
{
username: await prompt("enter username"),
password: await prompt("enter password"),
requestToken: fast,
},
mech,
);
}
```

## References

[SASL2](https://xmpp.org/extensions/xep-0388.html)
[BIND2](https://xmpp.org/extensions/xep-0386.html)
[FAST](https://xmpp.org/extensions/inbox/xep-fast.html)
sonnyp marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading