1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
//! Configuration information for onion services.
//
// TODO HSS: We may want rename some of the types and members here!

use base64ct::{Base64Unpadded, Encoding as _};
use derive_builder::Builder;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use tor_config::ConfigBuildError;
use tor_hscrypto::pk::HsClientDescEncKey;
use tor_llcrypto::pk::curve25519;

use crate::HsNickname;

/// Configuration for one onion service.
#[derive(Debug, Clone, Builder)]
#[builder(build_fn(error = "ConfigBuildError", validate = "Self::validate"))]
#[builder(derive(Serialize, Deserialize))]
pub struct OnionServiceConfig {
    /// The nickname used to look up this service's keys, state, configuration, etc,
    //
    // 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.
    // (^ ipt_mgr::IptManager contains a copy of this nickname, that should be fixed too)
    pub(crate) name: HsNickname,

    // TODO HSS: Perhaps this belongs at a higher level.
    // enabled: bool,
    /// 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.
    pub(crate) anonymity: crate::Anonymity,

    /// Number of intro points; defaults to 3; max 20.
    #[builder(default = "3")]
    pub(crate) num_intro_points: u8,

    /// A rate-limit on the acceptable rate of introduction requests.
    ///
    /// We send this to the send to the introduction point to configure how many
    /// introduction requests it sends us.
    rate_limit_at_intro: Option<TokenBucketConfig>,

    /// How many streams will we allow to be open at once for a single circuit on
    /// this service?
    #[builder(default = "65535")]
    max_concurrent_streams_per_circuit: u32,

    /// If true, we will require proof-of-work when we're under heavy load.
    enable_pow: bool,
    /// Disable the compiled backend for proof-of-work.
    disable_pow_compilation: bool,
    // TODO HSS: C tor has this, but I don't know if we want it.
    //
    // TODO HSS: It's possible that we want this to relate, somehow, to our
    // rate_limit_at_intro settings.
    //
    // /// A rate-limit on dispatching requests from the request queue when
    // /// our proof-of-work defense is enabled.
    // pow_queue_rate: TokenBucketConfig,
    // ...

    // /// Configure descriptor-based client authorization.
    // ///
    // /// When this is enabled, we encrypt our list of introduction point and keys
    // /// so that only clients holding one of the listed keys can decrypt it.
    //
    // TODO HSS: we'd like this to be an Option, but that doesn't work well with
    // sub_builder.  We need to figure out what to do there.
    //
    // TODO HSS: Temporarily disabled while we figure out how we want it to work;
    // see #1028
    //
    // pub(crate) encrypt_descriptor: Option<DescEncryptionConfig>,
    //
    // TODO HSS: Do we want a "descriptor_lifetime" setting? C tor doesn't have
    // one.
}

impl OnionServiceConfigBuilder {
    /// Builder helper: check wither the options in this builder are consistent.
    fn validate(&self) -> Result<(), ConfigBuildError> {
        /// Largest supported number of introduction points
        //
        // TODO HSS Is this a consensus parameter or anything?  What does C tor do?
        const MAX_INTRO_POINTS: u8 = 20;
        if let Some(ipts) = self.num_intro_points {
            if !(1..=MAX_INTRO_POINTS).contains(&ipts) {
                return Err(ConfigBuildError::Invalid {
                    field: "num_intro_points".into(),
                    problem: "Out of range 1..20".into(),
                });
            }
        }
        Ok(())
    }
}

/// Configure a token-bucket style limit on some process.
//
// TODO HSS: possibly lower this; it will be used in far more places.
//
// TODO: Do we want to parameterize this, or make it always u32?  Do we want to
// specify "per second"?
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenBucketConfig {
    /// The maximum number of items to process per second.
    rate: u32,
    /// The maximum number of items to process in a single burst.
    burst: u32,
}

impl TokenBucketConfig {
    /// Create a new token-bucket configuration to rate-limit some action.
    ///
    /// The "bucket" will have a maximum capacity of `burst`, and will fill at a
    /// rate of `rate` per second.  New actions are permitted if the bucket is nonempty;
    /// each action removes one token from the bucket.
    pub fn new(rate: u32, burst: u32) -> Self {
        Self { rate, burst }
    }
}

/// Configuration for descriptor encryption.
#[derive(Debug, Clone, Builder, PartialEq)]
#[builder(derive(Serialize, Deserialize))]
pub struct DescEncryptionConfig {
    /// A list of our authorized clients.
    ///
    /// Note that if this list is empty, no clients can connect.  
    //
    // TODO HSS: It might be good to replace this with a trait or something, so that
    // we can let callers give us a ClientKeyProvider or some plug-in that reads
    // keys from somewhere else. On the other hand, we might have this configure
    // our default ClientKeyProvider, and only allow programmatic ClientKeyProviders
    authorized_client: Vec<AuthorizedClientConfig>,
}

/// A single client (or a collection of clients) authorized using the descriptor encryption mechanism.
#[derive(Debug, Clone, PartialEq, serde_with::DeserializeFromStr, serde_with::SerializeDisplay)]
#[non_exhaustive]
pub enum AuthorizedClientConfig {
    /// A directory full of authorized public keys.
    DirectoryOfKeys(PathBuf),
    /// A single authorized public key.
    Curve25519Key(HsClientDescEncKey),
}

impl std::fmt::Display for AuthorizedClientConfig {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::DirectoryOfKeys(pb) => write!(f, "dir:{}", pb.display()),
            Self::Curve25519Key(key) => write!(
                f,
                "curve25519:{}",
                Base64Unpadded::encode_string(key.as_bytes())
            ),
        }
    }
}

/// A problem encountered while parsing an AuthorizedClientConfig.
#[derive(thiserror::Error, Clone, Debug)]
#[non_exhaustive]
pub enum AuthorizedClientParseError {
    /// Didn't recognize the type of this [`AuthorizedClientConfig`].
    ///
    /// Recognized types are `dir` and `curve25519`.
    #[error("Unrecognized authorized client type")]
    InvalidType,
    /// Couldn't parse a curve25519 key.
    #[error("Invalid curve25519 key")]
    InvalidKey,
}

impl std::str::FromStr for AuthorizedClientConfig {
    type Err = AuthorizedClientParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let Some((tp, val)) = s.split_once(':') else {
            return Err(Self::Err::InvalidType);
        };
        if tp == "dir" {
            Ok(Self::DirectoryOfKeys(val.into()))
        } else if tp == "curve25519" {
            let bytes: [u8; 32] = Base64Unpadded::decode_vec(val)
                .map_err(|_| Self::Err::InvalidKey)?
                .try_into()
                .map_err(|_| Self::Err::InvalidKey)?;

            Ok(Self::Curve25519Key(HsClientDescEncKey::from(
                curve25519::PublicKey::from(bytes),
            )))
        } else {
            Err(Self::Err::InvalidType)
        }
    }
}