Skip to content

Commit

Permalink
Implement support for 2617 MD5-sess algorithm.
Browse files Browse the repository at this point in the history
Update TODO list

Remove challenge.algorithm TODO note
  • Loading branch information
Dennis Keller committed Oct 8, 2015
1 parent d027211 commit 033e7fc
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 19 deletions.
23 changes: 19 additions & 4 deletions lib/auth.js
Expand Up @@ -50,8 +50,6 @@ Auth.prototype.bearer = function (bearer, sendImmediately) {

Auth.prototype.digest = function (method, path, authHeader) {
// TODO: More complete implementation of RFC 2617.
// - check challenge.algorithm
// - support algorithm="MD5-sess"
// - handle challenge.domain
// - support qop="auth-int" only
// - handle Authentication-Info (not necessarily?)
Expand All @@ -73,11 +71,28 @@ Auth.prototype.digest = function (method, path, authHeader) {
challenge[match[1]] = match[2] || match[3]
}

var ha1 = md5(self.user + ':' + challenge.realm + ':' + self.pass)
var ha2 = md5(method + ':' + path)
/**
* RFC 2617: handle both MD5 and MD5-sess algorithms.
*
* If the algorithm directive's value is "MD5" or unspecified, then HA1 is
* HA1=MD5(username:realm:password)
* If the algorithm directive's value is "MD5-sess", then HA1 is
* HA1=MD5(MD5(username:realm:password):nonce:cnonce)
*/
var ha1Compute = function (algorithm, user, realm, pass, nonce, cnonce) {
var ha1 = md5(user + ':' + realm + ':' + pass)
if (algorithm && algorithm.toLowerCase() === 'md5-sess') {
return md5(ha1 + ':' + nonce + ':' + cnonce)
} else {
return ha1
}
}

var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth'
var nc = qop && '00000001'
var cnonce = qop && uuid().replace(/-/g, '')
var ha1 = ha1Compute(challenge.algorithm, self.user, challenge.realm, self.pass, challenge.nonce, cnonce)
var ha2 = md5(method + ':' + path)
var digestResponse = qop
? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
: md5(ha1 + ':' + challenge.nonce + ':' + ha2)
Expand Down
99 changes: 84 additions & 15 deletions tests/test-digest-auth.js
Expand Up @@ -3,6 +3,7 @@
var http = require('http')
, request = require('../index')
, tape = require('tape')
, crypto = require('crypto')

function makeHeader() {
return [].join.call(arguments, ', ')
Expand All @@ -12,23 +13,27 @@ function makeHeaderRegex() {
return new RegExp('^' + makeHeader.apply(null, arguments) + '$')
}

function md5 (str) {
return crypto.createHash('md5').update(str).digest('hex')
}

var digestServer = http.createServer(function(req, res) {
var ok
, testHeader

if (req.url === '/test/') {
if (req.headers.authorization) {
testHeader = makeHeaderRegex(
'Digest username="test"',
'realm="Private"',
'nonce="WpcHS2/TBAA=dffcc0dbd5f96d49a5477166649b7c0ae3866a93"',
'uri="/test/"',
'qop=auth',
'response="[a-f0-9]{32}"',
'nc=00000001',
'cnonce="[a-f0-9]{32}"',
'algorithm=MD5',
'opaque="5ccc069c403ebaf9f0171e9517f40e41"'
'Digest username="test"',
'realm="Private"',
'nonce="WpcHS2/TBAA=dffcc0dbd5f96d49a5477166649b7c0ae3866a93"',
'uri="/test/"',
'qop=auth',
'response="[a-f0-9]{32}"',
'nc=00000001',
'cnonce="[a-f0-9]{32}"',
'algorithm=MD5',
'opaque="5ccc069c403ebaf9f0171e9517f40e41"'
)
if (testHeader.test(req.headers.authorization)) {
ok = true
Expand All @@ -40,11 +45,53 @@ var digestServer = http.createServer(function(req, res) {
// No auth header, send back WWW-Authenticate header
ok = false
res.setHeader('www-authenticate', makeHeader(
'Digest realm="Private"',
'nonce="WpcHS2/TBAA=dffcc0dbd5f96d49a5477166649b7c0ae3866a93"',
'algorithm=MD5',
'qop="auth"',
'opaque="5ccc069c403ebaf9f0171e9517f40e41"'
'Digest realm="Private"',
'nonce="WpcHS2/TBAA=dffcc0dbd5f96d49a5477166649b7c0ae3866a93"',
'algorithm=MD5',
'qop="auth"',
'opaque="5ccc069c403ebaf9f0171e9517f40e41"'
))
}
} else if (req.url === '/test/md5-sess') { // RFC 2716 MD5-sess w/ qop=auth
var user = 'test'
var realm = 'Private'
var pass = 'testing'
var nonce = 'WpcHS2/TBAA=dffcc0dbd5f96d49a5477166649b7c0ae3866a93'
var nonceCount = '00000001'
var qop = 'auth'
var algorithm = 'MD5-sess'
if (req.headers.authorization) {

//HA1=MD5(MD5(username:realm:password):nonce:cnonce)
//HA2=MD5(method:digestURI)
//response=MD5(HA1:nonce:nonceCount:clientNonce:qop:HA2)

var cnonce = /cnonce="(.*)"/.exec(req.headers.authorization)[1]
var ha1 = md5(md5(user + ':' + realm + ':' + pass) + ':' + nonce + ':' + cnonce)
var ha2 = md5('GET:/test/md5-sess')
var response = md5(ha1 + ':' + nonce + ':' + nonceCount + ':' + cnonce + ':' + qop + ':' + ha2)

testHeader = makeHeaderRegex(
'Digest username="' + user + '"',
'realm="' + realm + '"',
'nonce="' + nonce + '"',
'uri="/test/md5-sess"',
'qop=' + qop,
'response="' + response + '"',
'nc=' + nonceCount,
'cnonce="' + cnonce + '"',
'algorithm=' + algorithm
)

ok = testHeader.test(req.headers.authorization)
} else {
// No auth header, send back WWW-Authenticate header
ok = false
res.setHeader('www-authenticate', makeHeader(
'Digest realm="' + realm + '"',
'nonce="' + nonce + '"',
'algorithm=' + algorithm,
'qop="' + qop + '"'
))
}
} else if (req.url === '/dir/index.html') {
Expand Down Expand Up @@ -112,6 +159,28 @@ tape('with sendImmediately = false', function(t) {
})
})

tape('with MD5-sess algorithm', function(t) {
var numRedirects = 0

request({
method: 'GET',
uri: 'http://localhost:6767/test/md5-sess',
auth: {
user: 'test',
pass: 'testing',
sendImmediately: false
}
}, function(error, response, body) {
t.equal(error, null)
t.equal(response.statusCode, 200)
t.equal(numRedirects, 1)
t.end()
}).on('redirect', function() {
t.equal(this.response.statusCode, 401)
numRedirects++
})
})

tape('without sendImmediately = false', function(t) {
var numRedirects = 0

Expand Down

0 comments on commit 033e7fc

Please sign in to comment.