tor_proto/tunnel/circuit/
handshake.rs

1//! Features for manual invocation of Tor's cryptographic circuit handshakes.
2//!
3//! These features are used to implement onion services, by giving the onion
4//! service code more direct control over the lower-level pieces of the protocol.
5
6// Here we re-export some key types from our cryptographic code, for use when we
7// implement our onion handshake.
8//
9// TODO: it might be neat, someday,  to clean this all up so that the types
10// and functions in hs_ntor are all methods on a set of related traits.  But
11// that can wait IMO until we have a second circuit creation mechanism for use
12// with onion services.
13
14use tor_cell::relaycell::RelayCellFormat;
15use tor_error::internal;
16
17use crate::crypto::binding::CircuitBinding;
18#[cfg(feature = "counter-galois-onion")]
19use crate::crypto::cell::CgoRelayCrypto;
20#[cfg(feature = "hs-common")]
21use crate::crypto::cell::Tor1Hsv3RelayCrypto;
22use crate::crypto::cell::{
23    ClientLayer, CryptInit, InboundClientLayer, InboundRelayLayer, OutboundClientLayer,
24    OutboundRelayLayer, RelayLayer, Tor1RelayCrypto,
25};
26
27use crate::Result;
28
29#[cfg(feature = "hs-common")]
30#[cfg_attr(docsrs, doc(cfg(feature = "hs-common")))]
31pub use crate::crypto::handshake::hs_ntor;
32pub use crate::crypto::handshake::KeyGenerator;
33
34/// The relay protocol to use when extending a circuit manually with
35/// [`Circuit::extend_virtual`](crate::tunnel::circuit::ClientCirc::extend_virtual).
36//
37// NOTE: These correspond internally to implementations of
38// crate::crypto::cell::ClientLayer.
39#[derive(Copy, Clone, Debug)]
40#[non_exhaustive]
41#[cfg(feature = "hs-common")]
42pub enum RelayProtocol {
43    /// A variation of Tor's original protocol, using AES-256 and SHA-3.
44    HsV3,
45}
46
47/// Internal counterpart of RelayProtocol; includes variants that can't be
48/// negotiated from [`extend_virtual`](crate::tunnel::circuit::ClientCirc::extend_virtual).
49#[derive(Copy, Clone, Debug)]
50pub(crate) enum RelayCryptLayerProtocol {
51    /// The original Tor cell encryption protocol, using AES-128 and SHA-1.
52    ///
53    /// References:
54    /// - <https://spec.torproject.org/tor-spec/relay-cells.html>
55    /// - <https://spec.torproject.org/tor-spec/routing-relay-cells.html>
56    Tor1(RelayCellFormat),
57    /// A variation of Tor's original cell encryption protocol, using AES-256
58    /// and SHA3-256.
59    ///
60    /// Reference:
61    /// - <https://spec.torproject.org/rend-spec/encrypting-user-data.html>
62    /// - <https://spec.torproject.org/rend-spec/introduction-protocol.html#INTRO-HANDSHAKE-REQS>
63    #[cfg(feature = "hs-common")]
64    HsV3(RelayCellFormat),
65    /// The counter galois onion cell encryption protocol.
66    #[cfg(feature = "counter-galois-onion")]
67    Cgo,
68}
69
70#[cfg(feature = "hs-common")]
71impl From<RelayProtocol> for RelayCryptLayerProtocol {
72    fn from(value: RelayProtocol) -> Self {
73        match value {
74            // TODO #1948
75            RelayProtocol::HsV3 => RelayCryptLayerProtocol::HsV3(RelayCellFormat::V0),
76        }
77    }
78}
79
80/// What role we are playing in a handshake.
81#[derive(Copy, Clone, Debug, Eq, PartialEq)]
82#[non_exhaustive]
83pub enum HandshakeRole {
84    /// We are the party initiating the handshake.
85    Initiator,
86    /// We are the party responding to the handshake.
87    Responder,
88}
89
90/// A set of type-erased cryptographic layers to use for a single hop at a
91/// client.
92pub(crate) struct BoxedClientLayer {
93    /// The outbound cryptographic layer to use for this hop
94    pub(crate) fwd: Box<dyn OutboundClientLayer + Send>,
95    /// The inbound cryptogarphic layer to use for this hop
96    pub(crate) back: Box<dyn InboundClientLayer + Send>,
97    /// A circuit binding key for this hop.
98    pub(crate) binding: Option<CircuitBinding>,
99}
100
101impl RelayCryptLayerProtocol {
102    /// Construct the client cell-crypto layers that are needed for a given set of
103    /// circuit hop parameters.
104    ///
105    /// This returns layers for use in a client circuit,
106    /// whether as the initiator or responder of an onion service request.
107    pub(crate) fn construct_client_layers(
108        self,
109        role: HandshakeRole,
110        keygen: impl KeyGenerator,
111    ) -> Result<BoxedClientLayer> {
112        use RelayCellFormat::*;
113        use RelayCryptLayerProtocol::*;
114
115        match self {
116            Tor1(V0) => construct::<Tor1RelayCrypto, _, _, _, _>(keygen, role),
117            Tor1(_) => Err(internal!("protocol not implemented").into()),
118            #[cfg(feature = "hs-common")]
119            HsV3(V0) => construct::<Tor1Hsv3RelayCrypto, _, _, _, _>(keygen, role),
120            #[cfg(feature = "hs-common")]
121            HsV3(_) => Err(internal!("protocol not implemented").into()),
122            #[cfg(feature = "counter-galois-onion")]
123            Cgo => construct::<CgoRelayCrypto, _, _, _, _>(keygen, role),
124        }
125    }
126
127    /// Return the cell format used by this protocol.
128    pub(crate) fn relay_cell_format(&self) -> RelayCellFormat {
129        match self {
130            RelayCryptLayerProtocol::Tor1(v) => *v,
131            #[cfg(feature = "hs-common")]
132            RelayCryptLayerProtocol::HsV3(v) => *v,
133            #[cfg(feature = "counter-galois-onion")]
134            RelayCryptLayerProtocol::Cgo => RelayCellFormat::V1,
135        }
136    }
137}
138
139/// Wrapper to make a relay layer behave as a client layer.
140///
141/// We use this wrapper to implement onion services,
142/// which use relay layers to communicate with clients.
143struct ResponderOutboundLayer<L: InboundRelayLayer>(L);
144impl<L: InboundRelayLayer> OutboundClientLayer for ResponderOutboundLayer<L> {
145    fn originate_for(
146        &mut self,
147        cmd: tor_cell::chancell::ChanCmd,
148        cell: &mut crate::crypto::cell::RelayCellBody,
149    ) -> tor_cell::relaycell::msg::SendmeTag {
150        self.0.originate(cmd, cell)
151    }
152
153    fn encrypt_outbound(
154        &mut self,
155        cmd: tor_cell::chancell::ChanCmd,
156        cell: &mut crate::crypto::cell::RelayCellBody,
157    ) {
158        self.0.encrypt_inbound(cmd, cell);
159    }
160}
161/// Wrapper to make a relay layer behave as a client layer.
162///
163/// We use this wrapper to implement onion services,
164/// which use relay layers to communicate with clients.
165struct ResponderInboundLayer<L: OutboundRelayLayer>(L);
166impl<L: OutboundRelayLayer> InboundClientLayer for ResponderInboundLayer<L> {
167    fn decrypt_inbound(
168        &mut self,
169        cmd: tor_cell::chancell::ChanCmd,
170        cell: &mut crate::crypto::cell::RelayCellBody,
171    ) -> Option<tor_cell::relaycell::msg::SendmeTag> {
172        self.0.decrypt_outbound(cmd, cell)
173    }
174}
175
176/// Helper: Construct a BoxedClientLayer for a layer type L whose inbound and outbound
177/// cryptographic states are the same type.
178fn construct<L, FC, BC, FR, BR>(
179    keygen: impl KeyGenerator,
180    role: HandshakeRole,
181) -> Result<BoxedClientLayer>
182where
183    L: CryptInit + ClientLayer<FC, BC> + RelayLayer<FR, BR>,
184    FC: OutboundClientLayer + Send + 'static,
185    BC: InboundClientLayer + Send + 'static,
186    FR: OutboundRelayLayer + Send + 'static,
187    BR: InboundRelayLayer + Send + 'static,
188{
189    let layer = L::construct(keygen)?;
190    match role {
191        HandshakeRole::Initiator => {
192            let (fwd, back, binding) = layer.split_client_layer();
193            Ok(BoxedClientLayer {
194                fwd: Box::new(fwd),
195                back: Box::new(back),
196                binding: Some(binding),
197            })
198        }
199        HandshakeRole::Responder => {
200            let (fwd, back, binding) = layer.split_relay_layer();
201            Ok(BoxedClientLayer {
202                // We reverse the inbound and outbound layers before wrapping them,
203                // since from the responder's perspective, _they_ are the origin
204                // point of the circuit.
205                fwd: Box::new(ResponderOutboundLayer(back)),
206                back: Box::new(ResponderInboundLayer(fwd)),
207                binding: Some(binding),
208            })
209        }
210    }
211}