Skip to main content

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::ProtoResult,
22    rr::{RData, RecordData, RecordDataDecodable, RecordType},
23    serialize::{
24        binary::{BinDecoder, BinEncodable, BinEncoder, DecodeError, Restrict, RestrictedMath},
25        txt::ParseError,
26    },
27};
28
29/// HEX formatting specific to TLSA, SMIMEA and SSHFP encodings
30pub static HEX: Lazy<Encoding> = Lazy::new(|| {
31    let mut spec = Specification::new();
32    spec.symbols.push_str("0123456789abcdef");
33    spec.ignore.push_str(" \t\r\n");
34    spec.translate.from.push_str("ABCDEF");
35    spec.translate.to.push_str("abcdef");
36    spec.encoding().expect("error in sshfp HEX encoding")
37});
38
39/// [RFC 4255](https://tools.ietf.org/html/rfc4255#section-3.1)
40///
41/// ```text
42/// 3.1.  The SSHFP RDATA Format
43///
44///    The RDATA for a SSHFP RR consists of an algorithm number, fingerprint
45///    type and the fingerprint of the public host key.
46///
47///        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
48///        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
49///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
50///        |   algorithm   |    fp type    |                               /
51///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               /
52///        /                                                               /
53///        /                          fingerprint                          /
54///        /                                                               /
55///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
56///
57/// 3.1.3.  Fingerprint
58///
59///    The fingerprint is calculated over the public key blob as described
60///    in [7].
61///
62///    The message-digest algorithm is presumed to produce an opaque octet
63///    string output, which is placed as-is in the RDATA fingerprint field.
64/// ```
65#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
66#[derive(Debug, PartialEq, Eq, Hash, Clone)]
67#[non_exhaustive]
68pub struct SSHFP {
69    /// The SSH public key algorithm.
70    pub algorithm: Algorithm,
71
72    /// The fingerprint type to use.
73    pub fingerprint_type: FingerprintType,
74
75    /// The fingerprint of the public key.
76    pub fingerprint: Vec<u8>,
77}
78
79impl SSHFP {
80    /// Creates a new SSHFP record data.
81    ///
82    /// # Arguments
83    ///
84    /// * `algorithm` - the SSH public key algorithm.
85    /// * `fingerprint_type` - the fingerprint type to use.
86    /// * `fingerprint` - the fingerprint of the public key.
87    pub fn new(
88        algorithm: Algorithm,
89        fingerprint_type: FingerprintType,
90        fingerprint: Vec<u8>,
91    ) -> Self {
92        Self {
93            algorithm,
94            fingerprint_type,
95            fingerprint,
96        }
97    }
98
99    /// Parse the RData from a set of Tokens
100    ///
101    /// [RFC 4255](https://tools.ietf.org/html/rfc4255#section-3.2)
102    ///
103    /// ```text
104    /// 3.2.  Presentation Format of the SSHFP RR
105    ///
106    ///    The RDATA of the presentation format of the SSHFP resource record
107    ///    consists of two numbers (algorithm and fingerprint type) followed by
108    ///    the fingerprint itself, presented in hex, e.g.:
109    ///
110    ///        host.example.  SSHFP 2 1 123456789abcdef67890123456789abcdef67890
111    ///
112    ///    The use of mnemonics instead of numbers is not allowed.
113    /// ```
114    pub(crate) fn from_tokens<'i, I: Iterator<Item = &'i str>>(
115        mut tokens: I,
116    ) -> Result<Self, ParseError> {
117        fn missing_field<E: From<ParseError>>(field: &str) -> E {
118            ParseError::Msg(format!("SSHFP {field} field missing")).into()
119        }
120        let (algorithm, fingerprint_type) = {
121            let mut parse_u8 = |field: &str| {
122                tokens
123                    .next()
124                    .ok_or_else(|| missing_field(field))
125                    .and_then(|t| t.parse::<u8>().map_err(ParseError::from))
126            };
127            (
128                parse_u8("algorithm")?.into(),
129                parse_u8("fingerprint type")?.into(),
130            )
131        };
132        let fingerprint = HEX.decode(
133            tokens
134                .next()
135                .filter(|fp| !fp.is_empty())
136                .ok_or_else(|| missing_field::<ParseError>("fingerprint"))?
137                .as_bytes(),
138        )?;
139        if tokens.next().is_some() {
140            return Err(ParseError::Message("too many fields for SSHFP"));
141        }
142        Ok(Self::new(algorithm, fingerprint_type, fingerprint))
143    }
144}
145
146/// ```text
147/// 3.1.1.  Algorithm Number Specification
148///
149///    This algorithm number octet describes the algorithm of the public
150///    key.  The following values are assigned:
151///
152///           Value    Algorithm name
153///           -----    --------------
154///           0        reserved
155///           1        RSA
156///           2        DSS
157///
158///    Reserving other types requires IETF consensus [4].
159/// ```
160///
161/// The algorithm values have been updated in
162/// [RFC 6594](https://tools.ietf.org/html/rfc6594) and
163/// [RFC 7479](https://tools.ietf.org/html/rfc7479) and
164/// [RFC 8709](https://tools.ietf.org/html/rfc8709).
165#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
166#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
167pub enum Algorithm {
168    /// Reserved value
169    Reserved,
170
171    /// RSA
172    RSA,
173
174    /// DSS/DSA
175    DSA,
176
177    /// ECDSA
178    ECDSA,
179
180    /// Ed25519
181    Ed25519,
182
183    /// Ed448
184    Ed448,
185
186    /// Unassigned value
187    Unassigned(u8),
188}
189
190impl From<u8> for Algorithm {
191    fn from(alg: u8) -> Self {
192        match alg {
193            0 => Self::Reserved,
194            1 => Self::RSA,
195            2 => Self::DSA,
196            3 => Self::ECDSA,
197            4 => Self::Ed25519, // TODO more (XMSS)
198            6 => Self::Ed448,
199            _ => Self::Unassigned(alg),
200        }
201    }
202}
203
204impl From<Algorithm> for u8 {
205    fn from(algorithm: Algorithm) -> Self {
206        match algorithm {
207            Algorithm::Reserved => 0,
208            Algorithm::RSA => 1,
209            Algorithm::DSA => 2,
210            Algorithm::ECDSA => 3,
211            Algorithm::Ed25519 => 4,
212            Algorithm::Ed448 => 6,
213            Algorithm::Unassigned(alg) => alg,
214        }
215    }
216}
217
218/// ```text
219/// 3.1.2.  Fingerprint Type Specification
220///
221///    The fingerprint type octet describes the message-digest algorithm
222///    used to calculate the fingerprint of the public key.  The following
223///    values are assigned:
224///
225///           Value    Fingerprint type
226///           -----    ----------------
227///           0        reserved
228///           1        SHA-1
229///
230///    Reserving other types requires IETF consensus [4].
231///
232///    For interoperability reasons, as few fingerprint types as possible
233///    should be reserved.  The only reason to reserve additional types is
234///    to increase security.
235/// ```
236///
237/// The fingerprint type values have been updated in
238/// [RFC 6594](https://tools.ietf.org/html/rfc6594).
239#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
240#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
241pub enum FingerprintType {
242    /// Reserved value
243    Reserved,
244
245    /// SHA-1
246    #[cfg_attr(feature = "serde", serde(rename = "SHA-1"))]
247    SHA1,
248
249    /// SHA-256
250    #[cfg_attr(feature = "serde", serde(rename = "SHA-256"))]
251    SHA256,
252
253    /// Unassigned value
254    Unassigned(u8),
255}
256
257impl From<u8> for FingerprintType {
258    fn from(ft: u8) -> Self {
259        match ft {
260            0 => Self::Reserved,
261            1 => Self::SHA1,
262            2 => Self::SHA256,
263            _ => Self::Unassigned(ft),
264        }
265    }
266}
267
268impl From<FingerprintType> for u8 {
269    fn from(fingerprint_type: FingerprintType) -> Self {
270        match fingerprint_type {
271            FingerprintType::Reserved => 0,
272            FingerprintType::SHA1 => 1,
273            FingerprintType::SHA256 => 2,
274            FingerprintType::Unassigned(ft) => ft,
275        }
276    }
277}
278
279impl BinEncodable for SSHFP {
280    fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
281        encoder.emit_u8(self.algorithm.into())?;
282        encoder.emit_u8(self.fingerprint_type.into())?;
283        encoder.emit_vec(&self.fingerprint)
284    }
285}
286
287impl<'r> RecordDataDecodable<'r> for SSHFP {
288    fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict<u16>) -> Result<Self, DecodeError> {
289        let algorithm = decoder.read_u8()?.unverified().into();
290        let fingerprint_type = decoder.read_u8()?.unverified().into();
291        let fingerprint_len = length
292            .map(|l| l as usize)
293            .checked_sub(2)
294            .map_err(|len| DecodeError::IncorrectRDataLengthRead { read: 2, len })?
295            .unverified();
296        let fingerprint = decoder.read_vec(fingerprint_len)?.unverified();
297        Ok(SSHFP::new(algorithm, fingerprint_type, fingerprint))
298    }
299}
300
301impl RecordData for SSHFP {
302    fn try_borrow(data: &RData) -> Option<&Self> {
303        match data {
304            RData::SSHFP(data) => Some(data),
305            _ => None,
306        }
307    }
308
309    fn record_type(&self) -> RecordType {
310        RecordType::SSHFP
311    }
312
313    fn into_rdata(self) -> RData {
314        RData::SSHFP(self)
315    }
316}
317
318/// [RFC 4255](https://tools.ietf.org/html/rfc4255#section-3.2)
319///
320/// ```text
321/// 3.2.  Presentation Format of the SSHFP RR
322///
323///    The RDATA of the presentation format of the SSHFP resource record
324///    consists of two numbers (algorithm and fingerprint type) followed by
325///    the fingerprint itself, presented in hex, e.g.:
326///
327///        host.example.  SSHFP 2 1 123456789abcdef67890123456789abcdef67890
328///
329///    The use of mnemonics instead of numbers is not allowed.
330/// ```
331impl fmt::Display for SSHFP {
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
333        write!(
334            f,
335            "{algorithm} {ty} {fingerprint}",
336            algorithm = u8::from(self.algorithm),
337            ty = u8::from(self.fingerprint_type),
338            fingerprint = HEX.encode(&self.fingerprint),
339        )
340    }
341}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346
347    #[test]
348    fn read_algorithm() {
349        assert_eq!(Algorithm::Reserved, 0.into());
350        assert_eq!(Algorithm::RSA, 1.into());
351        assert_eq!(Algorithm::DSA, 2.into());
352        assert_eq!(Algorithm::ECDSA, 3.into());
353        assert_eq!(Algorithm::Ed25519, 4.into());
354        assert_eq!(Algorithm::Ed448, 6.into());
355        assert_eq!(Algorithm::Unassigned(17), 17.into());
356        assert_eq!(Algorithm::Unassigned(42), 42.into());
357
358        assert_eq!(0u8, Algorithm::Reserved.into());
359        assert_eq!(1u8, Algorithm::RSA.into());
360        assert_eq!(2u8, Algorithm::DSA.into());
361        assert_eq!(3u8, Algorithm::ECDSA.into());
362        assert_eq!(4u8, Algorithm::Ed25519.into());
363        assert_eq!(6u8, Algorithm::Ed448.into());
364        assert_eq!(17u8, Algorithm::Unassigned(17).into());
365        assert_eq!(42u8, Algorithm::Unassigned(42).into());
366    }
367
368    #[test]
369    fn read_fingerprint_type() {
370        assert_eq!(FingerprintType::Reserved, 0.into());
371        assert_eq!(FingerprintType::SHA1, 1.into());
372        assert_eq!(FingerprintType::SHA256, 2.into());
373        assert_eq!(FingerprintType::Unassigned(12), 12.into());
374        assert_eq!(FingerprintType::Unassigned(89), 89.into());
375
376        assert_eq!(0u8, FingerprintType::Reserved.into());
377        assert_eq!(1u8, FingerprintType::SHA1.into());
378        assert_eq!(2u8, FingerprintType::SHA256.into());
379        assert_eq!(12u8, FingerprintType::Unassigned(12).into());
380        assert_eq!(89u8, FingerprintType::Unassigned(89).into());
381    }
382
383    fn test_encode_decode(rdata: SSHFP, result: &[u8]) {
384        let mut bytes = Vec::new();
385        let mut encoder = BinEncoder::new(&mut bytes);
386        rdata.emit(&mut encoder).expect("failed to emit SSHFP");
387        let bytes = encoder.into_bytes();
388        assert_eq!(bytes, &result);
389
390        let mut decoder = BinDecoder::new(result);
391        let read_rdata = SSHFP::read_data(&mut decoder, Restrict::new(result.len() as u16))
392            .expect("failed to read SSHFP");
393        assert_eq!(read_rdata, rdata)
394    }
395
396    #[test]
397    fn test_encode_decode_sshfp() {
398        test_encode_decode(
399            SSHFP::new(Algorithm::RSA, FingerprintType::SHA256, vec![]),
400            &[1, 2],
401        );
402        test_encode_decode(
403            SSHFP::new(
404                Algorithm::ECDSA,
405                FingerprintType::SHA1,
406                vec![115, 115, 104, 102, 112],
407            ),
408            &[3, 1, 115, 115, 104, 102, 112],
409        );
410        test_encode_decode(
411            SSHFP::new(
412                Algorithm::Reserved,
413                FingerprintType::Reserved,
414                b"ssh fingerprint".to_vec(),
415            ),
416            &[
417                0, 0, 115, 115, 104, 32, 102, 105, 110, 103, 101, 114, 112, 114, 105, 110, 116,
418            ],
419        );
420        test_encode_decode(
421            SSHFP::new(
422                Algorithm::Unassigned(255),
423                FingerprintType::Unassigned(13),
424                vec![100, 110, 115, 115, 101, 99, 32, 100, 97, 110, 101],
425            ),
426            &[255, 13, 100, 110, 115, 115, 101, 99, 32, 100, 97, 110, 101],
427        );
428    }
429
430    #[test]
431    fn test_parsing() {
432        assert!(SSHFP::from_tokens(core::iter::empty()).is_err());
433        assert!(SSHFP::from_tokens(vec!["51", "13"].into_iter()).is_err());
434        assert!(SSHFP::from_tokens(vec!["1", "-1"].into_iter()).is_err());
435        assert!(SSHFP::from_tokens(vec!["1", "1", "abcd", "foo"].into_iter()).is_err());
436
437        use crate::rr::rdata::sshfp::Algorithm::*;
438        use crate::rr::rdata::sshfp::FingerprintType::*;
439        use crate::rr::rdata::sshfp::{Algorithm, FingerprintType};
440
441        fn test_parsing(input: Vec<&str>, a: Algorithm, ft: FingerprintType, f: &[u8]) {
442            assert!(
443                SSHFP::from_tokens(input.into_iter())
444                    .map(|rd| rd == SSHFP::new(a, ft, f.to_vec()))
445                    .unwrap_or(false)
446            );
447        }
448
449        test_parsing(
450            vec!["1", "1", "dd465c09cfa51fb45020cc83316fff21b9ec74ac"],
451            RSA,
452            SHA1,
453            &[
454                221, 70, 92, 9, 207, 165, 31, 180, 80, 32, 204, 131, 49, 111, 255, 33, 185, 236,
455                116, 172,
456            ],
457        );
458        test_parsing(
459            vec![
460                "1",
461                "2",
462                "b049f950d1397b8fee6a61e4d14a9acdc4721e084eff5460bbed80cfaa2ce2cb",
463            ],
464            RSA,
465            SHA256,
466            &[
467                176, 73, 249, 80, 209, 57, 123, 143, 238, 106, 97, 228, 209, 74, 154, 205, 196,
468                114, 30, 8, 78, 255, 84, 96, 187, 237, 128, 207, 170, 44, 226, 203,
469            ],
470        );
471        test_parsing(
472            vec!["2", "1", "3b6ba6110f5ffcd29469fc1ec2ee25d61718badd"],
473            DSA,
474            SHA1,
475            &[
476                59, 107, 166, 17, 15, 95, 252, 210, 148, 105, 252, 30, 194, 238, 37, 214, 23, 24,
477                186, 221,
478            ],
479        );
480        test_parsing(
481            vec![
482                "2",
483                "2",
484                "f9b8a6a460639306f1b38910456a6ae1018a253c47ecec12db77d7a0878b4d83",
485            ],
486            DSA,
487            SHA256,
488            &[
489                249, 184, 166, 164, 96, 99, 147, 6, 241, 179, 137, 16, 69, 106, 106, 225, 1, 138,
490                37, 60, 71, 236, 236, 18, 219, 119, 215, 160, 135, 139, 77, 131,
491            ],
492        );
493        test_parsing(
494            vec!["3", "1", "c64607a28c5300fec1180b6e417b922943cffcdd"],
495            ECDSA,
496            SHA1,
497            &[
498                198, 70, 7, 162, 140, 83, 0, 254, 193, 24, 11, 110, 65, 123, 146, 41, 67, 207, 252,
499                221,
500            ],
501        );
502        test_parsing(
503            vec![
504                "3",
505                "2",
506                "821eb6c1c98d9cc827ab7f456304c0f14785b7008d9e8646a8519de80849afc7",
507            ],
508            ECDSA,
509            SHA256,
510            &[
511                130, 30, 182, 193, 201, 141, 156, 200, 39, 171, 127, 69, 99, 4, 192, 241, 71, 133,
512                183, 0, 141, 158, 134, 70, 168, 81, 157, 232, 8, 73, 175, 199,
513            ],
514        );
515        test_parsing(
516            vec!["4", "1", "6b6f6165636874657266696e6765727072696e74"],
517            Ed25519,
518            SHA1,
519            &[
520                107, 111, 97, 101, 99, 104, 116, 101, 114, 102, 105, 110, 103, 101, 114, 112, 114,
521                105, 110, 116,
522            ],
523        );
524        test_parsing(
525            vec![
526                "4",
527                "2",
528                "a87f1b687ac0e57d2a081a2f282672334d90ed316d2b818ca9580ea384d92401",
529            ],
530            Ed25519,
531            SHA256,
532            &[
533                168, 127, 27, 104, 122, 192, 229, 125, 42, 8, 26, 47, 40, 38, 114, 51, 77, 144,
534                237, 49, 109, 43, 129, 140, 169, 88, 14, 163, 132, 217, 36, 1,
535            ],
536        );
537    }
538}