NPS

English 中文版

RFC Number: NPS-RFC-0002 Title: Adopt X.509 + ACME for NID certificates Status: Active Author(s): Ori Lynn iamzerolin@gmail.com (LabAcacia) Shepherd: Ori Lynn Created: 2026-04-21 Last-Updated: 2026-09-05 Accepted: 2026-05-17 Activated: 2026-05-17 (accepted after the reference implementation; six-SDK X.509 + agent-01 parity had landed by 2026-04-30) Supersedes: none Superseded-By: none Affected Specs: NPS-3 NIP, tools/nip-ca-server (all language variants), spec/error-codes.md Affected SDKs: .NET, Python, TypeScript, Java, Rust, Go —

NPS-RFC-0002: Adopt X.509 + ACME for NID certificates

1. Summary

Replace the NIP custom certificate format with X.509v3 certificates carrying a LabAcacia-registered Extended Key Usage (EKU) OID for “agent-identity”, and move NIP CA issuance from the bespoke REST API to ACME (RFC 8555) with an agent-01 challenge type suited to programmatic NID control. Ed25519 remains the primary signature algorithm (RFC 8410 — Ed25519 in X.509). CRL / OCSP revocation semantics are preserved.

2. Motivation

This RFC responds to a 2026-04-20 review comment: “CA 不如直接兼容 现有的 CA 套路” (CAs should be compatible with existing CA practice).

The commenter’s point stands. Current NIP CA (tools/nip-ca-server-*) replicates the operational model of a classic X.509 CA — CSR → issuance → CRL/OCSP revocation → renewal — but uses a bespoke serialization. This creates two unnecessary costs:

  1. No tooling reuse. OpenSSL, rustls, BouncyCastle, step-ca, HashiCorp Vault PKI, HSM vendors, cert-manager for Kubernetes — none of them can sign, validate, or store NIP certs without custom integration. The NIP CA server variants in 6 languages each re-implement the ceremony.
  2. No issuance-protocol reuse. Manual NID issuance via a REST API doesn’t benefit from the operational maturity that ACME has built: automated renewal, staging environments, rate-limited public CAs, Certbot/lego clients, Kubernetes operators.

Switching to X.509 + ACME costs us ASN.1 parser exposure and ~2–4× cert size, but buys us 15 years of PKI tooling and the ability to run “Let’s Encrypt for Agents” as a future public service — or to delegate NID issuance to existing CAs via cross-signing.

3. Non-Goals

4. Detailed Design

4.1 Wire Format / Frame Changes

IdentFrame.cert_chain encoding changes from nip-cert-v1 (custom) to DER-encoded X.509 certificate chain concatenated or length-prefixed per rule below.

Subject field mapping:

X.509 Field NIP Value
Subject CN NID string (nid:ed25519:...)
Subject Alternative Name (SAN) URI Same NID as a URI entry for RFC 5280 compliance
Issuer Issuing CA’s NID
NotBefore / NotAfter Standard X.509 validity period
Public Key Ed25519 (RFC 8410 OID 1.3.101.112)
Serial Number 128-bit random, per CA/B Forum baseline

Critical extension — Extended Key Usage:

Register LabAcacia IANA Private Enterprise Number (PEN) — once assigned, reserve OID arc 1.3.6.1.4.1.<PEN>.1.

OID Meaning
1.3.6.1.4.1.<PEN>.1.1 agent-identity — this cert’s subject is an NPS Agent
1.3.6.1.4.1.<PEN>.1.2 node-identity — this cert’s subject is an NPS Node
1.3.6.1.4.1.<PEN>.1.3 ca-intermediate-agent — this CA may sign agent-identity certs

EKU is marked critical. A verifier that doesn’t recognize these EKUs MUST reject the certificate for NIP purposes. (Generic TLS clients would also reject, which is intentional — NID certs MUST NOT be mistaken for TLS server certs.)

Custom non-critical extension — nid:assurance-level:

