hickory_proto/rr/rdata/
sshfp.rs

1// Copyright 2015-2023 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! SSHFP records for SSH public key fingerprints
9#![allow(clippy::use_self)]
10
11use alloc::vec::Vec;
12use core::fmt;
13
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Serialize};
16
17use data_encoding::{Encoding, Specification};
18use once_cell::sync::Lazy;
19
20use crate::{
21    error::{ProtoError, ProtoResult},
22    rr::{RData, RecordData, RecordDataDecodable, RecordType},
23    serialize::binary::{BinDecoder, BinEncodable, BinEncoder, Restrict, RestrictedMath},
24};
25
26/// HEX formatting specific to TLSA and SSHFP encodings
27pub static HEX: Lazy<Encoding> = Lazy::new(|| {
28    let mut spec = Specification::new();
29    spec.symbols.push_str("0123456789abcdef");
30    spec.ignore.push_str(" \t\r\n");
31    spec.translate.from.push_str("ABCDEF");
32    spec.translate.to.push_str("abcdef");
33    spec.encoding().expect("error in sshfp HEX encoding")
34});
35
36/// [RFC 4255](https://tools.ietf.org/html/rfc4255#section-3.1)
37///
38/// ```text
39/// 3.1.  The SSHFP RDATA Format
40///
41///    The RDATA for a SSHFP RR consists of an algorithm number, fingerprint
42///    type and the fingerprint of the public host key.
43///
44///        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
45///        0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
46///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
47///        |   algorithm   |    fp type    |                               /
48///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               /
49///        /                                                               /
50///        /                          fingerprint                          /
51///        /                                                               /
52///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
53///
54/// 3.1.3.  Fingerprint
55///
56///    The fingerprint is calculated over the public key blob as described
57///    in [7].
58///
59///    The message-digest algorithm is presumed to produce an opaque octet
60///    string output, which is placed as-is in the RDATA fingerprint field.
61/// ```
62#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
63#[derive(Debug, PartialEq, Eq, Hash, Clone)]
64pub struct SSHFP {
65    algorithm: Algorithm,
66    fingerprint_type: FingerprintType,
67    fingerprint: Vec<u8>,
68}
69
70impl SSHFP {
71    /// Creates a new SSHFP record data.
72    ///
73    /// # Arguments
74    ///
75    /// * `algorithm` - the SSH public key algorithm.
76    /// * `fingerprint_type` - the fingerprint type to use.
77    /// * `fingerprint` - the fingerprint of the public key.
78    pub fn new(
79        algorithm: Algorithm,
80        fingerprint_type: FingerprintType,
81        fingerprint: Vec<u8>,
82    ) -> Self {
83        Self {
84            algorithm,
85            fingerprint_type,
86            fingerprint,
87        }
88    }
89
90    /// The SSH public key algorithm.
91    pub fn algorithm(&self) -> Algorithm {
92        self.algorithm
93    }
94
95    /// The fingerprint type to use.
96    pub fn fingerprint_type(&self) -> FingerprintType {
97        self.fingerprint_type
98    }
99
100    /// The fingerprint of the public key.
101    pub fn fingerprint(&self) -> &[u8] {
102        &self.fingerprint
103    }
104}
105
106/// ```text
107/// 3.1.1.  Algorithm Number Specification
108///
109///    This algorithm number octet describes the algorithm of the public
110///    key.  The following values are assigned:
111///
112///           Value    Algorithm name
113///           -----    --------------
114///           0        reserved
115///           1        RSA
116///           2        DSS
117///
118///    Reserving other types requires IETF consensus [4].
119/// ```
120///
121/// The algorithm values have been updated in
122/// [RFC 6594](https://tools.ietf.org/html/rfc6594) and
123/// [RFC 7479](https://tools.ietf.org/html/rfc7479) and
124/// [RFC 8709](https://tools.ietf.org/html/rfc8709).
125#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
126#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
127pub enum Algorithm {
128    /// Reserved value
129    Reserved,
130
131    /// RSA
132    RSA,
133
134    /// DSS/DSA
135    DSA,
136
137    /// ECDSA
138    ECDSA,
139
140    /// Ed25519
141    Ed25519,
142
143    /// Ed448
144    Ed448,
145
146    /// Unassigned value
147    Unassigned(u8),
148}
149
150impl From<u8> for Algorithm {
151    fn from(alg: u8) -> Self {
152        match alg {
153            0 => Self::Reserved,
154            1 => Self::RSA,
155            2 => Self::DSA,
156            3 => Self::ECDSA,
157            4 => Self::Ed25519, // TODO more (XMSS)
158            6 => Self::Ed448,
159            _ => Self::Unassigned(alg),
160        }
161    }
162}
163
164impl From<Algorithm> for u8 {
165    fn from(algorithm: Algorithm) -> Self {
166        match algorithm {
167            Algorithm::Reserved => 0,
168            Algorithm::RSA => 1,
169            Algorithm::DSA => 2,
170            Algorithm::ECDSA => 3,
171            Algorithm::Ed25519 => 4,
172            Algorithm::Ed448 => 6,
173            Algorithm::Unassigned(alg) => alg,
174        }
175    }
176}
177
178/// ```text
179/// 3.1.2.  Fingerprint Type Specification
180///
181///    The fingerprint type octet describes the message-digest algorithm
182///    used to calculate the fingerprint of the public key.  The following
183///    values are assigned:
184///
185///           Value    Fingerprint type
186///           -----    ----------------
187///           0        reserved
188///           1        SHA-1
189///
190///    Reserving other types requires IETF consensus [4].
191///
192///    For interoperability reasons, as few fingerprint types as possible
193///    should be reserved.  The only reason to reserve additional types is
194///    to increase security.
195/// ```
196///
197/// The fingerprint type values have been updated in
198/// [RFC 6594](https://tools.ietf.org/html/rfc6594).
199#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
200#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
201pub enum FingerprintType {
202    /// Reserved value
203    Reserved,
204
205    /// SHA-1
206    #[cfg_attr(feature = "serde", serde(rename = "SHA-1"))]
207    SHA1,
208
209    /// SHA-256
210    #[cfg_attr(feature = "serde", serde(rename = "SHA-256"))]
211    SHA256,
212
213    /// Unassigned value
214    Unassigned(u8),
215}
216
217impl From<u8> for FingerprintType {
218    fn from(ft: u8) -> Self {
219        match ft {
220            0 => Self::Reserved,
221            1 => Self::SHA1,
222            2 => Self::SHA256,
223            _ => Self::Unassigned(ft),
224        }
225    }
226}
227
228impl From<FingerprintType> for u8 {
229    fn from(fingerprint_type: FingerprintType) -> Self {
230        match fingerprint_type {
231            FingerprintType::Reserved => 0,
232            FingerprintType::SHA1 => 1,
233            FingerprintType::SHA256 => 2,
234            FingerprintType::Unassigned(ft) => ft,
235        }
236    }
237}
238
239impl BinEncodable for SSHFP {
240    fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
241        encoder.emit_u8(self.algorithm().into())?;
242        encoder.emit_u8(self.fingerprint_type().into())?;
243        encoder.emit_vec(self.fingerprint())
244    }
245}
246
247impl<'r> RecordDataDecodable<'r> for SSHFP {
248    fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict<u16>) -> ProtoResult<Self> {
249        let algorithm = decoder.read_u8()?.unverified().into();
250        let fingerprint_type = decoder.read_u8()?.unverified().into();
251        let fingerprint_len = length
252            .map(|l| l as usize)
253            .checked_sub(2)
254            .map_err(|_| ProtoError::from("invalid rdata length in SSHFP"))?
255            .unverified();
256        let fingerprint = decoder.read_vec(fingerprint_len)?.unverified();
257        Ok(SSHFP::new(algorithm, fingerprint_type, fingerprint))
258    }
259}
260
261impl RecordData for SSHFP {
262    fn try_from_rdata(data: RData) -> Result<Self, RData> {
263        match data {
264            RData::SSHFP(data) => Ok(data),
265            _ => Err(data),
266        }
267    }
268
269    fn try_borrow(data: &RData) -> Option<&Self> {
270        match data {
271            RData::SSHFP(data) => Some(data),
272            _ => None,
273        }
274    }
275
276    fn record_type(&self) -> RecordType {
277        RecordType::SSHFP
278    }
279
280    fn into_rdata(self) -> RData {
281        RData::SSHFP(self)
282    }
283}
284
285/// [RFC 4255](https://tools.ietf.org/html/rfc4255#section-3.2)
286///
287/// ```text
288/// 3.2.  Presentation Format of the SSHFP RR
289///
290///    The RDATA of the presentation format of the SSHFP resource record
291///    consists of two numbers (algorithm and fingerprint type) followed by
292///    the fingerprint itself, presented in hex, e.g.:
293///
294///        host.example.  SSHFP 2 1 123456789abcdef67890123456789abcdef67890
295///
296///    The use of mnemonics instead of numbers is not allowed.
297/// ```
298impl fmt::Display for SSHFP {
299    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
300        write!(
301            f,
302            "{algorithm} {ty} {fingerprint}",
303            algorithm = u8::from(self.algorithm),
304            ty = u8::from(self.fingerprint_type),
305            fingerprint = HEX.encode(&self.fingerprint),
306        )
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    #[test]
315    fn read_algorithm() {
316        assert_eq!(Algorithm::Reserved, 0.into());
317        assert_eq!(Algorithm::RSA, 1.into());
318        assert_eq!(Algorithm::DSA, 2.into());
319        assert_eq!(Algorithm::ECDSA, 3.into());
320        assert_eq!(Algorithm::Ed25519, 4.into());
321        assert_eq!(Algorithm::Ed448, 6.into());
322        assert_eq!(Algorithm::Unassigned(17), 17.into());
323        assert_eq!(Algorithm::Unassigned(42), 42.into());
324
325        assert_eq!(0u8, Algorithm::Reserved.into());
326        assert_eq!(1u8, Algorithm::RSA.into());
327        assert_eq!(2u8, Algorithm::DSA.into());
328        assert_eq!(3u8, Algorithm::ECDSA.into());
329        assert_eq!(4u8, Algorithm::Ed25519.into());
330        assert_eq!(6u8, Algorithm::Ed448.into());
331        assert_eq!(17u8, Algorithm::Unassigned(17).into());
332        assert_eq!(42u8, Algorithm::Unassigned(42).into());
333    }
334
335    #[test]
336    fn read_fingerprint_type() {
337        assert_eq!(FingerprintType::Reserved, 0.into());
338        assert_eq!(FingerprintType::SHA1, 1.into());
339        assert_eq!(FingerprintType::SHA256, 2.into());
340        assert_eq!(FingerprintType::Unassigned(12), 12.into());
341        assert_eq!(FingerprintType::Unassigned(89), 89.into());
342
343        assert_eq!(0u8, FingerprintType::Reserved.into());
344        assert_eq!(1u8, FingerprintType::SHA1.into());
345        assert_eq!(2u8, FingerprintType::SHA256.into());
346        assert_eq!(12u8, FingerprintType::Unassigned(12).into());
347        assert_eq!(89u8, FingerprintType::Unassigned(89).into());
348    }
349
350    fn test_encode_decode(rdata: SSHFP, result: &[u8]) {
351        let mut bytes = Vec::new();
352        let mut encoder = BinEncoder::new(&mut bytes);
353        rdata.emit(&mut encoder).expect("failed to emit SSHFP");
354        let bytes = encoder.into_bytes();
355        assert_eq!(bytes, &result);
356
357        let mut decoder = BinDecoder::new(result);
358        let read_rdata = SSHFP::read_data(&mut decoder, Restrict::new(result.len() as u16))
359            .expect("failed to read SSHFP");
360        assert_eq!(read_rdata, rdata)
361    }
362
363    #[test]
364    fn test_encode_decode_sshfp() {
365        test_encode_decode(
366            SSHFP::new(Algorithm::RSA, FingerprintType::SHA256, vec![]),
367            &[1, 2],
368        );
369        test_encode_decode(
370            SSHFP::new(
371                Algorithm::ECDSA,
372                FingerprintType::SHA1,
373                vec![115, 115, 104, 102, 112],
374            ),
375            &[3, 1, 115, 115, 104, 102, 112],
376        );
377        test_encode_decode(
378            SSHFP::new(
379                Algorithm::Reserved,
380                FingerprintType::Reserved,
381                b"ssh fingerprint".to_vec(),
382            ),
383            &[
384                0, 0, 115, 115, 104, 32, 102, 105, 110, 103, 101, 114, 112, 114, 105, 110, 116,
385            ],
386        );
387        test_encode_decode(
388            SSHFP::new(
389                Algorithm::Unassigned(255),
390                FingerprintType::Unassigned(13),
391                vec![100, 110, 115, 115, 101, 99, 32, 100, 97, 110, 101],
392            ),
393            &[255, 13, 100, 110, 115, 115, 101, 99, 32, 100, 97, 110, 101],
394        );
395    }
396}