1use core::fmt;
5
6use super::{ComposeRdata, ParseRdata, ParseRdataText};
7use crate::wire::{Composer, OutBuf, WireReader};
8use crate::zone::Scanner;
9use crate::{Error, Result, Rtype};
10
11open_enum! {
12 pub struct SshfpAlgorithm(u8), generic "";
17 RSA = 1 => "RSA",
19 DSA = 2 => "DSA",
21 ECDSA = 3 => "ECDSA",
23 ED25519 = 4 => "Ed25519",
25 ED448 = 6 => "Ed448",
27}
28
29open_enum! {
30 pub struct SshfpFpType(u8), generic "";
35 SHA1 = 1 => "SHA-1",
37 SHA256 = 2 => "SHA-256",
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
43pub struct Sshfp<'a> {
44 pub algorithm: SshfpAlgorithm,
46 pub fp_type: SshfpFpType,
48 pub fingerprint: &'a [u8],
50}
51
52impl<'a> Sshfp<'a> {
53 #[inline]
55 #[must_use]
56 pub const fn new(
57 algorithm: SshfpAlgorithm,
58 fp_type: SshfpFpType,
59 fingerprint: &'a [u8],
60 ) -> Self {
61 Sshfp {
62 algorithm,
63 fp_type,
64 fingerprint,
65 }
66 }
67}
68
69impl ParseRdataText for Sshfp<'_> {
70 fn parse_text<B: OutBuf + ?Sized>(s: &mut Scanner<'_>, out: &mut B) -> Result<()> {
75 out.put_u8(s.u8()?)?;
76 out.put_u8(s.u8()?)?;
77 if s.hex_rest_into(out)? == 0 {
78 return Err(Error::UnexpectedEof);
79 }
80 Ok(())
81 }
82}
83
84impl<'a> ParseRdata<'a> for Sshfp<'a> {
85 const RTYPE: Rtype = Rtype::SSHFP;
86
87 fn parse_rdata(rdata: &mut WireReader<'a>) -> Result<Self> {
88 Ok(Sshfp {
89 algorithm: SshfpAlgorithm::new(rdata.read_u8()?),
90 fp_type: SshfpFpType::new(rdata.read_u8()?),
91 fingerprint: rdata.read_rest(),
92 })
93 }
94}
95
96impl ComposeRdata for Sshfp<'_> {
97 fn rtype(&self) -> Rtype {
98 Rtype::SSHFP
99 }
100
101 fn compose_rdata<C: Composer + ?Sized>(&self, c: &mut C) -> Result<()> {
102 c.put_u8(self.algorithm.get())?;
103 c.put_u8(self.fp_type.get())?;
104 c.put_bytes(self.fingerprint)
105 }
106}
107
108impl fmt::Display for Sshfp<'_> {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 if self.fingerprint.is_empty() {
114 return crate::text::fmt_generic_rdata(
115 f,
116 &[self.algorithm.get(), self.fp_type.get()],
117 );
118 }
119 write!(
120 f,
121 "{} {} {}",
122 self.algorithm.get(),
123 self.fp_type.get(),
124 crate::text::Hex(self.fingerprint)
125 )
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use crate::Class;
133 use crate::rdata::tests::{compose, parse, round_trip, text_error, text_round_trip};
134 use crate::testutil::hex;
135 use std::string::ToString;
136
137 #[test]
138 fn rfc4255_example() {
139 let mut wire = std::vec![2, 1];
142 wire.extend(hex("123456789abcdef67890123456789abcdef67890"));
143 round_trip(
144 Rtype::SSHFP,
145 &wire,
146 "2 1 123456789ABCDEF67890123456789ABCDEF67890",
147 );
148 let Ok(crate::RData::Sshfp(s)) = parse(Rtype::SSHFP, Class::IN, &wire) else {
149 panic!("not SSHFP")
150 };
151 assert_eq!(s.algorithm, SshfpAlgorithm::DSA);
152 assert_eq!(s.fp_type, SshfpFpType::SHA1);
153 assert_eq!(s.fingerprint.len(), 20);
154 }
155
156 #[test]
157 fn rfc6594_sha256() {
158 let fp = hex("821eb6c1c98d9cc827ab7f456304c0f14785b7008d9e8646a8519de80849afc7");
160 let s = Sshfp::new(SshfpAlgorithm::ECDSA, SshfpFpType::SHA256, &fp);
161 let wire = compose(&s);
162 assert_eq!(&wire[..2], [3, 2]);
163 round_trip(
164 Rtype::SSHFP,
165 &wire,
166 "3 2 821EB6C1C98D9CC827AB7F456304C0F14785B7008D9E8646A8519DE80849AFC7",
167 );
168 }
169
170 #[test]
171 fn registries_and_edge_cases() {
172 assert_eq!(SshfpAlgorithm::ED25519.get(), 4);
173 assert_eq!(SshfpAlgorithm::ED448.to_string(), "Ed448");
174 assert_eq!("sha-256".parse(), Ok(SshfpFpType::SHA256));
175 assert_eq!(SshfpFpType::new(9).to_string(), "9");
176 round_trip(Rtype::SSHFP, b"\x09\x07\xab", "9 7 AB");
178 round_trip(Rtype::SSHFP, b"\x01\x01", "\\# 2 0101");
180 assert_eq!(parse(Rtype::SSHFP, Class::IN, b"\x01"), Err(Error::UnexpectedEof));
181 }
182
183 #[test]
184 fn text() {
185 let mut wire = std::vec![2, 1];
188 wire.extend(hex("123456789abcdef67890123456789abcdef67890"));
189 text_round_trip(
190 Rtype::SSHFP,
191 "2 1 123456789abcdef67890123456789abcdef67890",
192 &wire,
193 "2 1 123456789ABCDEF67890123456789ABCDEF67890",
194 );
195 let mut wire = std::vec![3, 2];
198 wire.extend(hex(
199 "821eb6c1c98d9cc827ab7f456304c0f14785b7008d9e8646a8519de80849afc7",
200 ));
201 text_round_trip(
202 Rtype::SSHFP,
203 "3 2 (\n 821eb6c1c98d9cc827ab7f456304c0f1\n 4785b7008d9e8646a8519de80849afc7 )",
204 &wire,
205 "3 2 821EB6C1C98D9CC827AB7F456304C0F14785B7008D9E8646A8519DE80849AFC7",
206 );
207 let mut wire = std::vec![4, 2];
209 wire.extend(hex(
210 "a87f1b687ac0e57d2a081a2f282672334d90ed316d2b818ca9580ea384d92401",
211 ));
212 text_round_trip(
213 Rtype::SSHFP,
214 "4 2 ( a87f1b687ac0e57d2a081a2f2826723\n 34d90ed316d2b818ca9580ea384d924\n 01 )",
215 &wire,
216 "4 2 A87F1B687AC0E57D2A081A2F282672334D90ED316D2B818CA9580EA384D92401",
217 );
218 text_round_trip(Rtype::SSHFP, "255 9 0 0 a B", b"\xff\x09\x00\xab", "255 9 00AB");
220 }
221
222 #[test]
223 fn text_malformed() {
224 for (text, err) in [
225 ("", Error::UnexpectedEof),
226 ("2", Error::UnexpectedEof),
227 ("2 1", Error::UnexpectedEof),
229 ("256 1 00", Error::InvalidText),
230 ("2 -1 00", Error::InvalidText),
231 ("RSA 1 00", Error::InvalidText),
233 ("2 1 0", Error::InvalidText),
234 ("2 1 0g", Error::InvalidText),
235 ("2 1 \"00\"", Error::InvalidText),
236 ] {
237 assert_eq!(text_error(Rtype::SSHFP, text), err, "{text:?}");
238 }
239 }
240}