-
Notifications
You must be signed in to change notification settings - Fork 57
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
deriveBits length idl does not allow null #322
Comments
Hey 👋 It seems you're right. If you feel like making a PR when you're back, it'd be welcome, otherwise I can also do it :) |
Feel free to do so. |
But no one is using nullable type for deriveBits, wouldn't changing the IDL also change the behavior? Is this tested by WPT?
|
Oops, missed this part. https://wpt.fyi/results/WebCryptoAPI/derive_bits_keys/ecdh_bits.https.any.html?label=experimental&label=master&aligned says Chrome and Firefox fail the null length tests while Safari passes (how?). Interesting that Safari's implementation is also definitely using non-null length. |
Eh, so the IDL passes Interesting, should this be specced? 🤔 |
Node, CF Workers, Deno, and now Bun as well, correctly implement the null behaviour for ECDH deriveBits. We're beyond a point where we could remove the behaviour. I also suspect chrome and firefox could have the correct ECDH deriveBits operation implementation but the webidl conversion coerces null to 0 and we just don't know ;) |
@saschanaz is correct in that there is no "null" behaviour. The WebIDL definition does not allow for the value to be null. Any value is coerced into a number, with an absent value being turned into a 0. I think that omitting the argument is a useful bit of ergonomics (not everyone knows the native output size of this function), but I don't see any reason to change the IDL to allow for a null value to achieve that. Using 0 as a sentinel value works perfectly fine in this case, as asking for 0 derived bits is pointless. I would suggest that we just change the algorithm for ECDH so that it conditions the return of the whole secret on Lines 9662 to 9663 in 004f26a
s/null/0/ This would effectively formalize the Safari behaviour as noted above. |
@martinthomson There is an interaction to be considered with the I believe changing the IDL is the correct course of action. We have WPTs for this behaviour, indicating the original intention and there are more implementations conforming to it than the opposite. |
For HKDF, "If length is null or zero" becomes simply "If length is zero". Same for PBKDF2. I don't see any major problem there. A nullable value makes the IDL and the algorithm more complex. This is simpler. |
@martinthomson That's not dealing with the fact that |
Sorry, I thought that was clear: if you pass the output from getting the length as an argument to deriveBits, then it will coerce to zero happily, no problem. |
I think checking for zero instead of null is technically speaking not backwards-compatible. It's a bit of an edge-case (and not really a useful operation to do), but as currently specified, passing |
To be clear, what I'm proposing requires a change in Firefox, but I think that it is the better change. A zero-length array can be obtained more simply by other means. And implementations are already non-compatible. Edit: Note that there is no option here that is strictly backwards-compatible. |
@martinthomson what you're proposing makes every currently conforming implementation non-conform since they already return 0-length array as specified currently when |
Yeah, that's true, but I don't think checking for
You're right that changing the behavior for Finally, if developer ergonomics are the goal, I think we could (at some point) consider making the parameter optional entirely (i.e. allow passing undefined or two parameters instead of three), and I think making the parameter nullable is a step in that direction in a way that checking for zero isn't. |
There is no conformance because the spec is incoherent.
I think that this is the central disagreement. I assert that the null -> 0 coercion is fine and that anyone passing null should be getting an ECDH secret in full rather than a zero-length array. That is, the two are treated as equivalent. @twiss considers this distinction between null and 0 to be significant, such that 0 produces an error instead, where null produces the full ECDH secret. (Note that HKDF and PBKDF2 will be the same in both cases.) I probably haven't pointed this out, but changing the IDL is more disruptive. |
The spec isn't really incoherent on what to do when 0 is passed, only on what to do when null is passed. That's also part of why I think changing the former to address the latter isn't necessarily the best way to go.
To be pedantic, 0 doesn't produce an error, as currently specced - perhaps it should, but I think that's orthogonal - but you're right that I see null and 0 as distinct.
What disruption would changing the IDL cause? |
Implementations that build on WebIDL often require a lot more change when IDL changes as it affects the type of arguments, codegen, automated tests generated from IDL, and other things. |
There are existing implementations that conform to the language specified in the ECDH derive bits operation as well as the existing WPTs and there's likely userland code relying on that as well.
Changing the IDL isn't going to make any existing implementation less conform. OTOH changing the behaviour would only further fragment the implementations to a point where this behaviour would not be interoperable for a long while before all implementations and installed versions align. Changing the IDL fixes the spec incoherency problem and allows the two non-conform implementations to proceed to fix a problem they had for a long time (chrome, firefox), changing the behaviour would require every implementation to align to new behaviours. |
@martinthomson Alright, understood. I personally think it's more important to minimize disruption to developers, than to implementations, though. Even though admittedly the chance that anyone is actually relying on the behavior for passing 0 is very low, it seems better to me to fix this in the way that's minimally breaking backwards-compatibility, especially if it seems that's the behavior that was intended originally. |
Given that Safari implements the logic I'm proposing, from a web standpoint, that would point toward that solution rather than making the argument nullable. |
You're right that I can't really cite backwards compatibility concerns if Safari already implements what you're proposing ^.^ However, I think what Safari did is somewhere between "accidentally working", and a hack to work around a spec bug. I still think it's more straightforward to just fix the IDL in the spec to what was intended, rather than adopt that workaround. Changing the IDL won't make Safari any more nonconformant than it is today, either - it will make it conformant for Note also that Chrome does implement the behavior for |
@annevk Do you (or others from WebKit) have an opinion on this? Would you be OK with changing the IDL to address this issue? |
I can ask. I'd personally defer to @martinthomson on this. The PR here "restores" nullability, but doesn't deal with making the argument optional as suggested upthread. Was that feedback missed? |
Can we make it optional when other algorithms require it? 🤔 https://w3c.github.io/webcrypto/#hkdf-operations
|
I think we could make it optional in the WebIDL, and then (still) throw in the operation if it's required for that specific algorithm, however, I think we should consider that separately from this issue, which is about an inconsistency in the spec. (However, I do think that making it nullable here is in a way a step towards making it optional, as I said upthread 😊) |
I don't think one can say the "magic zero" behavior is what was "originally intended". The WPTs came from web-platform-tests/wpt#3305, long after WebCrypto was originally shipped the browsers. Sadly, WebCrypto is from a slightly earlier era of web platform development, and hasn't seen significant effort in browsers since. I suspect the spec indeed meant for null and zero to be different values, some folks misread the spec (understandably—it's confusing that "null" exists in an internal call to the operation), that misreading made its way into WPTs, which begat further misreadings, and then no one noticed this giant mess until now. As for the caller-level API, I definitely agree optionality in some form makes sense here. All this mess is really a symptom of WebCrypto inexplicably believing that HKDF and ECDH are the same operation. HKDF needs a length. ECDH does not want a length, but WebCrypto then decided it means truncation, despite this being incoherent and unsafe. And so, for the sake of ECDH, there should be a convenient way to say the correct length.
Discontinuities like this mean callers that pass in a variable input need to check for zero or risk weird behaviors. Even if it's a degenerate case, boundary inputs should behave continuously with the rest of the function. Thus, I think a separate null is the safest and best option for this API. I recognize, however, that due to the WPT mess, some implementations ended up non-compliant here, and so the safe option is an interop risk for Firefox and Safari, just as the unsafe one is a risk for Chrome. Have we done measurements on the web for how often things are called with zero? That will tell us what the risk is, and whether to make the decision based on this risk or based on what would be a good API design. |
I agree that would be confusing but I don't think it should lead to that, if we only change the WebIDL (and even today, according to a strict reading of the spec); passing 0 should return an empty array buffer, which is useless but not confusing, IMHO.
I agree with your reading of the intention of the spec, and I think the WPTs do actually match that as well - they only test So I don't think you and @panva are in disagreement either, since he was talking about making the behavior for The behavior for |
Ah, yeah. I agree I suppose even that would be a breaking change for Chrome (and the spec) because But I think that breaking change gets us to a better place. Hopefully it's unlikely that people had been passing in What a mess! All this because we decided HKDF and ECDH were the same thing. 😄 |
I have to say I'm still not sold on having special semantics for null. Even if we wanted distinct semantics between 0 and omitted (i.e., undefined), there's no reason to make omitted be null. The modern approach is to just make it optional and check for it being omitted instead of coercing undefined to null. But I also think @martinthomson is right that you never went 0 to begin with which makes me question the need for an additional value (i.e., instead of undefined or number just have number exactly as Safari does today, but do make the argument optional and default to 0 for ease-of-use). |
I agree if we were to create the API from scratch, that would be the best approach. But since there are WPTs for passing |
Ah good point about Why would defaulting to 0 give ease-of-use? If you don't want to specify a length, you omit the argument. If you are in a weird situation[*] where you sometimes want to and sometimes don't want to specify a length, you can and should spell "don't specify a length" as It seems to me the only real argument for applying this behavior on It sounds like, above all, we need some measurements for what's on the web today. Given browsers disagree, I highly doubt the behavior on zero (or [*] You will never be in this situation. Specifying a length with ECDH is always wrong, and not specifying a length with HKDF doesn't work. But it never makes sense to have a function that doesn't know whether it's calling ECDH and HKDF because these operations have nothing to do with each other. They're only put together out of WebCrypto design mistakes. BTW, a minor nitpick from earlier:
The spec doesn't match the WPTs in that the spec doesn't believe you'll allowed to omit the length. That seems to have been the original intention, for better or worse. But I think we all agree that was wrong for an ECDH API. |
But the WPTs don't omit the length, they pass |
The intention of the spec is unclear and I wish we'd stop using that line of reasoning. It's clearly in conflict with itself. Also, we often test passing JS values that either result in coercion or throwing due to IDL. And we should as sometimes bindings have bugs. You can't derive intent from that either, and in this case the tests were written much later and presumably different people so that is not a correct line of reasoning either.
You can't argue with only part of that sentence. Anyway, I agree that interop is unlikely to be a problem here and it seems like a lot of people want 0 to be mean 0-length so that would argue for optional unsigned long length; in Web IDL and replacing the null check in prose with a "length is not given" check. |
Sure, but the test expects the function to return the entire value; if it was testing the IDL, it should've expected an empty array buffer. Anyway, even ignoring intent, what's the advantage of On the off chance that there is some (Safari-only) web app or server-side code that passes |
Wouldn't OTOH It's only the |
It'd be useful if we all could settle on some fundamental agreements, so that we make some progress towards a final solution to this issue. If I've understood it correctly, I believe we can agree on the following points:
Then we have other considerations and possible approaches that have both, pros and cons, but I'd be useful for me to at least conform we all agree on the 3 points listed above. |
I've created a draft PR making it
which I think is the most straightforward way to address both this (taking into account the points above) and #329. But let me know if anyone objects to this |
I agree that this seems the easier approach to address this issue. It wouldn't break Chromium actually, since once it can receive null, it will return the full-length secret as it's specified (or the Exception if it's the case). However, and I'd agree to address this in a separate issue if we want to follow an incremental approach, there are some questions that remain unclear.
I think we've got an agreement regarding 1) which would require changes in WebKit and Firefox implementations (not in the spec, though) and additional test (no changes required in WPT). The point 2) is perhaps more complex and would require more discussion. I'd be sad, though, about delaying what it'd be a step forward cleared spec and tests and better interop between browsers, but as @davidben commented before, we should base our decision on what we consider a good API, instead of focusing on specific and isolated cases. So, summarizing, I'm fine with going ahead with the change described in PR#345, together with the improvements of the 0 length case, but I'd defer to the ones with longer experience in specs the decision of whether wait for a more complete proposal that covers the rest of the cases. |
Right. In the case of Firefox, it currently returns an error for 0, so handling that case properly shouldn't lead to backwards compatibility issues there, I think. Safari currently returns the entire value, and it would be nice to know if they'd be willing to change that. However, I do think it's a bit orthogonal to both this issue and #329; and even the change in #345 doesn't really affect it. And personally I think having consistent behavior for
Yeah, that's a good point. If I understand the overload resolution algorithm correctly, passing |
I don't have objections on defaulting in case of passing 'undefined', so if we have an agreement, lets go for it. |
It looks like "get key length" is an internal operation, is that correct? Also from at least https://w3c.github.io/webcrypto/#hmac-registration it's not clear that it can return null. And when looking at its definition at the end of https://w3c.github.io/webcrypto/#aes-ctr-operations it references a length dictionary member that also cannot be null. So I'm not sure I follow this line of reasoning. |
It returns null for HKDF: This is all a bit silly and ultimately comes from WebCrypto incorrectly jamming unrelated operations into a single function, but we're a bit stuck with it now. |
And PBKDF2 has the same return null definition. |
Okay, but it still seems like that's an internal API and not a public API and as such shouldn't really influence our decision here. |
That's right. In the spec formulation (and in Chromium), deriveBits does not take null at all and has no way to say "I want the default". Null is currently purely a spec-internal construction. |
(I'll repeat most of my comment from #345 (comment):) I agree the internal API shouldn't influence the external API, but regardless, when considering what to do if an application passes null, returning the entire value seems safer than returning an empty Uint8Array. It's admittedly unlikely any existing web app does so since only Safari behaves "as expected" (per #345, not per the current spec) in that case, but perhaps not entirely impossible since the web platform tests do test for it, so someone may have gotten the idea from there (or a loose reading of the current spec), and written a Safari-only application that depends on this, perhaps. Also note that if such code would run today on Chrome, it would silently return an empty value, potentially leading to a security issue. So this change would fix a security issue in such a web app, whereas changing Safari to return the empty value instead of the full value could actually cause a security issue instead (strictly speaking not by fault of Safari, but nevertheless it seems better to be cautious here). |
We are discussing [1] a backwards-incompatible change in the Web Cryptography API spec to modify the length parameter of the deriveBits operation. This CL adds counters to measure the usage of this operation with 2 different values. The first one is to detect '0', which cam be passed explicitly or coercer due to 'undefined' or 'null' values. The other counter wants to evalue values that may cause the derived bits to be truncated; this affects the ECDH and X2519 algorithms. [1] w3c/webcrypto#322 Bug: 1439774 Change-Id: Iffba1d167144aef25b35821acd955b221b395628 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5004678 Reviewed-by: Kentaro Hara <haraken@chromium.org> Reviewed-by: David Benjamin <davidben@chromium.org> Reviewed-by: Kent Tamura <tkent@chromium.org> Commit-Queue: Javier Fernandez <jfernandez@igalia.com> Cr-Commit-Position: refs/heads/main@{#1227708}
webcrypto/spec/Overview.html
Lines 1258 to 1260 in 004f26a
The SubtleCrypto.deriveBits function
length
argument's IDL does not allownull
to be passed despite there being a special handling for its possiblenull
value in all deriveBits operations, for most it throws but for ECDH, X25519, and X448 it is defined as returning all generated bits.I believe the
length
argument of deriveBits should be made nullable like so?In WPT there are even tests for this behaviour.
https://github.com/web-platform-tests/wpt/blob/3174b85610804424ac4c6c2a194a08220402a00e/WebCryptoAPI/derive_bits_keys/ecdh_bits.js#L58-L66
The text was updated successfully, but these errors were encountered: