Merge branch 'onion-proto-apis' into 'main'
Draft APIs for onion services in tor-proto See merge request tpo/core/arti!970
This commit is contained in:
commit
1834579460
|
@ -14,9 +14,13 @@ repository = "https://gitlab.torproject.org/tpo/core/arti.git/"
|
|||
[features]
|
||||
default = []
|
||||
full = ["tokio"]
|
||||
experimental = ["hs", "ntor_v3"]
|
||||
hs = []
|
||||
|
||||
experimental = ["experimental-api", "onion-client", "onion-service", "ntor_v3"]
|
||||
ntor_v3 = []
|
||||
onion-client = ["onion-common"]
|
||||
onion-service = ["onion-common"]
|
||||
onion-common = []
|
||||
experimental-api = []
|
||||
# Enable testing-only APIs. APIs under this feature are not
|
||||
# covered by semver.
|
||||
testing = []
|
||||
|
|
|
@ -41,6 +41,8 @@
|
|||
pub(crate) mod celltypes;
|
||||
pub(crate) mod halfcirc;
|
||||
mod halfstream;
|
||||
#[cfg(feature = "onion-common")]
|
||||
pub mod handshake;
|
||||
mod path;
|
||||
pub(crate) mod reactor;
|
||||
pub(crate) mod sendme;
|
||||
|
@ -228,6 +230,92 @@ impl ClientCirc {
|
|||
&self.channel
|
||||
}
|
||||
|
||||
/// Send a control message to the final hop on this circuit.
|
||||
///
|
||||
/// Note that it is quite possible to use this function to violate the tor
|
||||
/// protocol; most users of this API will not need to call it. It is used
|
||||
/// to implement most of the onion service handshake.
|
||||
///
|
||||
/// (This function is not yet implemented. Right now it will always panic.)
|
||||
//
|
||||
// TODO hs: rename this. "control_messages" is kind of ambiguous; we use
|
||||
// "control" for a lot of other things. We say "meta" elsewhere in the
|
||||
// reactor code, but "meta messages" just sounds odd.
|
||||
#[allow(clippy::missing_panics_doc, unused_variables)] // TODO hs remove
|
||||
#[cfg(feature = "experimental-api")]
|
||||
pub async fn send_control_message(&self, msg: RelayMsg) -> Result<()> {
|
||||
todo!() // TODO hs
|
||||
}
|
||||
|
||||
/// Begin accepting 'control' messages from the final hop on this circuit,
|
||||
/// and return an asynchronous stream of any such messages that arrive.
|
||||
///
|
||||
/// A "control" message is a message without a stream ID that `tor-proto`
|
||||
/// does not handle on its own. (The messages that `tor-proto` can handle
|
||||
/// are DESTROY, DATA, SENDME, ...) Ordinarily, any unexpected control
|
||||
/// message will cause the circuit to exit with an error.
|
||||
///
|
||||
/// There can only be one stream of this type created on a given circuit at
|
||||
/// a time. If a such a stream already exists, this method will return an
|
||||
/// error.
|
||||
///
|
||||
/// The caller should be sure to close the circuit if a command that _it_
|
||||
/// doesn't recognize shows up.
|
||||
///
|
||||
/// (This function is not yet implemented; right now, it will always panic.)
|
||||
//
|
||||
// TODO hs: Possibly this function (and send_control_message) should use
|
||||
// HopNum or similar to indicate which hop we're talking to, rather than
|
||||
// just doing "the last hop".
|
||||
//
|
||||
// TODO hs: There is possibly some kind of type trickery we could do here so
|
||||
// that the stream would return a chosen type that implements
|
||||
// `TryFrom<RelayMsg>` or something like that. Not sure whether that's a
|
||||
// good idea.
|
||||
//
|
||||
// TODO hs: Perhaps the stream here should yield a different type. Ian
|
||||
// thinks maybe we should store a callback instead.
|
||||
//
|
||||
// TODO hs: rename this. "control_messages" is kind of ambiguous; we use
|
||||
// "control" for a lot of other things. We say "meta" elsewhere in the
|
||||
// reactor code, but "meta messages" just sounds odd.
|
||||
#[cfg(feature = "experimental-api")]
|
||||
#[allow(clippy::missing_panics_doc, unused_variables)] // TODO hs remove
|
||||
pub fn receive_control_messages(
|
||||
&self,
|
||||
) -> Result<impl futures::Stream<Item = Box<chancell::RawCellBody>>> {
|
||||
if false {
|
||||
return Ok(futures::stream::empty()); // TODO hs remove; this is just here for type inference.
|
||||
}
|
||||
todo!() // TODO hs implement.
|
||||
}
|
||||
|
||||
/// Tell this circuit to begin allowing the final hop of the circuit to try
|
||||
/// to create new Tor streams, and to return those pending requests in an
|
||||
/// asynchronous stream.
|
||||
///
|
||||
/// Ordinarily, these requests are rejected.
|
||||
///
|
||||
/// There can only be one stream of this type created on a given circuit at
|
||||
/// a time. If a such a stream already exists, this method will return an
|
||||
/// error.
|
||||
///
|
||||
/// (This function is not yet implemented; right now, it will always panic.)
|
||||
///
|
||||
/// Only onion services (and eventually) exit relays should call this
|
||||
/// method.
|
||||
#[cfg(feature = "onion-service")]
|
||||
#[allow(clippy::missing_panics_doc, unused_variables)] // TODO hs remove
|
||||
pub fn allow_stream_requests(
|
||||
&self,
|
||||
allow_commands: &[tor_cell::relaycell::RelayCmd],
|
||||
) -> Result<impl futures::Stream<Item = crate::stream::IncomingStream>> {
|
||||
if false {
|
||||
return Ok(futures::stream::empty()); // TODO hs remove; this is just here for type inference.
|
||||
}
|
||||
todo!() // TODO hs implement.
|
||||
}
|
||||
|
||||
/// Extend the circuit via the ntor handshake to a new target last
|
||||
/// hop.
|
||||
pub async fn extend_ntor<Tg>(&self, target: &Tg, params: &CircParameters) -> Result<()>
|
||||
|
@ -266,6 +354,36 @@ impl ClientCirc {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Extend this circuit by a single, "virtual" hop.
|
||||
///
|
||||
/// This is used to implement onion services: the client and the service
|
||||
/// both build a circuit to a single rendezvous point, and tell the
|
||||
/// rendezvous point to relay traffic between their two circuits. Having
|
||||
/// completed a [`handshake`] out of band[^1], the parties each extend their
|
||||
/// circuits by a single "virtual" encryption hop that represents their
|
||||
/// shared cryptographic context.
|
||||
///
|
||||
/// Once a circuit has been extended in this way, it is an error to try to
|
||||
/// extend it in any other way.
|
||||
///
|
||||
/// [^1]: Technically, the handshake is only _mostly_ out of band: the
|
||||
/// client sends their half of the handshake in an ` message, and the
|
||||
/// service's response is inline in its `RENDEZVOUS2` message.
|
||||
//
|
||||
// TODO hs: let's try to enforce the "you can't extend a circuit again once
|
||||
// it has been extended this way" property. We could do that with internal
|
||||
// state, or some kind of a type state pattern.
|
||||
#[cfg(feature = "onion-common")]
|
||||
#[allow(clippy::missing_panics_doc, unused_variables)]
|
||||
pub async fn extend_virtual(
|
||||
&self,
|
||||
protocol: handshake::RelayProtocol,
|
||||
role: handshake::HandshakeRole,
|
||||
seed: impl handshake::KeyGenerator,
|
||||
) -> Result<()> {
|
||||
todo!() // TODO hs implement
|
||||
}
|
||||
|
||||
/// Helper, used to begin a stream.
|
||||
///
|
||||
/// This function allocates a stream ID, and sends the message
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
//! Features for manual invocation of Tor's cryptographic circuit handshakes.
|
||||
//!
|
||||
//! These features are used to implement onion services, by giving the onion
|
||||
//! service code more direct control over the lower-level pieces of the protocol.
|
||||
|
||||
// Here we re-export some key types from our cryptographic code, for use when we
|
||||
// implement our onion handshake.
|
||||
//
|
||||
// TODO hs: it might be neat, someday, to clean this all up so that the types
|
||||
// and functions in hs_ntor are all methods on a set of related traits. But
|
||||
// that can wait IMO until we have a second circuit creation mechanism for use
|
||||
// with ntor.
|
||||
|
||||
pub use crate::crypto::handshake::hs_ntor;
|
||||
pub use crate::crypto::handshake::KeyGenerator;
|
||||
|
||||
/// The relay protocol to use when extending a circuit manually with
|
||||
/// [`Circuit::extend_virtual`](crate::circuit::ClientCirc::extend_virtual).
|
||||
//
|
||||
// NOTE: These correspond internally to implementations of
|
||||
// crate::crypto::cell::ClientLayer.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum RelayProtocol {
|
||||
/// A variation of Tor's original protocol, using AES-256 and SHA-3.
|
||||
HsV3,
|
||||
}
|
||||
|
||||
/// What role we are playing in a handshake.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum HandshakeRole {
|
||||
/// We are the party initiating the handshake.
|
||||
Initiator,
|
||||
/// We are the party responding to the handshake.
|
||||
Responder,
|
||||
}
|
|
@ -11,8 +11,8 @@
|
|||
//! Currently, this module implements only the "ntor" handshake used
|
||||
//! for circuits on today's Tor.
|
||||
pub(crate) mod fast;
|
||||
#[cfg(feature = "hs")]
|
||||
pub(crate) mod hs_ntor;
|
||||
#[cfg(feature = "onion-common")]
|
||||
pub mod hs_ntor;
|
||||
pub(crate) mod ntor;
|
||||
#[cfg(feature = "ntor_v3")]
|
||||
pub(crate) mod ntor_v3;
|
||||
|
@ -72,7 +72,8 @@ pub(crate) trait ServerHandshake {
|
|||
/// Typically, it wraps a KDF function, and some seed key material.
|
||||
///
|
||||
/// It can only be used once.
|
||||
pub(crate) trait KeyGenerator {
|
||||
#[allow(unreachable_pub)] // This is only exported depending on enabled features.
|
||||
pub trait KeyGenerator {
|
||||
/// Consume the key
|
||||
fn expand(self, keylen: usize) -> Result<SecretBuf>;
|
||||
}
|
||||
|
|
|
@ -15,14 +15,14 @@
|
|||
//! This module is a work in progress, and is not actually used anywhere yet
|
||||
//! or tested: please expect the API to change.
|
||||
//!
|
||||
//! This module is available only when the `hs` feature is enabled.
|
||||
//! This module is available only when the `onion-common` feature is enabled.
|
||||
//
|
||||
// TODO hs: go through this code carefully and make sure that its APIs and
|
||||
// behavior are still what we want.
|
||||
|
||||
// We want to use the exact variable names from the rend-spec-v3.txt proposal.
|
||||
// This means that we allow variables to be named x (privkey) and X (pubkey).
|
||||
#![allow(non_snake_case)]
|
||||
// This module is still unused: so allow some dead code for now.
|
||||
#![allow(dead_code)]
|
||||
#![allow(unreachable_pub)]
|
||||
|
||||
use crate::crypto::handshake::KeyGenerator;
|
||||
use crate::crypto::ll::kdf::{Kdf, ShakeKdf};
|
||||
|
@ -52,7 +52,7 @@ type MacTag = [u8; 32];
|
|||
/// The AUTH_INPUT_MAC from the HS Ntor protocol
|
||||
type AuthInputMac = MacTag;
|
||||
/// The Service's subcredential
|
||||
pub type Subcredential = [u8; 32];
|
||||
pub type Subcredential = [u8; 32]; // TODO hs: use tor-hscrypto version instead.
|
||||
|
||||
/// The key generator used by the HS ntor handshake. Implements the simple key
|
||||
/// expansion protocol specified in section "Key expansion" of rend-spec-v3.txt .
|
||||
|
@ -82,24 +82,24 @@ impl KeyGenerator for HsNtorHkdfKeyGenerator {
|
|||
pub struct HsNtorClientInput {
|
||||
/// Introduction point encryption key (aka B)
|
||||
/// (found in the HS descriptor)
|
||||
pub B: curve25519::PublicKey,
|
||||
B: curve25519::PublicKey,
|
||||
|
||||
/// Introduction point authentication key (aka AUTH_KEY)
|
||||
/// (found in the HS descriptor)
|
||||
pub auth_key: ed25519::PublicKey,
|
||||
auth_key: ed25519::PublicKey,
|
||||
|
||||
/// Service subcredential
|
||||
pub subcredential: Subcredential,
|
||||
subcredential: Subcredential,
|
||||
|
||||
/// The plaintext that should be encrypted into ENCRYPTED_DATA It's
|
||||
/// structure is irrelevant for this crate, but can be found in section
|
||||
/// \[PROCESS_INTRO2\] of the spec
|
||||
pub plaintext: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
|
||||
/// The data of the INTRODUCE1 cell from the beginning and up to the start
|
||||
/// of the ENCRYPTED_DATA. It's used to compute the MAC at the end of the
|
||||
/// INTRODUCE1 cell.
|
||||
pub intro_cell_data: Vec<u8>,
|
||||
intro_cell_data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl HsNtorClientInput {
|
||||
|
@ -259,20 +259,20 @@ where
|
|||
/// The input required to enter the HS Ntor protocol as a service
|
||||
pub struct HsNtorServiceInput {
|
||||
/// Introduction point encryption privkey
|
||||
pub b: curve25519::StaticSecret,
|
||||
b: curve25519::StaticSecret,
|
||||
/// Introduction point encryption pubkey
|
||||
pub B: curve25519::PublicKey,
|
||||
B: curve25519::PublicKey,
|
||||
|
||||
/// Introduction point authentication key (aka AUTH_KEY)
|
||||
pub auth_key: ed25519::PublicKey,
|
||||
auth_key: ed25519::PublicKey,
|
||||
|
||||
/// Our subcredential
|
||||
pub subcredential: Subcredential,
|
||||
subcredential: Subcredential,
|
||||
|
||||
/// The data of the INTRODUCE1 cell from the beginning and up to the start
|
||||
/// of the ENCRYPTED_DATA. Will be used to verify the MAC at the end of the
|
||||
/// INTRODUCE1 cell.
|
||||
pub intro_cell_data: Vec<u8>,
|
||||
intro_cell_data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl HsNtorServiceInput {
|
||||
|
|
|
@ -10,11 +10,16 @@
|
|||
//! There is no fairness, rate-limiting, or flow control.
|
||||
|
||||
mod data;
|
||||
#[cfg(feature = "onion-service")]
|
||||
mod incoming;
|
||||
mod params;
|
||||
mod raw;
|
||||
mod resolve;
|
||||
|
||||
pub use data::{DataReader, DataStream, DataWriter};
|
||||
#[cfg(feature = "onion-service")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "onion-service")))]
|
||||
pub use incoming::{IncomingStream, IncomingStreamRequest};
|
||||
pub use params::StreamParameters;
|
||||
pub use raw::StreamReader;
|
||||
pub use resolve::ResolveStream;
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
//! Functionality for incoming streams, opened from the other side of a circuit.
|
||||
|
||||
#![allow(
|
||||
dead_code,
|
||||
unused_variables,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::needless_pass_by_value
|
||||
)] // TODO hs remove
|
||||
|
||||
use super::DataStream;
|
||||
|
||||
/// A pending request from the other end of the circuit for us to open a new
|
||||
/// stream.
|
||||
///
|
||||
/// Exits, directory caches, and onion services expect to receive these; others
|
||||
/// do not.
|
||||
///
|
||||
/// On receiving one of these objects, the party handling it should accept it or
|
||||
/// reject it. If it is dropped without being explicitly handled, a reject
|
||||
/// message will be sent anyway.
|
||||
#[derive(Debug)]
|
||||
pub struct IncomingStream {
|
||||
/// The message that the client sent us to begin the stream.
|
||||
request: IncomingStreamRequest,
|
||||
/// The information that we'll use to wire up the stream, if it is accepted.
|
||||
stream: crate::circuit::StreamTarget,
|
||||
}
|
||||
|
||||
/// A message that can be sent to begin a stream.
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum IncomingStreamRequest {
|
||||
/// A begin cell, which requests a new data stream.
|
||||
Begin(tor_cell::relaycell::msg::Begin),
|
||||
// TODO: Eventually, add a BeginDir variant
|
||||
// TODO: eventually, add a Resolve variant.
|
||||
}
|
||||
|
||||
impl IncomingStream {
|
||||
/// Return the underlying message that was used to try to begin this stream.
|
||||
pub fn request(&self) -> IncomingStreamRequest {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Accept this stream as a new [`DataStream`], and send the client a
|
||||
/// message letting them know the stream was accepted.
|
||||
pub fn accept_data(self, message: tor_cell::relaycell::msg::Connected) -> DataStream {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Reject this request and send an error message to the client.
|
||||
pub fn reject(self, message: tor_cell::relaycell::msg::End) {
|
||||
todo!() // TODO hs
|
||||
}
|
||||
|
||||
/// Ignore this request without replying to the client.
|
||||
///
|
||||
/// (If you drop an [`IncomingStream`] without calling `accept_data`,
|
||||
/// `reject`, or this method, the drop handler will cause it to be
|
||||
/// rejected.)
|
||||
pub fn discard(self) {
|
||||
todo!() // TODO hs
|
||||
}
|
||||
}
|
||||
|
||||
// TODO hs: dropping an IncomingStream without accepting or rejecting it should
|
||||
// cause it to call `reject`.
|
Loading…
Reference in New Issue