tor_netdoc/doc/
routerdesc.rs

1//!
2//! A "router descriptor" is a signed statement that a relay makes
3//! about itself, explaining its keys, its capabilities, its location,
4//! and its status.
5//!
6//! Relays upload their router descriptors to authorities, which use
7//! them to build consensus documents.  Old clients and relays used to
8//! fetch and use router descriptors for all the relays, but nowadays they use
9//! microdescriptors instead.
10//!
11//! Clients still use router descriptors when communicating with
12//! bridges: since bridges are not passed through an authority,
13//! clients accept their descriptors directly.
14//!
15//! For full information about the router descriptor format, see
16//! [dir-spec.txt](https://spec.torproject.org/dir-spec).
17//!
18//! # Limitations
19//!
20//! TODO: This needs to get tested much more!
21//!
22//! TODO: This implementation can be memory-inefficient.  In practice,
23//! it gets really expensive storing policy entries, family
24//! descriptions, parsed keys, and things like that.  We will probably want to
25//! de-duplicate those.
26//!
27//! TODO: There should be accessor functions for some or all of the
28//! fields in RouterDesc.  I'm deferring those until I know what they
29//! should be.
30//!
31//! # Availability
32//!
33//! Most of this module is only available when this crate is built with the
34//! `routerdesc` feature enabled.
35use crate::parse::keyword::Keyword;
36use crate::parse::parser::{Section, SectionRules};
37use crate::parse::tokenize::{ItemResult, NetDocReader};
38use crate::types::family::{RelayFamily, RelayFamilyId};
39use crate::types::misc::*;
40use crate::types::policy::*;
41use crate::types::version::TorVersion;
42use crate::util::PeekableIterator;
43use crate::{AllowAnnotations, Error, NetdocErrorKind as EK, Result, doc};
44
45use ll::pk::ed25519::Ed25519Identity;
46use std::sync::Arc;
47use std::sync::LazyLock;
48use std::{net, time};
49use tor_cert::CertType;
50use tor_checkable::{Timebound, signed, timed};
51use tor_error::{internal, into_internal};
52use tor_llcrypto as ll;
53use tor_llcrypto::pk::rsa::RsaIdentity;
54
55use digest::Digest;
56
57/// Length of a router descriptor digest
58pub const DOC_DIGEST_LEN: usize = 20;
59
60/// The digest of a RouterDesc document, as reported in a NS consensus.
61pub type RdDigest = [u8; DOC_DIGEST_LEN];
62
63/// A router descriptor, with possible annotations.
64#[allow(dead_code)]
65#[cfg_attr(
66    feature = "dangerous-expose-struct-fields",
67    visible::StructFields(pub),
68    non_exhaustive
69)]
70pub struct AnnotatedRouterDesc {
71    /// Annotation for this router descriptor; possibly empty.
72    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
73    ann: RouterAnnotation,
74    /// Underlying router descriptor; signatures not checked yet.
75    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
76    router: UncheckedRouterDesc,
77}
78
79/// Annotations about a router descriptor, as stored on disc.
80#[allow(dead_code)] // don't warn about fields not getting read.
81#[cfg_attr(
82    feature = "dangerous-expose-struct-fields",
83    visible::StructFields(pub),
84    non_exhaustive
85)]
86#[derive(Default)]
87pub struct RouterAnnotation {
88    /// Description of where we got this router descriptor
89    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
90    source: Option<String>,
91    /// When this descriptor was first downloaded.
92    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
93    downloaded: Option<time::SystemTime>,
94    /// Description of what we're willing to use this descriptor for.
95    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
96    purpose: Option<String>,
97}
98
99/// Information about a relay, parsed from a router descriptor.
100///
101/// This type does not hold all the information in the router descriptor
102///
103/// # Limitations
104///
105/// See module documentation.
106///
107/// Additionally, some fields that from router descriptors are not yet
108/// parsed: see the comments in ROUTER_BODY_RULES for information about those.
109///
110/// Before using this type to connect to a relay, you MUST check that
111/// it is valid, using is_expired_at().
112#[allow(dead_code)] // don't warn about fields not getting read.
113#[cfg_attr(
114    feature = "dangerous-expose-struct-fields",
115    visible::StructFields(pub),
116    non_exhaustive
117)]
118#[derive(Clone, Debug)]
119pub struct RouterDesc {
120    /// Human-readable nickname for this relay.
121    ///
122    /// This is not secure, and not guaranteed to be unique.
123    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
124    nickname: Nickname,
125    /// IPv4 address for this relay.
126    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
127    ipv4addr: Option<net::Ipv4Addr>,
128    /// IPv4 ORPort for this relay.
129    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
130    orport: u16,
131    /// IPv6 address and port for this relay.
132    // TODO: we don't use a socketaddrv6 because we don't care about
133    // the flow and scope fields.  We should decide whether that's a
134    // good idea.
135    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
136    ipv6addr: Option<(net::Ipv6Addr, u16)>,
137    /// Directory port for contacting this relay for direct HTTP
138    /// directory downloads.
139    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
140    dirport: u16,
141    /// Declared uptime for this relay, in seconds.
142    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
143    uptime: Option<u64>,
144    /// Time when this router descriptor was published.
145    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
146    published: time::SystemTime,
147    /// Ed25519 identity certificate (identity key authenticating a
148    /// signing key)
149    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
150    identity_cert: tor_cert::Ed25519Cert,
151    /// RSA identity key for this relay. (Deprecated; never use this without
152    /// the ed25519 identity as well).
153    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
154    rsa_identity_key: ll::pk::rsa::PublicKey,
155    /// RSA identity key for this relay. (Deprecated; never use this without
156    /// the ed25519 identity as well).
157    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
158    rsa_identity: ll::pk::rsa::RsaIdentity,
159    /// Key for extending a circuit to this relay using the ntor protocol.
160    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
161    ntor_onion_key: ll::pk::curve25519::PublicKey,
162    /// Key for extending a circuit to this relay using the
163    /// (deprecated) TAP protocol.
164    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
165    tap_onion_key: Option<ll::pk::rsa::PublicKey>,
166    /// List of subprotocol versions supported by this relay.
167    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
168    proto: Arc<tor_protover::Protocols>,
169    /// True if this relay says it's a directory cache.
170    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
171    is_dircache: bool,
172    /// True if this relay says that it caches extrainfo documents.
173    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
174    is_extrainfo_cache: bool,
175    /// Declared family members for this relay.  If two relays are in the
176    /// same family, they shouldn't be used in the same circuit.
177    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
178    family: Arc<RelayFamily>,
179    /// Declared (and proven) family IDs for this relay. If two relays
180    /// share a family ID, they shouldn't be used in the same circuit.
181    family_ids: Vec<RelayFamilyId>,
182    /// Software and version that this relay says it's running.
183    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
184    platform: Option<RelayPlatform>,
185    /// A complete address-level policy for which IPv4 addresses this relay
186    /// says it supports.
187    // TODO: these polices can get bulky too. Perhaps we should
188    // de-duplicate them too.
189    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
190    ipv4_policy: AddrPolicy,
191    /// A summary of which ports this relay is willing to connect to
192    /// on IPv6.
193    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
194    ipv6_policy: Arc<PortPolicy>,
195}
196
197/// Description of the software a relay is running.
198#[derive(Debug, Clone, PartialEq, Eq)]
199#[non_exhaustive]
200pub enum RelayPlatform {
201    /// Software advertised to be some version of Tor, on some platform.
202    Tor(TorVersion, String),
203    /// Software not advertised to be Tor.
204    Other(String),
205}
206
207impl std::str::FromStr for RelayPlatform {
208    type Err = Error;
209    fn from_str(args: &str) -> Result<Self> {
210        if args.starts_with("Tor ") {
211            let v: Vec<_> = args.splitn(4, ' ').collect();
212            match &v[..] {
213                ["Tor", ver, "on", p] => Ok(RelayPlatform::Tor(ver.parse()?, (*p).to_string())),
214                ["Tor", ver, ..] => Ok(RelayPlatform::Tor(ver.parse()?, "".to_string())),
215                _ => unreachable!(),
216            }
217        } else {
218            Ok(RelayPlatform::Other(args.to_string()))
219        }
220    }
221}
222
223decl_keyword! {
224    /// RouterKwd is an instance of Keyword, used to denote the different
225    /// Items that are recognized as appearing in a router descriptor.
226    RouterKwd {
227        annotation "@source" => ANN_SOURCE,
228        annotation "@downloaded-at" => ANN_DOWNLOADED_AT,
229        annotation "@purpose" => ANN_PURPOSE,
230        "accept" | "reject" => POLICY,
231        "bandwidth" => BANDWIDTH,
232        "bridge-distribution-request" => BRIDGE_DISTRIBUTION_REQUEST,
233        "caches-extra-info" => CACHES_EXTRA_INFO,
234        "contact" => CONTACT,
235        "extra-info-digest" => EXTRA_INFO_DIGEST,
236        "family" => FAMILY,
237        "family-cert" => FAMILY_CERT,
238        "fingerprint" => FINGERPRINT,
239        "hibernating" => HIBERNATING,
240        "identity-ed25519" => IDENTITY_ED25519,
241        "ipv6-policy" => IPV6_POLICY,
242        "master-key-ed25519" => MASTER_KEY_ED25519,
243        "ntor-onion-key" => NTOR_ONION_KEY,
244        "ntor-onion-key-crosscert" => NTOR_ONION_KEY_CROSSCERT,
245        "onion-key" => ONION_KEY,
246        "onion-key-crosscert" => ONION_KEY_CROSSCERT,
247        "or-address" => OR_ADDRESS,
248        "platform" => PLATFORM,
249        "proto" => PROTO,
250        "published" => PUBLISHED,
251        "router" => ROUTER,
252        "router-sig-ed25519" => ROUTER_SIG_ED25519,
253        "router-signature" => ROUTER_SIGNATURE,
254        "signing-key" => SIGNING_KEY,
255        "tunnelled_dir_server" => TUNNELLED_DIR_SERVER,
256        "uptime" => UPTIME,
257        // "protocols" once existed, but is obsolete
258        // "eventdns" once existed, but is obsolete
259        // "allow-single-hop-exits" is also obsolete.
260    }
261}
262
263/// Rules for parsing a set of router descriptor annotations.
264static ROUTER_ANNOTATIONS: LazyLock<SectionRules<RouterKwd>> = LazyLock::new(|| {
265    use RouterKwd::*;
266
267    let mut rules = SectionRules::builder();
268    rules.add(ANN_SOURCE.rule());
269    rules.add(ANN_DOWNLOADED_AT.rule().args(1..));
270    rules.add(ANN_PURPOSE.rule().args(1..));
271    rules.add(ANN_UNRECOGNIZED.rule().may_repeat().obj_optional());
272    // Unrecognized annotations are fine; anything else is an error in this
273    // context.
274    rules.reject_unrecognized();
275    rules.build()
276});
277/// Rules for tokens that are allowed in the first part of a
278/// router descriptor.
279static ROUTER_HEADER_RULES: LazyLock<SectionRules<RouterKwd>> = LazyLock::new(|| {
280    use RouterKwd::*;
281
282    let mut rules = SectionRules::builder();
283    rules.add(ROUTER.rule().required().args(5..));
284    rules.add(IDENTITY_ED25519.rule().required().no_args().obj_required());
285    // No other intervening tokens are permitted in the header.
286    rules.reject_unrecognized();
287    rules.build()
288});
289/// Rules for  tokens that are allowed in the first part of a
290/// router descriptor.
291static ROUTER_BODY_RULES: LazyLock<SectionRules<RouterKwd>> = LazyLock::new(|| {
292    use RouterKwd::*;
293
294    let mut rules = SectionRules::builder();
295    rules.add(MASTER_KEY_ED25519.rule().required().args(1..));
296    rules.add(PLATFORM.rule());
297    rules.add(PUBLISHED.rule().required());
298    rules.add(FINGERPRINT.rule());
299    rules.add(UPTIME.rule().args(1..));
300    rules.add(ONION_KEY.rule().no_args().obj_required());
301    rules.add(ONION_KEY_CROSSCERT.rule().no_args().obj_required());
302    rules.add(NTOR_ONION_KEY.rule().required().args(1..));
303    rules.add(
304        NTOR_ONION_KEY_CROSSCERT
305            .rule()
306            .required()
307            .args(1..=1)
308            .obj_required(),
309    );
310    rules.add(SIGNING_KEY.rule().no_args().required().obj_required());
311    rules.add(POLICY.rule().may_repeat().args(1..));
312    rules.add(IPV6_POLICY.rule().args(2..));
313    rules.add(FAMILY.rule().args(1..));
314    rules.add(FAMILY_CERT.rule().obj_required().may_repeat());
315    rules.add(CACHES_EXTRA_INFO.rule().no_args());
316    rules.add(OR_ADDRESS.rule().may_repeat().args(1..));
317    rules.add(TUNNELLED_DIR_SERVER.rule());
318    rules.add(PROTO.rule().required().args(1..));
319    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
320    // TODO: these aren't parsed yet.  Only authorities use them.
321    {
322        rules.add(BANDWIDTH.rule().required().args(3..));
323        rules.add(BRIDGE_DISTRIBUTION_REQUEST.rule().args(1..));
324        rules.add(HIBERNATING.rule().args(1..));
325        rules.add(CONTACT.rule());
326    }
327    // TODO: this is ignored for now.
328    {
329        rules.add(EXTRA_INFO_DIGEST.rule().args(1..));
330    }
331    rules.build()
332});
333
334/// Rules for items that appear at the end of a router descriptor.
335static ROUTER_SIG_RULES: LazyLock<SectionRules<RouterKwd>> = LazyLock::new(|| {
336    use RouterKwd::*;
337
338    let mut rules = SectionRules::builder();
339    rules.add(ROUTER_SIG_ED25519.rule().required().args(1..));
340    rules.add(ROUTER_SIGNATURE.rule().required().no_args().obj_required());
341    // No intervening tokens are allowed in the footer.
342    rules.reject_unrecognized();
343    rules.build()
344});
345
346impl RouterAnnotation {
347    /// Extract a single RouterAnnotation (possibly empty) from a reader.
348    fn take_from_reader(reader: &mut NetDocReader<'_, RouterKwd>) -> Result<RouterAnnotation> {
349        use RouterKwd::*;
350        let mut items = reader.pause_at(|item| item.is_ok_with_non_annotation());
351
352        let body = ROUTER_ANNOTATIONS.parse(&mut items)?;
353
354        let source = body.maybe(ANN_SOURCE).args_as_str().map(String::from);
355        let purpose = body.maybe(ANN_PURPOSE).args_as_str().map(String::from);
356        let downloaded = body
357            .maybe(ANN_DOWNLOADED_AT)
358            .parse_args_as_str::<Iso8601TimeSp>()?
359            .map(|t| t.into());
360        Ok(RouterAnnotation {
361            source,
362            downloaded,
363            purpose,
364        })
365    }
366}
367
368/// A parsed router descriptor whose signatures and/or validity times
369/// may or may not be invalid.
370pub type UncheckedRouterDesc = signed::SignatureGated<timed::TimerangeBound<RouterDesc>>;
371
372/// How long after its published time is a router descriptor officially
373/// supposed to be usable?
374const ROUTER_EXPIRY_SECONDS: u64 = 5 * 86400;
375
376/// How long before its published time is a router descriptor usable?
377// TODO(nickm): This valid doesn't match C tor, which only enforces this rule
378// ("routers should not some from the future") at directory authorities, and
379// there only enforces a 12-hour limit (`ROUTER_ALLOW_SKEW`).  Eventually we
380// should probably harmonize these cutoffs.
381const ROUTER_PRE_VALIDITY_SECONDS: u64 = 86400;
382
383impl RouterDesc {
384    /// Return a reference to this relay's RSA identity.
385    pub fn rsa_identity(&self) -> &RsaIdentity {
386        &self.rsa_identity
387    }
388
389    /// Return a reference to this relay's Ed25519 identity.
390    pub fn ed_identity(&self) -> &Ed25519Identity {
391        self.identity_cert
392            .signing_key()
393            .expect("No ed25519 identity key on identity cert")
394    }
395
396    /// Return a reference to the list of subprotocol versions supported by this
397    /// relay.
398    pub fn protocols(&self) -> &tor_protover::Protocols {
399        self.proto.as_ref()
400    }
401
402    /// Return a reference to this relay's Ntor onion key.
403    pub fn ntor_onion_key(&self) -> &ll::pk::curve25519::PublicKey {
404        &self.ntor_onion_key
405    }
406
407    /// Return the publication
408    pub fn published(&self) -> time::SystemTime {
409        self.published
410    }
411
412    /// Return an iterator of every `SocketAddr` at which this descriptor says
413    /// its relay can be reached.
414    pub fn or_ports(&self) -> impl Iterator<Item = net::SocketAddr> + '_ {
415        self.ipv4addr
416            .map(|a| net::SocketAddr::new(a.into(), self.orport))
417            .into_iter()
418            .chain(self.ipv6addr.map(net::SocketAddr::from))
419    }
420
421    /// Return the declared family of this descriptor.
422    pub fn family(&self) -> Arc<RelayFamily> {
423        Arc::clone(&self.family)
424    }
425
426    /// Return the authenticated family IDs of this descriptor.
427    pub fn family_ids(&self) -> &[RelayFamilyId] {
428        &self.family_ids[..]
429    }
430
431    /// Helper: tokenize `s`, and divide it into three validated sections.
432    fn parse_sections<'a>(
433        reader: &mut NetDocReader<'a, RouterKwd>,
434    ) -> Result<(
435        Section<'a, RouterKwd>,
436        Section<'a, RouterKwd>,
437        Section<'a, RouterKwd>,
438    )> {
439        use RouterKwd::*;
440
441        // Parse everything up through the header.
442        let header = ROUTER_HEADER_RULES.parse(
443            reader.pause_at(|item| item.is_ok_with_kwd_not_in(&[ROUTER, IDENTITY_ED25519])),
444        )?;
445
446        // Parse everything up to but not including the signature.
447        let body =
448            ROUTER_BODY_RULES.parse(reader.pause_at(|item| {
449                item.is_ok_with_kwd_in(&[ROUTER_SIGNATURE, ROUTER_SIG_ED25519])
450            }))?;
451
452        // Parse the signature.
453        let sig = ROUTER_SIG_RULES.parse(reader.pause_at(|item| {
454            item.is_ok_with_annotation() || item.is_ok_with_kwd(ROUTER) || item.is_empty_line()
455        }))?;
456
457        Ok((header, body, sig))
458    }
459
460    /// Try to parse `s` as a router descriptor.
461    ///
462    /// Does not actually check liveness or signatures; you need to do that
463    /// yourself before you can do the output.
464    pub fn parse(s: &str) -> Result<UncheckedRouterDesc> {
465        let mut reader = crate::parse::tokenize::NetDocReader::new(s)?;
466        let result = Self::parse_internal(&mut reader).map_err(|e| e.within(s))?;
467        // We permit empty lines at the end of router descriptors, since there's
468        // a known issue in Tor relays that causes them to return them this way.
469        reader
470            .should_be_exhausted_but_for_empty_lines()
471            .map_err(|e| e.within(s))?;
472        Ok(result)
473    }
474
475    /// Helper: parse a router descriptor from `s`.
476    ///
477    /// This function does the same as parse(), but returns errors based on
478    /// byte-wise positions.  The parse() function converts such errors
479    /// into line-and-byte positions.
480    fn parse_internal(r: &mut NetDocReader<'_, RouterKwd>) -> Result<UncheckedRouterDesc> {
481        // TODO: This function is too long!  The little "paragraphs" here
482        // that parse one item at a time should be made into sub-functions.
483        use RouterKwd::*;
484
485        let s = r.str();
486        let (header, body, sig) = RouterDesc::parse_sections(r)?;
487
488        // Unwrap should be safe because inline `required` call should return
489        // `Error::MissingToken` if `ROUTER` is not `Ok`
490        #[allow(clippy::unwrap_used)]
491        let start_offset = header.required(ROUTER)?.offset_in(s).unwrap();
492
493        // ed25519 identity and signing key.
494        let (identity_cert, ed25519_signing_key) = {
495            let cert_tok = header.required(IDENTITY_ED25519)?;
496            // Unwrap should be safe because above `required` call should
497            // return `Error::MissingToken` if `IDENTITY_ED25519` is not `Ok`
498            #[allow(clippy::unwrap_used)]
499            if cert_tok.offset_in(s).unwrap() < start_offset {
500                return Err(EK::MisplacedToken
501                    .with_msg("identity-ed25519")
502                    .at_pos(cert_tok.pos()));
503            }
504            let cert: tor_cert::UncheckedCert = cert_tok
505                .parse_obj::<UnvalidatedEdCert>("ED25519 CERT")?
506                .check_cert_type(tor_cert::CertType::IDENTITY_V_SIGNING)?
507                .into_unchecked()
508                .should_have_signing_key()
509                .map_err(|err| {
510                    EK::BadObjectVal
511                        .err()
512                        .with_source(err)
513                        .at_pos(cert_tok.pos())
514                })?;
515            let sk = *cert.peek_subject_key().as_ed25519().ok_or_else(|| {
516                EK::BadObjectVal
517                    .at_pos(cert_tok.pos())
518                    .with_msg("wrong type for signing key in cert")
519            })?;
520            let sk: ll::pk::ed25519::PublicKey = sk.try_into().map_err(|_| {
521                EK::BadObjectVal
522                    .at_pos(cert_tok.pos())
523                    .with_msg("invalid ed25519 signing key")
524            })?;
525            (cert, sk)
526        };
527
528        // master-key-ed25519: required, and should match certificate.
529        #[allow(unexpected_cfgs)]
530        {
531            let master_key_tok = body.required(MASTER_KEY_ED25519)?;
532            let ed_id: Ed25519Public = master_key_tok.parse_arg(0)?;
533            let ed_id: ll::pk::ed25519::Ed25519Identity = ed_id.into();
534            if ed_id != *identity_cert.peek_signing_key() {
535                #[cfg(not(fuzzing))] // No feature here; never omit in production.
536                return Err(EK::BadObjectVal
537                    .at_pos(master_key_tok.pos())
538                    .with_msg("master-key-ed25519 does not match key in identity-ed25519"));
539            }
540        }
541
542        // Legacy RSA identity
543        let rsa_identity_key: ll::pk::rsa::PublicKey = body
544            .required(SIGNING_KEY)?
545            .parse_obj::<RsaPublic>("RSA PUBLIC KEY")?
546            .check_len_eq(1024)?
547            .check_exponent(65537)?
548            .into();
549        let rsa_identity = rsa_identity_key.to_rsa_identity();
550
551        let ed_sig = sig.required(ROUTER_SIG_ED25519)?;
552        let rsa_sig = sig.required(ROUTER_SIGNATURE)?;
553        // Unwrap should be safe because above `required` calls should return
554        // an `Error::MissingToken` if `ROUTER_...` is not `Ok`
555        #[allow(clippy::unwrap_used)]
556        let ed_sig_pos = ed_sig.offset_in(s).unwrap();
557        #[allow(clippy::unwrap_used)]
558        let rsa_sig_pos = rsa_sig.offset_in(s).unwrap();
559
560        if ed_sig_pos > rsa_sig_pos {
561            return Err(EK::UnexpectedToken
562                .with_msg(ROUTER_SIG_ED25519.to_str())
563                .at_pos(ed_sig.pos()));
564        }
565
566        // Extract ed25519 signature.
567        let ed_signature: ll::pk::ed25519::ValidatableEd25519Signature = {
568            let mut d = ll::d::Sha256::new();
569            d.update(&b"Tor router descriptor signature v1"[..]);
570            let signed_end = ed_sig_pos + b"router-sig-ed25519 ".len();
571            d.update(&s[start_offset..signed_end]);
572            let d = d.finalize();
573            let sig: [u8; 64] = ed_sig
574                .parse_arg::<B64>(0)?
575                .into_array()
576                .map_err(|_| EK::BadSignature.at_pos(ed_sig.pos()))?;
577            let sig = ll::pk::ed25519::Signature::from(sig);
578            ll::pk::ed25519::ValidatableEd25519Signature::new(ed25519_signing_key, sig, &d)
579        };
580
581        // Extract legacy RSA signature.
582        let rsa_signature: ll::pk::rsa::ValidatableRsaSignature = {
583            let mut d = ll::d::Sha1::new();
584            let signed_end = rsa_sig_pos + b"router-signature\n".len();
585            d.update(&s[start_offset..signed_end]);
586            let d = d.finalize();
587            let sig = rsa_sig.obj("SIGNATURE")?;
588            // TODO: we need to accept prefixes here. COMPAT BLOCKER.
589
590            ll::pk::rsa::ValidatableRsaSignature::new(&rsa_identity_key, &sig, &d)
591        };
592
593        // router nickname ipv4addr orport socksport dirport
594        let (nickname, ipv4addr, orport, dirport) = {
595            let rtrline = header.required(ROUTER)?;
596            (
597                rtrline.parse_arg::<Nickname>(0)?,
598                Some(rtrline.parse_arg::<net::Ipv4Addr>(1)?),
599                rtrline.parse_arg(2)?,
600                // Skipping socksport.
601                rtrline.parse_arg(4)?,
602            )
603        };
604
605        // uptime
606        let uptime = body.maybe(UPTIME).parse_arg(0)?;
607
608        // published time.
609        let published = body
610            .required(PUBLISHED)?
611            .args_as_str()
612            .parse::<Iso8601TimeSp>()?
613            .into();
614
615        // ntor key
616        let ntor_onion_key: Curve25519Public = body.required(NTOR_ONION_KEY)?.parse_arg(0)?;
617        let ntor_onion_key: ll::pk::curve25519::PublicKey = ntor_onion_key.into();
618        // ntor crosscert
619        let crosscert_cert: tor_cert::UncheckedCert = {
620            let cc = body.required(NTOR_ONION_KEY_CROSSCERT)?;
621            let sign: u8 = cc.parse_arg(0)?;
622            if sign != 0 && sign != 1 {
623                return Err(EK::BadArgument.at_pos(cc.arg_pos(0)).with_msg("not 0 or 1"));
624            }
625            let ntor_as_ed: ll::pk::ed25519::PublicKey =
626                ll::pk::keymanip::convert_curve25519_to_ed25519_public(&ntor_onion_key, sign)
627                    .ok_or_else(|| {
628                        EK::BadArgument
629                            .at_pos(cc.pos())
630                            .with_msg("Uncheckable crosscert")
631                    })?;
632
633            cc.parse_obj::<UnvalidatedEdCert>("ED25519 CERT")?
634                .check_cert_type(tor_cert::CertType::NTOR_CC_IDENTITY)?
635                .check_subject_key_is(identity_cert.peek_signing_key())?
636                .into_unchecked()
637                .should_be_signed_with(&ntor_as_ed.into())
638                .map_err(|err| EK::BadSignature.err().with_source(err))?
639        };
640
641        // TAP key
642        let tap_onion_key: Option<ll::pk::rsa::PublicKey> = if let Some(tok) = body.get(ONION_KEY) {
643            Some(
644                tok.parse_obj::<RsaPublic>("RSA PUBLIC KEY")?
645                    .check_len_eq(1024)?
646                    .check_exponent(65537)?
647                    .into(),
648            )
649        } else {
650            None
651        };
652
653        // TAP crosscert
654        let tap_crosscert_sig = if let Some(cc_tok) = body.get(ONION_KEY_CROSSCERT) {
655            let cc_val = cc_tok.obj("CROSSCERT")?;
656            let mut signed = Vec::new();
657            signed.extend(rsa_identity.as_bytes());
658            signed.extend(identity_cert.peek_signing_key().as_bytes());
659            Some(ll::pk::rsa::ValidatableRsaSignature::new(
660                tap_onion_key.as_ref().ok_or_else(|| {
661                    EK::MissingToken.with_msg("onion-key-crosscert without onion-key")
662                })?,
663                &cc_val,
664                &signed,
665            ))
666        } else if tap_onion_key.is_some() {
667            return Err(EK::MissingToken.with_msg("onion-key without onion-key-crosscert"));
668        } else {
669            None
670        };
671
672        // List of subprotocol versions
673        let proto = {
674            let proto_tok = body.required(PROTO)?;
675            doc::PROTOVERS_CACHE.intern(
676                proto_tok
677                    .args_as_str()
678                    .parse::<tor_protover::Protocols>()
679                    .map_err(|e| EK::BadArgument.at_pos(proto_tok.pos()).with_source(e))?,
680            )
681        };
682
683        // tunneled-dir-server
684        let is_dircache = (dirport != 0) || body.get(TUNNELLED_DIR_SERVER).is_some();
685
686        // caches-extra-info
687        let is_extrainfo_cache = body.get(CACHES_EXTRA_INFO).is_some();
688
689        // fingerprint: check for consistency with RSA identity.
690        if let Some(fp_tok) = body.get(FINGERPRINT) {
691            let fp: RsaIdentity = fp_tok.args_as_str().parse::<SpFingerprint>()?.into();
692            if fp != rsa_identity {
693                return Err(EK::BadArgument
694                    .at_pos(fp_tok.pos())
695                    .with_msg("fingerprint does not match RSA identity"));
696            }
697        }
698
699        // Family
700        let family = {
701            let mut family = body
702                .maybe(FAMILY)
703                .parse_args_as_str::<RelayFamily>()?
704                .unwrap_or_else(RelayFamily::new);
705            if !family.is_empty() {
706                // If this family is nonempty, we add our own RSA id to it, on
707                // the theory that doing so will improve the odds of having a
708                // canonical family shared by all of the members of this family.
709                // If the family is empty, there's no point in adding our own ID
710                // to it, and doing so would only waste memory.
711                family.push(rsa_identity);
712            }
713            family.intern()
714        };
715
716        // Family ids (for "happy families")
717        let family_certs: Vec<tor_cert::UncheckedCert> = body
718            .slice(FAMILY_CERT)
719            .iter()
720            .map(|ent| {
721                ent.parse_obj::<UnvalidatedEdCert>("FAMILY CERT")?
722                    .check_cert_type(CertType::FAMILY_V_IDENTITY)?
723                    .check_subject_key_is(identity_cert.peek_signing_key())?
724                    .into_unchecked()
725                    .should_have_signing_key()
726                    .map_err(|e| {
727                        EK::BadObjectVal
728                            .with_msg("missing public key")
729                            .at_pos(ent.pos())
730                            .with_source(e)
731                    })
732            })
733            .collect::<Result<_>>()?;
734
735        let mut family_ids: Vec<_> = family_certs
736            .iter()
737            .map(|cert| RelayFamilyId::Ed25519(*cert.peek_signing_key()))
738            .collect();
739        family_ids.sort();
740        family_ids.dedup();
741
742        // or-address
743        // Extract at most one ipv6 address from the list.  It's not great,
744        // but it's what Tor does.
745        let mut ipv6addr = None;
746        for tok in body.slice(OR_ADDRESS) {
747            if let Ok(net::SocketAddr::V6(a)) = tok.parse_arg::<net::SocketAddr>(0) {
748                ipv6addr = Some((*a.ip(), a.port()));
749                break;
750            }
751            // We skip over unparsable addresses. Is that right?
752        }
753
754        // platform
755        let platform = body.maybe(PLATFORM).parse_args_as_str::<RelayPlatform>()?;
756
757        // ipv4_policy
758        let ipv4_policy = {
759            let mut pol = AddrPolicy::new();
760            for ruletok in body.slice(POLICY).iter() {
761                let accept = match ruletok.kwd_str() {
762                    "accept" => RuleKind::Accept,
763                    "reject" => RuleKind::Reject,
764                    _ => {
765                        return Err(Error::from(internal!(
766                            "tried to parse a strange line as a policy"
767                        ))
768                        .at_pos(ruletok.pos()));
769                    }
770                };
771                let pat: AddrPortPattern = ruletok
772                    .args_as_str()
773                    .parse()
774                    .map_err(|e| EK::BadPolicy.at_pos(ruletok.pos()).with_source(e))?;
775                pol.push(accept, pat);
776            }
777            pol
778        };
779
780        // ipv6 policy
781        let ipv6_policy = match body.get(IPV6_POLICY) {
782            Some(p) => p
783                .args_as_str()
784                .parse()
785                .map_err(|e| EK::BadPolicy.at_pos(p.pos()).with_source(e))?,
786            // Unwrap is safe here because str is not empty
787            #[allow(clippy::unwrap_used)]
788            None => "reject 1-65535".parse::<PortPolicy>().unwrap(),
789        };
790
791        // Now we're going to collect signatures and expiration times.
792        let (identity_cert, identity_sig) = identity_cert.dangerously_split().map_err(|err| {
793            EK::BadObjectVal
794                .with_msg("missing public key")
795                .with_source(err)
796        })?;
797        let (crosscert_cert, cc_sig) = crosscert_cert.dangerously_split().map_err(|err| {
798            EK::BadObjectVal
799                .with_msg("missing public key")
800                .with_source(err)
801        })?;
802        let mut signatures: Vec<Box<dyn ll::pk::ValidatableSignature>> = vec![
803            Box::new(rsa_signature),
804            Box::new(ed_signature),
805            Box::new(identity_sig),
806            Box::new(cc_sig),
807        ];
808        if let Some(s) = tap_crosscert_sig {
809            signatures.push(Box::new(s));
810        }
811
812        let identity_cert = identity_cert.dangerously_assume_timely();
813        let crosscert_cert = crosscert_cert.dangerously_assume_timely();
814        let mut expirations = vec![
815            published + time::Duration::new(ROUTER_EXPIRY_SECONDS, 0),
816            identity_cert.expiry(),
817            crosscert_cert.expiry(),
818        ];
819
820        for cert in family_certs {
821            let (inner, sig) = cert.dangerously_split().map_err(into_internal!(
822                "Missing a public key that was previously there."
823            ))?;
824            signatures.push(Box::new(sig));
825            expirations.push(inner.dangerously_assume_timely().expiry());
826        }
827
828        // Unwrap is safe here because `expirations` array is not empty
829        #[allow(clippy::unwrap_used)]
830        let expiry = *expirations.iter().min().unwrap();
831
832        let start_time = published - time::Duration::new(ROUTER_PRE_VALIDITY_SECONDS, 0);
833
834        let desc = RouterDesc {
835            nickname,
836            ipv4addr,
837            orport,
838            ipv6addr,
839            dirport,
840            uptime,
841            published,
842            identity_cert,
843            rsa_identity_key,
844            rsa_identity,
845            ntor_onion_key,
846            tap_onion_key,
847            proto,
848            is_dircache,
849            is_extrainfo_cache,
850            family,
851            family_ids,
852            platform,
853            ipv4_policy,
854            ipv6_policy: ipv6_policy.intern(),
855        };
856
857        let time_gated = timed::TimerangeBound::new(desc, start_time..expiry);
858        let sig_gated = signed::SignatureGated::new(time_gated, signatures);
859
860        Ok(sig_gated)
861    }
862}
863
864/// An iterator that parses one or more (possibly annotated
865/// router descriptors from a string.
866//
867// TODO: This is largely copy-pasted from MicrodescReader. Can/should they
868// be merged?
869pub struct RouterReader<'a> {
870    /// True iff we accept annotations
871    annotated: bool,
872    /// Reader that we're extracting items from.
873    reader: NetDocReader<'a, RouterKwd>,
874}
875
876/// Skip this reader forward until the next thing it reads looks like the
877/// start of a router descriptor.
878///
879/// Used to recover from errors.
880fn advance_to_next_routerdesc(reader: &mut NetDocReader<'_, RouterKwd>, annotated: bool) {
881    use RouterKwd::*;
882    loop {
883        let item = reader.peek();
884        match item {
885            Some(Ok(t)) => {
886                let kwd = t.kwd();
887                if (annotated && kwd.is_annotation()) || kwd == ROUTER {
888                    return;
889                }
890            }
891            Some(Err(_)) => {
892                // Skip over broken tokens.
893            }
894            None => {
895                return;
896            }
897        }
898        let _ = reader.next();
899    }
900}
901
902impl<'a> RouterReader<'a> {
903    /// Construct a RouterReader to take router descriptors from a string.
904    pub fn new(s: &'a str, allow: &AllowAnnotations) -> Result<Self> {
905        let reader = NetDocReader::new(s)?;
906        let annotated = allow == &AllowAnnotations::AnnotationsAllowed;
907        Ok(RouterReader { annotated, reader })
908    }
909
910    /// Extract an annotation from this reader.
911    fn take_annotation(&mut self) -> Result<RouterAnnotation> {
912        if self.annotated {
913            RouterAnnotation::take_from_reader(&mut self.reader)
914        } else {
915            Ok(RouterAnnotation::default())
916        }
917    }
918
919    /// Extract an annotated router descriptor from this reader
920    ///
921    /// (internal helper; does not clean up on failures.)
922    fn take_annotated_routerdesc_raw(&mut self) -> Result<AnnotatedRouterDesc> {
923        let ann = self.take_annotation()?;
924        let router = RouterDesc::parse_internal(&mut self.reader)?;
925        Ok(AnnotatedRouterDesc { ann, router })
926    }
927
928    /// Extract an annotated router descriptor from this reader
929    ///
930    /// Ensure that at least one token is consumed
931    fn take_annotated_routerdesc(&mut self) -> Result<AnnotatedRouterDesc> {
932        let pos_orig = self.reader.pos();
933        let result = self.take_annotated_routerdesc_raw();
934        if result.is_err() {
935            if self.reader.pos() == pos_orig {
936                // No tokens were consumed from the reader.  We need
937                // to drop at least one token to ensure we aren't in
938                // an infinite loop.
939                //
940                // (This might not be able to happen, but it's easier to
941                // explicitly catch this case than it is to prove that
942                // it's impossible.)
943                let _ = self.reader.next();
944            }
945            advance_to_next_routerdesc(&mut self.reader, self.annotated);
946        }
947        result
948    }
949}
950
951impl<'a> Iterator for RouterReader<'a> {
952    type Item = Result<AnnotatedRouterDesc>;
953    fn next(&mut self) -> Option<Self::Item> {
954        // Is there a next token? If not, we're done.
955        self.reader.peek()?;
956
957        Some(
958            self.take_annotated_routerdesc()
959                .map_err(|e| e.within(self.reader.str())),
960        )
961    }
962}
963
964#[cfg(test)]
965mod test {
966    // @@ begin test lint list maintained by maint/add_warning @@
967    #![allow(clippy::bool_assert_comparison)]
968    #![allow(clippy::clone_on_copy)]
969    #![allow(clippy::dbg_macro)]
970    #![allow(clippy::mixed_attributes_style)]
971    #![allow(clippy::print_stderr)]
972    #![allow(clippy::print_stdout)]
973    #![allow(clippy::single_char_pattern)]
974    #![allow(clippy::unwrap_used)]
975    #![allow(clippy::unchecked_duration_subtraction)]
976    #![allow(clippy::useless_vec)]
977    #![allow(clippy::needless_pass_by_value)]
978    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
979    use super::*;
980    const TESTDATA: &str = include_str!("../../testdata/routerdesc1.txt");
981    const TESTDATA2: &str = include_str!("../../testdata/routerdesc2.txt");
982    // Generated with a patched C tor to include "happy family" IDs.
983    const TESTDATA3: &str = include_str!("../../testdata/routerdesc3.txt");
984
985    fn read_bad(fname: &str) -> String {
986        use std::fs;
987        use std::path::PathBuf;
988        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
989        path.push("testdata");
990        path.push("bad-routerdesc");
991        path.push(fname);
992
993        fs::read_to_string(path).unwrap()
994    }
995
996    #[test]
997    fn parse_arbitrary() -> Result<()> {
998        use std::str::FromStr;
999        use tor_checkable::{SelfSigned, Timebound};
1000        let rd = RouterDesc::parse(TESTDATA)?
1001            .check_signature()?
1002            .dangerously_assume_timely();
1003
1004        assert_eq!(rd.nickname.as_str(), "Akka");
1005        assert_eq!(rd.orport, 443);
1006        assert_eq!(rd.dirport, 0);
1007        assert_eq!(rd.uptime, Some(1036923));
1008        assert_eq!(
1009            rd.family.as_ref(),
1010            &RelayFamily::from_str(
1011                "$303509ab910ef207b7438c27435c4a2fd579f1b1 \
1012                 $56927e61b51e6f363fb55498150a6ddfcf7077f2"
1013            )
1014            .unwrap()
1015        );
1016
1017        assert_eq!(
1018            rd.rsa_identity().to_string(),
1019            "$56927e61b51e6f363fb55498150a6ddfcf7077f2"
1020        );
1021        assert_eq!(
1022            rd.ed_identity().to_string(),
1023            "CVTjf1oeaL616hH+1+UvYZ8OgkwF3z7UMITvJzm5r7A"
1024        );
1025        assert_eq!(
1026            rd.protocols().to_string(),
1027            "Cons=1-2 Desc=1-2 DirCache=2 FlowCtrl=1-2 HSDir=2 \
1028             HSIntro=4-5 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 \
1029             Padding=2 Relay=1-4"
1030        );
1031
1032        assert_eq!(
1033            hex::encode(rd.ntor_onion_key().to_bytes()),
1034            "329b3b52991613392e35d1a821dd6753e1210458ecc3337f7b7d39bfcf5da273"
1035        );
1036        assert_eq!(
1037            rd.published(),
1038            humantime::parse_rfc3339("2022-11-14T19:58:52Z").unwrap()
1039        );
1040        assert_eq!(
1041            rd.or_ports().collect::<Vec<_>>(),
1042            vec![
1043                "95.216.33.58:443".parse().unwrap(),
1044                "[2a01:4f9:2a:2145::2]:443".parse().unwrap(),
1045            ]
1046        );
1047        assert!(rd.tap_onion_key.is_some());
1048
1049        Ok(())
1050    }
1051
1052    #[test]
1053    fn parse_no_tap_key() -> Result<()> {
1054        use tor_checkable::{SelfSigned, Timebound};
1055        let rd = RouterDesc::parse(TESTDATA2)?
1056            .check_signature()?
1057            .dangerously_assume_timely();
1058        assert!(rd.tap_onion_key.is_none());
1059
1060        Ok(())
1061    }
1062
1063    #[test]
1064    fn test_bad() {
1065        use crate::Pos;
1066        use crate::types::policy::PolicyError;
1067        fn check(fname: &str, e: &Error) {
1068            let text = read_bad(fname);
1069            let rd = RouterDesc::parse(&text);
1070            assert!(rd.is_err());
1071            assert_eq!(&rd.err().unwrap(), e);
1072        }
1073
1074        check(
1075            "bad-sig-order",
1076            &EK::UnexpectedToken
1077                .with_msg("router-sig-ed25519")
1078                .at_pos(Pos::from_line(50, 1)),
1079        );
1080        check(
1081            "bad-start1",
1082            &EK::MisplacedToken
1083                .with_msg("identity-ed25519")
1084                .at_pos(Pos::from_line(1, 1)),
1085        );
1086        check("bad-start2", &EK::MissingToken.with_msg("identity-ed25519"));
1087        check(
1088            "mismatched-fp",
1089            &EK::BadArgument
1090                .at_pos(Pos::from_line(12, 1))
1091                .with_msg("fingerprint does not match RSA identity"),
1092        );
1093        check("no-ed-sk", &EK::MissingToken.with_msg("identity-ed25519"));
1094
1095        check(
1096            "bad-cc-sign",
1097            &EK::BadArgument
1098                .at_pos(Pos::from_line(34, 26))
1099                .with_msg("not 0 or 1"),
1100        );
1101        check(
1102            "bad-ipv6policy",
1103            &EK::BadPolicy
1104                .at_pos(Pos::from_line(43, 1))
1105                .with_source(PolicyError::InvalidPolicy),
1106        );
1107        check(
1108            "no-ed-id-key-in-cert",
1109            &EK::BadObjectVal
1110                .at_pos(Pos::from_line(2, 1))
1111                .with_source(tor_cert::CertError::MissingPubKey),
1112        );
1113        check(
1114            "non-ed-sk-in-cert",
1115            &EK::BadObjectVal
1116                .at_pos(Pos::from_line(2, 1))
1117                .with_msg("wrong type for signing key in cert"),
1118        );
1119        check(
1120            "bad-ed-sk-in-cert",
1121            &EK::BadObjectVal
1122                .at_pos(Pos::from_line(2, 1))
1123                .with_msg("invalid ed25519 signing key"),
1124        );
1125        check(
1126            "mismatched-ed-sk-in-cert",
1127            &EK::BadObjectVal
1128                .at_pos(Pos::from_line(8, 1))
1129                .with_msg("master-key-ed25519 does not match key in identity-ed25519"),
1130        );
1131    }
1132
1133    #[test]
1134    fn parse_multiple_annotated() {
1135        use crate::AllowAnnotations;
1136        let mut s = read_bad("bad-cc-sign");
1137        s += "\
1138@uploaded-at 2020-09-26 18:15:41
1139@source \"127.0.0.1\"
1140";
1141        s += TESTDATA;
1142        s += "\
1143@uploaded-at 2020-09-26 18:15:41
1144@source \"127.0.0.1\"
1145";
1146        s += &read_bad("mismatched-fp");
1147
1148        let rd = RouterReader::new(&s, &AllowAnnotations::AnnotationsAllowed).unwrap();
1149        let v: Vec<_> = rd.collect();
1150        assert!(v[0].is_err());
1151        assert!(v[1].is_ok());
1152        assert_eq!(
1153            v[1].as_ref().unwrap().ann.source,
1154            Some("\"127.0.0.1\"".to_string())
1155        );
1156        assert!(v[2].is_err());
1157    }
1158
1159    #[test]
1160    fn test_platform() {
1161        let p = "Tor 0.4.4.4-alpha on a flying bison".parse::<RelayPlatform>();
1162        assert!(p.is_ok());
1163        assert_eq!(
1164            p.unwrap(),
1165            RelayPlatform::Tor(
1166                "0.4.4.4-alpha".parse().unwrap(),
1167                "a flying bison".to_string()
1168            )
1169        );
1170
1171        let p = "Tor 0.4.4.4-alpha on".parse::<RelayPlatform>();
1172        assert!(p.is_ok());
1173
1174        let p = "Tor 0.4.4.4-alpha ".parse::<RelayPlatform>();
1175        assert!(p.is_ok());
1176        let p = "Tor 0.4.4.4-alpha".parse::<RelayPlatform>();
1177        assert!(p.is_ok());
1178
1179        let p = "arti 0.0.0".parse::<RelayPlatform>();
1180        assert!(p.is_ok());
1181        assert_eq!(p.unwrap(), RelayPlatform::Other("arti 0.0.0".to_string()));
1182    }
1183
1184    #[test]
1185    fn test_family_ids() -> Result<()> {
1186        use tor_checkable::{SelfSigned, Timebound};
1187        let rd = RouterDesc::parse(TESTDATA3)?
1188            .check_signature()?
1189            .dangerously_assume_timely();
1190
1191        assert_eq!(
1192            rd.family_ids(),
1193            &[
1194                "ed25519:7sToQRuge1bU2hS0CG0ViMndc4m82JhO4B4kdrQey80"
1195                    .parse()
1196                    .unwrap(),
1197                "ed25519:szHUS3ItRd9uk85b1UVnOZx1gg4B0266jCpbuIMNjcM"
1198                    .parse()
1199                    .unwrap(),
1200            ]
1201        );
1202
1203        Ok(())
1204    }
1205}