mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge pull request #4100 from sroebert/feature/sha-digest-algorithms
Added SHA-256 and SHA-512-256 digest authentication
This commit is contained in:
commit
a10d07d1dc
@ -699,25 +699,43 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|||||||
});
|
});
|
||||||
|
|
||||||
const md5 = (value) => { return crypto.createHash('md5').update(value).digest('hex') }
|
const md5 = (value) => { return crypto.createHash('md5').update(value).digest('hex') }
|
||||||
|
const sha256 = (value) => { return crypto.createHash('sha256').update(value).digest('hex') }
|
||||||
|
const sha512 = (value) => { return crypto.createHash('sha512').update(value).digest('hex') }
|
||||||
|
|
||||||
|
function digestCompute(algorithm, value) {
|
||||||
|
var lowercaseAlgorithm = ""
|
||||||
|
if (algorithm) {
|
||||||
|
lowercaseAlgorithm = algorithm.toLowerCase().replace(/-sess$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lowercaseAlgorithm === "sha-256") {
|
||||||
|
return sha256(value)
|
||||||
|
} else if (lowercaseAlgorithm === "sha-512-256") {
|
||||||
|
var hash = sha512(value)
|
||||||
|
return hash.slice(0, 64) // Only use the first 256 bits
|
||||||
|
} else {
|
||||||
|
return md5(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function ha1Compute(algorithm, user, realm, pass, nonce, cnonce) {
|
function ha1Compute(algorithm, user, realm, pass, nonce, cnonce) {
|
||||||
/**
|
/**
|
||||||
* RFC 2617: handle both MD5 and MD5-sess algorithms.
|
* RFC 2617: handle both standard and -sess algorithms.
|
||||||
*
|
*
|
||||||
* If the algorithm directive's value is "MD5" or unspecified, then HA1 is
|
* If the algorithm directive's value ends with "-sess", then HA1 is
|
||||||
* HA1=MD5(username:realm:password)
|
* HA1=digestCompute(digestCompute(username:realm:password):nonce:cnonce)
|
||||||
* If the algorithm directive's value is "MD5-sess", then HA1 is
|
*
|
||||||
* HA1=MD5(MD5(username:realm:password):nonce:cnonce)
|
* If the algorithm directive's value does not end with "-sess", then HA1 is
|
||||||
|
* HA1=digestCompute(username:realm:password)
|
||||||
*/
|
*/
|
||||||
var ha1 = md5(user + ':' + realm + ':' + pass)
|
var ha1 = digestCompute(algorithm, user + ':' + realm + ':' + pass)
|
||||||
if (algorithm && algorithm.toLowerCase() === 'md5-sess') {
|
if (algorithm && /-sess$/i.test(algorithm)) {
|
||||||
return md5(ha1 + ':' + nonce + ':' + cnonce)
|
return digestCompute(algorithm, ha1 + ':' + nonce + ':' + cnonce)
|
||||||
} else {
|
} else {
|
||||||
return ha1
|
return ha1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function buildDigestHeader(user, pass, method, path, authHeader) {
|
function buildDigestHeader(user, pass, method, path, authHeader) {
|
||||||
var challenge = {}
|
var challenge = {}
|
||||||
var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
|
var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
|
||||||
@ -732,10 +750,10 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|||||||
var nc = qop && '00000001'
|
var nc = qop && '00000001'
|
||||||
var cnonce = qop && uuid().replace(/-/g, '')
|
var cnonce = qop && uuid().replace(/-/g, '')
|
||||||
var ha1 = ha1Compute(challenge.algorithm, user, challenge.realm, pass, challenge.nonce, cnonce)
|
var ha1 = ha1Compute(challenge.algorithm, user, challenge.realm, pass, challenge.nonce, cnonce)
|
||||||
var ha2 = md5(method + ':' + path)
|
var ha2 = digestCompute(challenge.algorithm, method + ':' + path)
|
||||||
var digestResponse = qop
|
var digestResponse = qop
|
||||||
? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
|
? digestCompute(challenge.algorithm, ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
|
||||||
: md5(ha1 + ':' + challenge.nonce + ':' + ha2)
|
: digestCompute(challenge.algorithm, ha1 + ':' + challenge.nonce + ':' + ha2)
|
||||||
var authValues = {
|
var authValues = {
|
||||||
username: user,
|
username: user,
|
||||||
realm: challenge.realm,
|
realm: challenge.realm,
|
||||||
|
@ -31,6 +31,7 @@ var multer = require("multer");
|
|||||||
var RED = require("nr-test-utils").require("node-red/lib/red");
|
var RED = require("nr-test-utils").require("node-red/lib/red");
|
||||||
var fs = require('fs-extra');
|
var fs = require('fs-extra');
|
||||||
var auth = require('basic-auth');
|
var auth = require('basic-auth');
|
||||||
|
var crypto = require("crypto");
|
||||||
const { version } = require("os");
|
const { version } = require("os");
|
||||||
const net = require('net')
|
const net = require('net')
|
||||||
|
|
||||||
@ -163,6 +164,100 @@ describe('HTTP Request Node', function() {
|
|||||||
delete process.env.NO_PROXY;
|
delete process.env.NO_PROXY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDigestPassword() {
|
||||||
|
return 'digest-test-password';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDigest(algorithm, value) {
|
||||||
|
var hash;
|
||||||
|
if (algorithm === 'SHA-256') {
|
||||||
|
hash = crypto.createHash('sha256');
|
||||||
|
} else if (algorithm === 'SHA-512-256') {
|
||||||
|
hash = crypto.createHash('sha512');
|
||||||
|
} else {
|
||||||
|
hash = crypto.createHash('md5');
|
||||||
|
}
|
||||||
|
|
||||||
|
var hex = hash.update(value).digest('hex');
|
||||||
|
if (algorithm === 'SHA-512-256') {
|
||||||
|
hex = hex.slice(0, 64);
|
||||||
|
}
|
||||||
|
return hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDigestResponse(req, algorithm, sess, realm, username, nonce, nc, cnonce, qop) {
|
||||||
|
var ha1 = getDigest(algorithm, username + ':' + realm + ':' + getDigestPassword());
|
||||||
|
if (sess) {
|
||||||
|
ha1 = getDigest(algorithm, ha1 + ':' + nonce + ':' + cnonce)
|
||||||
|
}
|
||||||
|
let ha2 = getDigest(algorithm, req.method + ':' + req.path);
|
||||||
|
return qop
|
||||||
|
? getDigest(algorithm, ha1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
|
||||||
|
: getDigest(algorithm, ha1 + ':' + nonce + ':' + ha2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDigestResponse(req, res, algorithm, sess, qop) {
|
||||||
|
let realm = "node-red";
|
||||||
|
let nonce = "123456";
|
||||||
|
let nc = '00000001';
|
||||||
|
let algorithmValue = sess ? `${algorithm}-sess` : algorithm;
|
||||||
|
|
||||||
|
let authHeader = req.headers['authorization'];
|
||||||
|
if (!authHeader) {
|
||||||
|
let qopField = qop ? `qop="${qop}", ` : '';
|
||||||
|
|
||||||
|
res.setHeader(
|
||||||
|
'WWW-Authenticate',
|
||||||
|
`Digest ${qopField}realm="${realm}", nonce="${nonce}", algorithm="${algorithmValue}"`
|
||||||
|
);
|
||||||
|
res.status(401).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var authFields = {};
|
||||||
|
let re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi;
|
||||||
|
for (;;) {
|
||||||
|
var match = re.exec(authHeader);
|
||||||
|
if (!match) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
authFields[match[1]] = match[2] || match[3];
|
||||||
|
}
|
||||||
|
console.log(JSON.stringify(authFields));
|
||||||
|
|
||||||
|
if (qop && authFields['qop'] != qop) {
|
||||||
|
console.log('test1');
|
||||||
|
res.status(401).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!authFields['username'] ||
|
||||||
|
!authFields['response'] ||
|
||||||
|
authFields['realm'] != realm ||
|
||||||
|
authFields['nonce'] != nonce ||
|
||||||
|
authFields['algorithm'] != algorithmValue
|
||||||
|
) {
|
||||||
|
console.log('test2');
|
||||||
|
res.status(401).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let username = authFields['username'];
|
||||||
|
let response = authFields['response'];
|
||||||
|
let cnonce = authFields['cnonce'] || '';
|
||||||
|
let expectedResponse = getDigestResponse(
|
||||||
|
req, algorithm, sess, realm, username, nonce, nc, cnonce, qop
|
||||||
|
);
|
||||||
|
if (!response || expectedResponse.toLowerCase() !== response.toLowerCase()) {
|
||||||
|
console.log('test3');
|
||||||
|
res.status(401).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(201).end();
|
||||||
|
}
|
||||||
|
|
||||||
before(function(done) {
|
before(function(done) {
|
||||||
|
|
||||||
testApp = express();
|
testApp = express();
|
||||||
@ -222,6 +317,21 @@ describe('HTTP Request Node', function() {
|
|||||||
}
|
}
|
||||||
res.json(result);
|
res.json(result);
|
||||||
});
|
});
|
||||||
|
testApp.get('/authenticate-digest-md5', function(req, res){
|
||||||
|
handleDigestResponse(req, res, "MD5", false, false);
|
||||||
|
});
|
||||||
|
testApp.get('/authenticate-digest-md5-sess', function(req, res){
|
||||||
|
handleDigestResponse(req, res, "MD5", true, 'auth');
|
||||||
|
});
|
||||||
|
testApp.get('/authenticate-digest-md5-qop', function(req, res){
|
||||||
|
handleDigestResponse(req, res, "MD5", false, 'auth');
|
||||||
|
});
|
||||||
|
testApp.get('/authenticate-digest-sha-256', function(req, res){
|
||||||
|
handleDigestResponse(req, res, "SHA-256", false, 'auth');
|
||||||
|
});
|
||||||
|
testApp.get('/authenticate-digest-sha-512-256', function(req, res){
|
||||||
|
handleDigestResponse(req, res, "SHA-512-256", false, 'auth');
|
||||||
|
});
|
||||||
testApp.get('/proxyAuthenticate', function(req, res){
|
testApp.get('/proxyAuthenticate', function(req, res){
|
||||||
// var user = auth.parse(req.headers['proxy-authorization']);
|
// var user = auth.parse(req.headers['proxy-authorization']);
|
||||||
var result = {
|
var result = {
|
||||||
@ -2018,6 +2128,100 @@ describe('HTTP Request Node', function() {
|
|||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
it('should authenticate on server - digest MD5', function(done) {
|
||||||
|
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",authType:"digest", url:getTestURL('/authenticate-digest-md5')},
|
||||||
|
{id:"n2", type:"helper"}];
|
||||||
|
helper.load(httpRequestNode, flow, function() {
|
||||||
|
var n1 = helper.getNode("n1");
|
||||||
|
var n2 = helper.getNode("n2");
|
||||||
|
n1.credentials = {user:'xxxuser', password:getDigestPassword()};
|
||||||
|
n2.on("input", function(msg) {
|
||||||
|
try {
|
||||||
|
msg.should.have.property('statusCode',201);
|
||||||
|
done();
|
||||||
|
} catch(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
n1.receive({payload:"foo"});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should authenticate on server - digest MD5 sess', function(done) {
|
||||||
|
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",authType:"digest", url:getTestURL('/authenticate-digest-md5-sess')},
|
||||||
|
{id:"n2", type:"helper"}];
|
||||||
|
helper.load(httpRequestNode, flow, function() {
|
||||||
|
var n1 = helper.getNode("n1");
|
||||||
|
var n2 = helper.getNode("n2");
|
||||||
|
n1.credentials = {user:'xxxuser', password:getDigestPassword()};
|
||||||
|
n2.on("input", function(msg) {
|
||||||
|
try {
|
||||||
|
msg.should.have.property('statusCode',201);
|
||||||
|
done();
|
||||||
|
} catch(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
n1.receive({payload:"foo"});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should authenticate on server - digest MD5 qop', function(done) {
|
||||||
|
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",authType:"digest", url:getTestURL('/authenticate-digest-md5-qop')},
|
||||||
|
{id:"n2", type:"helper"}];
|
||||||
|
helper.load(httpRequestNode, flow, function() {
|
||||||
|
var n1 = helper.getNode("n1");
|
||||||
|
var n2 = helper.getNode("n2");
|
||||||
|
n1.credentials = {user:'xxxuser', password:getDigestPassword()};
|
||||||
|
n2.on("input", function(msg) {
|
||||||
|
try {
|
||||||
|
msg.should.have.property('statusCode',201);
|
||||||
|
done();
|
||||||
|
} catch(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
n1.receive({payload:"foo"});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should authenticate on server - digest SHA-256', function(done) {
|
||||||
|
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",authType:"digest", url:getTestURL('/authenticate-digest-sha-256')},
|
||||||
|
{id:"n2", type:"helper"}];
|
||||||
|
helper.load(httpRequestNode, flow, function() {
|
||||||
|
var n1 = helper.getNode("n1");
|
||||||
|
var n2 = helper.getNode("n2");
|
||||||
|
n1.credentials = {user:'xxxuser', password:getDigestPassword()};
|
||||||
|
n2.on("input", function(msg) {
|
||||||
|
try {
|
||||||
|
msg.should.have.property('statusCode',201);
|
||||||
|
done();
|
||||||
|
} catch(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
n1.receive({payload:"foo"});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should authenticate on server - digest SHA-512-256', function(done) {
|
||||||
|
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",authType:"digest", url:getTestURL('/authenticate-digest-sha-512-256')},
|
||||||
|
{id:"n2", type:"helper"}];
|
||||||
|
helper.load(httpRequestNode, flow, function() {
|
||||||
|
var n1 = helper.getNode("n1");
|
||||||
|
var n2 = helper.getNode("n2");
|
||||||
|
n1.credentials = {user:'xxxuser', password:getDigestPassword()};
|
||||||
|
n2.on("input", function(msg) {
|
||||||
|
try {
|
||||||
|
msg.should.have.property('statusCode',201);
|
||||||
|
done();
|
||||||
|
} catch(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
n1.receive({payload:"foo"});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('file-upload', function() {
|
describe('file-upload', function() {
|
||||||
|
Loading…
Reference in New Issue
Block a user