trust_dns_proto/rr/rdata/
sshfp.rs

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