1use secrecy::SecretBox;
2
3pub use crate::base64::Base64Bytes;
4
5#[derive(Debug, thiserror::Error)]
15pub enum ErrorCode {
16 #[error("internal error: {0}")]
18 Internal(String),
19 #[error("certificate not found: {0}")]
21 CertificateNotFound(String),
22 #[error("unsupported scheme: {0}")]
24 UnsupportedScheme(String),
25 #[error("signing failed: {0}")]
27 SigningFailed(String),
28 #[error("error (code {code}): {message}")]
30 Other { code: i64, message: String },
31}
32
33impl ErrorCode {
34 pub fn as_i64(&self) -> i64 {
35 match self {
36 ErrorCode::Internal(_) => -1,
37 ErrorCode::CertificateNotFound(_) => 1,
38 ErrorCode::UnsupportedScheme(_) => 2,
39 ErrorCode::SigningFailed(_) => 3,
40 ErrorCode::Other { code, .. } => *code,
41 }
42 }
43}
44
45impl From<ErrorCode> for ErrorPayload {
46 fn from(e: ErrorCode) -> Self {
47 ErrorPayload {
48 code: e.as_i64(),
49 message: e.to_string(),
50 }
51 }
52}
53
54impl From<ErrorPayload> for ErrorCode {
55 fn from(p: ErrorPayload) -> Self {
56 match p.code {
57 -1 => ErrorCode::Internal(p.message),
58 1 => ErrorCode::CertificateNotFound(p.message),
59 2 => ErrorCode::UnsupportedScheme(p.message),
60 3 => ErrorCode::SigningFailed(p.message),
61 code => ErrorCode::Other {
62 code,
63 message: p.message,
64 },
65 }
66 }
67}
68
69#[derive(serde::Serialize, serde::Deserialize, Debug)]
73#[serde(tag = "method")]
74pub enum Request {
75 #[serde(rename = "initialize")]
76 Initialize { id: u64, params: InitializeParams },
77 #[serde(rename = "sign")]
78 Sign { id: u64, params: SignParams },
79}
80
81impl Request {
82 pub fn id(&self) -> u64 {
83 match self {
84 Request::Initialize { id, .. } => *id,
85 Request::Sign { id, .. } => *id,
86 }
87 }
88}
89
90#[derive(serde::Serialize, serde::Deserialize, Debug)]
92pub struct InitializeParams {}
93
94#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
96pub struct SignParams {
97 pub certificate_id: String,
99 pub scheme: String,
101 pub blob: SecretBox<Base64Bytes>,
104}
105
106#[derive(serde::Serialize, serde::Deserialize, Debug)]
108#[serde(tag = "method")]
109pub enum SuccessResponse {
110 #[serde(rename = "initialize")]
111 Initialize { id: u64, result: InitializeResult },
112 #[serde(rename = "sign")]
113 Sign { id: u64, result: SignResult },
114}
115
116#[derive(serde::Serialize, serde::Deserialize, Debug)]
118pub struct ErrorResponse {
119 pub id: u64,
120 pub error: ErrorPayload,
121}
122
123#[derive(serde::Serialize, serde::Deserialize, Debug)]
125#[serde(untagged)]
126pub enum Response {
127 Success(SuccessResponse),
128 Error(ErrorResponse),
129}
130
131impl From<SuccessResponse> for Response {
132 fn from(s: SuccessResponse) -> Self {
133 Response::Success(s)
134 }
135}
136
137impl From<ErrorResponse> for Response {
138 fn from(e: ErrorResponse) -> Self {
139 Response::Error(e)
140 }
141}
142
143impl Response {
144 pub fn id(&self) -> u64 {
145 match self {
146 Response::Success(SuccessResponse::Initialize { id, .. }) => *id,
147 Response::Success(SuccessResponse::Sign { id, .. }) => *id,
148 Response::Error(ErrorResponse { id, .. }) => *id,
149 }
150 }
151
152 pub fn initialize(id: u64, result: Result<InitializeResult, ErrorPayload>) -> Self {
153 match result {
154 Ok(result) => SuccessResponse::Initialize { id, result }.into(),
155 Err(error) => ErrorResponse { id, error }.into(),
156 }
157 }
158
159 pub fn sign(id: u64, result: Result<SignResult, ErrorPayload>) -> Self {
160 match result {
161 Ok(result) => SuccessResponse::Sign { id, result }.into(),
162 Err(error) => ErrorResponse { id, error }.into(),
163 }
164 }
165}
166
167#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
172pub struct ErrorPayload {
173 pub code: i64,
175 pub message: String,
177}
178
179#[derive(serde::Serialize, serde::Deserialize, Debug)]
181pub struct InitializeResult {
182 pub default: String,
184 pub certificates: Vec<CertificateInfo>,
186}
187
188#[derive(serde::Serialize, serde::Deserialize, Debug)]
190pub struct CertificateInfo {
191 pub id: String,
193 pub domains: Vec<String>,
195 pub pem: String,
197 #[serde(default)]
200 pub schemes: Vec<String>,
201}
202
203#[derive(serde::Serialize, serde::Deserialize, Debug)]
205pub struct SignResult {
206 pub signature: SecretBox<Base64Bytes>,
208}
209
210#[cfg(test)]
211mod tests {
212 use secrecy::ExposeSecret as _;
213
214 #[derive(serde::Deserialize, Debug)]
215 struct WireRequest {
216 id: u64,
217 method: String,
218 #[allow(dead_code)]
219 params: serde_json::Value,
220 }
221
222 #[test]
223 fn serialize_initialize_request() {
224 let req = super::Request::Initialize {
225 id: 1,
226 params: super::InitializeParams {},
227 };
228 let json = serde_json::to_string(&req).unwrap();
229 let wire: WireRequest = serde_json::from_str(&json).unwrap();
230 assert_eq!(wire.id, 1);
231 assert_eq!(wire.method, "initialize");
232 }
233
234 #[test]
235 fn serialize_sign_request() {
236 let req = super::Request::Sign {
237 id: 42,
238 params: super::SignParams {
239 certificate_id: "cert/v1".to_owned(),
240 scheme: "ECDSA_NISTP256_SHA256".to_owned(),
241 blob: super::Base64Bytes::from(vec![0xde, 0xad, 0xbe, 0xef]).into_secret(),
242 },
243 };
244 let json = serde_json::to_string(&req).unwrap();
245 let wire: WireRequest = serde_json::from_str(&json).unwrap();
246 assert_eq!(wire.id, 42);
247 assert_eq!(wire.method, "sign");
248 let params: super::SignParams = serde_json::from_value(wire.params).unwrap();
249 assert_eq!(params.certificate_id, "cert/v1");
250 assert_eq!(params.scheme, "ECDSA_NISTP256_SHA256");
251 assert_eq!(**params.blob.expose_secret(), vec![0xde, 0xad, 0xbe, 0xef]);
252 }
253
254 #[test]
255 fn deserialize_initialize_request() {
256 let json = r#"{"id":5,"method":"initialize","params":{}}"#;
257 let req: super::Request = serde_json::from_str(json).unwrap();
258 assert_eq!(req.id(), 5);
259 assert!(matches!(req, super::Request::Initialize { .. }));
260 }
261
262 #[test]
263 fn deserialize_sign_request() {
264 let json = r#"{"id":7,"method":"sign","params":{"certificate_id":"c1","scheme":"ED25519","blob":"AQID"}}"#;
265 let req: super::Request = serde_json::from_str(json).unwrap();
266 assert_eq!(req.id(), 7);
267 match req {
268 super::Request::Sign { params, .. } => {
269 assert_eq!(params.certificate_id, "c1");
270 assert_eq!(params.scheme, "ED25519");
271 assert_eq!(params.blob.expose_secret().as_slice(), &[1, 2, 3]);
272 }
273 _ => panic!("expected Sign"),
274 }
275 }
276
277 #[test]
278 fn request_round_trip() {
279 let req = super::Request::Sign {
280 id: 10,
281 params: super::SignParams {
282 certificate_id: "cert/v1".to_owned(),
283 scheme: "ECDSA_NISTP256_SHA256".to_owned(),
284 blob: super::Base64Bytes::from(vec![0xde, 0xad]).into_secret(),
285 },
286 };
287 let json = serde_json::to_string(&req).unwrap();
288 let decoded: super::Request = serde_json::from_str(&json).unwrap();
289 assert_eq!(decoded.id(), 10);
290 match decoded {
291 super::Request::Sign { params, .. } => {
292 assert_eq!(params.certificate_id, "cert/v1");
293 assert_eq!(params.blob.expose_secret().as_slice(), &[0xde, 0xad]);
294 }
295 _ => panic!("expected Sign"),
296 }
297 }
298
299 #[test]
300 fn serialize_initialize_result_response() {
301 let resp = super::Response::Success(super::SuccessResponse::Initialize {
302 id: 1,
303 result: super::InitializeResult {
304 default: "cert1".to_owned(),
305 certificates: vec![super::CertificateInfo {
306 id: "cert1".to_owned(),
307 domains: vec!["*.example.com".to_owned()],
308 pem: "PEM DATA".to_owned(),
309 schemes: vec!["ECDSA_NISTP256_SHA256".to_owned()],
310 }],
311 },
312 });
313 let json = serde_json::to_string(&resp).unwrap();
314 assert!(json.contains("\"method\":\"initialize\""));
315 assert!(json.contains("\"result\""));
316 assert!(!json.contains("\"error\""));
317 let v: serde_json::Value = serde_json::from_str(&json).unwrap();
318 assert_eq!(v["id"], 1);
319 assert_eq!(v["method"], "initialize");
320 assert_eq!(v["result"]["default"], "cert1");
321 }
322
323 #[test]
324 fn serialize_sign_result_response() {
325 let resp = super::Response::Success(super::SuccessResponse::Sign {
326 id: 2,
327 result: super::SignResult {
328 signature: super::Base64Bytes::from(vec![0xff, 0x00, 0xab]).into_secret(),
329 },
330 });
331 let json = serde_json::to_string(&resp).unwrap();
332 assert!(json.contains("\"method\":\"sign\""));
333 let v: serde_json::Value = serde_json::from_str(&json).unwrap();
334 assert_eq!(v["id"], 2);
335 assert_eq!(v["method"], "sign");
336 }
337
338 #[test]
339 fn serialize_error_response() {
340 let resp = super::Response::Error(super::ErrorResponse {
341 id: 3,
342 error: super::ErrorPayload {
343 code: 1,
344 message: "not found".to_owned(),
345 },
346 });
347 let json = serde_json::to_string(&resp).unwrap();
348 let v: serde_json::Value = serde_json::from_str(&json).unwrap();
349 assert_eq!(v["id"], 3);
350 assert_eq!(v["error"]["code"], 1);
351 assert_eq!(v["error"]["message"], "not found");
352 assert!(!json.contains("\"result\""));
353 assert!(!json.contains("\"method\""));
354 }
355
356 #[test]
357 fn deserialize_result_response() {
358 let json = r#"{"id":1,"method":"initialize","result":{"default":"c1","certificates":[{"id":"c1","domains":["*.test"],"pem":"---"}]}}"#;
359 let resp: super::Response = serde_json::from_str(json).unwrap();
360 assert_eq!(resp.id(), 1);
361 match resp {
362 super::Response::Success(super::SuccessResponse::Initialize { result, .. }) => {
363 assert_eq!(result.default, "c1");
364 assert_eq!(result.certificates.len(), 1);
365 }
366 _ => panic!("expected Initialize Result"),
367 }
368 }
369
370 #[test]
371 fn deserialize_error_response() {
372 let json = r#"{"id":2,"error":{"code":99,"message":"boom"}}"#;
373 let resp: super::Response = serde_json::from_str(json).unwrap();
374 assert_eq!(resp.id(), 2);
375 match resp {
376 super::Response::Error(super::ErrorResponse { error, .. }) => {
377 assert_eq!(error.code, 99);
378 assert_eq!(error.message, "boom");
379 }
380 _ => panic!("expected Error"),
381 }
382 }
383
384 #[test]
385 fn sign_params_blob_round_trip() {
386 let params = super::SignParams {
387 certificate_id: "x".to_owned(),
388 scheme: "RSA_PSS_SHA256".to_owned(),
389 blob: super::Base64Bytes::from((0..=255).collect::<Vec<u8>>()).into_secret(),
390 };
391 let json = serde_json::to_string(¶ms).unwrap();
392 let decoded: super::SignParams = serde_json::from_str(&json).unwrap();
393 assert_eq!(
394 decoded.blob.expose_secret().as_slice(),
395 params.blob.expose_secret().as_slice()
396 );
397 }
398
399 #[test]
400 fn error_code_as_i64() {
401 assert_eq!(super::ErrorCode::Internal("x".to_owned()).as_i64(), -1);
402 assert_eq!(
403 super::ErrorCode::CertificateNotFound("x".to_owned()).as_i64(),
404 1
405 );
406 assert_eq!(
407 super::ErrorCode::UnsupportedScheme("x".to_owned()).as_i64(),
408 2
409 );
410 assert_eq!(super::ErrorCode::SigningFailed("x".to_owned()).as_i64(), 3);
411 assert_eq!(
412 super::ErrorCode::Other {
413 code: 42,
414 message: "x".to_owned()
415 }
416 .as_i64(),
417 42
418 );
419 }
420
421 #[test]
422 fn error_code_from_error_payload() {
423 let payload = super::ErrorPayload {
424 code: -1,
425 message: "boom".to_owned(),
426 };
427 let code: super::ErrorCode = payload.into();
428 assert!(matches!(code, super::ErrorCode::Internal(m) if m == "boom"));
429
430 let payload = super::ErrorPayload {
431 code: 1,
432 message: "gone".to_owned(),
433 };
434 let code: super::ErrorCode = payload.into();
435 assert!(matches!(code, super::ErrorCode::CertificateNotFound(m) if m == "gone"));
436
437 let payload = super::ErrorPayload {
438 code: 99,
439 message: "custom".to_owned(),
440 };
441 let code: super::ErrorCode = payload.into();
442 assert!(
443 matches!(code, super::ErrorCode::Other { code: 99, message } if message == "custom")
444 );
445 }
446
447 #[test]
448 fn error_code_to_error_payload() {
449 let code = super::ErrorCode::CertificateNotFound("not found".to_owned());
450 let payload: super::ErrorPayload = code.into();
451 assert_eq!(payload.code, 1);
452 assert_eq!(payload.message, "certificate not found: not found");
453 }
454
455 #[test]
456 fn error_payload_serde_preserves_wire_format() {
457 let payload = super::ErrorPayload {
458 code: 1,
459 message: "not found".to_owned(),
460 };
461 let json = serde_json::to_string(&payload).unwrap();
462 let v: serde_json::Value = serde_json::from_str(&json).unwrap();
463 assert_eq!(v["code"], 1);
464
465 let decoded: super::ErrorPayload = serde_json::from_str(&json).unwrap();
466 assert_eq!(decoded.code, 1);
467 }
468}