Skip to content

security: add certificate revocation record#189

Open
Harsh23Kashyap wants to merge 5 commits into
named-data:revokefrom
Harsh23Kashyap:security/cert-revocation-record
Open

security: add certificate revocation record#189
Harsh23Kashyap wants to merge 5 commits into
named-data:revokefrom
Harsh23Kashyap:security/cert-revocation-record

Conversation

@Harsh23Kashyap

@Harsh23Kashyap Harsh23Kashyap commented Jun 13, 2026

Copy link
Copy Markdown

Summary

  • Add low-level Revoke and IsRevoked certificate revocation APIs in std/security.
  • Store revocation entries as process-local, mutex-protected records derived from UCLA-IRL/ndnrevoke's revocation record shape.
  • Derive REVOKE-style record names from certificate names and keep record metadata including the certificate name, public key hash, reason, and timestamp.
  • Add tests for nil handling, unrelated certificates, repeated revocation checks, and concurrent access.

Test plan

  • go test ./std/security -run 'TestCertificateRevocationRecord' -count=20
  • go test -race ./std/security
  • go test ./std/security
  • make test

@tianyuan129

Copy link
Copy Markdown
Collaborator

https://github.com/UCLA-IRL/ndnrevoke.

Please use this as reference for Revocation Record.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Harsh23Kashyap

Copy link
Copy Markdown
Author

Thanks for the pointer. I looked through UCLA-IRL/ndnrevoke, especially the record/revoker/checker flow, and updated this PR to make the local revocation record closer to that model.

The public API is still limited to the requested low-level surface:

Revoke(cert ndn.Data)
IsRevoked(cert ndn.Data) bool

Internally, Revoke now builds and stores a revocation record instead of just a bare certificate-name set entry. The record derives a REVOKE-style name from the certificate name, stores the original cert name, public key hash, reason, and timestamp, and IsRevoked checks using that derived record name. I kept ledger/checker integration out of this PR since this still looks like the low-level API step before the app-facing flow.

I reran:

go test ./std/security -run 'TestCertificateRevocationRecord' -count=20
go test -race ./std/security
go test ./std/security
make test

All passed.

@tianyuan129

Copy link
Copy Markdown
Collaborator

Thanks for the initial work! I saw that you currently have a struct storing revocation records and make the code checks against that. That's exactly the next step we want to work on.

The library has a module trust_config that automates the data authentication and authorization workflow, and actually the downstream applications who uses ndnd mainly rely on the high-level APIs that are binded to this module to get rid of the crypto details.

At high level, it executes the NDN trust schema and recursively climb the certification chain for a received data until reaching a bootstrapped (or otherwise externally authenticated) trust anchor. We need corporate the revocation here -- so obviously we need to store received revocation records here....but first lets think about design space.

@Harsh23Kashyap

Copy link
Copy Markdown
Author

Thanks for the context. I’ll pause on further implementation for now and read through trust_config and the papers first, so I understand where revocation should fit in the existing validation flow before making the next change.

@Harsh23Kashyap

Copy link
Copy Markdown
Author

I went through the trust_config validation path and the two papers at a high level.

My understanding is that trust_config is the right place to think about the next step, because it already handles the high-level data validation flow: checking the trust schema, fetching certificates, using the certificate cache, and recursively climbing the certificate chain until a trust anchor.

I also see why the Envelope-related code can look like it jumps between trust anchors. I’ll avoid touching that part for now unless needed, and focus only on where revocation records should be stored and checked inside the current validation workflow.

@Harsh23Kashyap

Copy link
Copy Markdown
Author

Based on my current understanding, I see the next step as a design problem around TrustConfig, not just the low-level Revoke / IsRevoked helpers.

The current PR gives us the low-level revocation record primitive. The next question is where that record should live and when trust_config should check it during validation.

I think the design space is:

  1. Local TrustConfig revocation store first: store received revocation records inside TrustConfig, and reject a certificate before it is used for validation. This would cover certificates coming from cache, fetch, or direct validation.

  2. Store revocation record Data, not just names: instead of only keeping a cert name or derived record name, store the received revocation record Data/metadata so we can preserve reason, timestamp, public key hash, revoker, and later signature validation state.

  3. Network or ledger check later: during validation, TrustConfig can derive the REVOKE record name and fetch either a revocation record or a nack, closer to ndnrevoke. I think this is larger and should probably come after the local store design is clear.

