Skip to content

Enable cose key_kms so the CoseError match stays exhaustive - #202

Open
Matt (matt-evervault) wants to merge 4 commits into
mainfrom
matt/cose-error-catch-all
Open

Enable cose key_kms so the CoseError match stays exhaustive#202
Matt (matt-evervault) wants to merge 4 commits into
mainfrom
matt/cose-error-catch-all

Conversation

@matt-evervault

@matt-evervault Matt (matt-evervault) commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Enables key_kms on our aws-nitro-enclaves-cose dependency so the CoseErrorNsmError match stays exhaustive, instead of compiling only by luck of which features a consumer happens to enable.

Why

aws-nitro-enclaves-cose puts three variants behind its key_kms feature:

AwsSignError(SdkError<SignError>),
AwsVerifyError(SdkError<VerifyError>),
AwsGetPublicKeyError(SdkError<GetPublicKeyError>),

We depend on the crate with default-features = false and never asked for key_kms. But Cargo features are additive and unified across the whole dependency graph — if any other crate in a consumer's tree enables key_kms, it is enabled for our copy too. CoseError then has three more variants, our match is no longer exhaustive, and we fail to build in someone else's project:

error[E0004]: non-exhaustive patterns: `CoseError::AwsSignError(_)`,
`CoseError::AwsVerifyError(_)` and `CoseError::AwsGetPublicKeyError(_)` not covered
  --> attestation-doc-validation-0.7.4/src/nsm/error.rs:38:15

This is not hypothetical. aws-nitro-enclaves-image-format 0.6.0+ declares aws-nitro-enclaves-cose with features = ["key_kms"], so any project depending on both that and this crate cannot build. It currently blocks evervault/evervault-cli#260, which needs image-format 0.6+ to get off sha2 0.9. Both published 0.7.4 and 0.10.1 are affected.

Approach

The obvious fix is a catch-all other => ... arm, and the first commit on this branch did that. The problem with a catch-all is that it also swallows any future variant we genuinely should handle — we would silently map it to UnsupportedError instead of getting a compile error telling us to look.

So instead: enable key_kms ourselves. The variants then always exist, we match all three explicitly, and there is no wildcard. An upstream addition to CoseError becomes a compile error here, which is what we want.

The wasm32 wrinkle

key_kms = ["aws-sdk-kms", "tokio", "key_openssl_pkey"], so it drags in tokio and (transitively) openssl. Neither builds for wasm32-unknown-unknown — enabling it unconditionally fails the wasm bindings with 49 errors in mio.

