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

Change randomString et al to be secure #4621

Merged
merged 6 commits into from
Jan 21, 2025
Merged

Conversation

dbkr
Copy link
Member

@dbkr dbkr commented Jan 16, 2025

...and renames them, removing the special lowercase and uppercase versions and exporting the underlying function instead.

I don't believe any of these uses are speed-sensitive, so I don't really see much value in maintaining separate, fast, insecure versions. I've just changed everything to use the secure versions.

Any apps that use these will either need to take the speed hit from secure random functions and use the new ones, or write their own insecure versions. Either way, they deliberately have different names so apps will need to make a choice rather then just get moved to the secure versions.

The lowercase and uppercase versions were used exactly once each in element-web and never in js-sdk itself. The underlying function is very simple and exporting just this gives more flexibility with fewer exports.

Checklist

  • Tests written for new code (and old code if feasible).
  • New or updated public/exported symbols have accurate TSDoc documentation.
  • Linter and other CI checks pass.
  • Sign-off given on the changes (see CONTRIBUTING.md).

...and renames them, removing the special lowercase and uppercase
versions and exporting the underlying function instead.

Any apps that use these will either need to take the speed hit from
secure random functions and use the new ones, or write their own
insecure versions.

The lowercase and uppercasde verisons were used exactly once each
in element-web and never in js-sdk itself. The underlying function
is very simple and exporting just this gives more flexibility with
fewer exports.
dbkr added a commit to element-hq/element-web that referenced this pull request Jan 16, 2025
Because the js-sdk methods are changing and there's no reason for these
not to use the secure versions. The dedicated upper/lower functions were
*only* used in this one case, so this should do the exact same thing with
the one exported function.

Requires matrix-org/matrix-js-sdk#4621 (merge both together)
dbkr added a commit to element-hq/element-web that referenced this pull request Jan 16, 2025
Because the js-sdk methods are changing and there's no reason for these
not to use the secure versions. The dedicated upper/lower functions were
*only* used in this one case, so this should do the exact same thing with
the one exported function.

Requires matrix-org/matrix-js-sdk#4621 (merge both together)
@dbkr dbkr added T-Defect and removed T-Defect labels Jan 16, 2025
@dbkr dbkr marked this pull request as ready for review January 16, 2025 17:15
@dbkr dbkr requested review from a team as code owners January 16, 2025 17:15
Copy link
Member

@hughns hughns left a comment

Choose a reason for hiding this comment

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

The MatrixRTC and WebRTC changes look good to me.

Copy link
Member

@richvdh richvdh left a comment

Choose a reason for hiding this comment

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

Any apps that use these

Are these functions supposed to be exported to apps? They don't appear to be exported via the public entry points to the library and in consequence aren't documented. I'd go so far as to say this is not an X-Breaking-Change: apps using internal functions within the js-sdk don't get to rely on the semver telling them if those internal functions are changing.

If you want them to be exported, I'd suggest that this would be a good time to fix that.

@dbkr
Copy link
Member Author

dbkr commented Jan 17, 2025

Mmm, yeah - we had been assuming that it was, but no, they should not really be exported as far as I'm concerned: I don't believe it's in the js-sdk remit to provide random string utilities (obviously element web is a special case as ever).

src/randomstring.ts Outdated Show resolved Hide resolved

