- Introduced AuthorityAdvisoryAiOptions and related classes for managing advisory AI configurations, including remote inference options and tenant-specific settings. - Added AuthorityApiLifecycleOptions to control API lifecycle settings, including legacy OAuth endpoint configurations. - Implemented validation and normalization methods for both advisory AI and API lifecycle options to ensure proper configuration. - Created AuthorityNotificationsOptions and its related classes for managing notification settings, including ack tokens, webhooks, and escalation options. - Developed IssuerDirectoryClient and related models for interacting with the issuer directory service, including caching mechanisms and HTTP client configurations. - Added support for dependency injection through ServiceCollectionExtensions for the Issuer Directory Client. - Updated project file to include necessary package references for the new Issuer Directory Client library.
		
			
				
	
	
	
		
			17 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	Attestor Verification Workflows
How StellaOps turns DSSE bundles into verifiable evidence, how the verification API reports outcomes, and how explainability signals surface in UI/CLI flows.
⚠️ 2025-11-01 coordination note:
StellaOps.Attestor.WebServiceis failing to compile until downstream fixes land (Contracts/AttestationBundleContracts.csnull-coalescing update and scope/token variables restored inProgram.cs). Verification flows ship in infrastructure/tests, but the WebService hand-off stays blocked — track viaATTESTOR-73-002(see Attestor task board).
1. Verification flow (API and service contract)
- Entry point. 
POST /api/v1/rekor/verifydeserialises toAttestorVerificationRequest. - Resolution order. The service tries 
uuid, then canonicalisedbundle, thenartifactSha256. At least one selector must be present (invalid_queryotherwise). - Optional proof refresh. 
refreshProof=trueforces a Rekor lookup before returning. Proofs are cached in Mongo. - Signature replay. Supplying 
bundlelets the service recompute the canonical hash and re-run signature checks; omitting the bundle skips those steps but still validates Merkle proofs and cached policy decisions. - Auth scopes. Endpoints demand 
attestor.verify(write scope is also accepted); read-only detail/list APIs requireattestor.readat minimum. 
1.1 Request properties
| Field | Type | Required | Purpose | 
|---|---|---|---|
uuid | 
string | optional | Rekor V2 UUID to verify and (optionally) refresh. | 
bundle | 
object | optional | DSSE envelope (same shape as submission) for signature re-verification. | 
artifactSha256 | 
string | optional | Resolve the most recent entry for an attestable artefact digest. | 
subject | 
string | optional | Logical subject identifier used for cache/telemetry tagging; defaults to the stored artifact digest. | 
envelopeId | 
string | optional | Stable identifier for the DSSE bundle (typically the canonical hash); enables cache lookups. | 
policyVersion | 
string | optional | Policy digest/version driving verification; feeds cache keys and observability dimensions. | 
refreshProof | 
bool | optional (default false) | 
Pull the current inclusion proof and checkpoint from Rekor before evaluating. | 
All selectors are mutually compatible; if more than one is set the service uses the first match (uuid → bundle → artifactSha256).
1.2 Response schema (AttestorVerificationResult)
| Field | Type | Description | 
|---|---|---|
ok | 
bool | true when the entry status is included and no issues were recorded. | 
uuid | 
string | Rekor UUID that satisfied the query. Useful for follow-up fetches. | 
index | 
number (int64) | Rekor log index, when supplied by the backend. | 
logUrl | 
string | Fully-qualified Rekor entry URL for operators and auditors. | 
status | 
string | Transparency-log status seen in Mongo (included, pending, failed, …). | 
checkedAt | 
string (ISO-8601 UTC) | Timestamp emitted when the response is created. | 
issues | 
array[string] | Machine-readable explainability codes. Empty when ok=true. | 
Note:
checkedAtis recomputed each call; cache hits do not recycle previous timestamps.
1.3 Success criteria
ok=true requires:
- Entry exists and status equals 
included. - Canonical DSSE hash matches the stored bundle hash.
 - Signature re-verification (when a bundle is supplied) succeeds.
 - Inclusion proof validates against the cached or refreshed checkpoint.
 
