Initial notes about onion service structs and APIs
This commit is contained in:
parent
43481d1797
commit
9330c70ebe
|
@ -0,0 +1,263 @@
|
|||
# A few notes about onion services
|
||||
|
||||
## A higher level vs a lower level.
|
||||
|
||||
As I'm working through all of the design elements below, I'm realizing
|
||||
that we have the possibility for both a high-level API and a low-level
|
||||
API. The low-level API wouldn't need to handle persistence or making
|
||||
outbound connections; it would just provide `DataStreams` and let the
|
||||
caller load and save things as needed. The higher level API would
|
||||
behave more like C tor, providing a 'reverse proxy' and opening
|
||||
stream connections to local applications.
|
||||
|
||||
|
||||
## What we need to save on disk
|
||||
|
||||
Right now C tor stores this information:
|
||||
|
||||
`DIR/hostname`: The hostname of the onion service, with trailing
|
||||
.onion. Tor writes this but does not read it.
|
||||
|
||||
`DIR/private_key`: The top level identity key (`KS_hs_id`) for the
|
||||
onion service.
|
||||
|
||||
`DIR/client_keys`: A list of authorized clients; optional. Tor reads
|
||||
this but does not write it.
|
||||
|
||||
`DIR/onion_service_non_anonymous`: present if this is a non-anonymous
|
||||
service.
|
||||
|
||||
It's absolutely necessary to support making `KS_hs_id` persistent, or
|
||||
we can't create the same onion service over time. The other stuff can
|
||||
be read or written in other ways, though maintaining the current interface
|
||||
_would_ allow compatibility with C installations.
|
||||
|
||||
|
||||
## Notes about configuration
|
||||
|
||||
Right now C tor has these options. They're all prefixed with
|
||||
`HiddenService` but let's just ignore that.
|
||||
|
||||
### High-level options.
|
||||
|
||||
Here we talk about the higher-level APIs. These are all APIs that we
|
||||
would or could implemen _on top of_ the lower-level API I'm going to
|
||||
be discussing below.
|
||||
|
||||
These are **must-have** **high-level** options. (They are must-have
|
||||
_in some form_, and the form can be quite different.)
|
||||
|
||||
`Dir`: Location on disk to store info for this onion service.
|
||||
|
||||
`Port`: mapping from virtual port received in begin cell
|
||||
to local port where we should send streams. AF_UNIX addresses are supported
|
||||
|
||||
|
||||
Client authorization file: Stored on disk. Is a directory full of files
|
||||
with the contents: `<auth-type>:<key-type>:<base32-encoded-public-key>`.
|
||||
|
||||
|
||||
These are **nice-to-have** **high-level** options:
|
||||
|
||||
`ExportCircuitId`: Special protocol to use in exposing a global
|
||||
circuit ID for whatever circuit originated each stream. Right now
|
||||
`haproxy` is supported.
|
||||
|
||||
`DirGroupReadable`: Allow the directory to be group-readable.
|
||||
|
||||
`OnionBalanceInstance`: Exposes extra data used by `OnionBalance`.
|
||||
(TODO: find out what this does?)
|
||||
|
||||
|
||||
|
||||
I suggest that we do not provide these **high-level** options:
|
||||
|
||||
`AllowUnknownPorts`: Do not close the circuit when we get a request
|
||||
for a port we don't recognize. (This is 0 by default to avoid port-scanning)
|
||||
|
||||
|
||||
### Low-level options
|
||||
|
||||
Here are the lower-level options. We'd need to port these to work with
|
||||
our lower-level APIs.
|
||||
|
||||
These are **should-have** **low-level** options:
|
||||
|
||||
`NumIntroductionPoints`: How many introduction points to
|
||||
try to have. (This turns out to be important for tuning, IIUC)
|
||||
|
||||
These are **nice-to-have** **low-level** options:
|
||||
|
||||
`SingleHopMode`, `NonAnonymousMode`: Makes services non-anonymous. Global.
|
||||
|
||||
|
||||
I **do now know** how necessary this option is:
|
||||
|
||||
`PublishHidServDescriptor`: Do not publish any
|
||||
descriptors. Global. Only useful if you're having something else
|
||||
publish for you.
|
||||
|
||||
I **do not know** whether this is high-level or low-level, or how
|
||||
necessary it is:
|
||||
|
||||
`MaxStreams`: Limit simultaneous connections on a rendezvous circuit.
|
||||
|
||||
`MaxStreamsCloseCircuit`: Whether to close the circuit if the number of
|
||||
streams tries to exceed the limit.
|
||||
|
||||
I suggest that we do not build this** low-level** option:
|
||||
|
||||
`Version`: Always 3.
|
||||
|
||||
|
||||
## Projected data structures and APIs
|
||||
|
||||
```
|
||||
enum Anonymity {
|
||||
Anonymous,
|
||||
DangerouslyNonAnonymous,
|
||||
}
|
||||
|
||||
struct ServiceConfig {
|
||||
/// An arbitrary identifier used to look up this service's keys
|
||||
/// and distinguish them from other keys. Perhaps we don't need this?
|
||||
///
|
||||
/// Alternatively, maybe each service gets its own keymgr instance
|
||||
/// (or a some kind of a keymgr view), and uses that to look up keys
|
||||
/// with a global name?
|
||||
keyid: 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: Anonymity,
|
||||
|
||||
/// Number of intro points; defaults to 3; max 20.
|
||||
num_intro_points: Option<u8>,
|
||||
|
||||
/// Not sure if client encryption belongs as a configuration item, or
|
||||
/// as a directory like C tor does?
|
||||
}
|
||||
|
||||
pub struct Service {
|
||||
inner: Mutex<Inner>
|
||||
}
|
||||
|
||||
struct ServiceInner {
|
||||
/// Configuration of this service
|
||||
config: ServiceConfig,
|
||||
|
||||
/// Used to look up our keys
|
||||
keymgr: Arc<KeyMgr>,
|
||||
|
||||
/// Used to decide whom to encrypt descriptor to.
|
||||
desc_encryption_auth: Option<DescEncryptionAuth>,
|
||||
|
||||
current_keys: {
|
||||
// Possibly we cache keys here that we've loaded?
|
||||
// Possibly we store keys here that don't go in the keymgr?
|
||||
// we probably want to keep certs around.
|
||||
},
|
||||
|
||||
|
||||
/// Possibly a generational arena , so we can make IntroPointId
|
||||
/// into a generational index?
|
||||
intro_point_state: Vec<IntroPointState>,
|
||||
|
||||
desc_upload_history: DescUploadHistory,
|
||||
}
|
||||
|
||||
|
||||
struct DescUploadHistory {
|
||||
/// We can have multiple simultaneous variants of our
|
||||
/// descriptor of there are different time periods active now,
|
||||
/// I think?
|
||||
///
|
||||
/// Possibly we should just have multiple instances of DescUploadHistory?
|
||||
///
|
||||
/// Possibly we should (eventually, not v1) try to decorrelate uploads
|
||||
/// where the BlindIdKey is different
|
||||
descriptors_last_rebuilt: Instant,
|
||||
|
||||
descriptors: HashMap<HsBlindIdKey, (String, HsDe)>,
|
||||
|
||||
/// Status with uploading the latest version of each descriptor to
|
||||
/// each relevant hsdir.
|
||||
upload_targets: HashMap<RelayIds, (HsBlindIdKey, RetryState)>,
|
||||
}
|
||||
|
||||
struct DescEncryptionAuth {
|
||||
Vec<x25519::PublicKey>,
|
||||
}
|
||||
|
||||
|
||||
struct IntroPointState {
|
||||
/// TODO diziet is writing this, I think.
|
||||
}
|
||||
|
||||
|
||||
impl Service {
|
||||
/// At some point you can launch a service and get a stream
|
||||
/// of all the requests to rendezvous.
|
||||
pub fn launch(...) -> Result<mpsc::Receiver<RendRequest>>;
|
||||
}
|
||||
|
||||
pub enum IntroAuth {
|
||||
/// Deliberately empty; nothing is implemented here.
|
||||
}
|
||||
|
||||
// TODO: Use Beth's api insted.
|
||||
enum ProofOfWork {
|
||||
EquixV1 { effort_level: usize }
|
||||
}
|
||||
|
||||
/// We create one of these whenever we get a well-formed INTRODUCE2
|
||||
/// message, based on this, we the caller decides whether to send a
|
||||
/// RENDEZVOUS2 message.
|
||||
pub struct RendRequest {
|
||||
from_intro_point: IntroPointId,
|
||||
client_auth_provided: Option<ClientAuth>,
|
||||
proof_of_work_provided: Option<ProofOfWork>,
|
||||
rend_circuit: Arc<ClientCirc>,
|
||||
|
||||
info_needed_to_send_rendezvous2 : (
|
||||
OwnedChanTarget, // The location of the rendezvous point.
|
||||
x25519::PublicKey, // The ntor key for the rendezvous point.
|
||||
HandshakeState, // Not a real type; used to finish the handshake.
|
||||
),
|
||||
}
|
||||
|
||||
// TODO: Not sure that the functions here and below need to be
|
||||
// async, or if they should send messages on one-shots.
|
||||
|
||||
impl RendRequest {
|
||||
pub async fn accept(self) -> Result<mpsc::Receiver<StreamRequest>>;
|
||||
pub async fn reject(self) -> Result<()>;
|
||||
// also various accessors
|
||||
}
|
||||
|
||||
pub struct StreamRequest {
|
||||
stream: DataStream, // Or possibly some other type that can turn
|
||||
// into a DataStream.
|
||||
target: SocketAddr,
|
||||
|
||||
// doesn't need to include a ClientCirc, since the DataStream
|
||||
// can give you its circuit.
|
||||
}
|
||||
|
||||
pub struct ServiceDataStream {
|
||||
inner: DataStream,
|
||||
}
|
||||
|
||||
impl StreamRequest
|
||||
pub async fn accept(self) -> Result<ServiceDataStream>;
|
||||
pub async fn reject(self) -> Result<()>;
|
||||
pub fn shutdown_circuit(self) -> Result<()>;
|
||||
// various accessors, including for circuit.
|
||||
}
|
||||
```
|
||||
|
||||
# Tickets to open
|
||||
|
||||
- API to inquire about number of current open streams on a circuit.
|
||||
|
Loading…
Reference in New Issue