hsservice: Adapt data structures from onion-service-notes.md
Also, removed some older structures that don't make sense in the current design. Closes #970
This commit is contained in:
parent
5906c2c92e
commit
20b3679dbb
|
@ -4920,6 +4920,8 @@ dependencies = [
|
|||
"thiserror",
|
||||
"tor-circmgr",
|
||||
"tor-hscrypto",
|
||||
"tor-keymgr",
|
||||
"tor-linkspec",
|
||||
"tor-llcrypto",
|
||||
"tor-netdir",
|
||||
"tor-proto",
|
||||
|
|
|
@ -30,6 +30,8 @@ rand_core = "0.6.2"
|
|||
thiserror = "1"
|
||||
tor-circmgr = { version = "0.9.1", path = "../tor-circmgr", features = ["hs-service"] }
|
||||
tor-hscrypto = { version = "0.3.0", path = "../tor-hscrypto" }
|
||||
tor-keymgr = { version = "0.1.0", path = "../tor-keymgr" }
|
||||
tor-linkspec = { version = "0.8.1", path = "../tor-linkspec" }
|
||||
tor-llcrypto = { version = "0.5.2", path = "../tor-llcrypto" }
|
||||
tor-netdir = { version = "0.9.2", path = "../tor-netdir" }
|
||||
tor-proto = { version = "0.11.1", path = "../tor-proto" }
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
//! Configuration information for onion services.
|
||||
|
||||
/// Configuration for a single onion service.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OnionServiceConfig {
|
||||
/// An arbitrary identifier or "nickname" used to look up this service's
|
||||
/// keys, state, configuration, etc,
|
||||
/// and distinguish them from other services. This is local-only.
|
||||
//
|
||||
// TODO HSS: It's possible that instead of having this be _part_ of the
|
||||
// service's configuration, we want this to be the key for a map in
|
||||
// which the service's configuration is stored. We'll see how the code
|
||||
// evolves.
|
||||
nickname: String,
|
||||
|
||||
/// Whether we want this to be a non-anonymous "single onion service".
|
||||
/// We could skip this in v1. We should make sure that our state
|
||||
/// is built to make it hard to accidentally set this.
|
||||
anonymity: crate::Anonymity,
|
||||
|
||||
/// Number of intro points; defaults to 3; max 20.
|
||||
num_intro_points: Option<u8>,
|
||||
// TODO HSS: I'm not sure if client encryption belongs as a configuration
|
||||
// item, or as a directory like C tor does it. Or both?
|
||||
}
|
|
@ -1,46 +1,74 @@
|
|||
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
|
||||
#![doc = include_str!("../README.md")]
|
||||
// TODO hss: Add complete suite of warnings here.
|
||||
// @@ begin lint list maintained by maint/add_warning @@
|
||||
#![cfg_attr(not(ci_arti_stable), allow(renamed_and_removed_lints))]
|
||||
#![cfg_attr(not(ci_arti_nightly), allow(unknown_lints))]
|
||||
#![deny(missing_docs)]
|
||||
#![warn(noop_method_call)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![warn(clippy::all)]
|
||||
#![deny(clippy::await_holding_lock)]
|
||||
#![deny(clippy::cargo_common_metadata)]
|
||||
#![deny(clippy::cast_lossless)]
|
||||
#![deny(clippy::checked_conversions)]
|
||||
#![warn(clippy::cognitive_complexity)]
|
||||
#![deny(clippy::debug_assert_with_mut_call)]
|
||||
#![deny(clippy::exhaustive_enums)]
|
||||
#![deny(clippy::exhaustive_structs)]
|
||||
#![deny(clippy::expl_impl_clone_on_copy)]
|
||||
#![deny(clippy::fallible_impl_from)]
|
||||
#![deny(clippy::implicit_clone)]
|
||||
#![deny(clippy::large_stack_arrays)]
|
||||
#![warn(clippy::manual_ok_or)]
|
||||
#![deny(clippy::missing_docs_in_private_items)]
|
||||
#![warn(clippy::needless_borrow)]
|
||||
#![warn(clippy::needless_pass_by_value)]
|
||||
#![warn(clippy::option_option)]
|
||||
#![deny(clippy::print_stderr)]
|
||||
#![deny(clippy::print_stdout)]
|
||||
#![warn(clippy::rc_buffer)]
|
||||
#![deny(clippy::ref_option_ref)]
|
||||
#![warn(clippy::semicolon_if_nothing_returned)]
|
||||
#![warn(clippy::trait_duplication_in_bounds)]
|
||||
#![deny(clippy::unnecessary_wraps)]
|
||||
#![warn(clippy::unseparated_literal_suffix)]
|
||||
#![deny(clippy::unwrap_used)]
|
||||
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
|
||||
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
|
||||
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
|
||||
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
|
||||
|
||||
#![allow(dead_code, unused_variables)] // TODO hss remove.
|
||||
|
||||
mod config;
|
||||
mod err;
|
||||
mod keys;
|
||||
mod req;
|
||||
mod status;
|
||||
mod streamproxy;
|
||||
mod svc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
pub use config::OnionServiceConfig;
|
||||
pub use err::Error;
|
||||
pub use req::{OnionServiceDataStream, RendRequest, StreamRequest};
|
||||
pub use status::OnionServiceStatus;
|
||||
pub use svc::OnionService;
|
||||
|
||||
/// A Result type describing an onion service operation.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// An object that knows how to handle stream requests.
|
||||
#[async_trait]
|
||||
pub trait StreamHandler {
|
||||
/// Handle an incoming stream request on a given onion service.
|
||||
/// The level of anonymity that an onion service should try to run with.
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum Anonymity {
|
||||
/// Try to keep the location of the onion service private.
|
||||
#[default]
|
||||
Anonymous,
|
||||
/// Do not try to keep the location of the onion service private.
|
||||
///
|
||||
/// (This is implemented using our "single onion service" design.)
|
||||
//
|
||||
// TODO hss: the `circ_info` argument should have data about the circuit on
|
||||
// which the request arrived. If the client authenticated, it might tell us
|
||||
// who they are. Or it might have information about how many requests
|
||||
// (and/or failed requests) we've gotten on the circuit.
|
||||
//
|
||||
// TODO hss: The `circ_info` argument should at a minimum include the
|
||||
// circuit; ideally in a form that we can get a weak reference to it, and
|
||||
// use it in the key of a `PtrWeakKeyHashMap`. (Or we could stick the info
|
||||
// in the circuit itself somehow, and access it as a Box<dyn Any>, but
|
||||
// that's a bit sketchy type-wise.)
|
||||
//
|
||||
// TODO hss: the `stream` argument should be an IncomingStream from
|
||||
// tor-proto, but that branch is not yet merged as of this writing.
|
||||
async fn handle_request(&self, circ_info: &(), stream: ());
|
||||
}
|
||||
|
||||
mod mgr {
|
||||
// TODO hss: Do we want to have the notion of a collection of onion services,
|
||||
// running in tandem? Or is that a higher-level crate, possibly a part of
|
||||
// TorClient?
|
||||
// TODO HSS: We may want to put this behind a feature?
|
||||
DangerouslyNonAnonymous,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
//! Request objects used to implement onion services.
|
||||
//!
|
||||
//! These requests are yielded on a stream, and the calling code needs to decide
|
||||
//! whether to permit or reject them.
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use tor_linkspec::OwnedChanTarget;
|
||||
use tor_llcrypto::pk::curve25519;
|
||||
use tor_proto::stream::DataStream;
|
||||
|
||||
/// Request to complete an introduction/rendezvous handshake.
|
||||
///
|
||||
/// A request of this kind indicates that a client has asked permission to
|
||||
/// connect to an onion service through an introduction point. The caller needs
|
||||
/// to decide whether or not to complete the handshake.
|
||||
///
|
||||
/// Protocol details: More specifically, we create one of these whenever we get a well-formed
|
||||
/// `INTRODUCE2` message. Based on this, the caller decides whether to send a
|
||||
/// `RENDEZVOUS1` message.
|
||||
#[derive(Debug)]
|
||||
pub struct RendRequest {
|
||||
/// Which introduction point gave us this request?
|
||||
from_intro_point: crate::svc::IntroPointId,
|
||||
|
||||
/// What proof-of-work did the client send us?
|
||||
proof_of_work_provided: Option<ProofOfWork>,
|
||||
|
||||
/// Information about the rendezvous point that the client wanted us to
|
||||
/// connect to.
|
||||
rend_pt: RendPt,
|
||||
//
|
||||
// TODO HSS: We'll also need additional information to actually complete the
|
||||
// request, maybe including a Weak<OnionService>, or maybe including a
|
||||
// oneshot::Sender.
|
||||
}
|
||||
|
||||
/// Information needed to complete a rendezvous handshake.
|
||||
#[derive(Debug, Clone)]
|
||||
struct RendPt {
|
||||
/// The location and identity of the rendezvous point.
|
||||
location: OwnedChanTarget,
|
||||
/// The public Ntor key for the rendezvous point.
|
||||
ntor_key: curve25519::PublicKey,
|
||||
/// Cryptographic state to use when completing the handshake.
|
||||
///
|
||||
/// TODO HSS: This is not at all final, and should be refactored freely.
|
||||
handshake: HandshakeState,
|
||||
}
|
||||
|
||||
/// The cryptographic state needed to complete an introduce/rendezvous
|
||||
/// handshake.
|
||||
#[derive(Debug, Clone)]
|
||||
struct HandshakeState {
|
||||
// TODO HSS: replace this type or its contents as needed.
|
||||
}
|
||||
|
||||
/// Information about a proof of work received from a client's introduction
|
||||
/// point.
|
||||
///
|
||||
// Todo: use Beth's API instead.
|
||||
#[derive(Debug, Clone)]
|
||||
enum ProofOfWork {
|
||||
/// TODO HSS document or replace.
|
||||
EquixV1 {
|
||||
/// TODO HSS document or replace
|
||||
effort_level: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// A request from a client to open a new stream to an onion service.
|
||||
///
|
||||
/// We can only receive these _after_ we have already permitted the client to
|
||||
/// connect via a [`RendRequest`].
|
||||
///
|
||||
/// Protocol details: More specifically, we create one of these whenever we get a well-formed
|
||||
/// `BEGIN` message. Based on this, the caller decides whether to send a
|
||||
/// `CONNECTED` message.
|
||||
#[derive(Debug)]
|
||||
pub struct StreamRequest {
|
||||
/// The object that will be used to send data to and from the client.
|
||||
///
|
||||
/// TODO HSS: Possibly instead this will be some type from tor_proto that
|
||||
/// can turn into a DataStream.
|
||||
stream: DataStream,
|
||||
|
||||
/// The address that the client has asked to connect to.
|
||||
///
|
||||
/// TODO HSS: This is the wrong type! It may be a hostname.
|
||||
target: SocketAddr,
|
||||
}
|
||||
|
||||
/// A stream opened over an onion service.
|
||||
//
|
||||
// TODO HSS: This may belong in another module.
|
||||
#[derive(Debug)]
|
||||
pub struct OnionServiceDataStream {
|
||||
/// The underlying data stream; this type is just a thin wrapper.
|
||||
inner: DataStream,
|
||||
}
|
|
@ -1,4 +1,10 @@
|
|||
//! Support for reporting the status of an onion service.
|
||||
|
||||
/// The current reported status of an onion service.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OnionServiceStatus {
|
||||
// TODO hss Should say how many intro points are active, how many descriptors
|
||||
// are updated, whether we're "healthy", etc.
|
||||
/// An ignored field to suppress warnings. TODO HSS remove this.
|
||||
_ignore: (),
|
||||
}
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
//! Implement a StreamHandler that proxies connections to ports, typically on
|
||||
//! localhost.
|
||||
|
||||
use std::{collections::HashMap, net::SocketAddr};
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::StreamHandler;
|
||||
|
||||
pub(crate) struct StreamProxy {
|
||||
/// Map from virtual port on the onion service to an address we connect to
|
||||
/// in order to implement that port.
|
||||
ports: HashMap<u16, SocketAddr>,
|
||||
}
|
||||
|
||||
impl StreamProxy {
|
||||
// TODO hss need a new() function. It should reject non-localhost addresses
|
||||
// by default, and have a way to override. (Alternatively, that should be
|
||||
// done in the configuration code?)
|
||||
}
|
||||
|
||||
#[allow(clippy::diverging_sub_expression)] // for todo!() + async_trait.
|
||||
#[async_trait]
|
||||
impl StreamHandler for StreamProxy {
|
||||
async fn handle_request(&self, circinfo: &(), stream: ()) {
|
||||
todo!() // TODO hss: implement
|
||||
|
||||
// - Look up the port for the incoming stream request.
|
||||
// - If no port is found, reject the request, and possibly increment a
|
||||
// counter in circinfo.
|
||||
// - Otherwise, open a TCP connection to the target address.
|
||||
// - On success, accept the stream, and launch tasks to relay traffic
|
||||
// from the stream to the TCP connection.
|
||||
// - On failure, reject the stream with an error.
|
||||
}
|
||||
}
|
|
@ -1,6 +1,16 @@
|
|||
use std::sync::Arc;
|
||||
//! Principal types for onion services.
|
||||
|
||||
use tor_circmgr::CircMgr;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use tor_circmgr::hspool::HsCircPool;
|
||||
use tor_hscrypto::pk::HsBlindIdKey;
|
||||
use tor_keymgr::KeyMgr;
|
||||
use tor_linkspec::RelayIds;
|
||||
use tor_llcrypto::pk::curve25519;
|
||||
use tor_netdir::NetDirProvider;
|
||||
use tor_rtcompat::Runtime;
|
||||
|
||||
|
@ -8,42 +18,114 @@ use crate::{OnionServiceStatus, Result};
|
|||
|
||||
/// A handle to an instance of an onion service.
|
||||
//
|
||||
// TODO hss: We might want to wrap this in an Arc<Mutex<>>, and have an inner
|
||||
// structure that contains these elements. Or we might want to refactor this in
|
||||
// some other way.
|
||||
// TODO HSS: Write more.
|
||||
//
|
||||
// (APIs should return Arc<OnionService>)
|
||||
//
|
||||
// NOTE: This might not need to be parameterized on Runtime; if we can avoid it
|
||||
// without too much trouble, we should.
|
||||
pub struct OnionService<R: Runtime> {
|
||||
/// Needs some kind of configuration about: what is our identity (if we know
|
||||
/// it), is this anonymous, do we store persistent info and if so where and
|
||||
/// how, etc.
|
||||
/// The mutable implementation details of this onion service.
|
||||
inner: Mutex<SvcInner<R>>,
|
||||
}
|
||||
|
||||
/// Implementation details for an onion service.
|
||||
struct SvcInner<R: Runtime> {
|
||||
/// Configuration information about this service.
|
||||
///
|
||||
/// Authorized client public keys might be here, or they might be in a
|
||||
/// TODO HSS: Authorized client public keys might be here, or they might be in a
|
||||
/// separate structure.
|
||||
config: (),
|
||||
config: crate::OnionServiceConfig,
|
||||
|
||||
/// A netdir provider to use in finding our directories and choosing our
|
||||
/// introduction points.
|
||||
netdir_provider: Arc<dyn NetDirProvider>,
|
||||
/// A circuit manager to use in making circuits to our introduction points,
|
||||
|
||||
/// A keymgr used to look up our keys and store new medium-term keys.
|
||||
keymgr: Arc<KeyMgr>,
|
||||
|
||||
/// A circuit pool to use in making circuits to our introduction points,
|
||||
/// HsDirs, and rendezvous points.
|
||||
//
|
||||
// TODO hss: Maybe we can make a trait that only gives a minimal "build a
|
||||
// circuit" API from CircMgr, so that we can have this be a dyn reference
|
||||
// too?
|
||||
circmgr: Arc<CircMgr<R>>,
|
||||
/// Private keys in actual use for this onion service.
|
||||
///
|
||||
/// TODO hss: This will need heavy refactoring.
|
||||
///
|
||||
/// TODO hss: There's a separate blinded ID, certificate, and signing key
|
||||
/// for each active time period.
|
||||
keys: (),
|
||||
/// Status for each active introduction point for this onion service.
|
||||
intro_points: Vec<()>,
|
||||
/// Status for our onion service descriptor
|
||||
descriptor_status: (),
|
||||
circmgr: Arc<HsCircPool<R>>,
|
||||
|
||||
/// Object that handles incoming streams from the client.
|
||||
stream_handler: Arc<dyn crate::StreamHandler>,
|
||||
/// Authentication information for descriptor encryption.
|
||||
///
|
||||
/// (Our protocol defines two kinds of client authentication: in the first
|
||||
/// type, we encrypt the descriptor to client public keys. In the second,
|
||||
/// we require authentictaion as part of the `INTRODUCE2` message. Only the
|
||||
/// first type has ever been implemented.)
|
||||
encryption_auth: Option<DescEncryptionAuth>,
|
||||
|
||||
/// Private keys in actual use for this onion service.
|
||||
//
|
||||
// TODO hss: This will need heavy refactoring.
|
||||
//
|
||||
// TODO hss: There's a separate blinded ID, certificate, and signing key
|
||||
// for each active time period.
|
||||
keys: (),
|
||||
|
||||
/// Status for each active introduction point for this onion service.
|
||||
//
|
||||
// TODO HSS: This might want to be a generational arena, and might want to be
|
||||
// use a different map for each descriptor epoch. Feel free to refactor!
|
||||
intro_points: Vec<IntroPointState>,
|
||||
|
||||
/// Status for our onion service descriptor
|
||||
desc_status: DescUploadHistory,
|
||||
}
|
||||
|
||||
/// Information about encryption-based authentication.
|
||||
|
||||
struct DescEncryptionAuth {
|
||||
/// A list of the public keys for which we should encrypt our
|
||||
/// descriptor.
|
||||
//
|
||||
// TODO HSS: maybe this should instead be a place to find the keys, so that
|
||||
// we can reload them on change?
|
||||
//
|
||||
// TODO HSS: maybe this should instead be part of our configuration
|
||||
keys: Vec<curve25519::PublicKey>,
|
||||
}
|
||||
|
||||
/// Current history and status for our descriptor uploads.
|
||||
///
|
||||
// TODO HSS: Remember, there are *multiple simultaneous variants* of our
|
||||
// descriptor. we will probably need to make this structure different.
|
||||
struct DescUploadHistory {
|
||||
/// When did we last rebuild our descriptors?
|
||||
last_rebuilt: Instant,
|
||||
|
||||
/// Each current descriptor that we need to try to maintain and upload.
|
||||
descriptors: HashMap<HsBlindIdKey, String>,
|
||||
|
||||
/// Status of uploading each descriptor to each HsDir.
|
||||
//
|
||||
// Note that is possible that multiple descriptors will need to be uploaded
|
||||
// to the same HsDir. When this happens, we MUST use separate circuits to
|
||||
// uplaod them.
|
||||
target_status: HashMap<HsBlindIdKey, HashMap<RelayIds, RetryState>>,
|
||||
}
|
||||
|
||||
/// State of uploading a single descriptor
|
||||
struct RetryState {
|
||||
// TODO HSS: implement this as needed.
|
||||
}
|
||||
|
||||
/// State of a current introduction point.
|
||||
struct IntroPointState {
|
||||
// TODO HSS: use diziet's structures from `hssvc-ipt-algorithms.md` once those are more settled.
|
||||
}
|
||||
|
||||
/// Identifier for a single introduction point of an onion point.
|
||||
//
|
||||
// TODO HSS maybe use a nicer type, like a generational arena index.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct IntroPointId(RelayIds);
|
||||
|
||||
impl<R: Runtime> OnionService<R> {
|
||||
/// Create (but do not launch) a new onion service.
|
||||
pub fn new(config: (), netdir_provider: (), circmgr: ()) -> Self {
|
||||
|
|
Loading…
Reference in New Issue