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}