NPS-Release

English 中文版

RFC Number: NPS-RFC-0002 Title: Adopt X.509 + ACME for NID certificates Status: Accepted Author(s): Ori Lynn iamzerolin@gmail.com (LabAcacia) Shepherd: TBD — assigned on PR open Created: 2026-04-21 Last-Updated: 2026-05-27 Accepted: 2026-05-27 Activated: (set when first reference SDK ships) 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 Ori Lynn pending Primary reference
Python TBD pending Use cryptography lib
TypeScript TBD pending Use @peculiar/x509
Java TBD pending Bouncy Castle
Rust TBD pending x509-parser + rcgen
Go TBD pending stdlib crypto/x509

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. Open Questions


11. Future Work


12. References


Appendix A. Revision History

Date Author Change
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