Skip to content

Attestation libraries - #386

Open
gmandyam wants to merge 33 commits into
OpenPRoT:mainfrom
gmandyam:Attestation-libraries
Open

Attestation libraries#386
gmandyam wants to merge 33 commits into
OpenPRoT:mainfrom
gmandyam:Attestation-libraries

Conversation

@gmandyam

Copy link
Copy Markdown
Contributor

Add attestation service. Initial cut.

@gmandyam
gmandyam requested a review from FerralCoder August 1, 2026 18:23
@FerralCoder
FerralCoder requested a review from rusty1968 August 7, 2026 14:47
@FerralCoder
FerralCoder requested a review from fdamato August 19, 2026 16:36
Comment thread third_party/crates_io/Cargo.toml Outdated

cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7.5"
cortex-m-rt = "=0.7.5"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We don't need the exact = pin here. The committed Cargo.lock already resolves cortex-m-rt (and cortex-m-rt-macros) to exactly 0.7.5 with a checksum, so every build that respects the lockfile is already reproducible — the = on the requirement doesn't add any determinism on top of that.

@gmandyam gmandyam Aug 31, 2026

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.

Changed accordingly. Exact pin removed.

Comment thread services/attest/api/Cargo.toml Outdated
Comment thread services/attest/producer/Cargo.toml Outdated
@@ -0,0 +1,22 @@
# Licensed under the Apache-2.0 license

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we drop Cargo.toml? This is a Bazel project and the crate is already fully defined in BUILD.bazel — targets and deps included.

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.

Dropped.

@rusty1968

Copy link
Copy Markdown
Collaborator

Quick note on Cargo.toml files: this is a Bazel project, so the only manifest we actually need is Cargo.toml — that's what drives crate_universe / external crate resolution for Bazel. All the per-crate Cargo.toml files scattered under services (attest, spdm, mctp, etc.) aren't used to build or test anything; the real target and dependency definitions live in each crate's BUILD.bazel.

Comment thread services/attest/api/src/types.rs Outdated
/// Production: implemented by a Caliptra mailbox driver.
/// Testing: implement with a software key (`SoftwareAttestProducer` in the
/// producer crate behind `test-support`).
pub trait CaliptraSigner: Send + Sync {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Small naming nit: CaliptraSigner lives in the "platform-independent" API crate, but the name (and sign_es384/alias_cert_der) bakes in a specific vendor and algorithm.

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.

Renamed to HWSigner

}

/// Return just the DER-encoded Alias (leaf) certificate.
pub fn alias_cert(signer: &dyn CaliptraSigner) -> Result<Vec<u8>, AttestError> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

alias_cert is dead — I grepped the whole tree and its only references are the definition itself and the one unit test that exists solely to test it. This is just a pass-thru method invoked only by the test.

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.

Removed function and unit test

@rusty1968

Copy link
Copy Markdown
Collaborator

As written, both attest crates are std-based and heap-allocating. Please make sure all crates are no_std.

//! ```
//!
//! Claim key numbers follow RFC 9711 and the OCP-EAT profile.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
#![no_std]
#![forbid(unsafe_code)]

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.

Added. See lib.rs files.

Comment thread services/attest/producer/src/builder.rs Outdated
Value::Text("https://openprot.example/caliptra/device".into()),
));

let iat = std::time::SystemTime::now()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The crate must be no_std. You are using O/S facilities in a crate that is going to be consumed in an embedded environment.

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.

stdlib dependencies are removed.

@rusty1968

Copy link
Copy Markdown
Collaborator

One thing I noticed: dice_identity.rs and measurements.rs are each basically a whole module wrapping a single pub fn that's only ~1-3 lines — collect is a loop + extend, cert_chain is one call + a length check, and alias_cert is a straight pass-through (and dead). It's a bit of a "one file per function" thing, where the module boundary costs more than the logic it's hiding. These would be better as private helpers/methods behind HwAttestProducer.

- Remove exact-pin from cortex-m-rt (lockfile already pins the checksum)
- Drop services/attest/api/Cargo.toml and services/attest/producer/Cargo.toml
  (Bazel project; crates fully defined in BUILD.bazel)
- Rename CaliptraSigner → HwSigner, sign_es384 → sign, alias_cert_der →
  leaf_cert_der to remove vendor/algorithm bake-in from platform-independent API
- Remove dead alias_cert() function and its unit test from dice_identity.rs
- Make api and producer crates no_std + alloc; replace std::time::Duration
  with core::time::Duration in AttestConfig
- Remove std::time::SystemTime from builder::build; caller now supplies iat: u64
  Unix timestamp — no OS clock in embedded context
…ration

- Add minicbor 0.21 to third_party/crates_io/Cargo.toml
- Add services/attest/api/src/consts.rs with all fixed-capacity constants
  (MAX_CHAIN_LEN=5, MAX_CERT_SIZE=2048, MAX_MEASUREMENTS=16, MAX_TOKEN_SIZE=8192, etc.)
- api/src/types.rs: replace Vec<u8>/String with heapless equivalents throughout;
  HwSigner::cert_chain_der and leaf_cert_der now write into caller-supplied bufs;
  MeasurementProvider::measurements appends into caller-supplied buf
- api/src/traits.rs: AttestProducer::generate_token writes into caller-supplied
  Vec<u8, MAX_TOKEN_SIZE>; cert_chain writes into caller-supplied buf
- api/src/error.rs: drop String payloads (no alloc); add BufferFull variant
- producer/src/builder.rs: replace ciborium with minicbor + stack BufWriter;
  no heap allocation in hot path
- producer/src/signer.rs: HwAttestProducer holds lifetime-tied refs instead
  of Arc/Box; providers stored in heapless::Vec<&dyn, 8>
- producer/src/measurements.rs / dice_identity.rs: updated to heapless types
- producer/BUILD.bazel: swap ciborium→minicbor in library deps;
  ciborium kept as test-only dep for integration test decode
- producer/tests/producer_integration.rs: updated for new API signatures
@rusty1968

Copy link
Copy Markdown
Collaborator

openprot-secure-coding — violation

zeroize is added as a dependency (producer/Cargo.toml:19, producer/BUILD.bazel:16) and
the README (producer/README.md:81) documents it as "Zero-on-drop for intermediate key
material and sensitive buffers" — but it's never imported or called anywhere in the source.
Dead dependency, and the security claim in the README is currently false.

@gmandyam

gmandyam commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

As written, both attest crates are std-based and heap-allocating. Please make sure all crates are no_std.

Moved to heapless.

Neither crate is referenced in the producer source; removing them from
BUILD.bazel deps and the README dependency table.
@gmandyam

gmandyam commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

openprot-secure-coding — violation

zeroize is added as a dependency (producer/Cargo.toml:19, producer/BUILD.bazel:16) and the README (producer/README.md:81) documents it as "Zero-on-drop for intermediate key material and sensitive buffers" — but it's never imported or called anywhere in the source. Dead dependency, and the security claim in the README is currently false.

Removed and README updated.

gmandyam and others added 10 commits September 1, 2026 14:34
0.74.0 upgrades cargo-lock from v10 to v11, which adds support for
Cargo.lock v4 format (generated by Cargo 1.78+). Without this, the
cargo-bazel splice step fails to parse the lockfile it generates.
Fix all rustfmt formatting violations caught by CI format check:
types.rs measurements() signature, builder.rs import/call-chain
style, measurements.rs struct literals and long calls,
producer_integration.rs function signature and call sites.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
Pre-populate the committed lockfile with the minicbor entry so
cargo-bazel does not need to re-run cargo during the crate_universe
splice step. Without this entry, cargo-bazel detects a stale lockfile
and re-generates it in a synthetic workspace; that synthetic cargo run
produces a v4 lockfile with inline-table dependency entries that the
cargo-lock library bundled in cargo-bazel cannot parse.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
The Cargo.lock on this branch had malformed [[package]] blocks for
zerocopy and zerocopy-derive (two versions merged into one block without
a separator), introduced by a prior 'Roll pigweed' commit. This caused
cargo-bazel's splice step to fail with a TOML parse error whenever our
Cargo.toml additions (ciborium, minicbor) forced a re-splice.

Reconstruct the lockfile starting from upstream main's clean baseline,
then add the entries our Cargo.toml needs:
- ciborium 0.2.2 + ciborium-io/ll (transitive)
- half 2.7.1 + crunchy 0.2.4 (ciborium-ll transitive)
- minicbor 0.21.1
- pldm-common/pldm-interface 0.1.0 (git deps already on branch)
- object 0.40.0 (replacing 0.37.3 per our Cargo.toml)
- zerocopy 0.8.56 / zerocopy-derive 0.8.56 (required by half)

All checksums taken from canonical crates.io or existing branch lockfile
entries. No inline-table disambiguation entries; all dependency references
use plain string form that cargo-bazel's cargo-lock library can parse.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
rules_rust 0.74.0 introduced a breaking requirement that all
crate_universe calls in transitive (non-root) modules must carry a
lockfile. Pigweed's 'crates_no_std' extension call does not have one,
causing CI to fail with:

  crate_universe extension call 'crates_no_std' is in a non-root module
  but has no lockfile. Transitive crate_universe repositories must ship a
  lockfile = ... because repinning is not supported across module
  boundaries.

Revert to 0.70.0 which is what upstream main uses.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
CertChain was imported but never referenced in traits.rs;
all cert chain handling uses the raw heapless Vec types directly.
Fixes -D warnings compile error caught by CI clippy build.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
Three -D warnings errors caught by CI:
- signer.rs: remove unused CertChain import
- signer.rs: change add_provider return type from Result<(),()>
  to Result<(),AttestError> (clippy::result-unit-err)
- builder.rs: remove unused BufWriter::written method

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
Clippy needless_lifetimes: find_claim_bytes and find_claim_str had
explicit 'a annotations that Rust's lifetime elision rules already cover.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
The rebuilt Cargo.lock was missing three entries that cargo-audit's
dependency resolver requires:
- mctp 0.2.0 (git+...?branch=main) — same commit as the plain git
  source entry but with the branch qualifier; referenced by mctp-estack
  and mctp-lib
- syn 2.0.119 — referenced by zerocopy-derive 0.8.56 and bitfield-struct;
  the upstream main lockfile only had 2.0.117

Without these, cargo-audit panics: "failed to find dependency".

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
builder.rs: n_claims was 9 but 11 claims are always written (ISS, IAT,
NONCE, UEID, OEMID, HWMODEL, HWVER, DBGSTAT, SWNAME, SWVER,
MEASUREMENTS). The undercounted map header caused the minicbor decoder
to stop iterating before reaching CLAIM_EVIDENCE, so the
non_empty_evidence_included_verbatim test failed.

deny.toml: minicbor 0.21.1 uses BlueOak-1.0.0 which is an OSI-approved
permissive license. Add it to the allow list.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>

pub use signer::HwAttestProducer;

#[cfg(feature = "test-support")]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can this live under mod tests?

@rusty1968
rusty1968 requested a review from wmaroneAMD September 2, 2026 20:57

@rusty1968 rusty1968 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM: [x]

Correctness

  • producer/src/builder.rs build(): the ueid claim (key 256) is hard-coded to a constant placeholder (ueid[0] = 0x01, the remaining 28 bytes always zero) for every device, in the shared build() path used by both HwAttestProducer (production) and SoftwareAttestProducer. The README documents this claim as "Unique Entity Identifier from Caliptra CDI", but nothing derives it from device identity — every token from every device will carry the same UEID. This defeats the purpose of the claim and should either be wired to a real per-device source (e.g. via HwSigner/DICE identity) or clearly gated so it can't ship silently wrong (a // TODO alone is not enough given this is the production code path, not just the test stub).
  • third_party/crates_io/Cargo.lock: diffed against current main tip (not just this PR's stale base), this PR's lockfile reintroduces pldm-common/pldm-interface (already removed on main), is missing statig (added on main since this branch's last lockfile rebuild), and downgrades ~15+ unrelated crates (bitflags, bytes, clap/clap_builder, hashbrown, foldhash, log, memchr, smallvec, uuid, ruzstd, syn, zerocopy, …) that have nothing to do with ciborium/minicbor. This needs a lockfile refresh/rebase before merge — merging as-is risks silently resurrecting removed dependencies and regressing pinned versions repo-wide.
  • Both services/attest/api/README.md and services/attest/producer/README.md document an API that no longer matches the code: the README shows a CaliptraSigner trait with sign_es384/alias_cert_der/cert_chain_der(&self) -> Result<Vec<Vec<u8>>, _>, and an AttestProducer::generate_token(&self, ...) -> Result<Vec<u8>, _> / cert_chain(&self) -> Result<CertChain, _>. The actual code (traits.rs, types.rs) has been reworked to no_std/heapless out-parameter signatures (HwSigner, generate_token(..., iat, out: &mut Vec<u8, MAX_TOKEN_SIZE>), cert_chain(&self, buf: &mut Vec<...>)), and the producer README's usage examples (Arc::new(caliptra_driver), Box::new(UefiFirmwareMeasurements), let token: Vec<u8> = ...generate_token(...)) don't compile against the real, reference-based, no-alloc constructors. These docs will actively mislead the first integrator who copies them. The README's claim table is also missing the sw-name(14)/sw-version(15) claims that builder.rs actually emits.

No dynamic allocation (openprot-no-alloc)

No violations found. Verified across the full diff: both crates declare #![no_std] (api/src/lib.rs, producer/src/lib.rs), and every buffer in consts.rs, error.rs, traits.rs, types.rs, builder.rs, dice_identity.rs, measurements.rs, and signer.rs uses heapless::{Vec, String} with named fixed capacities from consts.rs. The only place std-only crates (ciborium) appear is producer/tests/producer_integration.rs, a host-side integration-test binary, which is the documented exemption.

Panic-free / explicit errors (openprot-no-panic)

  • producer/src/measurements.rs::test_caliptra_measurements() is gated by #[cfg(feature = "test-support")], not #[cfg(test)] — it is reachable at runtime from SoftwareAttestProducer::generate_token, a build configuration the README documents as legitimate ("software stub enabled, no Caliptra hardware required"). It contains 4 .unwrap() calls (push_str, extend_from_slice, push). These are practically infallible given the hardcoded literal lengths, but the rule is unconditional outside #[cfg(test)]. Suggested fix (included in the diff): return Result<Vec<Measurement, MAX_MEASUREMENTS>, AttestError> and propagate with ?/map_err, matching the style already used everywhere else in this crate; update the one call site in signer.rs.
  • Everywhere else (builder.rs production path, signer.rs non-test-support impls, dice_identity.rs production fn) consistently uses map_err(...)? — good, this is the only outlier.

Secrets, crypto, and register access (openprot-secure-coding)

  • The prior finding (unused zeroize dependency) is resolved: zeroize no longer appears anywhere, and there is in fact no local sensitive/secret buffer in this diff to zeroize — the private Alias Key never leaves the (out-of-scope) Caliptra mailbox driver, and HwSigner::sign() only returns a public signature. Nothing to flag here.
  • No unsafe anywhere (#![forbid(unsafe_code)] in both crate roots), no secret-byte comparisons, and no key material appears in Debug/error output.
  • builder::build() stack-allocates two independent MAX_TOKEN_SIZE (8 KiB each) scratch buffers (payload_scratch, cose_scratch) simultaneously, plus a 128-byte header scratch and the caller-owned out buffer. Worth confirming this fits the target RoT's stack budget (~16-24 KiB of scratch alone) — not blocking, but should be measured/documented for an embedded target.

Verbosity / over-engineering (ponytail-review)

  • builder.rs::build() has ~30 near-identical e.<method>(...).map_err(|_| AttestError::Cbor)?; lines. The previously-flagged shape (claims.push((Value::Integer(...), Value::...)) → a claim() helper) doesn't map cleanly onto the new streaming minicbor::Encoder API, but the repeated .map_err(|_| AttestError::Cbor)? boilerplate could still be collapsed with a small local macro or closure. Nit, not blocking.
  • The stub DER-cert bytes ([0x30, 0x00]) are constructed inline and duplicated across signer.rs (StubSigner, SoftwareAttestProducer::cert_chain), dice_identity.rs tests, and builder.rs tests — a shared fn stub_cert() -> [u8; 2] test helper would remove the duplication. Low priority, test-only code.
  • decode_outer/find_claim* helpers in builder.rs tests (via minicbor) and producer_integration.rs (via ciborium) look like duplicated test scaffolding, but they intentionally use two different CBOR libraries to cross-validate the encoder against an independent decoder — that's reasonable, not bloat. The comment in builder.rs's decode_outer ("Use ciborium only in tests") is simply stale/wrong now (the code uses minicbor::Decoder); fixed in the diff.

Readability & Maintainability

  • HwAttestProducer.providers: Vec<&'a dyn MeasurementProvider, 8> uses a bare magic-number capacity where every other buffer in this crate uses a named MAX_* constant; fixed in the diff (MAX_PROVIDERS).
  • third_party/crates_io/Cargo.toml: ciborium/minicbor were inserted directly under the # Host tool crates needed by pigweed pw_kernel/tooling comment even though minicbor is a production, no_std dependency of the producer crate (not host tooling). Fixed with its own comment block in the diff.

Commit Message

  • This PR's commit history mixes Conventional-Commit-style prefixes (fix:, deps:, build:, style:) that don't match the plainer, component-first style used elsewhere in this repo's history (e.g. orchestrator: ..., Add X, Fix Y). Not blocking if this gets squash-merged, but worth a single descriptive commit message on merge rather than carrying the 20-commit fixup trail (several of which are literally "fix: resolve clippy warnings" / "fix: remove unused import" against this same PR's own earlier commits).

gmandyam and others added 2 commits September 2, 2026 15:20
RFC 9052 §4.4 requires the signature to cover Sig_Structure
(["Signature1", phdr_bstr, h'', payload_bstr]), not the raw payload.
RFC 8392 requires the CWT payload to carry tag 61, and RFC 9052
§2 requires tag 18 on the outer COSE_Sign1 object.

Additionally, the protected header scratch buffer was sized at 128 B,
too small for a full cert chain; replace with PHDR_SCRATCH computed
from MAX_CHAIN_LEN and MAX_CERT_SIZE. Move phdr encoding before the
signing step so it is available for Sig_Structure construction.

Add HwSigner::caliptra_measurements() so HwAttestProducer can collect
hardware-internal firmware digests (ROM, FMC, RT) instead of passing
an empty slice. Update all HwSigner test impls in builder.rs and
dice_identity.rs to satisfy the new trait method.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
Wrap two long tag().map_err() call chains in builder.rs and collapse
a two-line Vec::new() binding in signer.rs to satisfy rustfmt.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
@chrysh

chrysh commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

@rusty1968 @gmandyam Are we adding AI skills to the repo now?

@chrysh chrysh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Follow-up on the earlier review: 13 inline suggestions on style and API shape, all click-to-apply.

Three of them depend on another: comment on builder.rs:25 needs the builder.rs:82 slice change, builder.rs:95 needs the Cursor alias on builder.rs:51, and signer.rs:24 needs MAX_PROVIDERS in consts.rs. Each says so in its own text.

Commit history

20+ commits mixing fix:, deps:, build: and style: prefixes that do not
match this repo's plainer component-first style (orchestrator: ..., Add X).
Several are fixups against this PR's own earlier commits. Not blocking if this
is squash-merged, but the squash message should be a single descriptive one.

Comment thread services/attest/producer/src/builder.rs Outdated
Comment thread services/attest/producer/src/builder.rs Outdated
Comment thread services/attest/producer/src/builder.rs Outdated
Comment thread services/attest/producer/src/builder.rs Outdated
Comment thread services/attest/producer/src/builder.rs
Comment thread services/attest/producer/src/signer.rs Outdated
Comment thread services/attest/api/src/consts.rs
Comment thread services/attest/producer/src/signer.rs Outdated
// ── Stub signer used internally by SoftwareAttestProducer ────────────────────

#[cfg(feature = "test-support")]
struct StubSigner;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The stub DER bytes [0x30, 0x00] are written out at twelve places across
signer.rs, builder.rs and dice_identity.rs. One constant here, used from
all of them, makes it obvious they are the same placeholder and gives it a name
to grep for when a real cert appears.

Suggested change
struct StubSigner;
/// Placeholder DER: an empty SEQUENCE. Not a parseable certificate.
const STUB_CERT: [u8; 2] = [0x30, 0x00];
struct StubSigner;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, that covers signer.rs. The seven copies in the test modules are still
inline: builder.rs:236, 244, 246 and dice_identity.rs:46, 54, 70, 78. (The
0x30, 0x01 at dice_identity.rs:80 is a deliberately different cert, leave it.)

STUB_CERT is currently #[cfg(feature = "test-support")] and private, so those
modules cannot reach it. To share it, widen the gate and the visibility:

#[cfg(any(test, feature = "test-support"))]
pub(crate) const STUB_CERT: [u8; 2] = [0x30, 0x00];

then use crate::signer::STUB_CERT; in both test modules.

The [0x30, 0x00] in cert_ueid.rs are DER field placeholders, not stub certs, so
they stay as they are.

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.

Please review.

STUB_CERT is now pub(crate) under #[cfg(any(test, feature = "test-support"))] in signer.rs, and both builder.rs and dice_identity.rs test modules import and use it — seven inline [0x30, 0x00] literals replaced.

Comment thread third_party/crates_io/Cargo.toml
gmandyam and others added 2 commits September 3, 2026 08:42
consts.rs: add MAX_PROVIDERS = 8 next to other capacity bounds.

builder.rs:
- Replace hand-rolled BufWriter with type alias for
  minicbor::encode::write::Cursor<&mut [u8]>, eliminating ~20 lines
  of duplicated write machinery.
- Add cbor_err() to distinguish BufferFull (write overflow) from Cbor
  (malformed encoding) — previously both collapsed to Cbor.
- Refactor each of the four encode blocks into a closure so ? works
  without per-call .map_err().
- Change measurements parameter from &Vec<_, MAX_MEASUREMENTS> to
  &[Measurement]; callers coerce naturally, no capacity in the
  signature.
- Name the fixed claim count as FIXED_CLAIMS = 11 with an inline list
  so the next person adding a claim sees the invariant.
- Use e.u64(iat) instead of e.i64(iat as i64); iat is unsigned.

signer.rs:
- Use MAX_PROVIDERS constant instead of bare 8 for providers Vec.
- Change impl<'a> AttestProducer for HwAttestProducer<'a> to use
  anonymous lifetime '_ (the 'a on the struct still stands).
- Add STUB_CERT constant for the placeholder DER bytes; replace all
  twelve inline [0x30, 0x00] literals.

third_party/crates_io/Cargo.toml:
- Move minicbor out from under the "host tool crates" comment with its
  own comment describing it as a no_std production dependency.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
@gmandyam

gmandyam commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

producer/src/builder.rs build(): the ueid claim (key 256) is hard-coded to a constant placeholder (ueid[0] = 0x01, the remaining 28 bytes always zero) for every device, in the shared build() path used by both HwAttestProducer (production) and SoftwareAttestProducer. The README documents this claim as "Unique Entity Identifier from Caliptra CDI", but nothing derives it from device identity — every token from every device will carry the same UEID. This defeats the purpose of the claim and should either be wired to a real per-device source (e.g. via HwSigner/DICE identity) or clearly gated so it can't ship silently wrong (a // TODO alone is not enough given this is the production code path, not just the test stub).

ueid as I defined it in RFC 9711 (https://www.rfc-editor.org/info/rfc9711/#section-4.2.1) was meant to be determined directly from HW and not potentially overridden by a Caliptra cert ueid field (e.g. due to faulty implementation).

On target, I expect the ueid to be available in HW (e.g. via fuse sense register), but we don't have a specific HW target. That being said, I will modify the code to derive ueid from the cert chain, but I will also need to add a check to ensure that ueid is uniform across the entire cert chain. I can also add a note in the readme that the implementor will have to replace this part of the code if ueid is determined from a source other than the Caliptra cert chain.

gmandyam and others added 5 commits September 3, 2026 10:09
Add cert_ueid.rs, a no_std DER walker that extracts the 17-byte UEID
from the TCG UEID extension (OID 2.23.133.5.4.4) present in every
Caliptra DICE chain certificate. No external ASN.1 or x509 crate is
required; the walker follows only the tag/length fields needed to reach
the extension value.

extract_and_verify() reads the UEID from the leaf cert (AliasRT, index
0) and checks that every other cert in the chain that also carries the
extension agrees with that value, returning Caliptra("UEID mismatch
across certificate chain") if they differ.

HwAttestProducer::generate_token() calls extract_and_verify() before
token assembly and forwards the result to builder::build(). The builder
no longer constructs a placeholder UEID; it receives the real device
value as &[u8; UEID_LEN].

SoftwareAttestProducer uses a fixed deterministic stub UEID (type-byte
0x01 + 16 recognisable bytes) so unit and integration tests continue to
work without a real Caliptra cert chain.

Update producer/README.md to document the new source file and the
derivation source for the ueid claim.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
…ner.rs, integration test

- Add #[allow(clippy::too_many_arguments)] to builder::build (8 args after
  ueid parameter was added for DICE cert derivation)
- Fix cert_ueid.rs rustfmt: reorder imports (heapless before openprot_attest_api,
  AttestError last in group); reformat four .ok_or() chains and for loop body
- Fix signer.rs rustfmt: expand builder::build() call to one-arg-per-line
- Update integration test: use heapless types for OemId/hw_model/hw_version,
  pass iat and out buffer to generate_token, use cert_chain(&mut buf) API,
  unwrap COSE_Sign1 tag(18) and CWT tag(61) via ciborium as_tag()

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
Expand generate() and decode_payload_map() signatures to one-param-per-line;
wrap producer.generate_token() method chain.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
CertChain was a zero-method newtype over Vec<Vec<u8, MAX_CERT_SIZE>,
MAX_CHAIN_LEN> with a single call site that immediately destructured it.
Return the inner Vec directly from dice_identity::cert_chain().

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
api/README.md:
- Remove deleted CertChain type from types table
- Remove CaliptraSigner (replaced by HwSigner) from traits section
- Update AttestProducer signature to 4-arg generate_token with out buffer
- Update HwSigner trait (was CaliptraSigner): add caliptra_measurements,
  use &mut buf output pattern throughout
- Update MeasurementProvider::measurements to &mut out pattern
- Note no_std + heapless in Cargo section

producer/README.md:
- HwAttestProducer example: reference not Arc, &dyn not Box, 4-arg generate_token
- SoftwareAttestProducer example: 4-arg generate_token with out buffer
- Dependencies: minicbor (not ciborium), HwSigner (not CaliptraSigner)

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
@chrysh
chrysh self-requested a review September 4, 2026 11:42
Widen STUB_CERT gate to #[cfg(any(test, feature = "test-support"))] and
make it pub(crate) so the builder.rs and dice_identity.rs test modules
can reference it via use crate::signer::STUB_CERT, replacing seven
inline [0x30, 0x00] literals. The [0x30, 0x01] in dice_identity.rs is
intentionally distinct and is unchanged.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants