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 20 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
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
echo deb http://packages.prosody.im/debian $(lsb_release -sc) main | sudo tee /etc/apt/sources.list.d/prosody.list
sudo wget https://prosody.im/files/prosody-debian-packages.key -O/etc/apt/trusted.gpg.d/prosody.gpg
sudo apt-get update
sudo apt-get -y install prosody lua-bitop lua-sec
sudo apt-get -y install lua5.3 prosody-trunk lua-bitop lua-sec
sudo service prosody stop

# - run: npm install -g npm
Expand Down
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
26 changes: 22 additions & 4 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 @@ -22,7 +23,16 @@ const anonymous = require("@xmpp/sasl-anonymous");
const plain = require("@xmpp/sasl-plain");

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

const { domain, service } = params;
if (!domain && service) {
Expand All @@ -40,11 +50,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 +71,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 +85,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
26 changes: 22 additions & 4 deletions packages/client/react-native.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 @@ -22,7 +23,16 @@ const anonymous = require("@xmpp/sasl-anonymous");
const plain = require("@xmpp/sasl-plain");

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

const { domain, service } = params;
if (!domain && service) {
Expand All @@ -40,11 +50,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 +71,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 +85,7 @@ function client(options = {}) {
iqCaller,
iqCallee,
resolve,
sasl2,
sasl,
resourceBinding,
sessionEstablishment,
Expand Down
10 changes: 10 additions & 0 deletions packages/connection/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,16 @@ 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.

) {
// When the stream is secure there is no leak to setting the stream from
Copy link
Member

Choose a reason for hiding this comment

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

there is no leak to setting the stream from

Needs clarification

and a reference to the spec 🙏

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What kind of clarification would you like for that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Spec reference:

However, if the client knows the XMPP identity then it SHOULD include the 'from' attribute after the confidentiality and integrity of the stream are protected via TLS or an equivalent security layer.
https://www.rfc-editor.org/rfc/rfc6120.html#section-4.7.1

// This is suggested in general and in required for FAST implementations
// in particular
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
// This is suggested in general and in required for FAST implementations
// in particular
// This is recommended in general and required for FAST implementations

Copy link
Member

Choose a reason for hiding this comment

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

I don't see it in the spec

headerElement.attrs.from = (this.streamFrom || this.jid).toString();
}
headerElement.attrs["xml:lang"] = lang;
this.root = headerElement;

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

// https://datatracker.ietf.org/doc/draft-schmaus-kitten-sasl-ht/
const createHmac = require("create-hmac");

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
Loading
Loading