Any deviation records at least one issue and flips ok to false. Consumers must inspect issues rather than inferring from status alone.
2. Verification report schema
AttestorVerificationResult carries the flattened summary shown above. When callers request the detailed report (GET /api/v1/rekor/entries/{uuid}?refresh=true or via SDK) they receive a VerificationReport shaped as follows:
{
  "overallStatus": "pass",
  "succeeded": true,
  "policy": { ... },
  "issuer": { ... },
  "freshness": { ... },
  "signatures": { ... },
  "transparency": { ... },
  "issues": [ "bundle_hash_mismatch" ]
}
| Field | Type | Description | 
|---|---|---|
overallStatus | 
string (pass, warn, fail, skipped) | 
Aggregated verdict derived from the individual section statuses. | 
succeeded | 
bool | Convenience flag; true when overallStatus ∈ {pass, warn}. | 
policy | 
object | Results from policy evaluation (see below). | 
issuer | 
object | Identity/result of the signing entity. | 
freshness | 
object | Age analysis relative to policy settings. | 
signatures | 
object | Signature validation summary. | 
transparency | 
object | Inclusion proof / checkpoint evaluation summary. | 
issues | 
array[string] | De-duplicated set drawn from the sections; order is deterministic and stable. | 
2.1 policy
| Field | Description | 
|---|---|
status | 
Section verdict (pass, warn, fail, skipped). | 
policyId / policyVersion | 
DSL identifier and revision used for evaluation. | 
verdict | 
Policy outcome (allow, challenge, deny, etc.). | 
issues | 
Policy-specific explainability codes (e.g., policy_rule_blocked). | 
attributes | 
Key/value map emitted by the policy for downstream observability (e.g., applicable rules, matched waivers). | 
2.2 issuer
| Field | Description | 
|---|---|
status | 
Result of issuer validation. | 
mode | 
Signing mode detected (keyless, kms, unknown). | 
issuer | 
Distinguished name / issuer URI recorded during signing. | 
subjectAlternativeName | 
SAN pulled from the Fulcio certificate (keyless) or recorded KMS identity. | 
keyId | 
Logical key identifier associated with the signature. | 
issues | 
Issuer-specific issues (e.g., issuer_trust_root_mismatch, signer_mode_unsupported:kid). | 
2.3 freshness
| Field | Description | 
|---|---|
status | 
fail when the attestation exceeds verification.freshnessMaxAgeMinutes; warn when only the warning threshold is hit. | 
createdAt | 
Timestamp embedded in the attestation metadata. | 
evaluatedAt | 
Server-side timestamp used for age calculations. | 
age | 
ISO8601 duration of evaluatedAt - createdAt. | 
maxAge | 
Policy-driven ceiling (null when unchecked). | 
issues | 
freshness_max_age_exceeded, freshness_warning, etc. | 
2.4 signatures
| Field | Description | 
|---|---|
status | 
Signature validation verdict. | 
bundleProvided | 
true when canonical DSSE bytes were supplied. | 
totalSignatures | 
Count observed in the DSSE envelope. | 
verifiedSignatures | 
Number of signatures that validated against trusted keys. | 
requiredSignatures | 
Policy / configuration minimum enforced. | 
issues | 
Signature codes such as bundle_payload_invalid_base64, signature_invalid, signer_mode_unknown. | 
2.5 transparency
| Field | Description | 
|---|---|
status | 
Inclusion proof / checkpoint verdict. | 
proofPresent | 
Whether a proof document was available. | 
checkpointPresent | 
Indicates the Rekor checkpoint existed and parsed. | 
inclusionPathPresent | 
true when the Merkle path array contained nodes. | 
issues | 
Merkle/rekor codes (proof_missing, proof_leafhash_mismatch, checkpoint_missing, proof_root_mismatch). | 
2.6 Issue catalogue (non-exhaustive)
| Code | Trigger | Notes | 
|---|---|---|
bundle_hash_mismatch | 
Canonical DSSE hash differs from stored value. | Often indicates tampering or inconsistent canonicalisation. | 
bundle_payload_invalid_base64 | 
DSSE payload cannot be base64-decoded. | Validate producer pipeline; the attestation is unusable. | 
signature_invalid | 
At least one signature failed cryptographic verification. | Consider checking key rotation / revocation status. | 
signer_mode_unknown / signer_mode_unsupported:<mode> | 
Signing mode not configured for this installation. | Update attestorOptions.security.signerIdentity.mode. | 
issuer_trust_root_mismatch | 
Certificate chain does not terminate in configured Fulcio/KMS roots. | Check Fulcio bundle / KMS configuration. | 
freshness_max_age_exceeded | 
Attestation older than permitted maximum. | Regenerate attestation or extend policy window. | 
proof_missing | 
No inclusion proof stored or supplied. | When running offline, import bundles with proofs or allow warn-level policies. | 
proof_root_mismatch | 
Rebuilt Merkle root differs from checkpoint. | Proof may be stale or log compromised; escalate. | 
checkpoint_missing | 
No Rekor checkpoint available. | Configure RequireCheckpoint=false to downgrade severity. | 
Downstream consumers (UI, CLI, policy studio) should render human-readable messages but must retain the exact issue codes for automation and audit replay.
3. Explainability signals
- Canonicalisation. The service replays DSSE canonicalisation to derive 
bundleSha256. Failures surface asbundle_hash_mismatchor decoding errors. - Signature checks. Mode-aware handling:
kms(HMAC) compares against configured shared secrets.keylessrebuilds the certificate chain, enforces Fulcio roots, SAN allow-lists, and verifies with the leaf certificate.- Unknown modes emit 
signer_mode_unknown/signer_mode_unsupported:<mode>. 
 - Proof acquisition. When 
