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