Skip to content

Commit

Permalink
Merge branch 'main' into fast-avoid-round-trips
Browse files Browse the repository at this point in the history
  • Loading branch information
sonnyp committed Jan 6, 2025
2 parents c203c88 + 8932a84 commit 33b77a7
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 31 deletions.
3 changes: 0 additions & 3 deletions package-lock.json

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

6 changes: 3 additions & 3 deletions packages/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,16 @@ function client(options = {}) {
createOnAuthenticate(credentials ?? { username, password }, userAgent),
);

const fast = setupIfAvailable(_fast, {
const fast = _fast({
sasl2,
});
fast && sasl2.setup({ fast });
sasl2.setup({ fast });

// SASL2 inline features
const bind2 = _bind2({ sasl2, entity }, resource);

// FAST mechanisms - order matters and define priority
fast && setupIfAvailable(htsha256none, fast.saslFactory);
htsha256none(fast.saslFactory);

// Stream features - order matters and define priority
const sasl = _sasl(
Expand Down
4 changes: 1 addition & 3 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@
"@xmpp/tcp": false,
"@xmpp/tls": false,
"@xmpp/starttls": false,
"@xmpp/sasl-scram-sha-1": false,
"@xmpp/sasl-ht-sha-256-none": false,
"@xmpp/fast": false
"@xmpp/sasl-scram-sha-1": false
},
"engines": {
"node": ">= 20"
Expand Down
35 changes: 26 additions & 9 deletions packages/sasl-ht-sha-256-none/index.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
// https://datatracker.ietf.org/doc/draft-schmaus-kitten-sasl-ht/
import createHmac from "create-hmac";

export function Mechanism() {}

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

Mechanism.prototype.response = function response(cred) {
Mechanism.prototype.response = async function response(cred) {
this.password = cred.password;
const hmac = createHmac("sha256", this.password);
hmac.update("Initiator");
return cred.username + "\0" + hmac.digest("latin1");
// eslint-disable-next-line n/no-unsupported-features/node-builtins
const hmac = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(this.password),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign", "verify"]
);
// eslint-disable-next-line n/no-unsupported-features/node-builtins
const digest = await crypto.subtle.sign("HMAC", hmac, new TextEncoder().encode("Initiator"));
const digestS = String.fromCharCode.apply(null, new Uint8Array(digest));
return cred.username + "\0" + digestS;
};

Mechanism.prototype.final = function final(data) {
const hmac = createHmac("sha256", this.password);
hmac.update("Responder");
if (hmac.digest("latin1") !== data) {
Mechanism.prototype.final = async function final(data) {
// eslint-disable-next-line n/no-unsupported-features/node-builtins
const hmac = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(this.password),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign", "verify"]
);
// eslint-disable-next-line n/no-unsupported-features/node-builtins
const digest = await crypto.subtle.sign("HMAC", hmac, new TextEncoder().encode("Responder"));
const digestS = String.fromCharCode.apply(null, new Uint8Array(digest));
if (digestS !== data) {
throw new Error("Responder message from server was wrong");
}
};
Expand Down
3 changes: 0 additions & 3 deletions packages/sasl-ht-sha-256-none/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
"XMPP",
"sasl"
],
"dependencies": {
"create-hmac": "^1.1.7"
},
"engines": {
"node": ">= 20"
},
Expand Down
6 changes: 3 additions & 3 deletions packages/sasl/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ async function authenticate({ saslFactory, entity, mechanism, credentials }) {
xml(
"auth",
{ xmlns: NS, mechanism: mech.name },
encode(mech.response(creds)),
encode(await mech.response(creds)),
),
async (element, done) => {
if (element.getNS() !== NS) return;

if (element.name === "challenge") {
mech.challenge(decode(element.text()));
const resp = mech.response(creds);
await mech.challenge(decode(element.text()));
const resp = await mech.response(creds);
await entity.send(
xml(
"response",
Expand Down
15 changes: 8 additions & 7 deletions packages/sasl2/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,16 @@ async function authenticate({
entity,
xml("authenticate", { xmlns: NS, mechanism: mech.name }, [
mech.clientFirst &&
xml("initial-response", {}, encode(mech.response(creds))),
xml("initial-response", {}, encode(await mech.response(creds))),
userAgent,
...streamFeatures,
]),
async (element, done) => {
if (element.getNS() !== NS) return;

if (element.name === "challenge") {
mech.challenge(decode(element.text()));
const resp = mech.response(creds);
await mech.challenge(decode(element.text()));
const resp = await mech.response(creds);
await entity.send(
xml(
"response",
Expand All @@ -69,7 +69,7 @@ async function authenticate({
if (element.name === "success") {
const additionalData = element.getChild("additional-data")?.text();
if (additionalData && mech.final) {
mech.final(decode(additionalData));
await mech.final(decode(additionalData));
}

// https://xmpp.org/extensions/xep-0388.html#success
Expand Down Expand Up @@ -99,12 +99,13 @@ export default function sasl2({ streamFeatures, saslFactory }, onAuthenticate) {
NS,
async ({ entity }, _next, element) => {
const mechanisms = getAvailableMechanisms(element, NS, saslFactory);
if (mechanisms.length === 0) {
const streamFeatures = await getStreamFeatures({ element, features });
const fast_available = !!fast?.mechanism;

if (mechanisms.length === 0 && !fast_available) {
throw new SASLError("SASL: No compatible mechanism available.");
}

const streamFeatures = await getStreamFeatures({ element, features });
const fast_available = !!fast?.mechanism;
await onAuthenticate(done, mechanisms, fast_available && fast);

async function done(credentials, mechanism, userAgent) {
Expand Down
43 changes: 43 additions & 0 deletions packages/sasl2/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,49 @@ test("with function credentials", async () => {
expect(entity.jid.toString()).toBe(jid);
});

test("with FAST token", async () => {
const mech = "HT-SHA-256-NONE";
function onAuthenticate(authenticate, mechanisms, fast) {
expect(mechanisms).toEqual([]);
expect(fast.mechanism).toEqual(mech);
return authenticate({ token: { token: "hai", mechanism: fast.mechanism } }, null, userAgent);
}

const { entity } = mockClient({ credentials: onAuthenticate });

entity.mockInput(
<features xmlns="http://etherx.jabber.org/streams">
<authentication xmlns="urn:xmpp:sasl:2">
<inline>
<fast xmlns="urn:xmpp:fast:0">
<mechanism>{mech}</mechanism>
</fast>
</inline>
</authentication>
</features>,
);

expect(await promise(entity, "send")).toEqual(
<authenticate xmlns="urn:xmpp:sasl:2" mechanism={mech}>
<initial-response>bnVsbACNMNimsTBnxS04m8x7wgKjBHdDUL/nXPU4J4vqxqjBIg==</initial-response>
{userAgent}
<fast xmlns="urn:xmpp:fast:0" />
</authenticate>,
);

const jid = "username@localhost/example~Ln8YSSzsyK-b_3-vIFvOJNnE";

expect(entity.jid?.toString()).not.toBe(jid);

entity.mockInput(
<success xmlns="urn:xmpp:sasl:2">
<authorization-identifier>{jid}</authorization-identifier>
</success>,
);

expect(entity.jid.toString()).toBe(jid);
});

test("failure", async () => {
const { entity } = mockClient({ credentials, userAgent });

Expand Down

0 comments on commit 33b77a7

Please sign in to comment.