hickory_proto/serialize/txt/
trust_anchor.rs

1//! DNSSEC trust anchor file parser
2//!
3//! A trust anchor file largely adheres to the syntax of a zone file but may only contain
4//! DNSKEY or DS records. DS records are currently unsupported
5
6use alloc::{borrow::Cow, string::String, vec::Vec};
7use core::str::FromStr;
8
9use crate::{
10    dnssec::rdata::DNSKEY,
11    rr::{DNSClass, Name, RecordData, RecordType},
12    serialize::txt::{
13        ParseError, ParseErrorKind, ParseResult,
14        rdata_parsers::dnskey,
15        zone,
16        zone_lex::{Lexer, Token as LexToken},
17    },
18};
19
20/// DNSSEC trust anchor file parser
21pub struct Parser<'a> {
22    lexer: Lexer<'a>,
23}
24
25impl<'a> Parser<'a> {
26    /// Returns a new trust anchor file parser
27    pub fn new(input: impl Into<Cow<'a, str>>) -> Self {
28        Self {
29            lexer: Lexer::new(input),
30        }
31    }
32
33    /// Parse a file from the Lexer
34    ///
35    /// Returns the records found in the file
36    pub fn parse(mut self) -> ParseResult<Vec<Entry>> {
37        let mut state = State::StartLine;
38        let mut records = vec![];
39
40        while let Some(token) = self.lexer.next_token()? {
41            let token: Token = token.try_into()?;
42            state = match state {
43                State::StartLine => match token {
44                    Token::Blank | Token::EOL => State::StartLine,
45                    Token::CharData(data) => {
46                        let name = Name::parse(&data, None)?;
47                        State::Ttl { name }
48                    }
49                },
50
51                State::Ttl { name } => {
52                    if let Token::CharData(data) = token {
53                        if let Ok(class) = DNSClass::from_str(&data) {
54                            State::Type {
55                                name,
56                                ttl: None,
57                                class,
58                            }
59                        } else {
60                            let ttl = zone::Parser::parse_time(&data)?;
61                            State::Class { name, ttl }
62                        }
63                    } else {
64                        return Err(ParseErrorKind::UnexpectedToken(token.into()).into());
65                    }
66                }
67
68                State::Class { name, ttl } => {
69                    if let Token::CharData(mut data) = token {
70                        data.make_ascii_uppercase();
71                        let class = DNSClass::from_str(&data)?;
72                        State::Type {
73                            name,
74                            ttl: Some(ttl),
75                            class,
76                        }
77                    } else {
78                        return Err(ParseErrorKind::UnexpectedToken(token.into()).into());
79                    }
80                }
81
82                State::Type { name, ttl, class } => {
83                    if let Token::CharData(data) = token {
84                        let rtype = RecordType::from_str(&data)?;
85
86                        if !matches!(rtype, RecordType::DNSKEY) {
87                            return Err(ParseErrorKind::UnsupportedRecordType(rtype).into());
88                        }
89
90                        State::RData {
91                            name,
92                            ttl,
93                            class,
94                            parts: vec![],
95                        }
96                    } else {
97                        return Err(ParseErrorKind::UnexpectedToken(token.into()).into());
98                    }
99                }
100
101                State::RData {
102                    name,
103                    ttl,
104                    class,
105                    parts,
106                } => match token {
107                    Token::EOL => {
108                        Self::flush_record(parts, name, ttl, class, &mut records)?;
109                        State::StartLine
110                    }
111
112                    Token::CharData(data) => {
113                        let mut parts = parts;
114                        parts.push(data);
115                        State::RData {
116                            name,
117                            ttl,
118                            class,
119                            parts,
120                        }
121                    }
122
123                    _ => return Err(ParseErrorKind::UnexpectedToken(token.into()).into()),
124                },
125            };
126        }
127
128        if let State::RData {
129            name,
130            ttl,
131            class,
132            parts,
133        } = state
134        {
135            Self::flush_record(parts, name, ttl, class, &mut records)?;
136        }
137
138        Ok(records)
139    }
140
141    fn flush_record(
142        rdata_parts: Vec<String>,
143        name: Name,
144        ttl: Option<u32>,
145        class: DNSClass,
146        records: &mut Vec<Entry>,
147    ) -> ParseResult<()> {
148        let dnskey = dnskey::parse(rdata_parts.iter().map(AsRef::as_ref))?;
149
150        let record = Record {
151            name_labels: name,
152            dns_class: class,
153            ttl,
154            rdata: dnskey,
155        };
156
157        records.push(Entry::DNSKEY(record));
158
159        Ok(())
160    }
161}
162
163/// An entry in the trust anchor file
164#[derive(Debug)]
165#[non_exhaustive]
166pub enum Entry {
167    /// A DNSKEY record
168    DNSKEY(Record<DNSKEY>),
169}
170
171/// A resource record as it appears in a zone file
172// like `rr::Record` but with optional TTL field
173#[derive(Debug)]
174pub struct Record<R> {
175    name_labels: Name,
176    dns_class: DNSClass,
177    ttl: Option<u32>,
178    rdata: R,
179}
180
181impl<R> Record<R> {
182    /// Returns the Record Data, i.e. the record information
183    pub fn data(&self) -> &R {
184        &self.rdata
185    }
186
187    /// Returns the DNSClass of the Record, generally IN for internet
188    pub fn dns_class(&self) -> DNSClass {
189        self.dns_class
190    }
191
192    /// Returns the time-to-live of the record, if present
193    pub fn ttl(&self) -> Option<u32> {
194        self.ttl
195    }
196
197    /// Returns the name of the record
198    pub fn name(&self) -> &Name {
199        &self.name_labels
200    }
201}
202
203impl<R: RecordData> Record<R> {
204    /// Returns the type of the RecordData in the record
205    pub fn record_type(&self) -> RecordType {
206        self.data().record_type()
207    }
208}
209
210enum State {
211    /// Initial state
212    StartLine,
213    /// Expects TTL field (but it may be omitted)
214    Ttl { name: Name },
215    /// Expects Class field (after TTL has been parsed)
216    Class { name: Name, ttl: u32 },
217    /// Expects RecordType field
218    Type {
219        name: Name,
220        ttl: Option<u32>,
221        class: DNSClass,
222    },
223    /// Expects RDATA field
224    RData {
225        name: Name,
226        ttl: Option<u32>,
227        class: DNSClass,
228        parts: Vec<String>,
229    },
230}
231
232enum Token {
233    Blank,
234    CharData(String),
235    EOL,
236}
237
238impl TryFrom<LexToken> for Token {
239    type Error = ParseError;
240
241    fn try_from(token: LexToken) -> Result<Self, Self::Error> {
242        let token = match token {
243            LexToken::At
244            | LexToken::Include
245            | LexToken::Origin
246            | LexToken::Ttl
247            | LexToken::List(_) => return Err(ParseErrorKind::UnexpectedToken(token).into()),
248            LexToken::Blank => Self::Blank,
249            LexToken::CharData(data) => Self::CharData(data),
250            LexToken::EOL => Self::EOL,
251        };
252        Ok(token)
253    }
254}
255
256impl From<Token> for LexToken {
257    fn from(token: Token) -> Self {
258        match token {
259            Token::Blank => Self::Blank,
260            Token::CharData(data) => Self::CharData(data),
261            Token::EOL => Self::EOL,
262        }
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269    #[cfg(feature = "__dnssec")]
270    use crate::dnssec::crypto::EcdsaSigningKey;
271    use crate::dnssec::{Algorithm, PublicKey, PublicKeyBuf, SigningKey, rdata::DNSKEY};
272
273    const ENCODED: &str = "aGVsbG8=";
274
275    #[test]
276    fn empty() {
277        let records = parse_ok("");
278        assert!(records.is_empty());
279    }
280
281    #[cfg(feature = "__dnssec")]
282    #[test]
283    fn it_works() {
284        let algorithm = Algorithm::ECDSAP256SHA256;
285        let pkcs8 = EcdsaSigningKey::generate_pkcs8(algorithm).unwrap();
286        let signing_key = EcdsaSigningKey::from_pkcs8(&pkcs8, algorithm).unwrap();
287        let public_key = signing_key.to_public_key().unwrap();
288        let encoded = data_encoding::BASE64.encode(public_key.public_bytes());
289        let input = format!(".           34076   IN  DNSKEY  256 3 13 {encoded}");
290
291        let records = parse_ok(&input);
292        let [record] = records.try_into().unwrap();
293        assert_eq!(&Name::root(), record.name());
294        assert_eq!(Some(34076), record.ttl());
295        assert_eq!(DNSClass::IN, record.dns_class());
296        assert_eq!(RecordType::DNSKEY, record.record_type());
297
298        let expected = DNSKEY::new(
299            true,
300            false,
301            false,
302            PublicKeyBuf::new(
303                signing_key.to_public_key().unwrap().public_bytes().to_vec(),
304                algorithm,
305            ),
306        );
307        let actual = record.data();
308        assert_eq!(&expected, actual);
309    }
310
311    #[cfg(feature = "__dnssec")]
312    #[test]
313    fn no_ttl_field() {
314        let algorithm = Algorithm::ECDSAP256SHA256;
315        let pkcs8 = EcdsaSigningKey::generate_pkcs8(algorithm).unwrap();
316        let signing_key = EcdsaSigningKey::from_pkcs8(&pkcs8, algorithm).unwrap();
317        let public_key = signing_key.to_public_key().unwrap();
318        let encoded = data_encoding::BASE64.encode(public_key.public_bytes());
319        let input = format!(". IN DNSKEY 256 3 13 {encoded}");
320
321        let records = parse_ok(&input);
322        let [record] = records.try_into().unwrap();
323        assert_eq!(&Name::root(), record.name());
324        assert_eq!(None, record.ttl());
325        assert_eq!(DNSClass::IN, record.dns_class());
326        assert_eq!(RecordType::DNSKEY, record.record_type());
327
328        let expected = DNSKEY::new(
329            true,
330            false,
331            false,
332            PublicKeyBuf::new(
333                signing_key.to_public_key().unwrap().public_bytes().to_vec(),
334                algorithm,
335            ),
336        );
337        let actual = record.data();
338        assert_eq!(&expected, actual);
339    }
340
341    #[test]
342    fn accepts_real_world_data() {
343        let records = parse_ok(include_str!("../../../tests/test-data/root.key"));
344        assert_eq!(3, records.len());
345    }
346
347    #[test]
348    fn origin() {
349        let err = parse_err("$ORIGIN example.com.");
350        assert!(matches!(err.kind(), ParseErrorKind::UnexpectedToken(_)));
351    }
352
353    #[test]
354    fn at_sign() {
355        let input = format!("@           34076   IN  DNSKEY  256 3 8 {ENCODED}");
356        let err = parse_err(&input);
357        assert!(matches!(err.kind(), ParseErrorKind::UnexpectedToken(_)));
358    }
359
360    #[test]
361    fn wrong_record_type() {
362        // $ dig example.com. A
363        let input = "example.com.       657 IN  A   93.184.215.14";
364        let err = parse_err(input);
365        assert!(matches!(
366            err.kind(),
367            ParseErrorKind::UnsupportedRecordType(_)
368        ));
369    }
370
371    fn parse_ok(input: &str) -> Vec<Record<DNSKEY>> {
372        let parser = Parser::new(input);
373        let res = parser.parse();
374        let entries = res.expect("parsing failed");
375        entries
376            .into_iter()
377            .map(|Entry::DNSKEY(dnskey)| dnskey)
378            .collect()
379    }
380
381    fn parse_err(input: &str) -> ParseError {
382        let parser = Parser::new(input);
383        let res = parser.parse();
384        res.expect_err("parsing did not fail")
385    }
386}