So my proposed next step is to start with (1), while shaping the store so (2) can fit naturally later. I’ll keep the Envelope-related code untouched for now and only use the Envelope paper as context for why revocation belongs as a validation/checker step.

Does this direction sound right before I update the PR further?

@Harsh23Kashyap

Copy link
Copy Markdown
Author

Sharing the HLD and diagram for the TrustConfig revocation design discussed above.

Attachments

HLD

Goal: integrate revocation into the high-level validation workflow, not only as the low-level Revoke / IsRevoked helpers in this PR.

Proposed flow

flowchart TD
  dataPacket["Received Data"] --> validate["TrustConfig.Validate"]
  validate --> keyLocator["Read KeyLocator"]
  keyLocator --> certSource["Cert from cache, fetch, or direct args"]
  certSource --> revokedCheck["Check TrustConfig revocation store"]
  revokedCheck -->|"revoked"| reject["Fail validation"]
  revokedCheck -->|"not revoked"| schemaCheck["Trust schema check"]
  schemaCheck --> sigCheck["Signature check"]
  sigCheck --> certChain["Validate signing cert"]
  certChain --> validate
Loading

Where to check revocation

  • before using a cached certificate
  • after fetching a certificate, before caching it
  • before using args.cert for signature validation
  • when validating a certificate packet itself (ContentTypeKey)

Sequenced design space

  1. Local TrustConfig revocation store first
  2. Store revocation record Data/metadata next
  3. Network or ledger lookup later, closer to ndnrevoke

Out of scope for now

  • network revocation lookup
  • Envelope-like code changes
  • ledger/checker integration

The draw.io diagram shows the same flow with cert sources, fail path, trust-anchor path, and notes on where the store should live.

@Harsh23Kashyap

Copy link
Copy Markdown
Author

Following up on the design proposal above — no rush, just checking whether the direction makes sense before I start coding.

To summarize what I'd do first if this looks reasonable:

  • add a revocation store scoped to TrustConfig
  • check it inside Validate before a cert is used from cache, fetch, or direct args
  • keep the current Revoke / IsRevoked helpers as the low-level primitive for now

I'll hold off on further PR changes until I hear back. Happy to adjust the plan if you'd prefer a different starting point (e.g. storing received revocation record Data earlier, or a different hook in the validation path).

@tianyuan129

tianyuan129 commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

Based on my current understanding, I see the next step as a design problem around TrustConfig, not just the low-level Revoke / IsRevoked helpers.

The current PR gives us the low-level revocation record primitive. The next question is where that record should live and when trust_config should check it during validation.

I think the design space is:

  1. Local TrustConfig revocation store first: store received revocation records inside TrustConfig, and reject a certificate before it is used for validation. This would cover certificates coming from cache, fetch, or direct validation.
  2. Store revocation record Data, not just names: instead of only keeping a cert name or derived record name, store the received revocation record Data/metadata so we can preserve reason, timestamp, public key hash, revoker, and later signature validation state.
  3. Network or ledger check later: during validation, TrustConfig can derive the REVOKE record name and fetch either a revocation record or a nack, closer to ndnrevoke. I think this is larger and should probably come after the local store design is clear.

So my proposed next step is to start with (1), while shaping the store so (2) can fit naturally later. I’ll keep the Envelope-related code untouched for now and only use the Envelope paper as context for why revocation belongs as a validation/checker step.

Does this direction sound right before I update the PR further?

For storing revocation records, I am thinking probably we don't need a separate store. The same store for certificate works for revocation too, since both are data, and store support name-based query. For a certificate, it's straightforward to use naming convention to construct its potential revocation name and query the store.

Yeah now let's assume revocation records are already delivered to local...we need an API to install revocation records.
Technically, the engine can express an Interest to network to ask for revocation, but I feel we should start simple...but we definitely need leave comments in code.

Revocation records has a notBefore -- marking data produced before this timestamp should be valid. It's related to #186, which I am trying to review and merge now. We need to implement the timestamp checking logic after we merged the feature (soon).