So it is enabled per-target:

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
aws-nitro-enclaves-cose = { version = "0.5.1", default-features = false, features = ["key_kms"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
aws-nitro-enclaves-cose = { version = "0.5.1", default-features = false }

with the three arms #[cfg(not(target_arch = "wasm32"))]. On wasm32 the variants do not exist, so the remaining ten arms are exhaustive there as well. No catch-all and no #[allow] on either target.

This needs resolver = "2"

The workspace root did not set a resolver, so Cargo was defaulting to resolver 1, which unifies features across all targets and turns key_kms on for wasm32 regardless of the cfg gate. With resolver 1 the target gating above does nothing and wasm still dies in mio.

Adding resolver = "2" to the workspace makes the gate effective. That is the modern default and what these edition-2021 crates already assume — but it is a repo-wide change to feature resolution, so it is worth a careful look. Resolver 2 can reduce features relative to resolver 1, so if any binding was quietly relying on unification to get a feature turned on, this is where it would show up. The node, python and wasm CI jobs should catch that.

Verification

Run with the nix cargo/rustc/clippy/rustfmt 1.97.x toolchain, mirroring cargo make ci:

  • cargo check — passes
  • cargo fmt --check — clean
  • cargo clippy -- -W clippy::pedantic — no errors, no new warnings referencing nsm/error.rs
  • cargo test -- --skip time_sensitive — 5 passed, 0 failed

wasm32 is verified, not assumed

I now have a full local wasm toolchain working (nix LLVM 19 clang + llvm-ar + lld; the earlier ring failures were a missing wasm C toolchain on my machine, unrelated to this change).

cargo build -p wasm-attestation-bindings --target wasm32-unknown-unknown succeeds on this branch, producing a real wasm_attestation_bindings.wasm (27,204,797 bytes, debug profile). Running the same build with the four changed files reverted to main produces a byte-identical artifact and the same single pre-existing warning (unused import: std::str::Bytes).

Three independent pieces of evidence that wasm is unaffected:

  1. The resolved wasm dependency tree is byte-identical to main. cargo tree -p wasm-attestation-bindings --target wasm32-unknown-unknown -e normal,features is 897 lines on both, and diff reports no change — so neither the package set nor the feature flags moved.
  2. None of the KMS-side crates reach wasm at all. Grepping that tree for openssl|tokio|aws-sdk-kms|mio|hyper|rustls returns zero matches; aws-nitro-enclaves-cose appears there without them.
  3. Cargo did not even rebuild the artifact when switching between the two states — its fingerprint for the wasm target was unchanged, which is only possible if the build inputs are identical.

This is what resolver = "2" buys: the cfg(not(target_arch = "wasm32")) gate is honoured, so wasm resolves aws-nitro-enclaves-cose exactly as it does today. Under resolver 1 the same code fails with 49 errors in mio.

CI's build_wasm job (which runs wasm-pack rather than raw cargo build) is still the authoritative check, but the local result and the tree diff both say it will pass.

Trade-off to weigh

This adds aws-sdk-kms, tokio and openssl to every non-wasm build of this crate — including the node, python, kotlin and swift bindings, which currently have no C dependencies at all. openssl-sys needs OpenSSL headers at build time, which can complicate cross-compiled wheels and prebuilt binaries. wasm is explicitly excluded and verified unchanged (above), so the risk is concentrated in the native binding builds — the lint_and_test_node and build_and_test_python CI jobs are the ones to watch.

That is the price of compile-time exhaustiveness here.

If that cost is judged too high, the catch-all in the first commit (8bc652b) is the lighter alternative and I can drop back to it. There is also an upstream angle worth pursuing either way: aws-nitro-enclaves-image-format forcing key_kms on all its consumers looks like an upstream defect — if they made it opt-in, this crate would need no change at all.


Note on the branch history

Three commits in the middle of this branch explore the alternatives; the tree is back to the target-gated approach:

  • 8bc652b — catch-all other => arm. Works everywhere, but silently swallows future CoseError variants.
  • ae192b6 — per-target key_kms + resolver = "2". Current state.
  • f62e9b3key_kms unconditional, wasm exceptions removed. Reverted in 9bd92b3.

f62e9b3 was reverted because removing the wasm exceptions cannot be made to work, and specifically it is not an openssl configuration problem.

With OPENSSL_DIR, OPENSSL_INCLUDE_DIR, WASM32_UNKNOWN_UNKNOWN_OPENSSL_DIR, WASM32_UNKNOWN_UNKNOWN_OPENSSL_INCLUDE_DIR and OPENSSL_NO_VENDOR all set, the wasm build still fails, and the only crate that fails is mio:

error[E0433]: cannot find `event` in `sys`            (x10)
error[E0432]: unresolved import `crate::sys::tcp`
error[E0425]: cannot find type `Selector` in module `sys`
error: could not compile `mio` (lib) due to 49 previous errors

Those are missing-platform-backend errors: mio's sys module has no wasm32-unknown-unknown implementation, and no include path or environment variable can supply one. openssl-sys never even reaches its own build failure, because mio fails first. (wasm32-unknown-unknown has no libc or sysroot, so there is no OpenSSL build for it either.)

mio arrives via tokio, which is pulled by both aws-nitro-enclaves-cose itself and aws-sdk-kms/aws-smithy-*. It is inherent to key_kms rather than a configuration gap — which is why the per-target gate is the mechanism used here.

aws-nitro-enclaves-cose puts AwsSignError, AwsVerifyError and
AwsGetPublicKeyError on CoseError behind its key_kms feature. We depend on
the crate with default-features = false and never request key_kms, but
Cargo unifies features across the dependency graph, so any other crate in
a consumer's tree can enable it for our copy. CoseError then gains three
variants, our From<CoseError> match stops being exhaustive, and we fail to
build with E0004 in someone else's project.

This is not hypothetical: aws-nitro-enclaves-image-format 0.6.0+ declares
aws-nitro-enclaves-cose with features = ["key_kms"], so any project using
both it and this crate cannot build. Both published 0.7.4 and 0.10.1 are
affected.

Add a catch-all arm mapping to NsmError::UnsupportedError, which is the
honest mapping since we do local attestation verification and never use
KMS-backed keys. The #[allow(unreachable_patterns)] is needed because with
key_kms off every variant already has an explicit arm.

Verified that cargo check compiles clean with features = ["key_kms"]
temporarily enabled, instead of failing E0004.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ev-vaultkeeper

Copy link
Copy Markdown

Vaultkeeper Commands

Mention @ev-vaultkeeper <command> in a PR review thread:

  • review — Review this PR and leave a review.
  • address-comments — Push commits that address the review feedback on this PR.
  • fix-ci — Investigate the failing CI on this PR and push a fix.

You can also request evervault-dependencies as a reviewer to trigger a review.

Replaces the catch-all arm from the previous commit with explicit arms for
the three KMS variants, keeping compile-time exhaustiveness.

aws-nitro-enclaves-cose puts AwsSignError, AwsVerifyError and
AwsGetPublicKeyError on CoseError behind its key_kms feature. Because Cargo
unifies features across the dependency graph, any consumer that also depends
on aws-nitro-enclaves-image-format 0.6+ (which declares cose with
features = ["key_kms"]) gets those variants switched on for our copy too,
making our match non-exhaustive and failing the build with E0004.

Rather than paper over that with a catch-all, enable key_kms ourselves so the
variants always exist and can be matched explicitly. An upstream variant
addition is then a compile error here instead of something a wildcard
silently swallows.

key_kms pulls in tokio and openssl (it also implies key_openssl_pkey),
neither of which builds for wasm32-unknown-unknown, so it is enabled via a
cfg(not(target_arch = "wasm32")) dependency section and the three arms are
cfg'd to match. On wasm32 the variants do not exist, so the remaining arms
are exhaustive there too.

This requires resolver = "2" on the workspace. Without it Cargo falls back to
resolver 1, which unifies features across all targets and enables key_kms for
wasm32 regardless of the cfg gate — that made the wasm build fail with 49
errors in mio.

Verified: cargo check, cargo fmt --check, cargo clippy -- -W clippy::pedantic
and cargo test -- --skip time_sensitive all pass. The wasm32 check gets past
tokio/mio and fails only in ring's build script, which is where a clean tree
also fails locally (no wasm C toolchain here) — CI should confirm.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@socket-security

socket-security Bot commented Aug 31, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: cargo openssl is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?cargo/aws-nitro-enclaves-cose@0.5.2cargo/openssl@0.10.81

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore cargo/openssl@0.10.81. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@matt-evervault Matt (matt-evervault) changed the title fix: keep compiling when a consumer enables cose key_kms Enable cose key_kms so the CoseError match stays exhaustive Aug 31, 2026
Comment on lines +37 to +43
# `key_kms` is enabled so the CoseError match in src/nsm/error.rs stays exhaustive.
# aws-nitro-enclaves-image-format 0.6+ turns this feature on through Cargo's feature
# unification, which silently adds three variants to CoseError. Enabling it ourselves means
# those variants always exist here and are matched explicitly, so an upstream addition is a
# compile error rather than something a catch-all arm swallows.
# Excluded on wasm32: key_kms pulls in tokio (mio) and openssl, neither of which builds for
# wasm32-unknown-unknown.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
# `key_kms` is enabled so the CoseError match in src/nsm/error.rs stays exhaustive.
# aws-nitro-enclaves-image-format 0.6+ turns this feature on through Cargo's feature
# unification, which silently adds three variants to CoseError. Enabling it ourselves means
# those variants always exist here and are matched explicitly, so an upstream addition is a
# compile error rather than something a catch-all arm swallows.
# Excluded on wasm32: key_kms pulls in tokio (mio) and openssl, neither of which builds for
# wasm32-unknown-unknown.

Comment on lines +49 to +54
// These variants only exist when aws-nitro-enclaves-cose is built with `key_kms`,
// which Cargo.toml enables on every target except wasm32. Matching them
// explicitly keeps this match exhaustive, so adding a variant upstream is a
// compile error here rather than something silently swallowed by a catch-all.
// We never construct KMS-backed keys — attestation docs are verified locally —
// so in practice these are unreachable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
// These variants only exist when aws-nitro-enclaves-cose is built with `key_kms`,
// which Cargo.toml enables on every target except wasm32. Matching them
// explicitly keeps this match exhaustive, so adding a variant upstream is a
// compile error here rather than something silently swallowed by a catch-all.
// We never construct KMS-backed keys — attestation docs are verified locally —
// so in practice these are unreachable.

@matt-evervault Matt (matt-evervault) self-assigned this Aug 31, 2026
@matt-evervault
Matt (matt-evervault) marked this pull request as ready for review August 31, 2026 14:55
Matt (matt-evervault) and others added 2 commits August 31, 2026 16:35
Drops the wasm32 exceptions from the previous commit: key_kms is now a plain
dependency feature and the three KMS arms are matched without cfg gates. The
workspace resolver = "2" is also removed, since its only purpose was making
the per-target gate effective.

Rationale for enabling key_kms at all is unchanged: aws-nitro-enclaves-cose
puts AwsSignError, AwsVerifyError and AwsGetPublicKeyError on CoseError behind
that feature, and Cargo unifies features across the dependency graph, so any
consumer that also pulls aws-nitro-enclaves-image-format 0.6+ gets them
switched on and our match stops being exhaustive (E0004). Enabling it
ourselves means the variants always exist and are handled explicitly, so an
upstream addition is a compile error rather than something a catch-all
silently swallows.

KNOWN BREAKAGE: this breaks the wasm bindings. key_kms pulls in tokio and
openssl (it implies key_openssl_pkey), and neither builds for
wasm32-unknown-unknown:

  error: could not compile `mio` (lib) due to 49 previous errors

Verified with a working local wasm toolchain (LLVM 19 clang + llvm-ar + lld)
that does produce a wasm_attestation_bindings.wasm from the previous,
target-gated commit — so this is the removed gate, not a toolchain gap.
CI's build_wasm job will fail until the wasm bindings are either dropped or
the gate is restored.

Native side is green: cargo check, cargo fmt --check,
cargo clippy -- -W clippy::pedantic and cargo test -- --skip time_sensitive
all pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant