ndn_cert/protocol.rs
1//! NDNCERT wire protocol types.
2//!
3//! All messages are JSON-serialized and carried in NDN packet fields:
4//! - `CertRequest` / `ChallengeRequest` in ApplicationParameters
5//! - `CaProfile` / `NewResponse` / `ChallengeResponse` in Content
6//!
7//! # NDNCERT 0.3 TLV type assignments
8//!
9//! These constants are reserved for the Phase 1C TLV wire-format migration:
10//! ```text
11//! ca-prefix 0x81 ca-info 0x83 parameter-key 0x85
12//! parameter-value 0x87 ca-certificate 0x89 max-validity 0x8B
13//! probe-response 0x8D max-suffix-length 0x8F ecdh-pub 0x91
14//! cert-request 0x93 salt 0x95 request-id 0x97
15//! challenge 0x99 status 0x9B iv 0x9D
16//! encrypted-payload 0x9F selected-challenge 0xA1 challenge-status 0xA3
17//! remaining-tries 0xA5 remaining-time 0xA7 issued-cert-name 0xA9
18//! error-code 0xAB error-info 0xAD auth-tag 0xAF
19//! ```
20
21use serde::{Deserialize, Serialize};
22
23/// CA information returned by `/<ca>/CA/INFO`.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct CaProfile {
26 /// The CA's NDN prefix as a URI string (e.g. `/com/acme/fleet/CA`).
27 pub ca_prefix: String,
28 /// Human-readable description of this CA.
29 pub ca_info: String,
30 /// Base64url-encoded public key of the CA's signing key.
31 pub public_key: String,
32 /// Supported challenge types.
33 pub challenges: Vec<String>,
34 /// Default certificate validity in seconds.
35 pub default_validity_secs: u64,
36 /// Maximum certificate validity in seconds.
37 pub max_validity_secs: u64,
38}
39
40/// Certificate signing request submitted to `/<ca-prefix>/CA/NEW`.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct CertRequest {
43 /// Requested certificate name (full KEY name, e.g. `/com/acme/alice/KEY/v=0/self`).
44 pub name: String,
45 /// Base64url-encoded Ed25519 public key.
46 pub public_key: String,
47 /// Requested validity start (Unix ms).
48 pub not_before: u64,
49 /// Requested validity end (Unix ms).
50 pub not_after: u64,
51}
52
53/// Response to a NEW request — returns a request ID and available challenges.
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct NewResponse {
56 /// Opaque request identifier (32 hex chars).
57 pub request_id: String,
58 /// Challenge types the client may use.
59 pub challenges: Vec<String>,
60}
61
62/// Challenge request submitted to `/<ca-prefix>/CA/CHALLENGE/<request-id>`.
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct ChallengeRequest {
65 /// Must match the `request_id` from [`NewResponse`].
66 pub request_id: String,
67 /// Which challenge type the client is responding to.
68 pub challenge_type: String,
69 /// Challenge-specific parameters.
70 pub parameters: serde_json::Map<String, serde_json::Value>,
71}
72
73/// Response to a CHALLENGE request.
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct ChallengeResponse {
76 pub status: ChallengeStatus,
77 /// Base64url-encoded issued certificate bytes (present when `status == Approved`).
78 #[serde(skip_serializing_if = "Option::is_none")]
79 pub certificate: Option<String>,
80 /// Human-readable error (present when `status == Denied`).
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub error: Option<String>,
83 /// Numeric error code per NDNCERT 0.3 (present when `status == Denied`).
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub error_code: Option<ErrorCode>,
86 /// Status message for in-progress challenges (present when `status == Processing`).
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub status_message: Option<String>,
89 /// Remaining challenge attempts (present when `status == Processing`).
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub remaining_tries: Option<u8>,
92 /// Seconds remaining before this challenge expires (present when `status == Processing`).
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub remaining_time_secs: Option<u32>,
95}
96
97/// Challenge/request status per NDNCERT 0.3.
98#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
99#[serde(rename_all = "kebab-case")]
100pub enum ChallengeStatus {
101 /// Certificate has been issued successfully.
102 Approved,
103 /// Challenge is in progress; client must submit another CHALLENGE request.
104 Processing,
105 /// Challenge failed or request was rejected.
106 Denied,
107}
108
109/// Numeric error codes per NDNCERT 0.3 §3.3.
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
111#[serde(into = "u8", try_from = "u8")]
112pub enum ErrorCode {
113 BadInterest = 1,
114 BadApplicationParameters = 2,
115 InvalidSignature = 3,
116 InvalidParameters = 4,
117 NameNotAllowed = 5,
118 BadValidityPeriod = 6,
119 OutOfTries = 7,
120 OutOfTime = 8,
121 NoAvailableNames = 9,
122}
123
124impl From<ErrorCode> for u8 {
125 fn from(e: ErrorCode) -> u8 {
126 e as u8
127 }
128}
129
130impl TryFrom<u8> for ErrorCode {
131 type Error = String;
132 fn try_from(v: u8) -> Result<Self, Self::Error> {
133 match v {
134 1 => Ok(Self::BadInterest),
135 2 => Ok(Self::BadApplicationParameters),
136 3 => Ok(Self::InvalidSignature),
137 4 => Ok(Self::InvalidParameters),
138 5 => Ok(Self::NameNotAllowed),
139 6 => Ok(Self::BadValidityPeriod),
140 7 => Ok(Self::OutOfTries),
141 8 => Ok(Self::OutOfTime),
142 9 => Ok(Self::NoAvailableNames),
143 _ => Err(format!("unknown NDNCERT error code: {v}")),
144 }
145 }
146}
147
148/// Response to a PROBE request (`/<ca-prefix>/CA/PROBE`).
149///
150/// Allows a client to check whether the CA will serve a given name before
151/// committing to a full enrollment. Does not create any state on the CA.
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct ProbeResponse {
154 /// Whether the CA's namespace policy permits issuing for the requested name.
155 pub allowed: bool,
156 /// Reason for denial (present when `allowed == false`).
157 #[serde(skip_serializing_if = "Option::is_none")]
158 pub reason: Option<String>,
159 /// Maximum number of name components the CA permits after its own prefix.
160 /// `None` means no limit.
161 #[serde(skip_serializing_if = "Option::is_none")]
162 pub max_suffix_length: Option<u8>,
163}
164
165/// Request body for `/<ca-prefix>/CA/REVOKE`.
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct RevokeRequest {
168 /// Name of the certificate to revoke.
169 pub cert_name: String,
170 /// Base64url-encoded Ed25519 signature of `cert_name` bytes, proving possession.
171 pub signature: String,
172}
173
174/// Response to a REVOKE request.
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct RevokeResponse {
177 pub status: RevokeStatus,
178}
179
180/// Revocation outcome.
181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182#[serde(rename_all = "kebab-case")]
183pub enum RevokeStatus {
184 /// Certificate was revoked successfully.
185 Revoked,
186 /// Certificate not found in CA records.
187 NotFound,
188 /// Possession proof failed — requester does not own this certificate.
189 Unauthorized,
190}