diff --git a/tor-netdoc/src/argtype.rs b/tor-netdoc/src/argtype.rs index 1553c7585..9d7827c90 100644 --- a/tor-netdoc/src/argtype.rs +++ b/tor-netdoc/src/argtype.rs @@ -7,6 +7,7 @@ pub use b64impl::*; pub use curve25519impl::*; +pub use ed25519impl::*; pub use edcert::*; pub use fingerprint::*; pub use rsa::*; @@ -74,6 +75,32 @@ mod curve25519impl { } } +// ============================================================ +mod ed25519impl { + use super::B64; + use crate::{Error, Pos, Result}; + use tor_llcrypto::pk::ed25519::PublicKey; + + pub struct Ed25519Public(PublicKey); + + impl std::str::FromStr for Ed25519Public { + type Err = Error; + fn from_str(s: &str) -> Result { + let b64: B64 = s.parse()?; + let key = PublicKey::from_bytes(b64.as_bytes()).map_err(|_| { + Error::BadArgument(Pos::at(s), "bad length for ed25519 key.".into()) + })?; + Ok(Ed25519Public(key)) + } + } + + impl From for PublicKey { + fn from(pk: Ed25519Public) -> PublicKey { + pk.0 + } + } +} + // ============================================================ mod timeimpl { diff --git a/tor-netdoc/src/err.rs b/tor-netdoc/src/err.rs index 9f43b56f3..4e4e284eb 100644 --- a/tor-netdoc/src/err.rs +++ b/tor-netdoc/src/err.rs @@ -282,6 +282,11 @@ macro_rules! derive_from_err{ } } } - derive_from_err! {std::num::ParseIntError} derive_from_err! {std::net::AddrParseError} + +impl From for Error { + fn from(e: crate::policy::PolicyError) -> Error { + Error::BadPolicy(Pos::None, e) + } +} diff --git a/tor-netdoc/src/family.rs b/tor-netdoc/src/family.rs index 95984602c..e3f92d2bf 100644 --- a/tor-netdoc/src/family.rs +++ b/tor-netdoc/src/family.rs @@ -15,6 +15,19 @@ use tor_llcrypto::pk::rsa::RSAIdentity; /// TODO: This type probably belongs in a different crate. pub struct RelayFamily(Vec); +impl RelayFamily { + /// Return a new empty RelayFamily. + pub fn new() -> Self { + RelayFamily(Vec::new()) + } +} + +impl Default for RelayFamily { + fn default() -> Self { + RelayFamily::new() + } +} + impl std::str::FromStr for RelayFamily { type Err = Error; fn from_str(s: &str) -> Result { diff --git a/tor-netdoc/src/lib.rs b/tor-netdoc/src/lib.rs index 960806601..24b3843c1 100644 --- a/tor-netdoc/src/lib.rs +++ b/tor-netdoc/src/lib.rs @@ -33,6 +33,7 @@ mod util; mod macros; pub mod family; +pub mod microdesc; pub mod policy; pub mod routerdesc; pub mod version; diff --git a/tor-netdoc/src/microdesc.rs b/tor-netdoc/src/microdesc.rs new file mode 100644 index 000000000..88b9b1f90 --- /dev/null +++ b/tor-netdoc/src/microdesc.rs @@ -0,0 +1,145 @@ +//! Parsing implementation for Tor microdescriptors. +//! +//! A "microdescriptor" is an incomplete, infrequently-changing +//! summary of a relay's informatino information that is generated by +//! the directory authorities. +//! +//! Microdescriptors are much smaller than router descriptors, and +//! change less frequently. For this reason, they're currently used +//! for building circuits by all relays and clients. + +use crate::argtype::*; +use crate::family::RelayFamily; +use crate::parse::SectionRules; +use crate::policy::PortPolicy; +use crate::rules::Keyword; +use crate::util; +use crate::{Error, Result}; +use tor_llcrypto::d; +use tor_llcrypto::pk::{curve25519, ed25519, rsa}; + +use digest::Digest; +use lazy_static::lazy_static; + +/// A single microdescriptor. +#[allow(dead_code)] +pub struct Microdesc { + // TODO: maybe this belongs somewhere else. Once it's used to store + // correlate the microdesc to a consensus, it's never used again. + sha256: [u8; 32], + tap_onion_key: rsa::PublicKey, + ntor_onion_key: curve25519::PublicKey, + family: RelayFamily, + ipv4_policy: PortPolicy, + ipv6_policy: PortPolicy, + // TODO: this is redundant. + ed25519_id: Option, + // addr is obsolete and doesn't go here any more + // pr is obsolete and doesn't go here any more. +} + +decl_keyword! { + /// Keyword type for recognized objects in microdescriptors. + MicrodescKW { + "onion-key" => ONION_KEY, + "ntor-onion-key" => NTOR_ONION_KEY, + "family" => FAMILY, + "p" => P, + "p6" => P6, + "id" => ID, + } +} + +lazy_static! { + static ref MICRODESC_RULES: SectionRules = { + use MicrodescKW::*; + + let mut rules = SectionRules::new(); + rules.add(ONION_KEY.rule().required().no_args().obj_required()); + rules.add(NTOR_ONION_KEY.rule().required().args(1..)); + rules.add(FAMILY.rule().args(1..)); + rules.add(P.rule().args(2..)); + rules.add(P6.rule().args(2..)); + rules.add(ID.rule().may_repeat().args(2..)); + rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional()); + rules + }; +} + +impl Microdesc { + /// Parse a string into a new microdescriptor. + pub fn parse(s: &str) -> Result { + use MicrodescKW::*; + + let mut items = crate::tokenize::NetDocReader::new(s).peekable(); + + // We have to start with onion-key + let start_pos = { + let first = items.peek(); + let kwd = match first { + Some(Ok(tok)) => tok.get_kwd(), + _ => return Err(Error::MissingToken("onion-key")), + }; + if kwd != "onion-key" { + return Err(Error::MissingToken("onion-key")); + } + util::str_offset(s, kwd).unwrap() + }; + + let body = MICRODESC_RULES.parse(&mut items)?; + + // Legacy (tap) onion key + let tap_onion_key: rsa::PublicKey = body + .get_required(ONION_KEY)? + .parse_obj::("RSA PUBLIC KEY")? + .check_len_eq(1024)? + .check_exponent(65537)? + .into(); + + // Ntor onion key + let ntor_onion_key = body + .get_required(NTOR_ONION_KEY)? + .parse_arg::(0)? + .into(); + + // family + let family = body + .maybe(FAMILY) + .parse_args_as_str::()? + .unwrap_or_else(RelayFamily::new); + + // exit policies. + let ipv4_policy = body + .maybe(P) + .parse_args_as_str::()? + .unwrap_or_else(PortPolicy::new_reject_all); + let ipv6_policy = body + .maybe(P6) + .parse_args_as_str::()? + .unwrap_or_else(PortPolicy::new_reject_all); + + // ed25519 identity + let ed25519_id = { + let id_tok = body + .get_slice(ID) + .iter() + .find(|item| item.get_arg(1) == Some("ed25519")); + match id_tok { + None => None, + Some(tok) => Some(tok.parse_arg::(0)?.into()), + } + }; + + let sha256 = d::Sha256::digest(&s[start_pos..].as_bytes()).into(); + + Ok(Microdesc { + sha256, + tap_onion_key, + ntor_onion_key, + family, + ipv4_policy, + ipv6_policy, + ed25519_id, + }) + } +} diff --git a/tor-netdoc/src/parse.rs b/tor-netdoc/src/parse.rs index 95b1e753c..3e20d0cc5 100644 --- a/tor-netdoc/src/parse.rs +++ b/tor-netdoc/src/parse.rs @@ -237,6 +237,8 @@ impl SectionRules { let mut section = Section::new(); self.parse_unverified(tokens, &mut section)?; self.validate(§ion)?; + // TODO: unrecognized tokens with objects won't actually get their + // objects checked for valid base64 Ok(section) } } diff --git a/tor-netdoc/src/policy/portpolicy.rs b/tor-netdoc/src/policy/portpolicy.rs index 45faf8e98..9e934955f 100644 --- a/tor-netdoc/src/policy/portpolicy.rs +++ b/tor-netdoc/src/policy/portpolicy.rs @@ -47,6 +47,12 @@ impl Display for PortPolicy { } impl PortPolicy { + /// Return a new PortPolicy that rejects all ports. + pub fn new_reject_all() -> Self { + PortPolicy { + allowed: Vec::new(), + } + } /// Helper: replace this policy with its inverse. fn invert(&mut self) { let mut prev_hi = 0;