Lines
100 %
Functions
Branches
//! 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<Self>);
/// Obtain the actual document (and signatures), without verifying
fn unwrap_unverified(self) -> (Self::Body, SignaturesData<Self>);
/// 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>) -> 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
// 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<SomeDocumentSignatures>`
/// See [`NetdocUnverified`]
/// and the [`NetdocParseable`](derive_deftly_template_NetdocParseable) derive.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct SignaturesData<U: NetdocUnverified> {
/// 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: <U::Signatures as NetdocParseableSignatures>::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<H>` 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<SignatureItemParseable::HashAccu>`.
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<Self, ErrorProblem>;
/// 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
/// [`<ITEM as SignatureItemParseable>::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<Self>`](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!(),
/// 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 <https://gitlab.torproject.org/tpo/core/torspec/-/issues/322>.
// 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.
pub struct SignatureHashInputs<'s> {
/// The Orderly body (up to the first signature item)
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
pub(crate) signature_item_kw_spc: &'s str,
/// The whole signature item keyword line not including the final newline
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
pub mod sig_hashes {
/// SHA-1 including the whole keyword line
/// <https://spec.torproject.org/dir-spec/netdoc.html#signing>
#[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<SystemTime>,
) -> Result<(), VF> {
if now < *validity.start() {
Err(VF::TooNew)
} else if now > *validity.end() {
Err(VF::TooOld)
} else {
/// 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(
pre_tolerance: Duration,
post_tolerance: Duration,
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)