Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release proposal: 2.0.0 #10

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

UlisesGascon
Copy link
Member

@UlisesGascon UlisesGascon commented Oct 7, 2024

Main Changes

Breaking changes

  • Add Node@18 as the minimum version (72f925c)

Other changes

  • Adapt CI to run on Node@18 and above (3d101c4)
  • Rewrite to ES6 (b89021a)
  • Improve documentation (20d8b40)
  • Refactor Timing Safe Equal comparative using crypto library (e49b536). This remove a dependency

@UlisesGascon UlisesGascon marked this pull request as ready for review October 7, 2024 13:42
Copy link
Member

@inigomarquinez inigomarquinez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of comments added

.github/workflows/ci.yml Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
Co-authored-by: Íñigo Marquínez Prado <25435858+inigomarquinez@users.noreply.github.com>
Co-authored-by: Íñigo Marquínez Prado <25435858+inigomarquinez@users.noreply.github.com>
Copy link
Member

@inigomarquinez inigomarquinez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment on lines +56 to 72
callback = (user, pass) => {
const buffers = [
Buffer.from(user),
Buffer.from(pass),
Buffer.from(username),
Buffer.from(password)
];

// Determine the maximum length among all buffers
const maxLength = Math.max(...buffers.map(buf => buf.length));

// Pad each buffer to the maximum length
const paddedBuffers = buffers.map(buf => Buffer.concat([buf, Buffer.alloc(maxLength - buf.length)]));

const usernameValid = crypto.timingSafeEqual(paddedBuffers[0], paddedBuffers[2])
const passwordValid = crypto.timingSafeEqual(paddedBuffers[1], paddedBuffers[3])
return usernameValid && passwordValid;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lirantal any chance you can review this? 🙏

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using timingSafeEqual is indeed the best practice to follow for a timing safe comparison to avoid TOCTOU and side channel attacks that leak information about the username and password being used.

However, secure timing comparison is useful when you are comparing hashes which always produce the same length, regardless of the input string length. Here, you are padding it, which could also end up with false positives. Buffer.alloc() will pad with zero length strings (`\x00') and if the input provided to you includes that byte sequence you'd have a false positive. Here's an example based on the above:

const res = callbackComparison("user", "pass", "user\x00", "pass");
console.log("Zero Padding Result:", res);

You'd expect res to be false but it is actually true.
You could fix it by filling with another byte like ... Buffer.alloc(maxLength - buf.length, 0x01)])); but my recommendation would be to completely drop the padding altogether and keep it simple and rely on the timingSafeEqual to either throw if the length is different (which is ok and not a timing leak) or perform secure comparison if the length is the same.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants