Skip to main content

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