Skip to content

feat(deployment): check AMD CRL revocation in attestation validation#3381

Merged
baktun14 merged 2 commits into
mainfrom
feat/deployment-amd-crl-revocation-check
Jul 6, 2026
Merged

feat(deployment): check AMD CRL revocation in attestation validation#3381
baktun14 merged 2 commits into
mainfrom
feat/deployment-amd-crl-revocation-check

Conversation

@baktun14

@baktun14 baktun14 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Why

AMD SEV-SNP attestation validation ([CON-552]) proves a report came from genuine, nonce-bound AMD hardware, but it never checked AMD's certificate revocation list (CRL)node:crypto has no CRL support. So a chip whose ASK/ARK signing key AMD has revoked (e.g. a known-compromised part) still read as valid, with the verdict only noting "Revocation not checked". This closes that documented blind spot.

This is a back-port of console-air#52 into this repo's production attestation service (apps/api/src/confidential-compute/services/amd-snp/) — console-air's copy was itself ported from apps/api, so this brings the fix home and adapts it to the API's conventions (tsyringe DI, @Memoize, LoggerService).

Fixes CON-596

What

  • New amd-crl.parser.ts — a node:crypto-only manual ASN.1 parser for AMD KDS CRLs (no new dependency, matching the module's existing hand-rolled design). Extracts revoked serials + nextUpdate and verifies the ARK-signed CRL signature (RSA-PSS/SHA-384; an ECDSA issuer is also accepted so test fixtures need not be RSA).
  • amd-kds.client.tsgetCrl(product) fetching /vcek/v1/{product}/crl, cached via the existing @Memoize decorator (6h; the verifier additionally enforces the CRL's own nextUpdate), reusing the shared 404→null / transport-error→throw handling.
  • amd-snp.service.ts — once authenticity passes, checks the per-product CRL and resolves the verdict:
    • revoked ASK/ARK serial → invalid (notRevoked: false)
    • fresh, ARK-signed CRL not listing the chain → valid (notRevoked: true)
    • CRL unretrievable / unverifiable / stale → unverifiable (revocation unknown — never a silent pass)
    • Populates the previously-reserved notRevoked verdict check and drops the "Revocation not checked" detail.

Scope is backend-only: the deploy-web AttestationEvidenceModal renders status/detail (not the per-check breakdown) and notRevoked already exists in the generated API types, so a revoked key flips the badge to "invalid" with the new detail automatically. No schema change (notRevoked was already defined).

Testing

  • New amd-crl.parser.spec.ts — hermetic RSA-PSS CRL fixtures generated via openssl; covers parse, signature verify (+ tamper / wrong-issuer rejection), revoked-serial detection, serial normalisation, and expiry.
  • amd-snp.service.spec.ts extended with revoked / unretrievable / errored-CRL scenarios; the valid-path test now asserts notRevoked: true and no "Revocation not checked".
  • npx vitest run src/confidential-compute/66/66 pass; ESLint clean; tsc clean for changed files.

Summary by CodeRabbit

  • New Features

    • AMD SEV-SNP attestation verification now evaluates certificate revocation status using AMD CRLs, extending beyond authenticity-only checks.
    • Verdicts now reflect revocation state: valid, invalid (revoked), or unverifiable (revocation unavailable/verification failure).
  • Bug Fixes

    • Improved behavior for missing/unretrievable revocation data and for expired or malformed CRLs.
    • Stronger detection of tampered or mismatched CRLs to prevent incorrect revocation outcomes.
  • Tests

    • Added comprehensive CRL parsing/signature/revocation test coverage with hermetic OpenSSL-generated fixtures.

AMD SEV-SNP attestation proved a report came from genuine, nonce-bound
AMD hardware but never checked AMD's certificate revocation list (CRL),
so a chip whose ASK/ARK signing key AMD has revoked still read as
`valid`, with the verdict only noting "Revocation not checked".

Add a `node:crypto`-only manual ASN.1 CRL parser (no new dependency),
an `AmdKdsClient.getCrl` fetch cached via @memoize (6h; verifier also
enforces the CRL's own nextUpdate), and wire the check into the verdict:
revoked ASK/ARK -> `invalid` (notRevoked: false); fresh ARK-signed CRL
omitting them -> `valid` (notRevoked: true); CRL unretrievable /
unverifiable / stale -> `unverifiable` (never a silent pass).

Fixes CON-596
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds AMD CRL parsing and verification, fetches product CRLs through AmdKdsClient, and updates AmdSnpService.verify() to evaluate revocation status after authenticity checks. Tests generate hermetic OpenSSL fixtures and cover parser, client, and service behavior.

Changes

AMD CRL revocation support

Layer / File(s) Summary
CRL parsing contract and DER utilities
apps/api/src/confidential-compute/services/amd-snp/amd-crl.parser.ts
Defines ParsedAmdCrl, AmdCrlParseError, DER tag constants, and TLV/serial/time parsing helpers.
CRL parsing and signature checks
apps/api/src/confidential-compute/services/amd-snp/amd-crl.parser.ts
Implements parseAmdCrl, verifyCrlSignature, isCertRevoked, and isCrlExpired.
CRL parser test suite with openssl fixtures
apps/api/src/confidential-compute/services/amd-snp/amd-crl.parser.spec.ts
Generates hermetic ARK/ASK and clean/revoked CRL fixtures, then tests parsing, verification, revocation lookup, serial normalization, expiry, and malformed nextUpdate handling.
KDS client CRL fetch and shared error handling
apps/api/src/confidential-compute/services/amd-snp/amd-kds.client.ts
Refactors getVcek and getCaChain to use shared fetch-error handling and adds memoized getCrl(product) with the same 404/null mapping and logging behavior.
AmdSnpService revocation integration
apps/api/src/confidential-compute/services/amd-snp/amd-snp.service.ts
Adds CRL-based revocation evaluation to verify(), returning invalid, unverifiable, or valid after authenticity checks, and introduces #checkRevocation() for fetch, parse, expiry, signature, and serial checks.
Service revocation fixtures and verdict cases
apps/api/src/confidential-compute/services/amd-snp/amd-snp.service.spec.ts
Builds RSA/EC chain fixtures and CRLs, tightens the genuine-chain assertion to require notRevoked, and adds revoked, missing-CRL, and fetch-error verdict tests backed by a mocked getCrl().

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: size: XL

Suggested reviewers: stalniy

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/deployment-amd-crl-revocation-check

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.81022% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.68%. Comparing base (1112d73) to head (56c5f28).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...ential-compute/services/amd-snp/amd-snp.service.ts 92.59% 2 Missing ⚠️
...dential-compute/services/amd-snp/amd-crl.parser.ts 98.85% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3381      +/-   ##
==========================================
- Coverage   69.80%   68.68%   -1.13%     
==========================================
  Files        1100     1012      -88     
  Lines       26926    24716    -2210     
  Branches     6457     6030     -427     
==========================================
- Hits        18797    16977    -1820     
+ Misses       7135     6780     -355     
+ Partials      994      959      -35     
Flag Coverage Δ *Carryforward flag
api 85.60% <97.81%> (+0.46%) ⬆️
deploy-web 55.01% <ø> (ø) Carriedforward from 6fbde65
log-collector ?
notifications 91.44% <ø> (ø) Carriedforward from 6fbde65
provider-console 81.38% <ø> (ø) Carriedforward from 6fbde65
provider-inventory ?
provider-proxy 86.26% <ø> (ø) Carriedforward from 6fbde65
tx-signer ?

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
...-compute/services/amd-snp/amd-crl.test-fixtures.ts 100.00% <100.00%> (ø)
...dential-compute/services/amd-snp/amd-kds.client.ts 100.00% <100.00%> (+85.71%) ⬆️
...dential-compute/services/amd-snp/amd-crl.parser.ts 98.85% <98.85%> (ø)
...ential-compute/services/amd-snp/amd-snp.service.ts 89.28% <92.59%> (+0.05%) ⬆️

... and 90 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
apps/api/src/confidential-compute/services/amd-snp/amd-crl.parser.spec.ts (1)

6-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Wrap fixture lifecycle in a setup() helper.

This spec uses file-level mutable fixtures with beforeAll/afterAll. Please move fixture creation/cleanup behind a setup() helper to match the repo’s unit-test pattern.

Proposed direction
-let fixtures: ReturnType<typeof generateCrlFixtures>;
+let fixtures: ReturnType<typeof generateCrlFixtures>;
+let cleanupFixtures: (() => void) | undefined;
+
+function setup() {
+  const fixtures = generateCrlFixtures();
+  return {
+    fixtures,
+    cleanup: () => rmSync(fixtures.dir, { recursive: true, force: true })
+  };
+}
 
 beforeAll(() => {
-  fixtures = generateCrlFixtures();
+  const context = setup();
+  fixtures = context.fixtures;
+  cleanupFixtures = context.cleanup;
 });
 
 afterAll(() => {
-  if (fixtures) rmSync(fixtures.dir, { recursive: true, force: true });
+  cleanupFixtures?.();
 });

As per coding guidelines, **/*.spec.ts: “Use setup function instead of beforeEach in unit and service level tests.” As per path instructions, **/*.spec.ts: “Check that tests use a setup() function.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/confidential-compute/services/amd-snp/amd-crl.parser.spec.ts`
around lines 6 - 21, Wrap the AMD CRL fixture setup/teardown in a local setup()
helper instead of using file-level mutable state with beforeAll/afterAll. Update
amd-crl.parser.spec.ts so generateCrlFixtures and rmSync are owned by setup(),
and have the tests consume the returned fixtures through that helper to match
the repo’s spec pattern. Keep the existing test symbols like
generateCrlFixtures, fixtures, and the cleanup logic, but move their lifecycle
management behind setup().

Sources: Coding guidelines, Path instructions

apps/api/src/confidential-compute/services/amd-snp/amd-snp.service.spec.ts (1)

176-180: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Constrain the CRL mock to the resolved product.

getCrl currently returns a CRL for any product, so these tests would not catch a regression that fetches the wrong product’s CRL.

Suggested test hardening
-    kdsClient.getCrl.mockImplementation(async () => {
+    kdsClient.getCrl.mockImplementation(async product => {
+      if (product !== resolving) return null;
       if (input.crl === "missing") return null;
       if (input.crl === "error") throw new Error("AMD KDS unavailable");
       return input.crl === "revoked" ? revokedCrlDer : cleanCrlDer;
     });

As per path instructions, verify meaningful assertions in specs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/confidential-compute/services/amd-snp/amd-snp.service.spec.ts`
around lines 176 - 180, The CRL mock in amd-snp.service.spec.ts is too
permissive because getCrl returns a CRL regardless of the requested product, so
it won’t catch regressions that fetch the wrong one. Tighten the mock tied to
kdsClient.getCrl by asserting the requested product matches the resolved product
used by the service, and only returning the CRL for that product while rejecting
or returning null for others; keep the existing missing/error/revoked paths but
bind them to the expected product so the spec verifies the correct lookup
behavior.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/confidential-compute/services/amd-snp/amd-crl.parser.ts`:
- Around line 171-173: The `isCrlExpired` helper currently treats a missing
`nextUpdate` as non-expired, which lets stale CRLs remain valid indefinitely.
Update `isCrlExpired(crl, now)` in `amd-crl.parser.ts` so a `null`
`crl.nextUpdate` is considered expired/stale instead of fresh, while still
comparing timestamps normally when `nextUpdate` is present. Keep the behavior
aligned with the “fresh CRL” contract by making the function return true when
`nextUpdate` is absent.

In `@apps/api/src/confidential-compute/services/amd-snp/amd-snp.service.spec.ts`:
- Around line 235-261: The generateCrl helper leaves temporary CRL directories
behind when execFileSync or readFileSync throws. Update generateCrl in
amd-snp.service.spec.ts to wrap the OpenSSL/file-read flow in a try/finally and
always call rmSync on the mkdtempSync-created dir in the finally block, keeping
the cleanup tied to generateCrl and its ossl helper.

---

Nitpick comments:
In `@apps/api/src/confidential-compute/services/amd-snp/amd-crl.parser.spec.ts`:
- Around line 6-21: Wrap the AMD CRL fixture setup/teardown in a local setup()
helper instead of using file-level mutable state with beforeAll/afterAll. Update
amd-crl.parser.spec.ts so generateCrlFixtures and rmSync are owned by setup(),
and have the tests consume the returned fixtures through that helper to match
the repo’s spec pattern. Keep the existing test symbols like
generateCrlFixtures, fixtures, and the cleanup logic, but move their lifecycle
management behind setup().

In `@apps/api/src/confidential-compute/services/amd-snp/amd-snp.service.spec.ts`:
- Around line 176-180: The CRL mock in amd-snp.service.spec.ts is too permissive
because getCrl returns a CRL regardless of the requested product, so it won’t
catch regressions that fetch the wrong one. Tighten the mock tied to
kdsClient.getCrl by asserting the requested product matches the resolved product
used by the service, and only returning the CRL for that product while rejecting
or returning null for others; keep the existing missing/error/revoked paths but
bind them to the expected product so the spec verifies the correct lookup
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d9a1f804-8b34-4dfc-89e8-5752c1c32813

📥 Commits

Reviewing files that changed from the base of the PR and between b6fecee and 6fbde65.

📒 Files selected for processing (5)
  • apps/api/src/confidential-compute/services/amd-snp/amd-crl.parser.spec.ts
  • apps/api/src/confidential-compute/services/amd-snp/amd-crl.parser.ts
  • apps/api/src/confidential-compute/services/amd-snp/amd-kds.client.ts
  • apps/api/src/confidential-compute/services/amd-snp/amd-snp.service.spec.ts
  • apps/api/src/confidential-compute/services/amd-snp/amd-snp.service.ts

Comment thread apps/api/src/confidential-compute/services/amd-snp/amd-crl.parser.ts Outdated
Comment thread apps/api/src/confidential-compute/services/amd-snp/amd-snp.service.spec.ts Outdated
Fail closed when a CRL has no nextUpdate (treat as stale), reject out-of-range DER

time components instead of silently normalizing, and accept only RSA-PSS CRL

signatures as AMD KDS issues. Rework the service test chain to a realistic RSA

ARK/ASK + P-384 VCEK so RSA-only verification holds, and add AmdKdsClient tests

covering the getVcek/getCaChain/getCrl 404 and transport-error paths.

Resolves CodeRabbit review on CON-596.

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (2)
apps/api/src/confidential-compute/services/amd-snp/amd-crl.parser.spec.ts (1)

6-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a setup() helper instead of suite-level fixture state.

This spec imports beforeAll/afterAll and relies on shared fixtures; repo test guidance asks unit specs to use a setup() function and avoid shared mutable setup state. As per coding guidelines, **/*.spec.ts: “Use setup function instead of beforeEach in unit and service level tests”. As per path instructions, **/*.spec.ts: “Check that tests use a setup() function (not beforeEach with shared mutable state).”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/confidential-compute/services/amd-snp/amd-crl.parser.spec.ts` at
line 6, The spec is using suite-level fixture setup via beforeAll/afterAll
instead of the required local setup pattern. Refactor amd-crl.parser.spec.ts to
define a setup() helper that creates and returns the test fixtures used by the
describe/it blocks, and have each test call setup() rather than relying on
shared mutable state. Remove the unused beforeAll/afterAll imports from the spec
and keep the test state isolated through the setup() function.

Sources: Coding guidelines, Path instructions

apps/api/src/confidential-compute/services/amd-snp/amd-kds.client.spec.ts (1)

14-16: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move cache-clearing into setup() rather than beforeEach/afterEach.

The suite already has a setup() helper (line 120); the memoization-cache reset could live there instead of beforeEach/afterEach, keeping test setup centralized per convention.

♻️ Suggested change
-  // getCaChain and getCrl are `@Memoize`'d against a module-level cache; clear it so results don't bleed between tests.
-  beforeEach(() => cacheEngine.clearAllKeyInCache());
-  afterEach(() => cacheEngine.clearAllKeyInCache());
-
   describe("getVcek", () => {
@@
   function setup() {
+    // getCaChain and getCrl are `@Memoize`'d against a module-level cache; clear it so results don't bleed between tests.
+    cacheEngine.clearAllKeyInCache();
     const httpClient = mock<HttpClient>();
     const logger = mock<LoggerService>();
     const client = new AmdKdsClient(httpClient, logger);
     return { client, httpClient, logger };
   }

As per coding guidelines, "Use setup function instead of beforeEach in unit and service level tests" for files matching **/*.spec.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/confidential-compute/services/amd-snp/amd-kds.client.spec.ts`
around lines 14 - 16, The memoization cache reset for `getCaChain` and `getCrl`
is currently wired through `beforeEach`/`afterEach`, but this spec should follow
the project convention of using `setup()`. Move the
`cacheEngine.clearAllKeyInCache()` call into the existing `setup()` helper in
`amd-kds.client.spec.ts`, and remove the `beforeEach`/`afterEach` hooks so test
initialization stays centralized and consistent.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/api/src/confidential-compute/services/amd-snp/amd-crl.parser.spec.ts`:
- Line 6: The spec is using suite-level fixture setup via beforeAll/afterAll
instead of the required local setup pattern. Refactor amd-crl.parser.spec.ts to
define a setup() helper that creates and returns the test fixtures used by the
describe/it blocks, and have each test call setup() rather than relying on
shared mutable state. Remove the unused beforeAll/afterAll imports from the spec
and keep the test state isolated through the setup() function.

In `@apps/api/src/confidential-compute/services/amd-snp/amd-kds.client.spec.ts`:
- Around line 14-16: The memoization cache reset for `getCaChain` and `getCrl`
is currently wired through `beforeEach`/`afterEach`, but this spec should follow
the project convention of using `setup()`. Move the
`cacheEngine.clearAllKeyInCache()` call into the existing `setup()` helper in
`amd-kds.client.spec.ts`, and remove the `beforeEach`/`afterEach` hooks so test
initialization stays centralized and consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f4cb7bb9-739f-4744-82f3-c52acfef5dcd

📥 Commits

Reviewing files that changed from the base of the PR and between 6fbde65 and 56c5f28.

📒 Files selected for processing (5)
  • apps/api/src/confidential-compute/services/amd-snp/amd-crl.parser.spec.ts
  • apps/api/src/confidential-compute/services/amd-snp/amd-crl.parser.ts
  • apps/api/src/confidential-compute/services/amd-snp/amd-crl.test-fixtures.ts
  • apps/api/src/confidential-compute/services/amd-snp/amd-kds.client.spec.ts
  • apps/api/src/confidential-compute/services/amd-snp/amd-snp.service.spec.ts

@baktun14 baktun14 added this pull request to the merge queue Jul 6, 2026
Merged via the queue into main with commit c458d50 Jul 6, 2026
57 checks passed
@baktun14 baktun14 deleted the feat/deployment-amd-crl-revocation-check branch July 6, 2026 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants