forked from svrjs/svrjs
Add non-standard codes and HTTP authentication middleware, and SHA256 utility function.
This commit is contained in:
parent
eb0d30f307
commit
c1900ee128
4 changed files with 750 additions and 1 deletions
|
@ -184,7 +184,8 @@ let middleware = [
|
|||
require("./middleware/webRootPostfixes.js"),
|
||||
require("./middleware/rewriteURL.js"),
|
||||
require("./middleware/responseHeaders.js"),
|
||||
require("./middleware/checkForbiddenPaths.js")
|
||||
require("./middleware/checkForbiddenPaths.js"),
|
||||
require("./middleware/nonStandardCodesAndHttpAuthentication.js")
|
||||
];
|
||||
|
||||
function addMiddleware(mw) {
|
||||
|
|
453
src/middleware/nonStandardCodesAndHttpAuthentication.js
Normal file
453
src/middleware/nonStandardCodesAndHttpAuthentication.js
Normal file
|
@ -0,0 +1,453 @@
|
|||
const os = require("os");
|
||||
const sha256 = require("../utils/sha256.js");
|
||||
const createRegex = require("../utils/createRegex.js");
|
||||
const ipMatch = require("../utils/ipMatch.js");
|
||||
const matchHostname = require("../utils/matchHostname.js");
|
||||
const cluster = require("../utils/clusterBunShim.js");
|
||||
|
||||
// Brute force protection-related
|
||||
let bruteForceDb = {};
|
||||
|
||||
// PBKDF2/scrypt cache
|
||||
let pbkdf2Cache = [];
|
||||
let scryptCache = [];
|
||||
let passwordHashCacheIntervalId = -1;
|
||||
|
||||
if (!cluster.isPrimary) {
|
||||
passwordHashCacheIntervalId = setInterval(function () {
|
||||
pbkdf2Cache = pbkdf2Cache.filter(function (entry) {
|
||||
return entry.addDate > new Date() - 3600000;
|
||||
});
|
||||
scryptCache = scryptCache.filter(function (entry) {
|
||||
return entry.addDate > new Date() - 3600000;
|
||||
});
|
||||
}, 1800000);
|
||||
}
|
||||
|
||||
module.exports = (req, res, logFacilities, config, next) => {
|
||||
let nonscodeIndex = -1;
|
||||
let authIndex = -1;
|
||||
let regexI = [];
|
||||
let hrefWithoutDuplicateSlashes = "";
|
||||
const reqip = req.socket.realRemoteAddress
|
||||
? req.socket.realRemoteAddress
|
||||
: req.socket.remoteAddress;
|
||||
|
||||
// Scan for non-standard codes
|
||||
if (!req.isProxy && config.nonStandardCodes != undefined) {
|
||||
for (let i = 0; i < config.nonStandardCodes.length; i++) {
|
||||
if (
|
||||
matchHostname(config.nonStandardCodes[i].host, req.headers.host) &&
|
||||
ipMatch(
|
||||
config.nonStandardCodes[i].ip,
|
||||
req.socket ? req.socket.localAddress : undefined,
|
||||
)
|
||||
) {
|
||||
let isMatch = false;
|
||||
hrefWithoutDuplicateSlashes = req.parsedURL.pathname.replace(
|
||||
/\/+/g,
|
||||
"/",
|
||||
);
|
||||
if (config.nonStandardCodes[i].regex) {
|
||||
// Regex match
|
||||
var createdRegex = createRegex(
|
||||
config.nonStandardCodes[i].regex,
|
||||
true,
|
||||
);
|
||||
isMatch =
|
||||
req.url.match(createdRegex) ||
|
||||
hrefWithoutDuplicateSlashes.match(createdRegex);
|
||||
regexI[i] = createdRegex;
|
||||
} else {
|
||||
// Non-regex match
|
||||
isMatch =
|
||||
config.nonStandardCodes[i].url == hrefWithoutDuplicateSlashes ||
|
||||
(os.platform() == "win32" &&
|
||||
config.nonStandardCodes[i].url.toLowerCase() ==
|
||||
hrefWithoutDuplicateSlashes.toLowerCase());
|
||||
}
|
||||
if (isMatch) {
|
||||
if (config.nonStandardCodes[i].scode == 401) {
|
||||
// HTTP authentication
|
||||
if (authIndex == -1) {
|
||||
authIndex = i;
|
||||
}
|
||||
} else {
|
||||
if (nonscodeIndex == -1) {
|
||||
if (
|
||||
(config.nonStandardCodes[i].scode == 403 ||
|
||||
config.nonStandardCodes[i].scode == 451) &&
|
||||
config.nonStandardCodes[i].users !== undefined
|
||||
) {
|
||||
if (config.nonStandardCodes[i].users.check(reqip))
|
||||
nonscodeIndex = i;
|
||||
} else {
|
||||
nonscodeIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle non-standard codes
|
||||
if (nonscodeIndex > -1) {
|
||||
let nonscode = config.nonStandardCodes[nonscodeIndex];
|
||||
if (
|
||||
nonscode.scode == 301 ||
|
||||
nonscode.scode == 302 ||
|
||||
nonscode.scode == 307 ||
|
||||
nonscode.scode == 308
|
||||
) {
|
||||
let location = "";
|
||||
if (regexI[nonscodeIndex]) {
|
||||
location = req.url.replace(regexI[nonscodeIndex], nonscode.location);
|
||||
if (location == req.url) {
|
||||
// Fallback replacement
|
||||
location = hrefWithoutDuplicateSlashes.replace(
|
||||
regexI[nonscodeIndex],
|
||||
nonscode.location,
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
req.url.split("?")[1] == undefined ||
|
||||
req.url.split("?")[1] == null ||
|
||||
req.url.split("?")[1] == "" ||
|
||||
req.url.split("?")[1] == " "
|
||||
) {
|
||||
location = nonscode.location;
|
||||
} else {
|
||||
location = nonscode.location + "?" + req.url.split("?")[1];
|
||||
}
|
||||
res.redirect(
|
||||
location,
|
||||
nonscode.scode == 302 || nonscode.scode == 307,
|
||||
nonscode.scode == 307 || nonscode.scode == 308,
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
res.error(nonscode.scode);
|
||||
if (nonscode.scode == 403) {
|
||||
logFacilities.errmessage("Content blocked.");
|
||||
} else if (nonscode.scode == 410) {
|
||||
logFacilities.errmessage("Content is gone.");
|
||||
} else if (nonscode.scode == 418) {
|
||||
logFacilities.errmessage("SVR.JS is always a teapot ;)");
|
||||
} else {
|
||||
logFacilities.errmessage("Client fails receiving content.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle HTTP authentication
|
||||
if (authIndex > -1) {
|
||||
let authcode = config.nonStandardCodes[authIndex];
|
||||
|
||||
// Function to check if passwords match
|
||||
const checkIfPasswordMatches = (list, password, callback, _i) => {
|
||||
if (!_i) _i = 0;
|
||||
const cb = function (hash) {
|
||||
if (hash == list[_i].pass) {
|
||||
callback(true);
|
||||
} else if (_i >= list.length - 1) {
|
||||
callback(false);
|
||||
} else {
|
||||
checkIfPasswordMatches(list, password, callback, _i + 1);
|
||||
}
|
||||
};
|
||||
let hashedPassword = sha256(password + list[_i].salt);
|
||||
let cacheEntry = null;
|
||||
if (list[_i].scrypt) {
|
||||
if (!crypto.scrypt) {
|
||||
res.error(
|
||||
500,
|
||||
new Error(
|
||||
"SVR.JS doesn't support scrypt-hashed passwords on Node.JS versions without scrypt hash support.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
cacheEntry = scryptCache.find(function (entry) {
|
||||
return (
|
||||
entry.password == hashedPassword && entry.salt == list[_i].salt
|
||||
);
|
||||
});
|
||||
if (cacheEntry) {
|
||||
cb(cacheEntry.hash);
|
||||
} else {
|
||||
crypto.scrypt(
|
||||
password,
|
||||
list[_i].salt,
|
||||
64,
|
||||
function (err, derivedKey) {
|
||||
if (err) {
|
||||
res.error(500, err);
|
||||
} else {
|
||||
const key = derivedKey.toString("hex");
|
||||
scryptCache.push({
|
||||
hash: key,
|
||||
password: hashedPassword,
|
||||
salt: list[_i].salt,
|
||||
addDate: new Date(),
|
||||
});
|
||||
cb(key);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (list[_i].pbkdf2) {
|
||||
if (crypto.__disabled__ !== undefined) {
|
||||
res.error(
|
||||
500,
|
||||
new Error(
|
||||
"SVR.JS doesn't support PBKDF2-hashed passwords on Node.JS versions without crypto support.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
cacheEntry = pbkdf2Cache.find(function (entry) {
|
||||
return (
|
||||
entry.password == hashedPassword && entry.salt == list[_i].salt
|
||||
);
|
||||
});
|
||||
if (cacheEntry) {
|
||||
cb(cacheEntry.hash);
|
||||
} else {
|
||||
crypto.pbkdf2(
|
||||
password,
|
||||
list[_i].salt,
|
||||
36250,
|
||||
64,
|
||||
"sha512",
|
||||
function (err, derivedKey) {
|
||||
if (err) {
|
||||
res.error(500, err);
|
||||
} else {
|
||||
const key = derivedKey.toString("hex");
|
||||
pbkdf2Cache.push({
|
||||
hash: key,
|
||||
password: hashedPassword,
|
||||
salt: list[_i].salt,
|
||||
addDate: new Date(),
|
||||
});
|
||||
cb(key);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cb(hashedPassword);
|
||||
}
|
||||
};
|
||||
|
||||
const authorizedCallback = (bruteProtection) => {
|
||||
try {
|
||||
const ha = config.getCustomHeaders();
|
||||
ha["WWW-Authenticate"] =
|
||||
'Basic realm="' +
|
||||
(authcode.realm
|
||||
? authcode.realm.replace(/(\\|")/g, "\\$1")
|
||||
: "SVR.JS HTTP Basic Authorization") +
|
||||
'", charset="UTF-8"';
|
||||
const credentials = req.headers["authorization"];
|
||||
if (!credentials) {
|
||||
res.error(401, ha);
|
||||
logFacilities.errmessage("Content needs authorization.");
|
||||
return;
|
||||
}
|
||||
const credentialsMatch = credentials.match(/^Basic (.+)$/);
|
||||
if (!credentialsMatch) {
|
||||
res.error(401, ha);
|
||||
logFacilities.errmessage("Malformed credentials.");
|
||||
return;
|
||||
}
|
||||
const decodedCredentials = Buffer.from(
|
||||
credentialsMatch[1],
|
||||
"base64",
|
||||
).toString("utf8");
|
||||
const decodedCredentialsMatch =
|
||||
decodedCredentials.match(/^([^:]*):(.*)$/);
|
||||
if (!decodedCredentialsMatch) {
|
||||
res.error(401, ha);
|
||||
logFacilities.errmessage("Malformed credentials.");
|
||||
return;
|
||||
}
|
||||
const username = decodedCredentialsMatch[1];
|
||||
const password = decodedCredentialsMatch[2];
|
||||
let usernameMatch = [];
|
||||
let sha256Count = 0;
|
||||
let pbkdf2Count = 0;
|
||||
let scryptCount = 0;
|
||||
if (!authcode.userList || authcode.userList.indexOf(username) > -1) {
|
||||
usernameMatch = config.users.filter(function (entry) {
|
||||
if (entry.scrypt) {
|
||||
scryptCount++;
|
||||
} else if (entry.pbkdf2) {
|
||||
pbkdf2Count++;
|
||||
} else {
|
||||
sha256Count++;
|
||||
}
|
||||
return entry.name == username;
|
||||
});
|
||||
}
|
||||
if (usernameMatch.length == 0) {
|
||||
// Pushing false user match to prevent time-based user enumeration
|
||||
let fakeCredentials = {
|
||||
name: username,
|
||||
pass: "SVRJSAWebServerRunningOnNodeJS",
|
||||
salt: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0",
|
||||
};
|
||||
if (!process.isBun) {
|
||||
if (scryptCount > sha256Count && scryptCount > pbkdf2Count) {
|
||||
fakeCredentials.scrypt = true;
|
||||
} else if (pbkdf2Count > sha256Count) {
|
||||
fakeCredentials.pbkdf2 = true;
|
||||
}
|
||||
}
|
||||
usernameMatch.push(fakeCredentials);
|
||||
}
|
||||
checkIfPasswordMatches(usernameMatch, password, function (authorized) {
|
||||
try {
|
||||
if (!authorized) {
|
||||
if (bruteProtection) {
|
||||
if (process.send) {
|
||||
process.send("\x12AUTHW" + reqip);
|
||||
} else {
|
||||
if (!bruteForceDb[reqip])
|
||||
bruteForceDb[reqip] = {
|
||||
invalidAttempts: 0,
|
||||
};
|
||||
bruteForceDb[reqip].invalidAttempts++;
|
||||
if (bruteForceDb[reqip].invalidAttempts >= 10) {
|
||||
bruteForceDb[reqip].lastAttemptDate = new Date();
|
||||
}
|
||||
}
|
||||
}
|
||||
res.error(401, ha);
|
||||
logFacilities.errmessage(
|
||||
'User "' +
|
||||
String(username).replace(/[\r\n]/g, "") +
|
||||
'" failed to log in.',
|
||||
);
|
||||
} else {
|
||||
if (bruteProtection) {
|
||||
if (process.send) {
|
||||
process.send("\x12AUTHR" + reqip);
|
||||
} else {
|
||||
if (bruteForceDb[reqip])
|
||||
bruteForceDb[reqip] = {
|
||||
invalidAttempts: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
logFacilities.reqmessage(
|
||||
'Client is logged in as "' +
|
||||
String(username).replace(/[\r\n]/g, "") +
|
||||
'".',
|
||||
);
|
||||
req.authUser = username;
|
||||
next();
|
||||
}
|
||||
} catch (err) {
|
||||
res.error(500, err);
|
||||
return;
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
res.error(500, err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
if (authcode.disableBruteProtection) {
|
||||
// Don't brute-force protect it, just do HTTP authentication
|
||||
authorizedCallback(false);
|
||||
} else if (!process.send) {
|
||||
// Query data from JS object database
|
||||
if (
|
||||
!bruteForceDb[reqip] ||
|
||||
!bruteForceDb[reqip].lastAttemptDate ||
|
||||
new Date() - 300000 >= bruteForceDb[reqip].lastAttemptDate
|
||||
) {
|
||||
if (bruteForceDb[reqip] && bruteForceDb[reqip].invalidAttempts >= 10)
|
||||
bruteForceDb[reqip] = {
|
||||
invalidAttempts: 5,
|
||||
};
|
||||
authorizedCallback(true);
|
||||
} else {
|
||||
res.error(429);
|
||||
logFacilities.errmessage("Brute force limit reached!");
|
||||
}
|
||||
} else {
|
||||
var listenerEmitted = false;
|
||||
|
||||
// Listen for brute-force protection response
|
||||
const authMessageListener = (message) => {
|
||||
if (listenerEmitted) return;
|
||||
if (message == "\x14AUTHA" + reqip || message == "\x14AUTHD" + reqip) {
|
||||
process.removeListener("message", authMessageListener);
|
||||
listenerEmitted = true;
|
||||
}
|
||||
if (message == "\x14AUTHD" + reqip) {
|
||||
res.error(429);
|
||||
logFacilities.errmessage("Brute force limit reached!");
|
||||
} else if (message == "\x14AUTHA" + reqip) {
|
||||
authorizedCallback(true);
|
||||
}
|
||||
};
|
||||
process.on("message", authMessageListener);
|
||||
process.send("\x12AUTHQ" + reqip);
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
// IPC listener for brute force protection
|
||||
module.exports.mainMessageListenerWrapper = (worker) => {
|
||||
return function bruteForceListener(message) {
|
||||
let ip = "";
|
||||
if (message.substring(0, 6) == "\x12AUTHQ") {
|
||||
ip = message.substring(6);
|
||||
if (
|
||||
!bruteForceDb[ip] ||
|
||||
!bruteForceDb[ip].lastAttemptDate ||
|
||||
new Date() - 300000 >= bruteForceDb[ip].lastAttemptDate
|
||||
) {
|
||||
if (bruteForceDb[ip] && bruteForceDb[ip].invalidAttempts >= 10)
|
||||
bruteForceDb[ip] = {
|
||||
invalidAttempts: 5,
|
||||
};
|
||||
worker.send("\x14AUTHA" + ip);
|
||||
} else {
|
||||
worker.send("\x14AUTHD" + ip);
|
||||
}
|
||||
} else if (message.substring(0, 6) == "\x12AUTHR") {
|
||||
ip = message.substring(6);
|
||||
if (bruteForceDb[ip])
|
||||
bruteForceDb[ip] = {
|
||||
invalidAttempts: 0,
|
||||
};
|
||||
} else if (message.substring(0, 6) == "\x12AUTHW") {
|
||||
ip = message.substring(6);
|
||||
if (!bruteForceDb[ip])
|
||||
bruteForceDb[ip] = {
|
||||
invalidAttempts: 0,
|
||||
};
|
||||
bruteForceDb[ip].invalidAttempts++;
|
||||
if (bruteForceDb[ip].invalidAttempts >= 10) {
|
||||
bruteForceDb[ip].lastAttemptDate = new Date();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.commands = {
|
||||
stop: (args, passCommand) => {
|
||||
clearInterval(passwordHashCacheIntervalId);
|
||||
passCommand(args);
|
||||
},
|
||||
};
|
236
src/utils/sha256.js
Normal file
236
src/utils/sha256.js
Normal file
|
@ -0,0 +1,236 @@
|
|||
let crypto = { __disabled__: null };
|
||||
try {
|
||||
crypto = require("crypto");
|
||||
} catch (err) {
|
||||
// Crypto support is disabled.
|
||||
}
|
||||
|
||||
// SHA256 function
|
||||
function sha256(s) {
|
||||
if (crypto.__disabled__ === undefined) {
|
||||
let hash = crypto.createHash("SHA256");
|
||||
hash.update(s);
|
||||
return hash.digest("hex");
|
||||
} else {
|
||||
const chrsz = 8;
|
||||
const hexcase = 0;
|
||||
|
||||
const safeAdd = (x, y) => {
|
||||
const lsw = (x & 0xffff) + (y & 0xffff);
|
||||
const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
||||
return (msw << 16) | (lsw & 0xffff);
|
||||
};
|
||||
|
||||
const S = (X, n) => {
|
||||
return (X >>> n) | (X << (32 - n));
|
||||
};
|
||||
|
||||
const R = (X, n) => {
|
||||
return X >>> n;
|
||||
};
|
||||
|
||||
const Ch = (x, y, z) => {
|
||||
return (x & y) ^ (~x & z);
|
||||
};
|
||||
|
||||
const Maj = (x, y, z) => {
|
||||
return (x & y) ^ (x & z) ^ (y & z);
|
||||
};
|
||||
|
||||
const Sigma0256 = (x) => {
|
||||
return S(x, 2) ^ S(x, 13) ^ S(x, 22);
|
||||
};
|
||||
|
||||
const Sigma1256 = (x) => {
|
||||
return S(x, 6) ^ S(x, 11) ^ S(x, 25);
|
||||
};
|
||||
|
||||
const Gamma0256 = (x) => {
|
||||
return S(x, 7) ^ S(x, 18) ^ R(x, 3);
|
||||
};
|
||||
|
||||
const Gamma1256 = (x) => {
|
||||
return S(x, 17) ^ S(x, 19) ^ R(x, 10);
|
||||
};
|
||||
|
||||
function coreSha256(m, l) {
|
||||
const K = new Array(
|
||||
0x428a2f98,
|
||||
0x71374491,
|
||||
0xb5c0fbcf,
|
||||
0xe9b5dba5,
|
||||
0x3956c25b,
|
||||
0x59f111f1,
|
||||
0x923f82a4,
|
||||
0xab1c5ed5,
|
||||
0xd807aa98,
|
||||
0x12835b01,
|
||||
0x243185be,
|
||||
0x550c7dc3,
|
||||
0x72be5d74,
|
||||
0x80deb1fe,
|
||||
0x9bdc06a7,
|
||||
0xc19bf174,
|
||||
0xe49b69c1,
|
||||
0xefbe4786,
|
||||
0xfc19dc6,
|
||||
0x240ca1cc,
|
||||
0x2de92c6f,
|
||||
0x4a7484aa,
|
||||
0x5cb0a9dc,
|
||||
0x76f988da,
|
||||
0x983e5152,
|
||||
0xa831c66d,
|
||||
0xb00327c8,
|
||||
0xbf597fc7,
|
||||
0xc6e00bf3,
|
||||
0xd5a79147,
|
||||
0x6ca6351,
|
||||
0x14292967,
|
||||
0x27b70a85,
|
||||
0x2e1b2138,
|
||||
0x4d2c6dfc,
|
||||
0x53380d13,
|
||||
0x650a7354,
|
||||
0x766a0abb,
|
||||
0x81c2c92e,
|
||||
0x92722c85,
|
||||
0xa2bfe8a1,
|
||||
0xa81a664b,
|
||||
0xc24b8b70,
|
||||
0xc76c51a3,
|
||||
0xd192e819,
|
||||
0xd6990624,
|
||||
0xf40e3585,
|
||||
0x106aa070,
|
||||
0x19a4c116,
|
||||
0x1e376c08,
|
||||
0x2748774c,
|
||||
0x34b0bcb5,
|
||||
0x391c0cb3,
|
||||
0x4ed8aa4a,
|
||||
0x5b9cca4f,
|
||||
0x682e6ff3,
|
||||
0x748f82ee,
|
||||
0x78a5636f,
|
||||
0x84c87814,
|
||||
0x8cc70208,
|
||||
0x90befffa,
|
||||
0xa4506ceb,
|
||||
0xbef9a3f7,
|
||||
0xc67178f2,
|
||||
);
|
||||
let HASH = new Array(
|
||||
0x6a09e667,
|
||||
0xbb67ae85,
|
||||
0x3c6ef372,
|
||||
0xa54ff53a,
|
||||
0x510e527f,
|
||||
0x9b05688c,
|
||||
0x1f83d9ab,
|
||||
0x5be0cd19,
|
||||
);
|
||||
let W = new Array(64);
|
||||
let a, b, c, d, e, f, g, h, i, j;
|
||||
let T1, T2;
|
||||
|
||||
m[l >> 5] |= 0x80 << (24 - (l % 32));
|
||||
m[(((l + 64) >> 9) << 4) + 15] = l;
|
||||
|
||||
for (let i = 0; i < m.length; i += 16) {
|
||||
a = HASH[0];
|
||||
b = HASH[1];
|
||||
c = HASH[2];
|
||||
d = HASH[3];
|
||||
e = HASH[4];
|
||||
f = HASH[5];
|
||||
g = HASH[6];
|
||||
h = HASH[7];
|
||||
|
||||
for (let j = 0; j < 64; j++) {
|
||||
if (j < 16) W[j] = m[j + i];
|
||||
else
|
||||
W[j] = safeAdd(
|
||||
safeAdd(
|
||||
safeAdd(Gamma1256(W[j - 2]), W[j - 7]),
|
||||
Gamma0256(W[j - 15]),
|
||||
),
|
||||
W[j - 16],
|
||||
);
|
||||
|
||||
T1 = safeAdd(
|
||||
safeAdd(safeAdd(safeAdd(h, Sigma1256(e)), Ch(e, f, g)), K[j]),
|
||||
W[j],
|
||||
);
|
||||
T2 = safeAdd(Sigma0256(a), Maj(a, b, c));
|
||||
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = safeAdd(d, T1);
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = safeAdd(T1, T2);
|
||||
}
|
||||
|
||||
HASH[0] = safeAdd(a, HASH[0]);
|
||||
HASH[1] = safeAdd(b, HASH[1]);
|
||||
HASH[2] = safeAdd(c, HASH[2]);
|
||||
HASH[3] = safeAdd(d, HASH[3]);
|
||||
HASH[4] = safeAdd(e, HASH[4]);
|
||||
HASH[5] = safeAdd(f, HASH[5]);
|
||||
HASH[6] = safeAdd(g, HASH[6]);
|
||||
HASH[7] = safeAdd(h, HASH[7]);
|
||||
}
|
||||
return HASH;
|
||||
}
|
||||
|
||||
const str2binb = (str) => {
|
||||
let bin = Array();
|
||||
const mask = (1 << chrsz) - 1;
|
||||
for (let i = 0; i < str.length * chrsz; i += chrsz) {
|
||||
bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - (i % 32));
|
||||
}
|
||||
return bin;
|
||||
};
|
||||
|
||||
const Utf8Encode = (string) => {
|
||||
string = string.replace(/\r\n/g, "\n");
|
||||
let utftext = "";
|
||||
|
||||
for (let n = 0; n < string.length; n++) {
|
||||
let c = string.charCodeAt(n);
|
||||
|
||||
if (c < 128) {
|
||||
utftext += String.fromCharCode(c);
|
||||
} else if (c > 127 && c < 2048) {
|
||||
utftext += String.fromCharCode((c >> 6) | 192);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
} else {
|
||||
utftext += String.fromCharCode((c >> 12) | 224);
|
||||
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
}
|
||||
|
||||
return utftext;
|
||||
};
|
||||
|
||||
const binb2hex = (binarray) => {
|
||||
const hexTab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
|
||||
let str = "";
|
||||
for (let i = 0; i < binarray.length * 4; i++) {
|
||||
str +=
|
||||
hexTab.charAt((binarray[i >> 2] >> ((3 - (i % 4)) * 8 + 4)) & 0xf) +
|
||||
hexTab.charAt((binarray[i >> 2] >> ((3 - (i % 4)) * 8)) & 0xf);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
s = Utf8Encode(s);
|
||||
return binb2hex(coreSha256(str2binb(s), s.length * chrsz));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = sha256;
|
59
tests/utils/sha256.test.js
Normal file
59
tests/utils/sha256.test.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
const sha256 = require("../../src/utils/sha256");
|
||||
const crypto = require("crypto");
|
||||
|
||||
// Mock the crypto module to simulate the absence of crypto support
|
||||
jest.mock("crypto", () => ({
|
||||
createHash: jest.fn(() => ({
|
||||
update: jest.fn(),
|
||||
digest: jest.fn(() => "mockedHash"),
|
||||
})),
|
||||
}));
|
||||
|
||||
describe("SHA256 hash", () => {
|
||||
test("should use crypto module if available", () => {
|
||||
const result = sha256("test");
|
||||
expect(result).toBe("mockedHash");
|
||||
expect(crypto.createHash).toHaveBeenCalledWith("SHA256");
|
||||
});
|
||||
|
||||
test("should fallback to manual SHA256 implementation if crypto is disabled", () => {
|
||||
crypto.__disabled__ = null;
|
||||
const result = sha256("test");
|
||||
expect(result).toBe(
|
||||
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
|
||||
);
|
||||
});
|
||||
|
||||
test("should handle empty string", () => {
|
||||
crypto.__disabled__ = null;
|
||||
const result = sha256("");
|
||||
expect(result).toBe(
|
||||
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
);
|
||||
});
|
||||
|
||||
test("should handle special characters", () => {
|
||||
crypto.__disabled__ = null;
|
||||
const result = sha256("!@#$%^&*()");
|
||||
expect(result).toBe(
|
||||
"95ce789c5c9d18490972709838ca3a9719094bca3ac16332cfec0652b0236141",
|
||||
);
|
||||
});
|
||||
|
||||
test("should handle long strings", () => {
|
||||
crypto.__disabled__ = null;
|
||||
const longString = "a".repeat(1000);
|
||||
const result = sha256(longString);
|
||||
expect(result).toBe(
|
||||
"41edece42d63e8d9bf515a9ba6932e1c20cbc9f5a5d134645adb5db1b9737ea3",
|
||||
);
|
||||
});
|
||||
|
||||
test("should handle non-ASCII characters", () => {
|
||||
crypto.__disabled__ = null;
|
||||
const result = sha256("éñ");
|
||||
expect(result).toBe(
|
||||
"c53435f74d8215688e74112f1c6527ad31fd3b72939769a75d09a14cd8a80cfe",
|
||||
);
|
||||
});
|
||||
});
|
Reference in a new issue