Repository navigation
Don't store the placeholder provider in the process OnceLock - #540
Conversation
|
I don't think we should make cryptographic choices for people. This also breaks down when someone installs |
Agreed. The latest commit drops the both-features auto-pick. Both backends, or neither, now return The neither-feature panic-to-error change is unchanged. |
|
I don't quite see the point of this though, in general using |
No. encode/decode already return Result, and a miss should not consume the install slot. The previous dummy OnceLock made the first Err unrecoverable. This tip leaves the lock empty, so A test without either feature now checks that |
|
But does someone really write code like this? let token = match jsonwebtoken::encode(&header, &claims, &encoding_key) {
Ok(t) => t,
Err(e) => match e.kind() {
jsonwebtoken::errors::ErrorKind::MissingCryptoProvider => {
some_provider::install_default().unwrap();
jsonwebtoken::encode(&header, &claims, &encoding_key)?
}
_ => todo!(), // something else failed
},
};I understand that the encoding failure immediately kills the program, as that was the point. I don't understand how / why someone would write code to deal with a failure of this kind, when the simple solution is just to initialise the provider beforehand. some_provider::install_default().unwrap();
let token = jsonwebtoken::encode(&header, &claims, &encoding_key)?; // if an error, a different much more helpful one |
f1e1fd1 to
3d05401
Compare
|
You're right. A missing provider is a configuration error, there's no sensible code to write at the call site, and wrapping it in There's a separate bug underneath that I buried in the API argument, and it stands whether or not we keep the panic.
rustls, which this module is modelled on, has the same two functions and never stores a sentinel: // rustls 0.23.35, crypto/mod.rs:238-257
pub(crate) fn get_default_or_install_from_crate_features() -> &'static Arc<Self> {
if let Some(provider) = Self::get_default() {
return provider;
}
let provider = Self::from_crate_features()
.expect(r###"
Could not automatically determine the process-level CryptoProvider from Rustls crate features.
..."###);
let _ = provider.install_default();
Self::get_default().unwrap()
}
It matters anywhere unwinds get caught: I've pushed the reduced version, so the diff is the real thing rather than a description. It's now one source file:
I'll move One thing I noticed while testing and am not touching here: |
|
Split out as #541 (draft, stacked on this one — only the second commit belongs to it). Keeping it in draft until this PR is resolved, since on |
arckoor
left a comment
There was a problem hiding this comment.
Sorry for the delay, also needs a rebase to latest master please
|
As a side note, I don't greatly care for corporate advertising in the contributor list, so I personally think the Co-Authored-By should be dropped |
3d05401 to
4ff92e4
Compare
|
Dropped the Co-Authored-By trailer from the commit, and the session URL that was next to it. |
|
@SebTardif Are you going to address the rest of the review and the conflict too? :) |
`get_default` was `PROCESS_DEFAULT_PROVIDER.get_or_init(from_crate_features)`, and `from_crate_features` returned a dummy `CryptoProvider` whose factory fns panic. The dummy was therefore stored in the `OnceLock` before the panic fired, so any caller that catches the unwind - `tokio::spawn`, `CatchPanicLayer`, an actix worker, libtest - kept running with the install slot consumed, and `install_default` returned `Err` from then on. The panic message's own instruction to call `install_default` was no longer possible to follow. rustls, which this module is modelled on, has the same two functions and never stores a sentinel: `from_crate_features` returns `Option<Self>` and `get_default_or_install_from_crate_features` calls `.expect()` on it, panicking before anything reaches the lock. Restore that shape, keeping the panic and its message unchanged. Callers with exactly one backend feature enabled are unaffected, since that path never touched the dummy. Also split the panic message so it names which case applies, since both backends enabled and neither enabled previously produced the same text, and add `CryptoProvider::try_get_default() -> Option<&'static CryptoProvider>` so an application can assert a backend is available at startup without consuming the install slot to find out. (rustls spells this one `get_default`; named `try_get_default` here to leave the existing internal accessor alone.)
Keep one panic message for both backends enabled and for neither. Selecting a backend belongs to the final consumer, not to a library that turned a crypto feature on. Run the no-backend test whenever the backend is aws_lc_rs, including pinned and nightly. Use the requested Unreleased changelog lines.
4ff92e4 to
828c27d
Compare
Yes. Rebased onto current master, and the three review notes are in the latest commit. |
The five fn pointers in KeyUtils already return Result. new_unimplemented filled them with stubs that panic. A provider that opts out of JWK support is configured correctly. Whether a caller hits a stub depends on the key passed at runtime, so the stubs return ErrorKind::Provider. The message is UNIMPLEMENTED_ERROR: "This CryptoProvider does not support JWKs". After Keats#540, new_unimplemented has no callers inside the crate.
Reduced after review. The earlier version of this PR converted the missing-provider panic into
ErrorKind::MissingCryptoProvider. @arckoor pushed back: a missing provider is a configuration error, not something a caller can act on. I agree. That change is gone. The panic stays.What's left is a bug that is independent of the panic-vs-error question.
The bug
get_defaultwas:from_crate_featuresreturns a dummyCryptoProviderwhose factory fns panic. It does not panic itself. So the dummy is stored in theOnceLockfirst, and the panic only fires later, when a factory fn is called.install_defaultisOnceLock::set, so from that point it returnsErrforever.This bites wherever unwinds are caught and the process keeps running:
tokio::spawnreturnsErr(JoinError),tower-http'sCatchPanicLayerturns it into a 500, actix-web restarts the worker thread, libtest catches it per test. In all of those the panic message's own instruction ("CallCryptoProvider::install_default()before this point") has become impossible to follow.tests/missing_provider.rscatches the panic and assertsinstall_default()still succeeds. It fails on master and passes here.The fix
rustls, which this module is modelled on, has the same two functions and never stores a sentinel (0.23.35, crypto/mod.rs:238-257):
from_crate_featuresreturnsOption<Self>, andget_default_or_install_from_crate_featurescalls.expect()on it, so the panic fires before anything reaches the lock. We inherited rustls's panic message almost verbatim but replaced theOptionwith a stored dummy. This restores the upstream shape.static INSTANCEdummy;from_crate_featuresreturnsOption.OnceLock. The panic, its message, and its signature are unchanged. No call sites change, and anyone with exactly one backend feature enabled is unaffected, since that path never touched the dummy.pub fn CryptoProvider::try_get_default() -> Option<&'static CryptoProvider>for a startup assertion. There is currently no way to ask "is a provider installed?" without callinginstall_defaultand consuming the slot to find out. rustls spells this oneget_default; I usedtry_get_defaultto avoid renaming the existing internal accessor and churning every call site.Rebased onto current master (includes 11.1.0).
Validation
cargo test --features aws_lc_rscargo test --features rust_cryptocargo test --no-default-features --features use_pem --test missing_providercargo clippy --all-targets -- -D warnings, all three configscargo fmt --checkSplit out of this PR
KeyUtils::new_unimplementedreturningErrinstead of panicking is Return an error from unimplemented JWK key utils #541. With a custom provider that skips JWKs, whether you reach that path depends on the key the caller passes at runtime, not onCargo.toml, which reads like the same line drawn in Don't panic when computing thumbprint #524.Separately:
cargo test --features aws_lc_rs,rust_cryptofails on master today (4jwk::tests) because the dummy'sKeyUtilspanics. Identical before and after this change, so not addressed here.Ref #456