-
-
Notifications
You must be signed in to change notification settings - Fork 608
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
Conversation
...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.
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)
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)
There was a problem hiding this 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.
There was a problem hiding this 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.
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
|
||
for (let i = 0; i < len; ++i) { | ||
ret += chars.charAt(Math.floor(Math.random() * chars.length)); | ||
crypto.getRandomValues(positions); |
There was a problem hiding this comment.
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.
src/randomstring.ts
Outdated
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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).
There was a problem hiding this 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.
src/randomstring.ts
Outdated
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; |
There was a problem hiding this comment.
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?
src/randomstring.ts
Outdated
// 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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
Co-authored-by: Richard van der Hoff <[email protected]>
Co-authored-by: Richard van der Hoff <[email protected]>
* 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
Force merging because it won't pass element web tests without the corresponding web PR |
* 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
* 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)
* 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]>
...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
public
/exported
symbols have accurate TSDoc documentation.