SimpleWebAuthn: Error prone toUint8Array method
I’ve faced that toUint8Array method treats input as ASCII string: https://github.com/MasterKale/SimpleWebAuthn/blob/bc9ad0f68fc49c4ac23cd22428248faa26d3f9b6/packages/browser/src/helpers/toUint8Array.ts#L6
It is impossible to change transformation of challenge on the client.
- Attestation https://github.com/MasterKale/SimpleWebAuthn/blob/bc9ad0f68fc49c4ac23cd22428248faa26d3f9b6/packages/browser/src/methods/startAttestation.ts#L27
- Assertion https://github.com/MasterKale/SimpleWebAuthn/blob/bc9ad0f68fc49c4ac23cd22428248faa26d3f9b6/packages/browser/src/methods/startAssertion.ts#L27
Whats wrong?
First: Challenge verification will fail if challenge string contains non ASCII character. For example: abcж yep, ж occupy 2 bytes.
const value = 'abcж';
const array = Uint8Array.from(value, c => c.charCodeAt(0));
const string = String.fromCharCode.apply(null, array);
/// 'abcж' !== 'abc6'
value !== string
Second: user may want to transfer challenge from server in different format:
- ASCII string - current assumption
- HEX string - I prefer this format
- OCTET string
- base64 or base64url strings
- and so on
So it would be better to allow user configure challenge encoding or move conversion and appropriate helper(s) to userland instead hard coded into the lib.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 28 (16 by maintainers)
PR #42 has been merged in with a fix for this issue. It is available in the newly released v0.8.0.
@mahnunchik #42 is the PR containing my tentative solution to this issue. Fortunately it’s not as breaking a change as I described earlier, and more importantly it manages a variety of values for
challengeincludingasciistrings,utf-8strings, andcrypto.randomBytes(64).And for good measure I hand-checked the values being passed to authenticators by
startAttestation()/startAssertion()to confirm that they were the actual strings/buffers I was passing in aschallengetogenerateAttesationOptions()andgenerateAssertionOptions()respectively.Another opinion:
challengefor the server library is too storage related (*sql, mongo, redis etc) so it may be declared as out of scope of simple library.Good readme how to generate
challenge(crypto.randomBytes(64)) should be enough for the user.My plan is to update Server to make the
challengeparameter ingenerateAttestationOptions()andgenerateAssertionOptions()an optionalstring | Buffer. If no value forchallengeis provided, the library will generate one. If a value forchallengeis provided, it’ll be used instead.The return values from these methods will now include the raw challenge, whether it was passed in or generated by the library:
This will make it possible to pass directly into Server’s
verify...response()methods.This’ll be a breaking change, but it’ll be worth it because it’ll simplify and secure the use of the library even more ✌️
Thank you for your patience, it’s a subtle flaw in the system and I was having a hard time seeing it.
You’re absolutely right, everything needed to encode and decode base64url already exist in both the front end and the back end. In fact I should be able to get rid of
toUint8Array()completely after updating Browser accordingly. I’ll go ahead and make these changes.I’m also going to update
generateAttestationOptions()andgenerateAssertionOptions()to generate challenges internally. I thought it’d be okay to leave it to the implementer to construct sufficiently-complex challenges, but after you’re talking this through with me I realized the library should be in charge of generating these values because of all the gotcha’s associated with it.Awesome! We made a progress!
What you think in term of security? “640K ought to be enough for anybody.”
I think it should be possible to keep simplicity of library and solve the issue by the following:
base64url.encodein the server package to convertchallenge. It accepts true Buffer and/or String as you like.toUint8Arraybybase64URLStringToBufferto convertchallengetoUint8Array.It allows to use any source for the
challenge:crypto.randomBytes, any strings or even some special random source.