diff --git a/package-lock.json b/package-lock.json
index 260773a6..13d18a4d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14326,9 +14326,6 @@
"name": "@xmpp/sasl-ht-sha-256-none",
"version": "0.14.0",
"license": "ISC",
- "dependencies": {
- "create-hmac": "^1.1.7"
- },
"engines": {
"node": ">= 20"
}
diff --git a/packages/client/index.js b/packages/client/index.js
index 81e18b62..9888f5af 100644
--- a/packages/client/index.js
+++ b/packages/client/index.js
@@ -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(
diff --git a/packages/client/package.json b/packages/client/package.json
index 78efcbe7..f23b77b7 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -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"
diff --git a/packages/sasl-ht-sha-256-none/index.js b/packages/sasl-ht-sha-256-none/index.js
index 195c4f2f..591fa32f 100644
--- a/packages/sasl-ht-sha-256-none/index.js
+++ b/packages/sasl-ht-sha-256-none/index.js
@@ -1,5 +1,4 @@
// https://datatracker.ietf.org/doc/draft-schmaus-kitten-sasl-ht/
-import createHmac from "create-hmac";
export function Mechanism() {}
@@ -7,17 +6,35 @@ 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");
}
};
diff --git a/packages/sasl-ht-sha-256-none/package.json b/packages/sasl-ht-sha-256-none/package.json
index 84b07016..6af011c2 100644
--- a/packages/sasl-ht-sha-256-none/package.json
+++ b/packages/sasl-ht-sha-256-none/package.json
@@ -12,9 +12,6 @@
"XMPP",
"sasl"
],
- "dependencies": {
- "create-hmac": "^1.1.7"
- },
"engines": {
"node": ">= 20"
},
diff --git a/packages/sasl/index.js b/packages/sasl/index.js
index 2ae4ae4e..7c47312a 100644
--- a/packages/sasl/index.js
+++ b/packages/sasl/index.js
@@ -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",
diff --git a/packages/sasl2/index.js b/packages/sasl2/index.js
index a00d83a2..d81ddfed 100644
--- a/packages/sasl2/index.js
+++ b/packages/sasl2/index.js
@@ -38,7 +38,7 @@ 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,
]),
@@ -46,8 +46,8 @@ async function authenticate({
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",
@@ -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
@@ -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) {
diff --git a/packages/sasl2/test.js b/packages/sasl2/test.js
index 547de79c..e311e264 100644
--- a/packages/sasl2/test.js
+++ b/packages/sasl2/test.js
@@ -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(
+
+
+
+
+ {mech}
+
+
+
+ ,
+ );
+
+ expect(await promise(entity, "send")).toEqual(
+
+ bnVsbACNMNimsTBnxS04m8x7wgKjBHdDUL/nXPU4J4vqxqjBIg==
+ {userAgent}
+
+ ,
+ );
+
+ const jid = "username@localhost/example~Ln8YSSzsyK-b_3-vIFvOJNnE";
+
+ expect(entity.jid?.toString()).not.toBe(jid);
+
+ entity.mockInput(
+
+ {jid}
+ ,
+ );
+
+ expect(entity.jid.toString()).toBe(jid);
+});
+
test("failure", async () => {
const { entity } = mockClient({ credentials, userAgent });