Reserved for NPS-RFC-0003. This RFC defines the encoding shape only:

nid-assurance-level EXTENSION ::= {
    SYNTAX NidAssuranceLevel
    IDENTIFIED BY id-nid-assurance-level  -- 1.3.6.1.4.1.<PEN>.2.1
}
NidAssuranceLevel ::= ENUMERATED {
    anonymous (0),
    attested  (1),
    verified  (2)
}

Non-critical so v0.1 verifiers ignore the field; RFC-0003 flips it to critical when assurance-level enforcement lands.

4.2 Manifest / NWM Changes

None. X.509 cert chain travels inside IdentFrame, transparent to NWM.

4.3 Error Codes

New entries in spec/error-codes.md:

Error Code NPS Status Description
NIP-CERT-FORMAT-INVALID NPS-CLIENT-BAD-FRAME Cert chain is not DER-encoded X.509 or fails ASN.1 parsing
NIP-CERT-EKU-MISSING NPS-CLIENT-BAD-FRAME Required NPS EKU (agent-identity or node-identity) absent
NIP-CERT-SUBJECT-NID-MISMATCH NPS-CLIENT-BAD-FRAME Cert Subject CN / SAN URI does not match the NID in IdentFrame
NIP-ACME-CHALLENGE-FAILED NPS-CLIENT-BAD-FRAME ACME agent-01 challenge validation failed

4.4 State Machines / Flows

ACME agent-01 challenge — a new challenge type for NID identity validation (parallels HTTP/DNS/TLS-ALPN challenges in RFC 8555 §8):

Client (Agent)                 ACME Server (NIP CA)
    │                                 │
    │── newAccount (Ed25519 JWK) ──→ │
    │ ←── 201 + kid ────────────────  │
    │                                 │
    │── newOrder (identifiers) ────→ │
    │          identifier: type=nid,  │
    │          value=nid:ed25519:... │
    │ ←── order + authz URL ──────── │
    │                                 │
    │── GET authz URL ──────────────→ │
    │ ←── challenge: type=agent-01,   │
    │     token=T ──────────────────  │
    │                                 │
    │   (Agent signs `T` with its     │
    │    NID private key; serves      │
    │    the signature at a           │
    │    well-known /nip/auth/T       │
    │    on its announced endpoint    │
    │    — OR — posts back to ACME    │
    │    as a JWS)                    │
    │                                 │
    │── POST challenge (signed T) ─→ │
    │ ←── 200 + status=valid ──────── │
    │                                 │
    │── finalize (CSR) ─────────────→ │
    │ ←── 200 + cert URL ──────────── │
    │── GET cert URL ───────────────→ │
    │ ←── 200 + X.509 DER ──────────  │

The agent-01 challenge proves possession of the NID private key in a way that mirrors TLS-ALPN-01’s simplicity: one signed token, no external DNS / HTTP dependency. Servers MUST implement constant-time (timing-safe) comparison when verifying the signed challenge token against the expected value, to prevent timing-oracle attacks on the NID private key. (NIP §10.2 covers a distinct concern — OCSP response-time normalization — and does not apply here.)

4.5 Backward Compatibility


5. Alternatives Considered

5.1 Keep proprietary format, add X.509 export shim

Keep current cert format; offer a one-way export to X.509 for interop (e.g., nipc export --x509).

5.2 Adopt X.509 without ACME (keep REST issuance)

Use X.509 but keep the current bespoke /certs/issue REST endpoint.

5.3 Do nothing


6. Drawbacks & Risks


7. Security Considerations


8. Implementation Plan

8.1 Phasing

Phase Scope Exit criterion
1 .NET NIP + .NET CA server emit + accept X.509; ACME agent-01 in .NET CA; v1 + v2 coexist Unit tests green; cross-format interop (v1 client ↔ v2 server and vice versa)
2 All 6 SDKs + 6 CA servers support X.509 + ACME; cert_format=v2 default-off Cross-SDK cert-accept matrix green; ACME issuance works across all 6 CA variants
3 Flip cert_format=v2 default-on; 21-day deprecation notice for v1 No regressions for 1 release cycle
4 Remove v1 codepath v1 support removed from all 12 repos

8.2 SDK Coverage Matrix

SDK Owner Status Notes
.NET NPS SDK maintainers Implemented — X.509 + agent-01 Primary library and CA reference
Python NPS SDK maintainers Implemented — X.509 + agent-01 cryptography; SDK and CA tests
TypeScript NPS SDK maintainers Implemented — X.509 + agent-01 SDK and CA tests
Java NPS SDK maintainers Implemented — X.509 + agent-01 Bouncy Castle; SDK and CA tests
Rust NPS SDK maintainers Implemented — X.509 + agent-01 x509-parser + rcgen; SDK and CA tests
Go NPS SDK maintainers Implemented — X.509 + agent-01 stdlib crypto/x509; SDK and CA tests

This records source/test coverage, not Phase 3 activation. The v2 default-on flip and v1 removal remain future compatibility transitions.

8.3 Test Plan

  1. X.509 round-trip: CA issues v2 cert with agent-identity EKU; Agent presents it; verifier accepts.
  2. EKU-critical enforcement: v2 cert without EKU → NIP-CERT-EKU-MISSING.
  3. Subject / NID mismatch: cert Subject CN != IdentFrame.nid → NIP-CERT-SUBJECT-NID-MISMATCH.
  4. ACME agent-01 happy path end-to-end.
  5. ACME replay protection: repeated challenge token → 400.
  6. Cross-format: v1 client + v2 server; v2 client + v1 server (both during Phase 2; both fail cleanly during Phase 3+).

8.4 Benchmarks

Thresholds revised after the 2026-04-27 prototype run (see §9.2):


9. Empirical Data

9.1 Prototype branch

The prototype lives at feat/rfc-0002-x509-acme-prototype on dev. It delivers, in order:

9.2 Measurements

Numbers below are produced by NPS.Benchmarks.NipCert (dotnet run -c Release --project impl/dotnet/benchmarks/NPS.Benchmarks.NipCert -- --emit), which writes a Markdown report to docs/benchmarks/nip-cert-prototype.md.

Metric Baseline (v1) Proposed (v2) Delta Method
IdentFrame JSON size 459 B 1512 B +1053 B (+229%) UTF-8 byte count of JsonSerializer.Serialize(IdentFrame)
Verification latency (mean of 2000 iters) 597.8 µs 1698.5 µs 2.84× Stopwatch over verifier.VerifyAsync(frame)

Two RFC §8.4 thresholds are revisited by this data:

9.3 Implications for §10 OQ-1

The prototype ran X.509 first, then layered ACME. Effort: ~5 days of working time for X.509 + verifier + tests; ~2 days for ACME (client + in-process server with agent-01 + tests). pebble HTTP-01 interop would have added another ~1 day if the new agent-01 challenge type weren’t blocking direct interop.

Recommendation for OQ-1: bundle X.509 + ACME in the same acceptance (the original “both together” default position). The ACME piece is mostly mechanical once X.509 issuance is wired; splitting forces a second wave of cross-language port work that can be saved by doing it once.


10. Resolved Questions


11. Future Work


12. References


Appendix A. Revision History

Date Author Change
2026-09-05 NPS maintainers Reconciled status to Active and replaced stale six-SDK owner/status placeholders with current X.509 + agent-01 source/test coverage. Phase 3/4 compatibility transitions remain inactive.
2026-04-27 Claude (prototype) Backfill §9 Empirical Data with measurements from feat/rfc-0002-x509-acme-prototype (.NET prototype + NPS.Benchmarks.NipCert). Resolve OQ-1 (bundle X.509 + ACME). Revise §8.4 thresholds (size 1200 → 1600 B; drop absolute latency target, keep ratio).
2026-04-21 Ori Lynn Initial draft