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