//! Handling of netdoc signatures // // TODO use tor_checkable to provide a generic .verify function. // // But the tor_checkable API might need some updates and this seems nontrivial. // Each verification function seems to take different inputs. use saturating_time::SaturatingTime; use super::*; /// A network document with (unverified) signatures /// /// Typically implemented automatically, for `FooUnverified` structs, as defined by /// [`#[derive_deftly(NetdocParseableUnverified)]`](derive_deftly_template_NetdocParseableUnverified). /// /// Each `FooUnverified` embodies precisely the body `Body` /// and the signatures data `SignaturesData` needed to verify it, /// This trait is precisely the constructors/accessors/deconstructors. pub trait NetdocUnverified: Sized { /// The body, ie not including the signatures type Body: Sized; /// The signatures (the whole signature section) type Signatures: NetdocParseableSignatures; /// Inspect the document (and its signatures) /// /// # Security hazard /// /// The signature has not been verified, so the returned data must not be trusted. fn inspect_unverified(&self) -> (&Self::Body, &SignaturesData); /// Obtain the actual document (and signatures), without verifying /// /// # Security hazard /// /// The signature has not been verified, so the returned data must not be trusted. fn unwrap_unverified(self) -> (Self::Body, SignaturesData); /// Construct a new `NetdocUnverified` from a body and signatures /// /// (Called by code generated by `#[derive_deftly(NetdocUnverified)]`.) fn from_parts(body: Self::Body, signatures: SignaturesData) -> Self; } /// Network document that has an unparsed body type (internal trait) /// /// This is used internally by the /// [`NetdocParseableUnverified` derive](derive_deftly_template_NetdocParseableUnverified). // // This is a separate trait so that we don't complicate `NetdocUnverified` // with the additional internal `UnverifiedParsedBody` type. // That keeps `NetdocUnverified` as simply the accessors/constructors for `FooUnverified`. pub trait HasUnverifiedParsedBody { /// The actual body payload. type UnverifiedParsedBody: NetdocParseable; /// Extract the payload /// /// # Security hazard /// /// The signature has not been verified, so the returned data must not be trusted. // // There is one call site, in `ItemStream::parse_signed`. fn unverified_into_inner_unchecked(unverified: Self::UnverifiedParsedBody) -> Self; } /// The signatures information extracted from a signed network document /// /// Each `SomeDocumentUnverified` contains: /// * private `SomeDocument`, /// * public `SignatureData` /// /// See [`NetdocUnverified`] /// and the [`NetdocParseable`](derive_deftly_template_NetdocParseable) derive. #[derive(Debug, Clone)] #[non_exhaustive] pub struct SignaturesData { /// The signatures themselves, each including the corresponding hash pub sigs: U::Signatures, /// The length in bytes of the body, up to the start of the first signature item. pub unsigned_body_len: usize, /// The hashes which were computed as part of parsing. /// /// This will include every hash computed by any signature item's /// `SignatureItemParseable` implementation. /// /// See [`NetdocParseableSignatures::HashesAccu`]. pub hashes: ::HashesAccu, } /// A signature item that can appear in a netdoc /// /// This is the type `T` of a field `item: T` in a netdoc signatures section type. /// /// Types that implement this embody both: /// /// * The item, parameters, and signature data, provided in the document. /// /// They do *not* embody: /// /// * The hash of the document body, which will needed during verification. /// /// However, the hash *is* calculated by `from_unparsed_and_body`, during parsing, /// and stored in `hash`. /// /// Typically derived with /// [`#[derive_deftly(ItemValueParseable)]`](derive_deftly_template_ItemValueParseable). /// /// Normal (non-signature) items implement [`ItemValueParseable`]. pub trait SignatureItemParseable: Sized { /// The Rust type of the hash value accumulator for this item. /// /// Often this will be `Option` where `H` is the actual hash value. /// /// This specific item's `HashAccu` will be found via the document's signatures' /// `NetdocParseableSignatures::HashesAccu`, /// which must `impl AsMut`. type HashAccu; /// Parse the item's value, and also calculate the relevant document hash /// /// If the document hash needed for this item is not already present in `hash`, /// this function must store it there. /// An existing hash should not be overwritten: /// this is because multiple signature items of the same type and hash /// are supposed to be as multiple signatures on the same base document, /// not cumulative signatures where each signer signs the previous signatures. /// /// (Parsing is entangled with hashing because some items have the hash algorithm /// as an argument, and we don't want to parse that twice.) // // This API supports both these cases: // - consensuses have multiple signatures that don't cover each other // - routerdescs have multiple signatures from different algorithms where the // later one in the document *does* cover the earlier one // // In principle it could deal with other kinds of anomalies too, // since the signature item parser gets fed the items in sequence, and can // maintain whatever state it needs in NetdocParseableSignatures::HashesAccu. fn from_unparsed_and_body( item: UnparsedItem<'_>, hash_inputs: &SignatureHashInputs<'_>, hash: &mut Self::HashAccu, ) -> Result; } /// The signatures section of a network document, that can be parsed // // This is separate from `NetdocParseable` because it needs to deal with hashing too. // // Its keyword classification can be a bit simpler because all signature items // are structural and we do not need to impose an ordering on them during parsing. // So long as the body data is appropriately hashed and therefore covered // by whatever signature(s) we are relying on, we don't care what other irrelevant // signatures might be present, and we don't care if they are or are not over-signed. pub trait NetdocParseableSignatures: Sized { /// The type used to accumulate document hashes during parsing /// /// Initialised to `Default` at the start of parsing, /// by the [`parse2` core](ItemStream::parse_signed) /// /// Each item in a signatures section is parsed by a `SignatureItemParseable` impl. /// That impl definites an item-specific /// [`HashAccu`](SignatureItemParseable::HashAccu) /// type. /// /// The [derived](derive_deftly_template_NetdocParseableSignatures) /// signatures section parsing code finds /// the item-specific hash accumulator type /// [`::HashAccu`](SignatureItemParseable::HashAccu) /// via `AsMut`: /// `NetdocParseableSignatures::HashesAccu` /// must impl `AsMut` for each /// `SignatureItemParseable::HashAccu`. /// /// For a signatures section that can contain multiple signatures with different /// hashes, the `AsMut` will normally be derived by [`derive_more::AsMut`]. /// For a document with only one hash type, /// `NetdocParseableSignatures::HashesAccu` and `SignatureItemParseable::HashAccu` /// can be the same newtype, /// [deriving `AsMut`](derive_deftly_template_AsMutSelf). /// /// During signature verification, the document-specific verification could /// should throw [`VerifyFailed::Bug`] if a hash needed for a signature item /// wasn't populated. /// (This isn't possible if each item's `SignatureItemParseable::from_unparsed_and_body` /// always calculates and stores the hash.) type HashesAccu: Default + Debug + Clone; /// Is `kw` one of this signature section's keywords fn is_item_keyword(kw: KeywordRef<'_>) -> bool; /// Parse the signature section from a stream of items fn from_items<'s>( input: &mut ItemStream<'s>, signed_doc_body: SignedDocumentBody<'s>, sig_hashes: &mut Self::HashesAccu, stop_at: stop_at!(), ) -> Result; } /// Hash(es) for a signature item /// /// Used by the derived implementation of [`SignatureItemParseable`] /// generated by /// [`ItemValueParseable`](derive_deftly_template_ItemValueParseable) /// with `#[deftly(netdoc(signature))]`. pub trait SignatureHashesAccumulator: Clone { /// Update `self`, ensuring that this hash is computed /// /// Should perform precisely the hash-related parts specified for /// [`SignatureItemParseable::from_unparsed_and_body`]. /// /// So, if this hash is already recorded in `self`, it should not be updated. fn update_from_netdoc_body( &mut self, document_body: &SignatureHashInputs<'_>, ) -> Result<(), EP>; } /// The part of a network document before the first signature item /// /// This is used for both Orderly signatures /// where the hash does not contain any part of the signature Item /// nor of any further signatures. /// and Disorderly signatures /// where the hash contains part of the signature Item. /// (The Tor protocols currently only have Disorderly signatures.) /// /// See "Signature item ordering, and signatures covering signatures" /// in the [`NetdocParseableSignatures` derive](derive_deftly_template_NetdocParseableSignatures) /// and . // // This type exists as a separate newtype mostly to avoid mistakes inside // parser implementations, where lots of different strings are floating about. // In particular, the parser must save this value when it starts parsing // signatures and must then reuse it for later ones. #[derive(Copy, Debug, Clone, Eq, PartialEq, Hash, amplify::Getters)] pub struct SignedDocumentBody<'s> { /// The actual body as a string #[getter(as_copy)] pub(crate) body: &'s str, } /// Inputs needed to calculate a specific signature hash for a specific Item /// /// Embodies: /// /// * `&str` for the body, as for `SignedDocumentBody`. /// For calculating Orderly signatures. /// (That is, ones that do not include any part of the signature Item; /// See [`SignedDocumentBody`].) /// /// * Extra information for calculating Disorderly signatures. /// Disorderly signature Items can only be implemented within this crate. #[derive(Copy, Debug, Clone, Eq, PartialEq, Hash, amplify::Getters)] pub struct SignatureHashInputs<'s> { /// The Orderly body (up to the first signature item) #[getter(as_copy)] pub(crate) body: SignedDocumentBody<'s>, /// The part of the document up to just before this signature item. #[getter(skip)] pub(crate) document_sofar: &'s str, /// The signature item keyword and the following space #[getter(skip)] pub(crate) signature_item_kw_spc: &'s str, /// The whole signature item keyword line not including the final newline #[getter(skip)] pub(crate) signature_item_line: &'s str, } impl<'s> SignatureHashInputs<'s> { /// Hash into `h` the body and the whole of the signature item's keyword line pub(crate) fn hash_whole_keyword_line(&self, h: &mut impl Digest) { h.update(self.body().body()); h.update(self.signature_item_line); h.update("\n"); } } /// Hash types suitable for use as `#[deftly(netdoc(signature(hash_accu = TY))]` /// /// See /// [`#[derive_deftly(ItemValueParseable)]`](derive_deftly_template_ItemValueParseable). pub mod sig_hashes { use super::*; /// SHA-1 including the whole keyword line /// /// #[derive(Debug, Clone, Default, Deftly)] #[derive_deftly(AsMutSelf)] #[allow(clippy::exhaustive_structs)] pub struct Sha1WholeKeywordLine(pub Option<[u8; 20]>); impl SignatureHashesAccumulator for Sha1WholeKeywordLine { fn update_from_netdoc_body(&mut self, body: &SignatureHashInputs<'_>) -> Result<(), EP> { self.0.get_or_insert_with(|| { let mut h = tor_llcrypto::d::Sha1::new(); body.hash_whole_keyword_line(&mut h); h.finalize().into() }); Ok(()) } } } /// Utility function to check that a time is within a validity period pub fn check_validity_time( now: SystemTime, validity: std::ops::RangeInclusive, ) -> Result<(), VF> { if now < *validity.start() { Err(VF::TooNew) } else if now > *validity.end() { Err(VF::TooOld) } else { Ok(()) } } /// Like [`check_validity_time()`] but with a tolerance to support clock skews. /// /// This function does not use the `DirTolerance` struct because we want to be /// agnostic of directories in this context. pub fn check_validity_time_tolerance( now: SystemTime, validity: std::ops::RangeInclusive, pre_tolerance: Duration, post_tolerance: Duration, ) -> Result<(), VF> { let start = *validity.start(); let end = *validity.end(); let validity = start.saturating_sub(pre_tolerance)..=end.saturating_add(post_tolerance); check_validity_time(now, validity) }