for (let i = 0; i < len; ++i) {
ret += chars.charAt(Math.floor(Math.random() * chars.length));
crypto.getRandomValues(positions);
Copy link
Member

Choose a reason for hiding this comment

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

at line 26, we use globalThis.crypto. It would be good to remember why we use globalThis there, and to be consistent one way or the other.

Comment on lines 48 to 55
const positions = new Uint32Array(chars.length);
let ret = "";

for (let i = 0; i < len; ++i) {
ret += chars.charAt(Math.floor(Math.random() * chars.length));
crypto.getRandomValues(positions);
for (let i = 0; i < len; i++) {
const currentCharPlace = positions[i % chars.length] % chars.length;
ret += chars[currentCharPlace];
}

return ret;
Copy link
Member

Choose a reason for hiding this comment

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

I'm far from convinced this algorithm is correct; in particular afaict you're generating a random number for each character in the input set, rather than the desired length of the output string.

Please add some comments.

Copy link
Member

Choose a reason for hiding this comment

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

Well spotted. If we do find a bug I unit test reproducing it would be awesome. You might be able to mock crypto to get predictable results?

Copy link
Member Author

Choose a reason for hiding this comment

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

After some back & forth with crypto, I've updated the algorithm. The conclusion seemed to be to prioritise strict correctness so hopefully this is an acceptably comprehensible version. I have made the unit test assert a little about the behaviour but haven't gone as far as asserting specific outputs for specific random values which would really be nailing down the exact algorithm. However we could do this assert that it picks all values exactly equal number of times (for certain specific inputs).

Copy link
Member

@andybalaam andybalaam left a comment

Choose a reason for hiding this comment

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

No comments beyond what Rich said, but please do address the question about whether it works the way we expect it to.

Comment on lines 48 to 55
const positions = new Uint32Array(chars.length);
let ret = "";

for (let i = 0; i < len; ++i) {
ret += chars.charAt(Math.floor(Math.random() * chars.length));
crypto.getRandomValues(positions);
for (let i = 0; i < len; i++) {
const currentCharPlace = positions[i % chars.length] % chars.length;
ret += chars[currentCharPlace];
}

return ret;
Copy link
Member

Choose a reason for hiding this comment

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

Well spotted. If we do find a bug I unit test reproducing it would be awesome. You might be able to mock crypto to get predictable results?

// this as we can't possibly map them onto the character set while keeping each character equally
// likely to be chosen (minus 1 to convert to indices in a string). (Essentially, we're using a d8
// to choose between 7 possibilities and re-rolling on an 8, keeping all 7 outcomes equally likely.)
const maxRandValue = Math.floor(255 / chars.length) * chars.length - 1;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const maxRandValue = Math.floor(255 / chars.length) * chars.length - 1;
const maxRandValue = 255 - 255 % chars.length - 1;

We can avoid Math.floor using modular arithmetic.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed, and adjusted to be 256 which is what it surely should have been, and don't subtract 1 because we're already using < (rather than <=) below.

Copy link
Member

@dkasak dkasak Jan 21, 2025

Choose a reason for hiding this comment

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

Yes. I started writing an elaborate explanation of why the bounds were wrong, but you beat me to it by grokking it yourself beforehand. :)

Also, the reason we don't need the - 1 is because taking the value modulo chars.length ensures that the largest possible resulting index is chars.length - 1.

also I think this was just wrong in that it was subtracting 1
unnercessarily because we already used < rather than <= below.
Copy link
Member

@richvdh richvdh left a comment

Choose a reason for hiding this comment

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

LGTM other than nitpicking about documentation

src/randomstring.ts Outdated Show resolved Hide resolved
src/randomstring.ts Outdated Show resolved Hide resolved
Co-authored-by: Richard van der Hoff <[email protected]>
Co-authored-by: Richard van der Hoff <[email protected]>
@dbkr dbkr added this pull request to the merge queue Jan 21, 2025
github-merge-queue bot pushed a commit to element-hq/element-web that referenced this pull request Jan 21, 2025
* Switch to secure random strings

Because the js-sdk methods are changing and there's no reason for these
not to use the secure versions. The dedicated upper/lower functions were
*only* used in this one case, so this should do the exact same thing with
the one exported function.

Requires matrix-org/matrix-js-sdk#4621 (merge both together)

* Change remaining instances of randomString

which I somehow entirely missed the first time.

* Fix import order
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Jan 21, 2025
@dbkr
Copy link
Member Author

dbkr commented Jan 21, 2025

Force merging because it won't pass element web tests without the corresponding web PR

@dbkr dbkr merged commit 424c258 into develop Jan 21, 2025
30 checks passed
@dbkr dbkr deleted the dbkr/secure_random_string branch January 21, 2025 13:54
github-merge-queue bot pushed a commit to element-hq/element-web that referenced this pull request Jan 21, 2025
* Switch to secure random strings

Because the js-sdk methods are changing and there's no reason for these
not to use the secure versions. The dedicated upper/lower functions were
*only* used in this one case, so this should do the exact same thing with
the one exported function.

Requires matrix-org/matrix-js-sdk#4621 (merge both together)

* Change remaining instances of randomString

which I somehow entirely missed the first time.

* Fix import order
RiotRobot pushed a commit to element-hq/element-web that referenced this pull request Jan 21, 2025
* Switch to secure random strings

Because the js-sdk methods are changing and there's no reason for these
not to use the secure versions. The dedicated upper/lower functions were
*only* used in this one case, so this should do the exact same thing with
the one exported function.

Requires matrix-org/matrix-js-sdk#4621 (merge both together)

* Change remaining instances of randomString

which I somehow entirely missed the first time.

* Fix import order

(cherry picked from commit 56eafc9)
github-merge-queue bot pushed a commit to element-hq/element-web that referenced this pull request Jan 21, 2025
* Switch to secure random strings

Because the js-sdk methods are changing and there's no reason for these
not to use the secure versions. The dedicated upper/lower functions were
*only* used in this one case, so this should do the exact same thing with
the one exported function.

Requires matrix-org/matrix-js-sdk#4621 (merge both together)

* Change remaining instances of randomString

which I somehow entirely missed the first time.

* Fix import order

(cherry picked from commit 56eafc9)

Co-authored-by: David Baker <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants