Attestation libraries - #386
Conversation
Version 0.7.6 deprecated the pre_init! macro as a hard error, breaking //target/ast10x0:entry. Pin to the same version as upstream main. Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
|
|
||
| cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] } | ||
| cortex-m-rt = "0.7.5" | ||
| cortex-m-rt = "=0.7.5" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Changed accordingly. Exact pin removed.
| @@ -0,0 +1,22 @@ | |||
| # Licensed under the Apache-2.0 license | |||
There was a problem hiding this comment.
Can we drop Cargo.toml? This is a Bazel project and the crate is already fully defined in BUILD.bazel — targets and deps included.
|
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. |
| /// 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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Renamed to HWSigner
| } | ||
|
|
||
| /// Return just the DER-encoded Alias (leaf) certificate. | ||
| pub fn alias_cert(signer: &dyn CaliptraSigner) -> Result<Vec<u8>, AttestError> { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Removed function and unit test
|
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. | ||
|
|
There was a problem hiding this comment.
| #![no_std] | |
| #![forbid(unsafe_code)] |
There was a problem hiding this comment.
Added. See lib.rs files.
| Value::Text("https://openprot.example/caliptra/device".into()), | ||
| )); | ||
|
|
||
| let iat = std::time::SystemTime::now() |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
stdlib dependencies are removed.
|
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
openprot-secure-coding — violation
|
Moved to heapless. |
Neither crate is referenced in the producer source; removing them from BUILD.bazel deps and the README dependency table.
Removed and README updated. |
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")] |
There was a problem hiding this comment.
Can this live under mod tests?
rusty1968
left a comment
There was a problem hiding this comment.
LGTM: [x]
Correctness
producer/src/builder.rsbuild(): theueidclaim (key 256) is hard-coded to a constant placeholder (ueid[0] = 0x01, the remaining 28 bytes always zero) for every device, in the sharedbuild()path used by bothHwAttestProducer(production) andSoftwareAttestProducer. 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. viaHwSigner/DICE identity) or clearly gated so it can't ship silently wrong (a// TODOalone is not enough given this is the production code path, not just the test stub).third_party/crates_io/Cargo.lock: diffed against currentmaintip (not just this PR's stale base), this PR's lockfile reintroducespldm-common/pldm-interface(already removed onmain), is missingstatig(added onmainsince 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 withciborium/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.mdandservices/attest/producer/README.mddocument an API that no longer matches the code: the README shows aCaliptraSignertrait withsign_es384/alias_cert_der/cert_chain_der(&self) -> Result<Vec<Vec<u8>>, _>, and anAttestProducer::generate_token(&self, ...) -> Result<Vec<u8>, _>/cert_chain(&self) -> Result<CertChain, _>. The actual code (traits.rs,types.rs) has been reworked tono_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 thesw-name(14)/sw-version(15) claims thatbuilder.rsactually 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 fromSoftwareAttestProducer::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): returnResult<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 insigner.rs.- Everywhere else (
builder.rsproduction path,signer.rsnon-test-support impls,dice_identity.rsproduction fn) consistently usesmap_err(...)?— good, this is the only outlier.
Secrets, crypto, and register access (openprot-secure-coding)
- The prior finding (unused
zeroizedependency) is resolved:zeroizeno 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, andHwSigner::sign()only returns a public signature. Nothing to flag here. - No
unsafeanywhere (#![forbid(unsafe_code)]in both crate roots), no secret-byte comparisons, and no key material appears inDebug/error output. builder::build()stack-allocates two independentMAX_TOKEN_SIZE(8 KiB each) scratch buffers (payload_scratch,cose_scratch) simultaneously, plus a 128-byte header scratch and the caller-ownedoutbuffer. 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-identicale.<method>(...).map_err(|_| AttestError::Cbor)?;lines. The previously-flagged shape (claims.push((Value::Integer(...), Value::...))→ aclaim()helper) doesn't map cleanly onto the new streamingminicbor::EncoderAPI, 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 acrosssigner.rs(StubSigner,SoftwareAttestProducer::cert_chain),dice_identity.rstests, andbuilder.rstests — a sharedfn stub_cert() -> [u8; 2]test helper would remove the duplication. Low priority, test-only code. decode_outer/find_claim*helpers inbuilder.rstests (viaminicbor) andproducer_integration.rs(viaciborium) 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 inbuilder.rs'sdecode_outer("Use ciborium only in tests") is simply stale/wrong now (the code usesminicbor::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 namedMAX_*constant; fixed in the diff (MAX_PROVIDERS).third_party/crates_io/Cargo.toml:ciborium/minicborwere inserted directly under the# Host tool crates needed by pigweed pw_kernel/toolingcomment even thoughminicboris a production,no_stddependency 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).
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>
|
@rusty1968 @gmandyam Are we adding AI skills to the repo now? |
There was a problem hiding this comment.
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.
| // ── Stub signer used internally by SoftwareAttestProducer ──────────────────── | ||
|
|
||
| #[cfg(feature = "test-support")] | ||
| struct StubSigner; |
There was a problem hiding this comment.
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.
| struct StubSigner; | |
| /// Placeholder DER: an empty SEQUENCE. Not a parseable certificate. | |
| const STUB_CERT: [u8; 2] = [0x30, 0x00]; | |
| struct StubSigner; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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>
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. |
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>
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>
Add attestation service. Initial cut.