1use bytes::Bytes;
37use ndn_tlv::{TlvReader, TlvWriter};
38
39use crate::error::CertError;
40
41pub const TLV_CA_PREFIX: u64 = 0x81;
44pub const TLV_CA_INFO: u64 = 0x83;
45pub const TLV_PARAMETER_KEY: u64 = 0x85;
46pub const TLV_PARAMETER_VALUE: u64 = 0x87;
47pub const TLV_CA_CERTIFICATE: u64 = 0x89;
48pub const TLV_MAX_VALIDITY: u64 = 0x8B;
49pub const TLV_PROBE_RESPONSE: u64 = 0x8D;
50pub const TLV_MAX_SUFFIX_LENGTH: u64 = 0x8F;
51pub const TLV_ECDH_PUB: u64 = 0x91;
52pub const TLV_CERT_REQUEST: u64 = 0x93;
53pub const TLV_SALT: u64 = 0x95;
54pub const TLV_REQUEST_ID: u64 = 0x97;
55pub const TLV_CHALLENGE: u64 = 0x99;
56pub const TLV_STATUS: u64 = 0x9B;
57pub const TLV_IV: u64 = 0x9D;
58pub const TLV_ENCRYPTED_PAYLOAD: u64 = 0x9F;
59pub const TLV_SELECTED_CHALLENGE: u64 = 0xA1;
60pub const TLV_CHALLENGE_STATUS: u64 = 0xA3;
61pub const TLV_REMAINING_TRIES: u64 = 0xA5;
62pub const TLV_REMAINING_TIME: u64 = 0xA7;
63pub const TLV_ISSUED_CERT_NAME: u64 = 0xA9;
64pub const TLV_ERROR_CODE: u64 = 0xAB;
65pub const TLV_ERROR_INFO: u64 = 0xAD;
66pub const TLV_AUTH_TAG: u64 = 0xAF;
67
68pub struct CaProfileTlv {
72 pub ca_prefix: String,
73 pub ca_info: String,
74 pub ca_certificate: Bytes,
75 pub max_validity_secs: u64,
76 pub challenges: Vec<String>,
77}
78
79impl CaProfileTlv {
80 pub fn encode(&self) -> Bytes {
81 let mut w = TlvWriter::new();
82 w.write_tlv(TLV_CA_PREFIX, self.ca_prefix.as_bytes());
83 w.write_tlv(TLV_CA_INFO, self.ca_info.as_bytes());
84 w.write_tlv(TLV_CA_CERTIFICATE, &self.ca_certificate);
85 w.write_tlv(TLV_MAX_VALIDITY, &self.max_validity_secs.to_be_bytes());
86 for challenge in &self.challenges {
87 w.write_tlv(TLV_CHALLENGE, challenge.as_bytes());
88 }
89 w.finish()
90 }
91
92 pub fn decode(buf: Bytes) -> Result<Self, CertError> {
93 let mut r = TlvReader::new(buf);
94 let mut ca_prefix = None;
95 let mut ca_info = None;
96 let mut ca_certificate = Bytes::new();
97 let mut max_validity_secs = 86400u64;
98 let mut challenges = Vec::new();
99
100 while !r.is_empty() {
101 let (typ, val) = r
102 .read_tlv()
103 .map_err(|e| CertError::InvalidRequest(format!("TLV parse error: {e}")))?;
104 match typ {
105 TLV_CA_PREFIX => {
106 ca_prefix = Some(
107 std::str::from_utf8(&val)
108 .map_err(|_| {
109 CertError::InvalidRequest("invalid ca-prefix UTF-8".into())
110 })?
111 .to_string(),
112 );
113 }
114 TLV_CA_INFO => {
115 ca_info = Some(
116 std::str::from_utf8(&val)
117 .map_err(|_| CertError::InvalidRequest("invalid ca-info UTF-8".into()))?
118 .to_string(),
119 );
120 }
121 TLV_CA_CERTIFICATE => {
122 ca_certificate = val;
123 }
124 TLV_MAX_VALIDITY => {
125 if val.len() >= 8 {
126 max_validity_secs = u64::from_be_bytes(val[..8].try_into().unwrap());
127 }
128 }
129 TLV_CHALLENGE => {
130 let s = std::str::from_utf8(&val)
131 .map_err(|_| CertError::InvalidRequest("invalid challenge UTF-8".into()))?
132 .to_string();
133 challenges.push(s);
134 }
135 _ => {} }
137 }
138
139 Ok(Self {
140 ca_prefix: ca_prefix
141 .ok_or_else(|| CertError::InvalidRequest("missing ca-prefix".into()))?,
142 ca_info: ca_info.unwrap_or_default(),
143 ca_certificate,
144 max_validity_secs,
145 challenges,
146 })
147 }
148}
149
150pub struct NewRequestTlv {
157 pub ecdh_pub: Bytes,
159 pub cert_request: Bytes,
161}
162
163impl NewRequestTlv {
164 pub fn encode(&self) -> Bytes {
165 let mut w = TlvWriter::new();
166 w.write_tlv(TLV_ECDH_PUB, &self.ecdh_pub);
167 w.write_tlv(TLV_CERT_REQUEST, &self.cert_request);
168 w.finish()
169 }
170
171 pub fn decode(buf: Bytes) -> Result<Self, CertError> {
172 let mut r = TlvReader::new(buf);
173 let mut ecdh_pub = None;
174 let mut cert_request = None;
175
176 while !r.is_empty() {
177 let (typ, val) = r
178 .read_tlv()
179 .map_err(|e| CertError::InvalidRequest(format!("TLV parse error: {e}")))?;
180 match typ {
181 TLV_ECDH_PUB => ecdh_pub = Some(val),
182 TLV_CERT_REQUEST => cert_request = Some(val),
183 _ => {}
184 }
185 }
186
187 Ok(Self {
188 ecdh_pub: ecdh_pub
189 .ok_or_else(|| CertError::InvalidRequest("missing ecdh-pub".into()))?,
190 cert_request: cert_request
191 .ok_or_else(|| CertError::InvalidRequest("missing cert-request".into()))?,
192 })
193 }
194}
195
196pub struct NewResponseTlv {
198 pub ecdh_pub: Bytes,
200 pub salt: [u8; 32],
202 pub request_id: [u8; 8],
204 pub challenges: Vec<String>,
206}
207
208impl NewResponseTlv {
209 pub fn encode(&self) -> Bytes {
210 let mut w = TlvWriter::new();
211 w.write_tlv(TLV_ECDH_PUB, &self.ecdh_pub);
212 w.write_tlv(TLV_SALT, &self.salt);
213 w.write_tlv(TLV_REQUEST_ID, &self.request_id);
214 for challenge in &self.challenges {
215 w.write_tlv(TLV_CHALLENGE, challenge.as_bytes());
216 }
217 w.finish()
218 }
219
220 pub fn decode(buf: Bytes) -> Result<Self, CertError> {
221 let mut r = TlvReader::new(buf);
222 let mut ecdh_pub = None;
223 let mut salt = None;
224 let mut request_id = None;
225 let mut challenges = Vec::new();
226
227 while !r.is_empty() {
228 let (typ, val) = r
229 .read_tlv()
230 .map_err(|e| CertError::InvalidRequest(format!("TLV parse error: {e}")))?;
231 match typ {
232 TLV_ECDH_PUB => ecdh_pub = Some(val),
233 TLV_SALT => {
234 if val.len() == 32 {
235 let mut arr = [0u8; 32];
236 arr.copy_from_slice(&val);
237 salt = Some(arr);
238 }
239 }
240 TLV_REQUEST_ID => {
241 if val.len() == 8 {
242 let mut arr = [0u8; 8];
243 arr.copy_from_slice(&val);
244 request_id = Some(arr);
245 }
246 }
247 TLV_CHALLENGE => {
248 if let Ok(s) = std::str::from_utf8(&val) {
249 challenges.push(s.to_string());
250 }
251 }
252 _ => {}
253 }
254 }
255
256 Ok(Self {
257 ecdh_pub: ecdh_pub
258 .ok_or_else(|| CertError::InvalidRequest("missing ecdh-pub".into()))?,
259 salt: salt.ok_or_else(|| CertError::InvalidRequest("missing salt".into()))?,
260 request_id: request_id
261 .ok_or_else(|| CertError::InvalidRequest("missing request-id".into()))?,
262 challenges,
263 })
264 }
265}
266
267pub struct ChallengeRequestTlv {
272 pub request_id: [u8; 8],
274 pub selected_challenge: String,
276 pub iv: [u8; 12],
278 pub encrypted_payload: Bytes,
280 pub auth_tag: [u8; 16],
282}
283
284impl ChallengeRequestTlv {
285 pub fn encode(&self) -> Bytes {
286 let mut w = TlvWriter::new();
287 w.write_tlv(TLV_REQUEST_ID, &self.request_id);
288 w.write_tlv(TLV_SELECTED_CHALLENGE, self.selected_challenge.as_bytes());
289 w.write_tlv(TLV_IV, &self.iv);
290 w.write_tlv(TLV_ENCRYPTED_PAYLOAD, &self.encrypted_payload);
291 w.write_tlv(TLV_AUTH_TAG, &self.auth_tag);
292 w.finish()
293 }
294
295 pub fn decode(buf: Bytes) -> Result<Self, CertError> {
296 let mut r = TlvReader::new(buf);
297 let mut request_id = None;
298 let mut selected_challenge = None;
299 let mut iv = None;
300 let mut encrypted_payload = None;
301 let mut auth_tag = None;
302
303 while !r.is_empty() {
304 let (typ, val) = r
305 .read_tlv()
306 .map_err(|e| CertError::InvalidRequest(format!("TLV parse error: {e}")))?;
307 match typ {
308 TLV_REQUEST_ID => {
309 if val.len() == 8 {
310 let mut arr = [0u8; 8];
311 arr.copy_from_slice(&val);
312 request_id = Some(arr);
313 }
314 }
315 TLV_SELECTED_CHALLENGE => {
316 selected_challenge = Some(
317 std::str::from_utf8(&val)
318 .map_err(|_| {
319 CertError::InvalidRequest("invalid challenge type UTF-8".into())
320 })?
321 .to_string(),
322 );
323 }
324 TLV_IV => {
325 if val.len() == 12 {
326 let mut arr = [0u8; 12];
327 arr.copy_from_slice(&val);
328 iv = Some(arr);
329 }
330 }
331 TLV_ENCRYPTED_PAYLOAD => encrypted_payload = Some(val),
332 TLV_AUTH_TAG => {
333 if val.len() == 16 {
334 let mut arr = [0u8; 16];
335 arr.copy_from_slice(&val);
336 auth_tag = Some(arr);
337 }
338 }
339 _ => {}
340 }
341 }
342
343 Ok(Self {
344 request_id: request_id
345 .ok_or_else(|| CertError::InvalidRequest("missing request-id".into()))?,
346 selected_challenge: selected_challenge
347 .ok_or_else(|| CertError::InvalidRequest("missing selected-challenge".into()))?,
348 iv: iv.ok_or_else(|| CertError::InvalidRequest("missing iv".into()))?,
349 encrypted_payload: encrypted_payload
350 .ok_or_else(|| CertError::InvalidRequest("missing encrypted-payload".into()))?,
351 auth_tag: auth_tag
352 .ok_or_else(|| CertError::InvalidRequest("missing auth-tag".into()))?,
353 })
354 }
355}
356
357pub struct ChallengeResponseTlv {
359 pub status: u8,
361 pub challenge_status: Option<String>,
363 pub remaining_tries: Option<u8>,
364 pub remaining_time_secs: Option<u32>,
365 pub issued_cert_name: Option<String>,
367 pub error_code: Option<u8>,
369 pub error_info: Option<String>,
370 pub iv: Option<[u8; 12]>,
372 pub encrypted_payload: Option<Bytes>,
373 pub auth_tag: Option<[u8; 16]>,
374}
375
376pub const STATUS_BEFORE_CHALLENGE: u8 = 0;
378pub const STATUS_CHALLENGE: u8 = 1;
379pub const STATUS_PENDING: u8 = 2;
380pub const STATUS_SUCCESS: u8 = 3;
381pub const STATUS_FAILURE: u8 = 4;
382
383impl ChallengeResponseTlv {
384 pub fn encode(&self) -> Bytes {
385 let mut w = TlvWriter::new();
386 w.write_tlv(TLV_STATUS, &[self.status]);
387
388 if let Some(ref cs) = self.challenge_status {
389 w.write_tlv(TLV_CHALLENGE_STATUS, cs.as_bytes());
390 }
391 if let Some(rt) = self.remaining_tries {
392 w.write_tlv(TLV_REMAINING_TRIES, &[rt]);
393 }
394 if let Some(rt) = self.remaining_time_secs {
395 w.write_tlv(TLV_REMAINING_TIME, &rt.to_be_bytes());
396 }
397 if let Some(ref cn) = self.issued_cert_name {
398 w.write_tlv(TLV_ISSUED_CERT_NAME, cn.as_bytes());
399 }
400 if let Some(ec) = self.error_code {
401 w.write_tlv(TLV_ERROR_CODE, &[ec]);
402 }
403 if let Some(ref ei) = self.error_info {
404 w.write_tlv(TLV_ERROR_INFO, ei.as_bytes());
405 }
406 if let Some(ref iv) = self.iv {
407 w.write_tlv(TLV_IV, iv);
408 }
409 if let Some(ref ep) = self.encrypted_payload {
410 w.write_tlv(TLV_ENCRYPTED_PAYLOAD, ep);
411 }
412 if let Some(ref at) = self.auth_tag {
413 w.write_tlv(TLV_AUTH_TAG, at);
414 }
415 w.finish()
416 }
417
418 pub fn decode(buf: Bytes) -> Result<Self, CertError> {
419 let mut r = TlvReader::new(buf);
420 let mut status = None;
421 let mut challenge_status = None;
422 let mut remaining_tries = None;
423 let mut remaining_time_secs = None;
424 let mut issued_cert_name = None;
425 let mut error_code = None;
426 let mut error_info = None;
427 let mut iv = None;
428 let mut encrypted_payload = None;
429 let mut auth_tag = None;
430
431 while !r.is_empty() {
432 let (typ, val) = r
433 .read_tlv()
434 .map_err(|e| CertError::InvalidRequest(format!("TLV parse error: {e}")))?;
435 match typ {
436 TLV_STATUS => {
437 status = val.first().copied();
438 }
439 TLV_CHALLENGE_STATUS => {
440 challenge_status = std::str::from_utf8(&val).ok().map(str::to_string);
441 }
442 TLV_REMAINING_TRIES => {
443 remaining_tries = val.first().copied();
444 }
445 TLV_REMAINING_TIME => {
446 if val.len() >= 4 {
447 remaining_time_secs =
448 Some(u32::from_be_bytes(val[..4].try_into().unwrap()));
449 }
450 }
451 TLV_ISSUED_CERT_NAME => {
452 issued_cert_name = std::str::from_utf8(&val).ok().map(str::to_string);
453 }
454 TLV_ERROR_CODE => {
455 error_code = val.first().copied();
456 }
457 TLV_ERROR_INFO => {
458 error_info = std::str::from_utf8(&val).ok().map(str::to_string);
459 }
460 TLV_IV => {
461 if val.len() == 12 {
462 let mut arr = [0u8; 12];
463 arr.copy_from_slice(&val);
464 iv = Some(arr);
465 }
466 }
467 TLV_ENCRYPTED_PAYLOAD => encrypted_payload = Some(val),
468 TLV_AUTH_TAG => {
469 if val.len() == 16 {
470 let mut arr = [0u8; 16];
471 arr.copy_from_slice(&val);
472 auth_tag = Some(arr);
473 }
474 }
475 _ => {}
476 }
477 }
478
479 Ok(Self {
480 status: status.ok_or_else(|| CertError::InvalidRequest("missing status".into()))?,
481 challenge_status,
482 remaining_tries,
483 remaining_time_secs,
484 issued_cert_name,
485 error_code,
486 error_info,
487 iv,
488 encrypted_payload,
489 auth_tag,
490 })
491 }
492}
493
494pub struct ProbeResponseTlv {
496 pub allowed: bool,
498 pub reason: Option<String>,
500 pub max_suffix_length: Option<u8>,
502}
503
504impl ProbeResponseTlv {
505 pub fn encode(&self) -> Bytes {
506 let mut w = TlvWriter::new();
507 w.write_tlv(TLV_STATUS, &[if self.allowed { 1u8 } else { 0u8 }]);
508 if let Some(ref reason) = self.reason {
509 w.write_tlv(TLV_ERROR_INFO, reason.as_bytes());
510 }
511 if let Some(msl) = self.max_suffix_length {
512 w.write_tlv(TLV_MAX_SUFFIX_LENGTH, &[msl]);
513 }
514 w.finish()
515 }
516
517 pub fn decode(buf: Bytes) -> Result<Self, CertError> {
518 let mut r = TlvReader::new(buf);
519 let mut allowed = None;
520 let mut reason = None;
521 let mut max_suffix_length = None;
522
523 while !r.is_empty() {
524 let (typ, val) = r
525 .read_tlv()
526 .map_err(|e| CertError::InvalidRequest(format!("TLV parse error: {e}")))?;
527 match typ {
528 TLV_STATUS => {
529 allowed = val.first().map(|&b| b != 0);
530 }
531 TLV_ERROR_INFO => {
532 reason = std::str::from_utf8(&val).ok().map(str::to_string);
533 }
534 TLV_MAX_SUFFIX_LENGTH => {
535 max_suffix_length = val.first().copied();
536 }
537 _ => {}
538 }
539 }
540
541 Ok(Self {
542 allowed: allowed.unwrap_or(false),
543 reason,
544 max_suffix_length,
545 })
546 }
547}
548
549pub struct RevokeRequestTlv {
551 pub cert_name: String,
553 pub signature: Bytes,
555}
556
557pub const REVOKE_STATUS_REVOKED: u8 = 0;
559pub const REVOKE_STATUS_NOT_FOUND: u8 = 1;
560pub const REVOKE_STATUS_UNAUTHORIZED: u8 = 2;
561
562impl RevokeRequestTlv {
563 pub fn encode(&self) -> Bytes {
564 let mut w = TlvWriter::new();
565 w.write_tlv(TLV_ISSUED_CERT_NAME, self.cert_name.as_bytes());
566 w.write_tlv(TLV_AUTH_TAG, &self.signature);
567 w.finish()
568 }
569
570 pub fn decode(buf: Bytes) -> Result<Self, CertError> {
571 let mut r = TlvReader::new(buf);
572 let mut cert_name = None;
573 let mut signature = None;
574
575 while !r.is_empty() {
576 let (typ, val) = r
577 .read_tlv()
578 .map_err(|e| CertError::InvalidRequest(format!("TLV parse error: {e}")))?;
579 match typ {
580 TLV_ISSUED_CERT_NAME => {
581 cert_name = Some(
582 std::str::from_utf8(&val)
583 .map_err(|_| {
584 CertError::InvalidRequest("invalid cert-name UTF-8".into())
585 })?
586 .to_string(),
587 );
588 }
589 TLV_AUTH_TAG => {
590 signature = Some(val);
591 }
592 _ => {}
593 }
594 }
595
596 Ok(Self {
597 cert_name: cert_name
598 .ok_or_else(|| CertError::InvalidRequest("missing cert-name".into()))?,
599 signature: signature
600 .ok_or_else(|| CertError::InvalidRequest("missing signature".into()))?,
601 })
602 }
603}
604
605pub struct RevokeResponseTlv {
607 pub status: u8,
609 pub reason: Option<String>,
611}
612
613impl RevokeResponseTlv {
614 pub fn encode(&self) -> Bytes {
615 let mut w = TlvWriter::new();
616 w.write_tlv(TLV_STATUS, &[self.status]);
617 if let Some(ref reason) = self.reason {
618 w.write_tlv(TLV_ERROR_INFO, reason.as_bytes());
619 }
620 w.finish()
621 }
622
623 pub fn decode(buf: Bytes) -> Result<Self, CertError> {
624 let mut r = TlvReader::new(buf);
625 let mut status = None;
626 let mut reason = None;
627
628 while !r.is_empty() {
629 let (typ, val) = r
630 .read_tlv()
631 .map_err(|e| CertError::InvalidRequest(format!("TLV parse error: {e}")))?;
632 match typ {
633 TLV_STATUS => {
634 status = val.first().copied();
635 }
636 TLV_ERROR_INFO => {
637 reason = std::str::from_utf8(&val).ok().map(str::to_string);
638 }
639 _ => {}
640 }
641 }
642
643 Ok(Self {
644 status: status.ok_or_else(|| CertError::InvalidRequest("missing status".into()))?,
645 reason,
646 })
647 }
648}
649
650#[cfg(test)]
651mod tests {
652 use super::*;
653
654 #[test]
655 fn ca_profile_tlv_roundtrip() {
656 let profile = CaProfileTlv {
657 ca_prefix: "/com/acme/CA".to_string(),
658 ca_info: "ACME CA".to_string(),
659 ca_certificate: Bytes::from_static(b"\x01\x02\x03"),
660 max_validity_secs: 86400,
661 challenges: vec!["pin".to_string(), "email".to_string()],
662 };
663 let encoded = profile.encode();
664 let decoded = CaProfileTlv::decode(encoded).unwrap();
665 assert_eq!(decoded.ca_prefix, "/com/acme/CA");
666 assert_eq!(decoded.ca_info, "ACME CA");
667 assert_eq!(decoded.max_validity_secs, 86400);
668 assert_eq!(decoded.challenges, vec!["pin", "email"]);
669 }
670
671 #[test]
672 fn new_request_tlv_roundtrip() {
673 let req = NewRequestTlv {
674 ecdh_pub: Bytes::from(vec![0x04u8; 65]),
675 cert_request: Bytes::from_static(b"cert-data"),
676 };
677 let encoded = req.encode();
678 let decoded = NewRequestTlv::decode(encoded).unwrap();
679 assert_eq!(decoded.ecdh_pub.len(), 65);
680 assert_eq!(&decoded.cert_request[..], b"cert-data");
681 }
682
683 #[test]
684 fn new_response_tlv_roundtrip() {
685 let resp = NewResponseTlv {
686 ecdh_pub: Bytes::from(vec![0x04u8; 65]),
687 salt: [0xABu8; 32],
688 request_id: [0x01u8; 8],
689 challenges: vec!["possession".to_string()],
690 };
691 let encoded = resp.encode();
692 let decoded = NewResponseTlv::decode(encoded).unwrap();
693 assert_eq!(decoded.salt, [0xABu8; 32]);
694 assert_eq!(decoded.request_id, [0x01u8; 8]);
695 assert_eq!(decoded.challenges, vec!["possession"]);
696 }
697
698 #[test]
699 fn challenge_response_tlv_success_roundtrip() {
700 let resp = ChallengeResponseTlv {
701 status: STATUS_SUCCESS,
702 challenge_status: None,
703 remaining_tries: None,
704 remaining_time_secs: None,
705 issued_cert_name: Some("/com/acme/alice/KEY/v=0".to_string()),
706 error_code: None,
707 error_info: None,
708 iv: None,
709 encrypted_payload: None,
710 auth_tag: None,
711 };
712 let encoded = resp.encode();
713 let decoded = ChallengeResponseTlv::decode(encoded).unwrap();
714 assert_eq!(decoded.status, STATUS_SUCCESS);
715 assert_eq!(
716 decoded.issued_cert_name.as_deref(),
717 Some("/com/acme/alice/KEY/v=0")
718 );
719 }
720}