refreshProofis requested the Rekor backend may contribute a textual issue (Proof refresh failed: …) without stopping evaluation. - Merkle validation. Structured helper ensures leaf hash, path orientation, and checkpoint root are consistent; each validation failure has a discrete issue code.
 - Observability. The meter 
attestor.verify_totalincrements withresult=ok|failed; structured logs and traces carry the sameissuesvector for UI/CLI drill-down. 
All issues are appended in detection order to simplify chronological replay in the Console’s chain-of-custody view.
3. Issue catalogue
| Code | Trigger | Operator guidance | 
|---|---|---|
bundle_hash_mismatch | 
Canonicalised DSSE hash differs from stored bundle hash. | Re-download artefact; investigate tampering or submission races. | 
bundle_payload_invalid_base64 | 
Payload could not be base64-decoded. | Ensure bundle transport preserved payload; capture original DSSE for forensics. | 
signature_invalid_kms | 
HMAC verification failed for mode=kms. | 
Confirm shared secret alignment with Signer; rotate keys if drift detected. | 
signer_mode_unknown | 
Entry lacks signer mode metadata and bundle omitted it. | Re-ingest bundle or inspect submission pipeline metadata. | 
signer_mode_unsupported:<mode> | 
Signer mode is unsupported by the verifier. | Add support or block unsupported issuers in policy. | 
kms_key_missing | 
No configured KMS secrets to verify mode=kms. | 
Populate security:signerIdentity:kmsKeys in Attestor config before retry. | 
signature_invalid_base64 | 
One or more signatures were not valid base64. | Bundle corruption; capture raw payload and re-submit. | 
certificate_chain_missing | 
mode=keyless bundle lacked any certificates. | 
Ensure Signer attaches Fulcio chain; review submission pipeline. | 
certificate_chain_invalid | 
Certificates could not be parsed. | Fetch original DSSE bundle for repair; confirm certificate encoding. | 
certificate_chain_untrusted[:detail] | 
Chain failed custom-root validation. | Import correct Fulcio roots or investigate potential impersonation. | 
certificate_san_untrusted | 
Leaf SAN not in configured allow-list. | Update allow-list or revoke offending issuer. | 
signature_invalid | 
No signature validated with supplied public keys. | Treat as tampering; trigger incident response. | 
proof_missing | 
No Merkle proof stored for the entry. | Re-run with refreshProof=true; check Rekor availability. | 
bundle_hash_decode_failed | 
Stored bundle hash could not be decoded. | Verify Mongo record integrity; re-enqueue submission if necessary. | 
proof_inclusion_missing | 
Inclusion section absent from proof. | Retry proof refresh; inspect Rekor health. | 
proof_leafhash_decode_failed | 
Leaf hash malformed. | Replay submission; inspect Rekor data corruption. | 
proof_leafhash_mismatch | 
Leaf hash differs from canonical bundle hash. | Raises tamper alert; reconcile Rekor entry vs stored bundle. | 
proof_path_decode_failed | 
Inclusion path entry malformed. | Same action as above; likely Rekor data corruption. | 
proof_path_orientation_missing | 
Inclusion path lacks left/right marker. | File Rekor bug; fallback to mirror log if configured. | 
checkpoint_missing | 
Proof lacks checkpoint metadata. | Retry refresh; ensure Rekor is configured to return checkpoints. | 
checkpoint_root_decode_failed | 
Checkpoint root hash malformed. | Investigate Rekor/mirror integrity before trusting log. | 
proof_root_mismatch | 
Computed root hash != checkpoint root. | Critical alert; assume inclusion proof compromised. | 
Proof refresh failed: … | 
Rekor fetch threw an exception. | Message includes upstream error; surface alongside telemetry for debugging. | 
Future explainability flags must follow the same pattern: short, lowercase codes with optional suffix payload (code:detail).
4. Worked examples
4.1 Successful verification
{
  "ok": true,
  "uuid": "0192fdb4-a82b-7f90-b894-6fd1dd918b85",
  "index": 73421,
  "logUrl": "https://rekor.stellaops.test/api/v2/log/entries/0192fdb4a82b7f90b8946fd1dd918b85",
  "status": "included",
  "checkedAt": "2025-11-01T17:06:52.182394Z",
  "issues": []
}
This mirrors the happy-path asserted in AttestorVerificationServiceTests.VerifyAsync_ReturnsOk_ForExistingUuid, which replays the entire submission→verification loop.
4.2 Tampered bundle
{
  "ok": false,
  "uuid": "0192fdb4-a82b-7f90-b894-6fd1dd918b85",
  "index": 73421,
  "logUrl": "https://rekor.stellaops.test/api/v2/log/entries/0192fdb4a82b7f90b8946fd1dd918b85",
  "status": "included",
  "checkedAt": "2025-11-01T17:09:05.443218Z",
  "issues": [
    "bundle_hash_mismatch",
    "signature_invalid"
  ]
}
Derived from AttestorVerificationServiceTests.VerifyAsync_FlagsTamperedBundle, which flips the DSSE payload and expects both issues to surface. CLI and Console consumers should display these codes verbatim and provide remediation tips from the table above.
5. Validating the documentation
- Run 
dotnet test src/Attestor/StellaOps.Attestor/StellaOps.Attestor.Teststo exercise the scenarios behind the examples. - API integrators can 
curlthe verify endpoint and compare responses with the JSON above. - UI/CLI teams should ensure explainability tooltips and runbooks reference the same issue catalogue.
 
