1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! Translate relay information from the formats used in the onion service
//! protocol into `CircTarget`s that we can use for building circuits.
//!
//! (Later this will include support for INTRODUCE2 messages too.)

use tor_error::{into_internal, HasRetryTime, RetryTime};
use tor_linkspec::{
    decode::Strictness, verbatim::VerbatimLinkSpecCircTarget, CircTarget, EncodedLinkSpec,
    OwnedChanTargetBuilder, OwnedCircTarget,
};
use tor_llcrypto::pk::curve25519;
use tor_netdir::NetDir;
use tor_netdoc::doc::hsdesc::IntroPointDesc;

/// Helper: create a [`CircTarget`] from its component parts as provided by
/// another party on the network.
///
/// This function is used to build a `CircTarget` from an `IntroPointDesc` (for
/// extending to an introduction point).  Later, it can also be used to build a
/// CircTarget from an `Introduce2` message (for extending to a rendezvous
/// point).
fn circtarget_from_pieces(
    linkspecs: &[EncodedLinkSpec],
    ntor_onion_key: &curve25519::PublicKey,
    netdir: &NetDir,
) -> Result<impl CircTarget, InvalidTarget> {
    let mut bld = OwnedCircTarget::builder();
    // Decode the link specifiers and use them to find out what we can about
    // this relay.
    *bld.chan_target() =
        OwnedChanTargetBuilder::from_encoded_linkspecs(Strictness::Standard, linkspecs)?;
    // Look up the relay in the directory, to see:
    //    1) if it is flatly impossible,
    //    2) what subprotocols we should assume it implements.
    let protocols = {
        let chan_target = bld.chan_target().build().map_err(into_internal!(
            "from_linkspecs gave us a non-working ChanTargetBuilder"
        ))?;
        match netdir.by_ids_detailed(&chan_target)? {
            Some(relay) => relay.protovers().clone(),
            None => netdir.relay_protocol_status().required_protocols().clone(),
        }
    };
    bld.protocols(protocols);
    bld.ntor_onion_key(*ntor_onion_key);
    let circ_target = bld.build().map_err(into_internal!(
        "somehow we made an invalid CircTargetBuilder"
    ))?;
    Ok(VerbatimLinkSpecCircTarget::new(
        circ_target,
        linkspecs.to_vec(),
    ))
}

/// Construct a [`CircTarget`] from a provided [`IntroPointDesc`].
///
/// Onion service clients use this function to convert an `IntroPointDesc` in
/// the onion service descriptor into a form that they can use when building a
/// circuit to an introduction point.
///
/// The `netdir` argument is used to fill in missing information about the
/// target relay, and to make sure that the target relay's identities are not
/// inconsistent with the rest of the network.
pub(crate) fn ipt_to_circtarget(
    desc: &IntroPointDesc,
    netdir: &NetDir,
) -> Result<impl CircTarget, InvalidTarget> {
    circtarget_from_pieces(desc.link_specifiers(), desc.ipt_ntor_key(), netdir)
}

/// We were given unusable information about an introduction point or rendezvous
/// point.
//
// This is returned by `ipt_to_circtarget`.  It will also be used for rendezvous
// points when we implement the HS server side.
// At that point, this module will need to move to a crate where it can be used
// by the HS server code.
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum InvalidTarget {
    /// The provided link specifiers included some that, when we tried to parse
    /// them, proved to be misformed.
    #[error("Misformed channel target information provided")]
    UnparseableChanTargetInfo(#[from] tor_bytes::Error),

    /// The provided link specifiers were inconsistent with one another, or missing
    /// key information.
    #[error("Invalid channel target information provided")]
    InvalidChanTargetInfo(#[from] tor_linkspec::decode::ChanTargetDecodeError),

    /// The provided relay identities (in the link specifiers) described a relay
    /// which, according to the network directory, cannot possibly exist.
    #[error("Impossible combination of relay identities")]
    ImpossibleRelayIds(#[from] tor_netdir::RelayLookupError),

    /// An internal error occurred.
    #[error("{0}")]
    Bug(#[from] tor_error::Bug),
}

/// When to maybe retry *with the same inputs* that generated this error.
///
/// When returned from `ipt_to_circtarget`, that means this is when to retry
/// the *same introduction point* for the *same hidden service*.
///
/// "The same introduction point" means one with precisely the same set of identities
/// and link specifiers.
//
// Note about correctness, and introduction point identity:
//
// We use this as part of HasRetryTime for FailedAttemptError.
// HasRetryTime for FailedAttemptError is used for selecting which intro point to retry.
// Our introduction point experiences are recorded according to *one* relay identity,
// not the complete set.
//
// Nevertheless, this is correct, because: we only select from, and record experiences for,
// *usable* introduction points.  An InvalidTarget error is detected early enough
// to avoid regarding the introduction point as usable at all.  So we never use
// this RetryTime impl, here, to choose between introduction points.
impl HasRetryTime for InvalidTarget {
    fn retry_time(&self) -> RetryTime {
        use InvalidTarget as IT;
        use RetryTime as RT;
        match self {
            IT::UnparseableChanTargetInfo(..) => RT::Never,
            IT::InvalidChanTargetInfo(..) => RT::Never,
            IT::ImpossibleRelayIds(..) => RT::Never,
            IT::Bug(..) => RT::Never,
        }
    }
}