-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(scram): verify server digest, ensuring mutual authentication
- Loading branch information
Showing
2 changed files
with
89 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,6 @@ try { | |
var parsePayload = function(payload) { | ||
var dict = {}; | ||
var parts = payload.split(','); | ||
|
||
for (var i = 0; i < parts.length; i++) { | ||
var valueParts = parts[i].split('='); | ||
dict[valueParts[0]] = valueParts[1]; | ||
|
@@ -105,6 +104,23 @@ function HI(data, salt, iterations, cryptoMethod) { | |
return saltedData; | ||
} | ||
|
||
function compareDigest(lhs, rhs) { | ||
if (lhs.length !== rhs.length) { | ||
return false; | ||
} | ||
|
||
if (typeof crypto.timingSafeEqual === 'function') { | ||
return crypto.timingSafeEqual(lhs, rhs); | ||
} | ||
|
||
let result = 0; | ||
for (let i = 0; i < lhs.length; i++) { | ||
result |= lhs[i] ^ rhs[i]; | ||
} | ||
|
||
return result === 0; | ||
} | ||
|
||
/** | ||
* Creates a new ScramSHA authentication mechanism | ||
* @class | ||
|
@@ -179,9 +195,19 @@ class ScramSHA extends AuthProvider { | |
|
||
const payload = Buffer.isBuffer(r.payload) ? new Binary(r.payload) : r.payload; | ||
const dict = parsePayload(payload.value()); | ||
|
||
const iterations = parseInt(dict.i, 10); | ||
if (iterations && iterations < 4096) { | ||
callback(new MongoError(`Server returned an invalid iteration count ${iterations}`), false); | ||
return; | ||
} | ||
|
||
const salt = dict.s; | ||
const rnonce = dict.r; | ||
if (rnonce.startsWith('nonce')) { | ||
callback(new MongoError(`Server returned an invalid nonce: ${rnonce}`), false); | ||
return; | ||
} | ||
|
||
// Set up start of proof | ||
const withoutProof = `c=biws,r=${rnonce}`; | ||
|
@@ -192,25 +218,35 @@ class ScramSHA extends AuthProvider { | |
cryptoMethod | ||
); | ||
|
||
if (iterations && iterations < 4096) { | ||
const error = new MongoError(`Server returned an invalid iteration count ${iterations}`); | ||
return callback(error, false); | ||
} | ||
|
||
const clientKey = HMAC(cryptoMethod, saltedPassword, 'Client Key'); | ||
const serverKey = HMAC(cryptoMethod, saltedPassword, 'Server Key'); | ||
const storedKey = H(cryptoMethod, clientKey); | ||
const authMessage = [firstBare, payload.value().toString('base64'), withoutProof].join(','); | ||
|
||
const clientSignature = HMAC(cryptoMethod, storedKey, authMessage); | ||
const clientProof = `p=${xor(clientKey, clientSignature)}`; | ||
const clientFinal = [withoutProof, clientProof].join(','); | ||
|
||
const serverSignature = HMAC(cryptoMethod, serverKey, authMessage); | ||
|
||
const saslContinueCmd = { | ||
saslContinue: 1, | ||
conversationId: r.conversationId, | ||
payload: new Binary(Buffer.from(clientFinal)) | ||
}; | ||
|
||
sendAuthCommand(connection, `${db}.$cmd`, saslContinueCmd, (err, r) => { | ||
if (r && typeof r.ok === 'number' && r.ok === 0) { | ||
callback(err, r); | ||
return; | ||
} | ||
|
||
const parsedResponse = parsePayload(r.payload.value()); | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
mbroadst
Author
Member
|
||
if (!compareDigest(Buffer.from(parsedResponse.v, 'base64'), serverSignature)) { | ||
callback(new MongoError('Server returned an invalid signature')); | ||
return; | ||
} | ||
|
||
if (!r || r.done !== false) { | ||
return callback(err, r); | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
TypeError: Cannot read property 'payload' of null.
We are getting these errors every now and then in our app, so it would be great to get that fixed 🙂