Keeping the documentation aligned with the test suite guarantees explainability remains deterministic and audit-friendly.
6. Offline bundles & air-gapped verification
Stella Ops Attestor now supports packaging attestations for sealed environments and rehydrating them without calling Rekor:
- Export bundles. 
POST /api/v1/attestations:exportaccepts either a list of Rekor UUIDs or filter criteria (subject,type,issuer,scope,createdAfter|Before,limit,continuationToken) and returns anattestor.bundle.v1document. Each item contains the attestation entry, canonical DSSE payload (base64), optional proof payload, and metadata. Responses include acontinuationTokenso callers can page through large result sets (limits default to 100 and are capped at 200). JSON content is required and requests are gated by theattestor.readscope. - Import bundles. 
POST /api/v1/attestations:importingests the bundle document, upserts attestation metadata, and restores the canonical DSSE/proof into the configured archive store. The S3 archive integration must be enabled; the response reports how many entries were imported versus updated, any skipped items, and issue codes (bundle_payload_invalid_base64,bundle_hash_mismatch,archive_disabled, …). - Offline verification. When replaying verification without log connectivity, submit the DSSE bundle and set 
offline=trueonPOST /api/v1/rekor/verify. The service reuses imported proofs when present and surfaces deterministic explainability codes (proof_missing,proof_inclusion_missing, …) instead of attempting Rekor fetches. 
Tests AttestorBundleServiceTests.ExportAsync_AppliesFiltersAndContinuation, AttestationBundleEndpointsTests, AttestorVerificationServiceTests.VerifyAsync_OfflineSkipsProofRefreshWhenMissing, and AttestorVerificationServiceTests.VerifyAsync_OfflineUsesImportedProof exercise the exporter/importer, API contracts, and the offline verification path with and without witness data.