@tianyuan129

Copy link
Copy Markdown
Collaborator

Sharing the HLD and diagram for the TrustConfig revocation design discussed above.

Attachments

HLD

Goal: integrate revocation into the high-level validation workflow, not only as the low-level Revoke / IsRevoked helpers in this PR.

Proposed flow

flowchart TD
  dataPacket["Received Data"] --> validate["TrustConfig.Validate"]
  validate --> keyLocator["Read KeyLocator"]
  keyLocator --> certSource["Cert from cache, fetch, or direct args"]
  certSource --> revokedCheck["Check TrustConfig revocation store"]
  revokedCheck -->|"revoked"| reject["Fail validation"]
  revokedCheck -->|"not revoked"| schemaCheck["Trust schema check"]
  schemaCheck --> sigCheck["Signature check"]
  sigCheck --> certChain["Validate signing cert"]
  certChain --> validate
Loading

Where to check revocation

  • before using a cached certificate
  • after fetching a certificate, before caching it
  • before using args.cert for signature validation
  • when validating a certificate packet itself (ContentTypeKey)

Sequenced design space

  1. Local TrustConfig revocation store first
  2. Store revocation record Data/metadata next
  3. Network or ledger lookup later, closer to ndnrevoke

Out of scope for now

  • network revocation lookup
  • Envelope-like code changes
  • ledger/checker integration

The draw.io diagram shows the same flow with cert sources, fail path, trust-anchor path, and notes on where the store should live.

LGTM. Not sure if your diagram captures that, but we need to skip trust anchor for revocation checking.

@Harsh23Kashyap

Copy link
Copy Markdown
Author

Thanks for the feedback — this helps a lot. A few notes on how I'm reading it and what I'll do next.

On storage: I'll drop the idea of a separate TrustConfig revocation store. Revocation records are Data like certificates, so reusing the existing name-based store (via keychain.Store()) makes sense — derive the REVOKE name from the cert and query the store. I'll update the HLD/diagram to reflect that.

On the install API: Agreed that the next concrete piece is an API to install received revocation records locally (assuming they're already delivered). I'll shape that before wiring checks into Validate.

On validation hooks: I'll keep the same hook points from the HLD (cache hit, post-fetch before cache insert, direct args.cert, cert packet validation), with one addition from your comment: skip revocation checking for trust anchors.

On notBefore: I'll hold off on timestamp-based revocation logic until #186 lands, then wire it in as a follow-up.

On network fetch: I'll start with local install + store lookup only, and leave comments in code where a future Interest-based revocation fetch could plug in.

Plan for the next PR update:

  1. Add install API for revocation record Data into the shared store
  2. During Validate, derive REVOKE name and query store before using a cert (non-anchor only)
  3. Update the diagram to show shared store + trust-anchor skip
  4. Tests for install + rejected validation when a revocation record is present

I'll start on this unless you'd rather split install vs. validation into separate PRs.

@tianyuan129

Copy link
Copy Markdown
Collaborator

On notBefore: I'll hold off on timestamp-based revocation logic until #186 lands, then wire it in as a follow-up.

Given #186 has already been merged, you can use the timestamp from Data signature now.

@Harsh23Kashyap

Copy link
Copy Markdown
Author

Pushed the TrustConfig integration in 0e2c8b2:

  • InstallRevocationRecord stores revocation record Data in keychain.Store()
  • Validate checks the derived REVOKE name before using a cert (cache / fetch / direct / cert packet paths)
  • Trust anchors are skipped for revocation checks
  • TODO(revocation) left for future network Interest fetch; notBefore deferred until Clean up old data validation code and remove ignoreValidity #186

Tests: go test -race ./std/security, make test — all green.

@Harsh23Kashyap

Copy link
Copy Markdown
Author

Addressing the feedback that revocation records should be Data packets (not in-memory structs):

  • MakeRevocationRecord builds a proper revocation record Data packet with ndnrevoke-style content TLVs (timestamp, reason, public key hash)
  • Revoke now stores the encoded Data wire instead of a Go struct
  • InstallRevocationRecord validates both REVOKE naming and record content
  • TrustConfig store lookup path unchanged

Tests pass (go test -race ./std/security, make test).

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.

2 participants