use crate::encode::{ItemEncoder, ItemValueEncodable};
use crate::parse::keyword::Keyword;
use crate::parse::parser::{Section, SectionRules};
use crate::parse::tokenize::{ItemResult, NetDocReader};
use crate::parse2::{
ArgumentError, ErrorProblem, ItemValueParseable, SignaturesData, UnparsedItem, VerifyFailed,
};
use crate::types::family::{RelayFamily, RelayFamilyIds};
use crate::types::policy::*;
use crate::types::routerdesc::*;
use crate::types::version::TorVersion;
use crate::types::{EmbeddedCert, misc::*};
use crate::util::PeekableIterator;
use crate::{AllowAnnotations, Error, KeywordEncodable, NetdocErrorKind as EK, Result};
use derive_deftly::Deftly;
use ll::pk::ed25519::Ed25519Identity;
use saturating_time::SaturatingTime;
use std::fmt::Display;
use std::sync::LazyLock;
use std::{iter, net, time};
use tor_basic_utils::intern::Intern;
use tor_cert::{CertType, KeyUnknownCert};
use tor_checkable::timed::TimeRangeBound;
use tor_checkable::{Timebound, signed, timed};
use tor_error::{internal, into_internal};
use tor_llcrypto as ll;
use tor_llcrypto::pk::ed25519;
use tor_llcrypto::pk::keymanip::convert_curve25519_to_ed25519_public;
use tor_llcrypto::pk::rsa::RsaIdentity;
use digest::Digest;
pub const DOC_DIGEST_LEN: usize = 20;
pub type RdDigest = [u8; DOC_DIGEST_LEN];
pub type ExtraInfoDigest = [u8; DOC_DIGEST_LEN];
#[non_exhaustive]
pub struct AnnotatedRouterDesc {
pub ann: RouterAnnotation,
pub router: UncheckedRouterDesc,
}
#[derive(Default)]
#[non_exhaustive]
pub struct RouterAnnotation {
pub source: Option<String>,
pub downloaded: Option<time::SystemTime>,
pub purpose: Option<String>,
}
#[derive(Clone, Debug, Deftly, PartialEq)]
#[derive_deftly(NetdocParseableUnverified, NetdocEncodable)]
#[non_exhaustive]
pub struct RouterDesc {
pub router: RouterDescIntroItem,
pub identity_ed25519: EmbeddedCert<Ed25519IdentityCert, KeyUnknownCert>,
#[deftly(netdoc(single_arg))]
pub master_key_ed25519: Ed25519Public,
pub bandwidth: Bandwidth,
pub platform: Option<RelayPlatform>,
#[deftly(netdoc(single_arg))]
pub published: Iso8601TimeSp,
#[deftly(netdoc(single_arg))]
pub fingerprint: Option<SpFingerprint>,
#[deftly(netdoc(single_arg, default(skip)))]
pub hibernating: NumericBoolean,
#[deftly(netdoc(single_arg))]
pub uptime: Option<u64>,
#[deftly(netdoc(single_arg))]
pub ntor_onion_key: Curve25519Public,
pub ntor_onion_key_crosscert: NtorOnionKeyCrossCert,
pub signing_key: ll::pk::rsa::PublicKey,
#[deftly(netdoc(flatten))]
pub ipv4_policy: AddrPolicy,
#[deftly(netdoc(default(skip)))]
pub ipv6_policy: Intern<PortPolicy>,
pub overload_general: Option<OverloadGeneral>,
pub contact: Option<ContactInfo>,
#[deftly(netdoc(default(skip)))]
pub family: Intern<RelayFamily>,
pub family_cert: RetainedOrderVec<EmbeddedCert<Ed25519FamilyCert, KeyUnknownCert>>,
pub caches_extra_info: Option<ItemPresent<CachesExtraInfoToken>>,
pub extra_info_digest: Option<ExtraInfoDigests>,
pub hidden_service_dir: Option<ItemPresent<HiddenServiceDirToken>>,
#[deftly(netdoc(single_arg))]
pub or_address: Vec<net::SocketAddr>,
pub tunnelled_dir_server: Option<ItemPresent<TunnelledDirServerToken>>,
pub proto: tor_protover::Protocols,
}
#[derive(Clone, Debug, PartialEq, Deftly)]
#[derive_deftly(NetdocParseableSignatures, NetdocEncodable)]
#[deftly(netdoc(signatures(hashes_accu = "RouterHashAccu")))]
#[non_exhaustive]
pub struct RouterDescSignatures {
pub router_sig_ed25519: RouterSigEd25519,
pub router_signature: RouterSignature,
}
impl RouterDescUnverified {
#[deny(unused_variables)]
#[cfg(feature = "incomplete")]
pub fn verify(self) -> std::result::Result<TimeRangeBound<RouterDesc>, VerifyFailed> {
let (mut body, sigs): (RouterDesc, SignaturesData<_>) = (self.body, self.sigs);
let mut timebounds = Vec::new();
let (identity_ed25519, identity_ed25519_bounds) =
Ed25519IdentityCert::verify(body.identity_ed25519.raw_unverified().clone())?
.dangerously_into_parts(); let Ed25519IdentityCert {
id_ed25519,
sign_ed25519,
} = identity_ed25519;
if id_ed25519 != body.master_key_ed25519.0 {
return Err(VerifyFailed::Inconsistent);
}
body.identity_ed25519.set_verified(identity_ed25519);
timebounds.push(identity_ed25519_bounds);
timebounds.push(TimeRangeBound::new_from_start_end(
(),
Some(body.published.0),
None,
));
if body
.fingerprint
.is_some_and(|fp| fp.0 != body.signing_key.to_rsa_identity())
{
return Err(VerifyFailed::Inconsistent);
}
let ntor_pk = convert_curve25519_to_ed25519_public(
&body.ntor_onion_key.0,
body.ntor_onion_key_crosscert.bit.0.into(),
)
.ok_or(VerifyFailed::Other)?;
let (ntor_cc, ntor_cc_bounds) = Ed25519NtorCrossCert::verify(
ntor_pk.into(),
id_ed25519,
body.ntor_onion_key_crosscert.cert.raw_unverified().clone(),
)?
.dangerously_into_parts(); body.ntor_onion_key_crosscert.cert.set_verified(ntor_cc);
timebounds.push(ntor_cc_bounds);
if body.signing_key.bits() != 1024 || !body.signing_key.exponent_is(65537) {
return Err(VerifyFailed::Other);
}
for cert in body.family_cert.0.iter_mut() {
let (cert_verified, cert_bounds) =
Ed25519FamilyCert::verify(id_ed25519, cert.raw_unverified().clone())?
.dangerously_into_parts(); cert.set_verified(cert_verified);
timebounds.push(cert_bounds);
}
ed25519::PublicKey::try_from(sign_ed25519)
.map_err(|_| VerifyFailed::Other)?
.verify(
&sigs.hashes.sha256.ok_or(VerifyFailed::VerifyFailed)?,
&sigs.sigs.router_sig_ed25519.0,
)?;
body.signing_key.verify(
sigs.hashes.sha1.ok_or(VerifyFailed::VerifyFailed)?.as_ref(),
sigs.sigs.router_signature.0.as_ref(),
)?;
let start_time = timebounds
.iter()
.filter_map(|x| x.bounds_start_end().0)
.max();
let end_time = timebounds
.iter()
.filter_map(|x| x.bounds_start_end().1)
.min();
debug_assert!(start_time.is_some()); debug_assert!(end_time.is_some());
Ok(TimeRangeBound::new_from_start_end(
body, start_time, end_time,
))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum RelayPlatform {
Tor(TorVersion, Option<String>),
Other(String),
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub struct CachesExtraInfoToken;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub struct HiddenServiceDirToken;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub struct TunnelledDirServerToken;
impl std::str::FromStr for RelayPlatform {
type Err = Error;
fn from_str(args: &str) -> Result<Self> {
if args.starts_with("Tor ") {
let v: Vec<_> = args.splitn(4, ' ').collect();
match &v[..] {
["Tor", ver, "on", p] => {
Ok(RelayPlatform::Tor(ver.parse()?, Some((*p).to_string())))
}
["Tor", ver, ..] => Ok(RelayPlatform::Tor(ver.parse()?, None)),
_ => unreachable!(),
}
} else {
Ok(RelayPlatform::Other(args.to_string()))
}
}
}
impl Display for RelayPlatform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
Self::Tor(v, Some(p)) => write!(f, "Tor {v} on {p}"),
Self::Tor(v, None) => write!(f, "Tor {v}"),
Self::Other(s) => write!(f, "{s}"),
}
}
}
impl ItemValueParseable for RelayPlatform {
fn from_unparsed(item: UnparsedItem<'_>) -> std::result::Result<Self, ErrorProblem> {
let mut args = item.args_copy();
item.check_no_object()?;
args.into_remaining()
.parse()
.map_err(|_| args.handle_error("platform", ArgumentError::Invalid))
}
}
impl ItemValueEncodable for RelayPlatform {
fn write_item_value_onto(
&self,
mut out: ItemEncoder,
) -> std::result::Result<(), tor_error::Bug> {
out.args_raw_string(&self);
Ok(())
}
}
decl_keyword! {
RouterKwd {
annotation "@source" => ANN_SOURCE,
annotation "@downloaded-at" => ANN_DOWNLOADED_AT,
annotation "@purpose" => ANN_PURPOSE,
"accept" | "reject" => POLICY,
"bandwidth" => BANDWIDTH,
"bridge-distribution-request" => BRIDGE_DISTRIBUTION_REQUEST,
"caches-extra-info" => CACHES_EXTRA_INFO,
"contact" => CONTACT,
"extra-info-digest" => EXTRA_INFO_DIGEST,
"family" => FAMILY,
"family-cert" => FAMILY_CERT,
"fingerprint" => FINGERPRINT,
"hibernating" => HIBERNATING,
"identity-ed25519" => IDENTITY_ED25519,
"ipv6-policy" => IPV6_POLICY,
"master-key-ed25519" => MASTER_KEY_ED25519,
"ntor-onion-key" => NTOR_ONION_KEY,
"ntor-onion-key-crosscert" => NTOR_ONION_KEY_CROSSCERT,
"or-address" => OR_ADDRESS,
"platform" => PLATFORM,
"proto" => PROTO,
"published" => PUBLISHED,
"router" => ROUTER,
"router-sig-ed25519" => ROUTER_SIG_ED25519,
"router-signature" => ROUTER_SIGNATURE,
"signing-key" => SIGNING_KEY,
"tunnelled_dir_server" => TUNNELLED_DIR_SERVER,
"uptime" => UPTIME,
}
}
static ROUTER_ANNOTATIONS: LazyLock<SectionRules<RouterKwd>> = LazyLock::new(|| {
use RouterKwd::*;
let mut rules = SectionRules::builder();
rules.add(ANN_SOURCE.rule());
rules.add(ANN_DOWNLOADED_AT.rule().args(1..));
rules.add(ANN_PURPOSE.rule().args(1..));
rules.add(ANN_UNRECOGNIZED.rule().may_repeat().obj_optional());
rules.reject_unrecognized();
rules.build()
});
static ROUTER_HEADER_RULES: LazyLock<SectionRules<RouterKwd>> = LazyLock::new(|| {
use RouterKwd::*;
let mut rules = SectionRules::builder();
rules.add(ROUTER.rule().required().args(5..));
rules.add(IDENTITY_ED25519.rule().required().no_args().obj_required());
rules.reject_unrecognized();
rules.build()
});
static ROUTER_BODY_RULES: LazyLock<SectionRules<RouterKwd>> = LazyLock::new(|| {
use RouterKwd::*;
let mut rules = SectionRules::builder();
rules.add(MASTER_KEY_ED25519.rule().required().args(1..));
rules.add(PLATFORM.rule());
rules.add(PUBLISHED.rule().required());
rules.add(FINGERPRINT.rule());
rules.add(UPTIME.rule().args(1..));
rules.add(NTOR_ONION_KEY.rule().required().args(1..));
rules.add(
NTOR_ONION_KEY_CROSSCERT
.rule()
.required()
.args(1..=1)
.obj_required(),
);
rules.add(SIGNING_KEY.rule().no_args().required().obj_required());
rules.add(POLICY.rule().may_repeat().args(1..));
rules.add(IPV6_POLICY.rule().args(2..));
rules.add(FAMILY.rule().args(1..));
rules.add(FAMILY_CERT.rule().obj_required().may_repeat());
rules.add(CACHES_EXTRA_INFO.rule().no_args());
rules.add(OR_ADDRESS.rule().may_repeat().args(1..));
rules.add(TUNNELLED_DIR_SERVER.rule());
rules.add(PROTO.rule().required().args(1..));
rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
{
rules.add(BANDWIDTH.rule().required().args(3..));
rules.add(BRIDGE_DISTRIBUTION_REQUEST.rule().args(1..));
rules.add(HIBERNATING.rule().args(1..));
rules.add(CONTACT.rule());
}
{
rules.add(EXTRA_INFO_DIGEST.rule().args(1..));
}
rules.build()
});
static ROUTER_SIG_RULES: LazyLock<SectionRules<RouterKwd>> = LazyLock::new(|| {
use RouterKwd::*;
let mut rules = SectionRules::builder();
rules.add(ROUTER_SIG_ED25519.rule().required().args(1..));
rules.add(ROUTER_SIGNATURE.rule().required().no_args().obj_required());
rules.reject_unrecognized();
rules.build()
});
impl RouterAnnotation {
fn take_from_reader(reader: &mut NetDocReader<'_, RouterKwd>) -> Result<RouterAnnotation> {
use RouterKwd::*;
let mut items = reader.pause_at(|item| item.is_ok_with_non_annotation());
let body = ROUTER_ANNOTATIONS.parse(&mut items)?;
let source = body.maybe(ANN_SOURCE).args_as_str().map(String::from);
let purpose = body.maybe(ANN_PURPOSE).args_as_str().map(String::from);
let downloaded = body
.maybe(ANN_DOWNLOADED_AT)
.parse_args_as_str::<Iso8601TimeSp>()?
.map(|t| t.into());
Ok(RouterAnnotation {
source,
downloaded,
purpose,
})
}
}
pub type UncheckedRouterDesc = signed::SignatureGated<timed::TimeRangeBound<RouterDesc>>;
const ROUTER_EXPIRY_SECONDS: u64 = 5 * 86400;
const ROUTER_PRE_VALIDITY_SECONDS: u64 = 86400;
impl RouterDesc {
pub fn rsa_identity(&self) -> RsaIdentity {
self.signing_key.to_rsa_identity()
}
pub fn ed_identity(&self) -> &Ed25519Identity {
&self
.identity_ed25519
.get()
.expect("ed25519 identity cert should be verified")
.id_ed25519
}
pub fn protocols(&self) -> &tor_protover::Protocols {
&self.proto
}
pub fn ntor_onion_key(&self) -> &ll::pk::curve25519::PublicKey {
&self.ntor_onion_key.0
}
pub fn published(&self) -> time::SystemTime {
self.published.0
}
pub fn or_ports(&self) -> impl Iterator<Item = net::SocketAddr> + '_ {
iter::once(net::SocketAddr::new(
self.router.address.into(),
self.router.orport,
))
.chain(self.or_address.iter().copied())
}
pub fn family(&self) -> Intern<RelayFamily> {
Intern::clone(&self.family)
}
pub fn family_ids(&self) -> RelayFamilyIds {
RelayFamilyIds::from_iter(
self.family_cert
.iter()
.map(|cert| cert.get().expect("unverified family cert?"))
.map(|cert| cert.family_ed25519.into()),
)
}
fn parse_sections<'a>(
reader: &mut NetDocReader<'a, RouterKwd>,
) -> Result<(
Section<'a, RouterKwd>,
Section<'a, RouterKwd>,
Section<'a, RouterKwd>,
)> {
use RouterKwd::*;
let header = ROUTER_HEADER_RULES.parse(
reader.pause_at(|item| item.is_ok_with_kwd_not_in(&[ROUTER, IDENTITY_ED25519])),
)?;
let body =
ROUTER_BODY_RULES.parse(reader.pause_at(|item| {
item.is_ok_with_kwd_in(&[ROUTER_SIGNATURE, ROUTER_SIG_ED25519])
}))?;
let sig = ROUTER_SIG_RULES.parse(reader.pause_at(|item| {
item.is_ok_with_annotation() || item.is_ok_with_kwd(ROUTER) || item.is_empty_line()
}))?;
Ok((header, body, sig))
}
pub fn parse(s: &str) -> Result<UncheckedRouterDesc> {
let mut reader = crate::parse::tokenize::NetDocReader::new(s)?;
let result = Self::parse_internal(&mut reader).map_err(|e| e.within(s))?;
reader
.should_be_exhausted_but_for_empty_lines()
.map_err(|e| e.within(s))?;
Ok(result)
}
fn parse_internal(r: &mut NetDocReader<'_, RouterKwd>) -> Result<UncheckedRouterDesc> {
use RouterKwd::*;
let s = r.str();
let (header, body, sig) = RouterDesc::parse_sections(r)?;
#[allow(clippy::unwrap_used)]
let start_offset = header.required(ROUTER)?.offset_in(s).unwrap();
let (ku_identity_cert, identity_cert, ed25519_signing_key) = {
let cert_tok = header.required(IDENTITY_ED25519)?;
#[allow(clippy::unwrap_used)]
if cert_tok.offset_in(s).unwrap() < start_offset {
return Err(EK::MisplacedToken
.with_msg("identity-ed25519")
.at_pos(cert_tok.pos()));
}
let ku_cert = cert_tok
.parse_obj::<UnvalidatedEdCert>("ED25519 CERT")?
.check_cert_type(tor_cert::CertType::IDENTITY_V_SIGNING)?
.into_unchecked();
let cert = ku_cert.clone().should_have_signing_key().map_err(|err| {
EK::BadObjectVal
.err()
.with_source(err)
.at_pos(cert_tok.pos())
})?;
let sk = *cert.peek_subject_key().as_ed25519().ok_or_else(|| {
EK::BadObjectVal
.at_pos(cert_tok.pos())
.with_msg("wrong type for signing key in cert")
})?;
let sk: ll::pk::ed25519::PublicKey = sk.try_into().map_err(|_| {
EK::BadObjectVal
.at_pos(cert_tok.pos())
.with_msg("invalid ed25519 signing key")
})?;
(ku_cert, cert, sk)
};
#[allow(unexpected_cfgs)]
let ed25519_identity_key = {
let master_key_tok = body.required(MASTER_KEY_ED25519)?;
let ed_id: Ed25519Public = master_key_tok.parse_arg(0)?;
let ed_id: ll::pk::ed25519::Ed25519Identity = ed_id.into();
if ed_id != *identity_cert.peek_signing_key() {
#[cfg(not(fuzzing))] return Err(EK::BadObjectVal
.at_pos(master_key_tok.pos())
.with_msg("master-key-ed25519 does not match key in identity-ed25519"));
}
ed_id
};
let rsa_identity_key: ll::pk::rsa::PublicKey = body
.required(SIGNING_KEY)?
.parse_obj::<RsaPublicParse1Helper>("RSA PUBLIC KEY")?
.check_len_eq(1024)?
.check_exponent(65537)?
.into();
let rsa_identity = rsa_identity_key.to_rsa_identity();
let ed_sig = sig.required(ROUTER_SIG_ED25519)?;
let rsa_sig = sig.required(ROUTER_SIGNATURE)?;
#[allow(clippy::unwrap_used)]
let ed_sig_pos = ed_sig.offset_in(s).unwrap();
#[allow(clippy::unwrap_used)]
let rsa_sig_pos = rsa_sig.offset_in(s).unwrap();
if ed_sig_pos > rsa_sig_pos {
return Err(EK::UnexpectedToken
.with_msg(ROUTER_SIG_ED25519.to_str())
.at_pos(ed_sig.pos()));
}
let ed_signature: ll::pk::ed25519::ValidatableEd25519Signature = {
let mut d = ll::d::Sha256::new();
d.update(&b"Tor router descriptor signature v1"[..]);
let signed_end = ed_sig_pos + b"router-sig-ed25519 ".len();
d.update(
s.get(start_offset..signed_end)
.ok_or(internal!("chopped utf8"))?,
);
let d = d.finalize();
let sig: [u8; 64] = ed_sig
.parse_arg::<B64>(0)?
.into_array()
.map_err(|_| EK::BadSignature.at_pos(ed_sig.pos()))?;
let sig = ll::pk::ed25519::Signature::from(sig);
ll::pk::ed25519::ValidatableEd25519Signature::new(ed25519_signing_key, sig, &d)
};
let rsa_signature: ll::pk::rsa::ValidatableRsaSignature = {
let mut d = ll::d::Sha1::new();
let signed_end = rsa_sig_pos + b"router-signature\n".len();
d.update(
s.get(start_offset..signed_end)
.ok_or(internal!("chopped utf8"))?,
);
let d = d.finalize();
let sig = rsa_sig.obj("SIGNATURE")?;
ll::pk::rsa::ValidatableRsaSignature::new(&rsa_identity_key, &sig, &d)
};
let (nickname, ipv4addr, orport, dirport) = {
let rtrline = header.required(ROUTER)?;
(
rtrline.required_arg(0)?.parse::<Nickname>().map_err(|e| {
EK::BadArgument
.with_msg(e.to_string())
.at_pos(rtrline.pos())
})?,
rtrline.parse_arg::<net::Ipv4Addr>(1)?,
rtrline.parse_arg(2)?,
rtrline.parse_arg(4)?,
)
};
let uptime = body.maybe(UPTIME).parse_arg(0)?;
let published = body
.required(PUBLISHED)?
.args_as_str()
.parse::<Iso8601TimeSp>()?;
let ntor_onion_key: Curve25519Public = body.required(NTOR_ONION_KEY)?.parse_arg(0)?;
let (cc_sig, cc_expiry, cc_cert) = {
let cc = body.required(NTOR_ONION_KEY_CROSSCERT)?;
let sign: u8 = cc.parse_arg(0)?;
if sign != 0 && sign != 1 {
return Err(EK::BadArgument.at_pos(cc.arg_pos(0)).with_msg("not 0 or 1"));
}
let ntor_as_ed: ll::pk::ed25519::PublicKey =
ll::pk::keymanip::convert_curve25519_to_ed25519_public(&ntor_onion_key.0, sign)
.ok_or_else(|| {
EK::BadArgument
.at_pos(cc.pos())
.with_msg("Uncheckable crosscert")
})?;
let cert = cc
.parse_obj::<UnvalidatedEdCert>("ED25519 CERT")?
.into_unchecked();
let (_, sig, expiry) = Ed25519NtorCrossCert::verify_inner(
ntor_as_ed.into(),
ed25519_identity_key,
cert.clone(),
)
.map_err(|_| EK::BadSignature.err())?;
let cert = NtorOnionKeyCrossCert {
bit: NumericBoolean(sign != 0),
cert: EmbeddedCert::new(Ed25519NtorCrossCert::dangerous_new_unverified(), cert),
};
(sig, expiry, cert)
};
let proto = {
let proto_tok = body.required(PROTO)?;
proto_tok
.args_as_str()
.parse::<tor_protover::Protocols>()
.map_err(|e| EK::BadArgument.at_pos(proto_tok.pos()).with_source(e))?
};
let is_dircache = ((dirport != 0) || body.get(TUNNELLED_DIR_SERVER).is_some())
.then_some(ItemPresent::default());
let is_extrainfo_cache = body.get(CACHES_EXTRA_INFO).map(|_| ItemPresent::default());
if let Some(fp_tok) = body.get(FINGERPRINT) {
let fp: RsaIdentity = fp_tok.args_as_str().parse::<SpFingerprint>()?.into();
if fp != rsa_identity {
return Err(EK::BadArgument
.at_pos(fp_tok.pos())
.with_msg("fingerprint does not match RSA identity"));
}
}
let family = {
let mut family = body
.maybe(FAMILY)
.parse_args_as_str::<RelayFamily>()?
.unwrap_or_else(RelayFamily::new);
if !family.is_empty() {
family.push(rsa_identity);
}
family.intern()
};
let family_certs = body
.slice(FAMILY_CERT)
.iter()
.map(|ent| {
let ku = ent
.parse_obj::<UnvalidatedEdCert>("FAMILY CERT")?
.check_cert_type(CertType::FAMILY_V_IDENTITY)?
.check_subject_key_is(identity_cert.peek_signing_key())?
.into_unchecked();
let unchecked = ku.clone().should_have_signing_key().map_err(|e| {
EK::BadObjectVal
.with_msg("missing public key")
.at_pos(ent.pos())
.with_source(e)
})?;
Ok((ku, unchecked))
})
.collect::<Result<Vec<_>>>()?;
let mut ipv6addr = Vec::with_capacity(1);
for tok in body.slice(OR_ADDRESS) {
if let Ok(net::SocketAddr::V6(a)) = tok.parse_arg::<net::SocketAddr>(0) {
ipv6addr.push(a.into());
break;
}
}
let platform = body.maybe(PLATFORM).parse_args_as_str::<RelayPlatform>()?;
let ipv4_policy = {
let mut pol = AddrPolicy::new();
for ruletok in body.slice(POLICY).iter() {
let accept = match ruletok.kwd_str() {
"accept" => RuleKind::Accept,
"reject" => RuleKind::Reject,
_ => {
return Err(Error::from(internal!(
"tried to parse a strange line as a policy"
))
.at_pos(ruletok.pos()));
}
};
let pat: AddrPortPattern = ruletok
.args_as_str()
.parse()
.map_err(|e| EK::BadPolicy.at_pos(ruletok.pos()).with_source(e))?;
pol.push(accept, pat);
}
pol
};
let ipv6_policy = match body.get(IPV6_POLICY) {
Some(p) => p
.args_as_str()
.parse()
.map_err(|e| EK::BadPolicy.at_pos(p.pos()).with_source(e))?,
#[allow(clippy::unwrap_used)]
None => "reject 1-65535".parse::<PortPolicy>().unwrap(),
};
let (identity_cert, identity_sig) = identity_cert.dangerously_split().map_err(|err| {
EK::BadObjectVal
.with_msg("missing public key")
.with_source(err)
})?;
let mut signatures: Vec<Box<dyn ll::pk::ValidatableSignature>> = vec![
Box::new(rsa_signature),
Box::new(ed_signature),
Box::new(identity_sig),
Box::new(cc_sig),
];
let identity_cert = identity_cert.dangerously_assume_timely();
let mut expirations = vec![
published
.0
.saturating_add(time::Duration::new(ROUTER_EXPIRY_SECONDS, 0)),
identity_cert.expiry(),
cc_expiry,
];
let mut embedded_family_certs = Vec::with_capacity(family_certs.len());
for (ku_cert, cert) in family_certs {
let family_ed25519 = *cert.peek_signing_key();
let (inner, sig) = cert.dangerously_split().map_err(into_internal!(
"Missing a public key that was previously there."
))?;
let embedded_cert = EmbeddedCert::new(Ed25519FamilyCert { family_ed25519 }, ku_cert);
signatures.push(Box::new(sig));
expirations.push(inner.dangerously_assume_timely().expiry());
embedded_family_certs.push(embedded_cert);
}
#[allow(clippy::unwrap_used)]
let expiry = *expirations.iter().min().unwrap();
let start_time = published
.0
.saturating_sub(time::Duration::new(ROUTER_PRE_VALIDITY_SECONDS, 0));
let desc = RouterDesc {
router: RouterDescIntroItem {
nickname,
address: ipv4addr,
orport,
socksport: 0,
dirport,
},
identity_ed25519: EmbeddedCert::new(
Ed25519IdentityCert {
id_ed25519: ed25519_identity_key,
sign_ed25519: ed25519_signing_key.into(),
},
ku_identity_cert,
),
master_key_ed25519: ed25519_identity_key.into(),
bandwidth: Default::default(),
platform,
published,
fingerprint: Some(rsa_identity.into()),
hibernating: Default::default(),
uptime,
ntor_onion_key,
ntor_onion_key_crosscert: cc_cert,
signing_key: rsa_identity_key,
ipv4_policy,
ipv6_policy: ipv6_policy.intern(),
overload_general: Default::default(),
contact: Default::default(),
family,
family_cert: embedded_family_certs.into(),
caches_extra_info: is_extrainfo_cache,
extra_info_digest: Default::default(),
hidden_service_dir: Default::default(),
or_address: ipv6addr,
tunnelled_dir_server: is_dircache,
proto,
};
let time_gated = timed::TimeRangeBound::new(desc, start_time..expiry);
let sig_gated = signed::SignatureGated::new(time_gated, signatures);
Ok(sig_gated)
}
}
pub struct RouterReader<'a> {
annotated: bool,
reader: NetDocReader<'a, RouterKwd>,
}
fn advance_to_next_routerdesc(reader: &mut NetDocReader<'_, RouterKwd>, annotated: bool) {
use RouterKwd::*;
loop {
let item = reader.peek();
match item {
Some(Ok(t)) => {
let kwd = t.kwd();
if (annotated && kwd.is_annotation()) || kwd == ROUTER {
return;
}
}
Some(Err(_)) => {
}
None => {
return;
}
}
let _ = reader.next();
}
}
impl<'a> RouterReader<'a> {
pub fn new(s: &'a str, allow: &AllowAnnotations) -> Result<Self> {
let reader = NetDocReader::new(s)?;
let annotated = allow == &AllowAnnotations::AnnotationsAllowed;
Ok(RouterReader { annotated, reader })
}
fn take_annotation(&mut self) -> Result<RouterAnnotation> {
if self.annotated {
RouterAnnotation::take_from_reader(&mut self.reader)
} else {
Ok(RouterAnnotation::default())
}
}
fn take_annotated_routerdesc_raw(&mut self) -> Result<AnnotatedRouterDesc> {
let ann = self.take_annotation()?;
let router = RouterDesc::parse_internal(&mut self.reader)?;
Ok(AnnotatedRouterDesc { ann, router })
}
fn take_annotated_routerdesc(&mut self) -> Result<AnnotatedRouterDesc> {
let pos_orig = self.reader.pos();
let result = self.take_annotated_routerdesc_raw();
if result.is_err() {
if self.reader.pos() == pos_orig {
let _ = self.reader.next();
}
advance_to_next_routerdesc(&mut self.reader, self.annotated);
}
result
}
}
impl<'a> Iterator for RouterReader<'a> {
type Item = Result<AnnotatedRouterDesc>;
fn next(&mut self) -> Option<Self::Item> {
self.reader.peek()?;
Some(
self.take_annotated_routerdesc()
.map_err(|e| e.within(self.reader.str())),
)
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::string_slice)] use std::{net::Ipv4Addr, str::FromStr, time::Duration};
use crate::{
encode::{NetdocEncodable, NetdocEncoder},
parse2::{self, NetdocParseableUnverified, ParseInput},
};
use tor_basic_utils::test_rng::testing_rng;
use tor_checkable::TimeValidityError;
use tor_llcrypto::pk::{curve25519, ed25519::Ed25519PublicKey, rsa};
use super::*;
const TESTDATA: &str = include_str!("../../testdata/routerdesc1.txt");
const TESTDATA2: &str = include_str!("../../testdata/routerdesc2.txt");
const TESTDATA3: &str = include_str!("../../testdata/routerdesc3.txt");
fn read_bad(fname: &str) -> String {
use std::fs;
use std::path::PathBuf;
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("testdata");
path.push("bad-routerdesc");
path.push(fname);
fs::read_to_string(path).unwrap()
}
#[test]
fn parse_arbitrary() -> Result<()> {
use std::str::FromStr;
use tor_checkable::{SelfSigned, TimeBound};
let rd = RouterDesc::parse(TESTDATA)?
.check_signature()?
.dangerously_assume_timely();
assert_eq!(rd.router.nickname.as_str(), "Akka");
assert_eq!(rd.router.orport, 443);
assert_eq!(rd.router.dirport, 0);
assert_eq!(rd.uptime, Some(1036923));
assert_eq!(
rd.family.as_ref(),
&RelayFamily::from_str(
"$303509ab910ef207b7438c27435c4a2fd579f1b1 \
$56927e61b51e6f363fb55498150a6ddfcf7077f2"
)
.unwrap()
);
assert_eq!(
rd.rsa_identity().to_string(),
"$56927e61b51e6f363fb55498150a6ddfcf7077f2"
);
assert_eq!(
rd.ed_identity().to_string(),
"CVTjf1oeaL616hH+1+UvYZ8OgkwF3z7UMITvJzm5r7A"
);
assert_eq!(
rd.protocols().to_string(),
"Cons=1-2 Desc=1-2 DirCache=2 FlowCtrl=1-2 HSDir=2 \
HSIntro=4-5 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 \
Padding=2 Relay=1-4"
);
assert_eq!(
hex::encode(rd.ntor_onion_key().to_bytes()),
"329b3b52991613392e35d1a821dd6753e1210458ecc3337f7b7d39bfcf5da273"
);
assert_eq!(
rd.published(),
humantime::parse_rfc3339("2022-11-14T19:58:52Z").unwrap()
);
assert_eq!(
rd.or_ports().collect::<Vec<_>>(),
vec![
"95.216.33.58:443".parse().unwrap(),
"[2a01:4f9:2a:2145::2]:443".parse().unwrap(),
]
);
Ok(())
}
#[test]
fn parse_no_tap_key() -> Result<()> {
use tor_checkable::{SelfSigned, TimeBound};
let _rd = RouterDesc::parse(TESTDATA2)?
.check_signature()?
.dangerously_assume_timely();
Ok(())
}
#[test]
fn test_bad() {
use crate::Pos;
use crate::types::policy::PolicyError;
fn check(fname: &str, e: &Error) {
let text = read_bad(fname);
let rd = RouterDesc::parse(&text);
assert!(rd.is_err());
assert_eq!(&rd.err().unwrap(), e);
}
check(
"bad-sig-order",
&EK::UnexpectedToken
.with_msg("router-sig-ed25519")
.at_pos(Pos::from_line(50, 1)),
);
check(
"bad-start1",
&EK::MisplacedToken
.with_msg("identity-ed25519")
.at_pos(Pos::from_line(1, 1)),
);
check("bad-start2", &EK::MissingToken.with_msg("identity-ed25519"));
check(
"mismatched-fp",
&EK::BadArgument
.at_pos(Pos::from_line(12, 1))
.with_msg("fingerprint does not match RSA identity"),
);
check("no-ed-sk", &EK::MissingToken.with_msg("identity-ed25519"));
check(
"bad-cc-sign",
&EK::BadArgument
.at_pos(Pos::from_line(34, 26))
.with_msg("not 0 or 1"),
);
check(
"bad-ipv6policy",
&EK::BadPolicy
.at_pos(Pos::from_line(43, 1))
.with_source(PolicyError::InvalidPolicy),
);
check(
"no-ed-id-key-in-cert",
&EK::BadObjectVal
.at_pos(Pos::from_line(2, 1))
.with_source(tor_cert::CertError::MissingPubKey),
);
check(
"non-ed-sk-in-cert",
&EK::BadObjectVal
.at_pos(Pos::from_line(2, 1))
.with_msg("wrong type for signing key in cert"),
);
check(
"bad-ed-sk-in-cert",
&EK::BadObjectVal
.at_pos(Pos::from_line(2, 1))
.with_msg("invalid ed25519 signing key"),
);
check(
"mismatched-ed-sk-in-cert",
&EK::BadObjectVal
.at_pos(Pos::from_line(8, 1))
.with_msg("master-key-ed25519 does not match key in identity-ed25519"),
);
}
#[test]
fn parse_multiple_annotated() {
use crate::AllowAnnotations;
let mut s = read_bad("bad-cc-sign");
s += "\
@uploaded-at 2020-09-26 18:15:41
@source \"127.0.0.1\"
";
s += TESTDATA;
s += "\
@uploaded-at 2020-09-26 18:15:41
@source \"127.0.0.1\"
";
s += &read_bad("mismatched-fp");
let rd = RouterReader::new(&s, &AllowAnnotations::AnnotationsAllowed).unwrap();
let v: Vec<_> = rd.collect();
assert!(v[0].is_err());
assert!(v[1].is_ok());
assert_eq!(
v[1].as_ref().unwrap().ann.source,
Some("\"127.0.0.1\"".to_string())
);
assert!(v[2].is_err());
}
#[test]
fn test_platform() {
let tests = [
(
"Tor 0.4.4.4-alpha on a flying bison",
RelayPlatform::Tor(
"0.4.4.4-alpha".parse().unwrap(),
Some("a flying bison".to_string()),
),
),
(
"Tor 0.4.4.4-alpha on",
RelayPlatform::Tor("0.4.4.4-alpha".parse().unwrap(), None),
),
(
"Tor 0.4.4.4-alpha ",
RelayPlatform::Tor("0.4.4.4-alpha".parse().unwrap(), None),
),
(
"Tor 0.4.4.4-alpha",
RelayPlatform::Tor("0.4.4.4-alpha".parse().unwrap(), None),
),
("arti 0.0.0", RelayPlatform::Other("arti 0.0.0".to_string())),
];
for (input, output) in tests {
assert_eq!(input.parse::<RelayPlatform>().unwrap(), output);
let input = input.strip_suffix(" on").unwrap_or(input);
let input = input.trim();
assert_eq!(output.to_string(), input);
}
}
#[test]
fn test_family_ids() -> Result<()> {
use tor_checkable::{SelfSigned, TimeBound};
let rd = RouterDesc::parse(TESTDATA3)?
.check_signature()?
.dangerously_assume_timely();
assert_eq!(
rd.family_ids().as_ref(),
&[
"ed25519:7sToQRuge1bU2hS0CG0ViMndc4m82JhO4B4kdrQey80"
.parse()
.unwrap(),
"ed25519:szHUS3ItRd9uk85b1UVnOZx1gg4B0266jCpbuIMNjcM"
.parse()
.unwrap(),
]
);
Ok(())
}
#[test]
fn test_parse2_simple() {
let input = ParseInput::new(
include_str!("../../testdata2/cached-descriptors.new"),
"cached-descriptors.new",
);
let rd = parse2::parse_netdoc_multiple::<RouterDescUnverified>(&input)
.unwrap()
.into_iter()
.map(|rd| {
#[cfg(feature = "incomplete")]
rd.clone().verify().unwrap();
rd.unwrap_unverified()
})
.map(|(body, sig)| (body, sig.sigs))
.collect::<Vec<(RouterDesc, RouterDescSignatures)>>();
assert_eq!(rd.len(), 20);
assert_eq!(
rd[0].0.router,
RouterDescIntroItem {
nickname: "test002a".parse().unwrap(),
address: net::Ipv4Addr::LOCALHOST,
orport: 5102,
socksport: 0,
dirport: 7102
}
);
assert_eq!(
rd[0].0.fingerprint.unwrap(),
"257D 06F0 360B B224 6388 724F 109E C089 5A1D 41FB"
.parse()
.unwrap()
);
let mut out = NetdocEncoder::new();
for (body, sig) in &rd {
body.encode_unsigned(&mut out).unwrap();
sig.encode_unsigned(&mut out).unwrap();
}
let out = out.finish().unwrap();
let input2 = ParseInput::new(out.as_str(), "<router descriptor encoding>");
let rd2 = parse2::parse_netdoc_multiple::<RouterDescUnverified>(&input2)
.unwrap()
.into_iter()
.map(|rd| rd.unwrap_unverified())
.map(|(body, sig)| (body, sig.sigs))
.collect::<Vec<(RouterDesc, _)>>();
assert_eq!(rd, rd2);
}
fn rd_encode_sign(doc: &RouterDesc, rsa: &rsa::KeyPair, ed25519: &ed25519::Keypair) -> String {
#[derive(Deftly)]
#[derive_deftly(NetdocEncodable)]
struct Ed25519Writer {
router_sig_ed25519: RouterSigEd25519,
}
#[derive(Deftly)]
#[derive_deftly(NetdocEncodable)]
struct RsaWriter {
router_signature: RouterSignature,
}
let mut out = NetdocEncoder::new();
doc.encode_unsigned(&mut out).unwrap();
Ed25519Writer {
router_sig_ed25519: RouterSigEd25519::new_sign_netdoc(
ed25519,
&out,
"router-sig-ed25519",
)
.unwrap(),
}
.encode_unsigned(&mut out)
.unwrap();
RsaWriter {
router_signature: RouterSignature(
RsaSha1Signature::new_sign_netdoc(rsa, &out, "router-signature")
.unwrap()
.signature,
),
}
.encode_unsigned(&mut out)
.unwrap();
out.finish().unwrap()
}
#[test]
#[cfg(feature = "incomplete")]
fn test_verify() {
let rng = &mut testing_rng();
let rsa_id = rsa::KeyPair::generate(rng).unwrap();
let ed25519_id = ed25519::Keypair::generate(rng);
let ed25519_sign = ed25519::Keypair::generate(rng);
let curve25519_ntor = curve25519::StaticSecret::random_from_rng(&mut *rng);
let curve25519_ntor = curve25519::StaticKeypair {
secret: curve25519_ntor.clone(),
public: (&curve25519_ntor).into(),
};
let ed25519_ntor = tor_llcrypto::pk::keymanip::convert_curve25519_to_ed25519_private(
&curve25519_ntor.secret,
)
.unwrap();
let too_early = Iso8601TimeSp::from_str("2026-05-01 03:00:00").unwrap().0;
let published = Iso8601TimeSp::from_str("2026-06-01 03:00:00").unwrap().0;
let now = Iso8601TimeSp::from_str("2026-06-10 15:30:12").unwrap().0;
let expiration = Iso8601TimeSp::from_str("2026-06-20 03:00:00").unwrap().0;
let expired = expiration + Duration::from_secs(60 * 60 * 24);
let mut rd = RouterDesc {
router: RouterDescIntroItem {
nickname: "foo".parse().unwrap(),
address: Ipv4Addr::LOCALHOST,
orport: 9000,
socksport: 0,
dirport: 0,
},
identity_ed25519: Ed25519IdentityCert::new_signed(
&ed25519_id,
Ed25519Identity::from(ed25519_sign.public_key()),
expiration,
)
.unwrap(),
master_key_ed25519: Ed25519Identity::from(ed25519_id.public_key()).into(),
bandwidth: Bandwidth {
average: 0,
burst: 0,
observed: 0,
},
platform: None,
published: published.into(),
fingerprint: Some(rsa_id.to_public_key().to_rsa_identity().into()),
hibernating: NumericBoolean(false),
uptime: None,
ntor_onion_key: Curve25519Public(curve25519_ntor.public),
ntor_onion_key_crosscert: NtorOnionKeyCrossCert {
bit: NumericBoolean(ed25519_ntor.1 == 1),
cert: Ed25519NtorCrossCert::new_signed(
&ed25519_ntor.0,
ed25519_id.public_key().into(),
expiration,
)
.unwrap(),
},
signing_key: rsa_id.to_public_key(),
ipv4_policy: AddrPolicy::default(),
ipv6_policy: Default::default(),
overload_general: None,
contact: None,
family: Default::default(),
family_cert: Default::default(),
caches_extra_info: Some(Default::default()),
extra_info_digest: None,
hidden_service_dir: Some(Default::default()),
or_address: Default::default(),
tunnelled_dir_server: Some(Default::default()),
proto: tor_protover::Protocols::new(),
};
let rd_original = rd.clone();
let verify =
|rd: &RouterDesc| -> std::result::Result<TimeRangeBound<RouterDesc>, VerifyFailed> {
let encoded = rd_encode_sign(rd, &rsa_id, &ed25519_sign);
let decoded = parse2::parse_netdoc::<RouterDescUnverified>(&ParseInput::new(
&encoded,
"<test_invalid>",
))
.unwrap();
decoded.verify()
};
assert_eq!(
verify(&rd).unwrap().if_valid_at(&too_early).unwrap_err(),
TimeValidityError::NotYetValid(published.duration_since(too_early).unwrap())
);
verify(&rd).unwrap().if_valid_at(&published).unwrap();
verify(&rd).unwrap().if_valid_at(&now).unwrap();
verify(&rd).unwrap().if_valid_at(&expiration).unwrap();
assert_eq!(
verify(&rd).unwrap().if_valid_at(&expired).unwrap_err(),
TimeValidityError::Expired(expired.duration_since(expiration).unwrap())
);
rd.master_key_ed25519 = Ed25519Public(Ed25519Identity::from([0x12; 32]));
assert_eq!(verify(&rd).unwrap_err(), VerifyFailed::Inconsistent);
rd = rd_original.clone();
rd.published = expired.into();
assert_eq!(
verify(&rd).unwrap().if_valid_at(&now).unwrap_err(),
TimeValidityError::NotYetValid(expired.duration_since(now).unwrap())
);
rd = rd_original.clone();
let other_rsa_key = rsa::KeyPair::generate(rng).unwrap();
rd.fingerprint = Some(other_rsa_key.to_public_key().to_rsa_identity().into());
assert_eq!(verify(&rd).unwrap_err(), VerifyFailed::Inconsistent);
rd.signing_key = other_rsa_key.to_public_key();
assert_eq!(verify(&rd).unwrap_err(), VerifyFailed::VerifyFailed);
rd.signing_key = rd_original.signing_key.clone();
rd.fingerprint = None;
verify(&rd).unwrap().if_valid_at(&now).unwrap();
rd = rd_original.clone();
let other_curve25519 = curve25519::StaticSecret::random_from_rng(&mut *rng);
let other_curve25519 = (&other_curve25519).into();
rd.ntor_onion_key = Curve25519Public(other_curve25519);
assert_eq!(verify(&rd).unwrap_err(), VerifyFailed::VerifyFailed);
rd = rd_original.clone();
let different_ed25519_sign = ed25519::Keypair::generate(rng);
rd.identity_ed25519 = Ed25519IdentityCert::new_signed(
&ed25519_id,
Ed25519Identity::from(different_ed25519_sign.public_key()),
expiration,
)
.unwrap();
assert_eq!(verify(&rd).unwrap_err(), VerifyFailed::VerifyFailed);
rd = rd_original.clone();
let different_rsa_id = rsa::KeyPair::generate(rng).unwrap();
rd.signing_key = different_rsa_id.to_public_key();
rd.fingerprint = None;
assert_eq!(verify(&rd).unwrap_err(), VerifyFailed::VerifyFailed);
}
}