1
//! Handle the middle document of an onion service descriptor.
2

            
3
use std::sync::LazyLock;
4
use subtle::ConstantTimeEq;
5
use tor_hscrypto::pk::{HsBlindId, HsClientDescEncSecretKey, HsSvcDescEncKey};
6
use tor_hscrypto::{RevisionCounter, Subcredential};
7
use tor_llcrypto::pk::curve25519;
8
use tor_llcrypto::util::ct::CtByteArray;
9

            
10
use crate::doc::hsdesc::desc_enc::build_descriptor_cookie_key;
11
use crate::parse::tokenize::{Item, NetDocReader};
12
use crate::parse::{keyword::Keyword, parser::SectionRules};
13
use crate::types::misc::B64;
14
use crate::{Pos, Result};
15

            
16
use super::HsDescError;
17
use super::desc_enc::{
18
    HS_DESC_CLIENT_ID_LEN, HS_DESC_ENC_NONCE_LEN, HS_DESC_IV_LEN, HsDescEncNonce, HsDescEncryption,
19
};
20

            
21
/// The only currently recognized `desc-auth-type`.
22
//
23
// TODO: In theory this should be an enum, if we ever add a second value here.
24
pub(super) const HS_DESC_AUTH_TYPE: &str = "x25519";
25

            
26
/// A more-or-less verbatim representation of the middle document of an onion
27
/// service descriptor.
28
#[derive(Debug, Clone)]
29
pub struct HsDescMiddle {
30
    /// A public key used by authorized clients to decrypt the key used to
31
    /// decrypt the encryption layer and decode the inner document.  This is
32
    /// ignored if restricted discovery is not in use.
33
    ///
34
    /// This is `KP_hss_desc_enc`, and appears as `desc-auth-ephemeral-key` in
35
    /// the document format; It is used along with `KS_hsc_desc_enc` to perform
36
    /// a diffie-hellman operation and decrypt the encryption layer.
37
    svc_desc_enc_key: HsSvcDescEncKey,
38
    /// One or more authorized clients, and the key exchange information that
39
    /// they use to compute shared keys for decrypting the encryption layer.
40
    ///
41
    /// Each of these is parsed from a `auth-client` line.
42
    auth_clients: Vec<AuthClient>,
43
    /// The (encrypted) inner document of the onion service descriptor.
44
    encrypted: Vec<u8>,
45
}
46

            
47
impl HsDescMiddle {
48
    /// Decrypt the encrypted inner document contained within this middle
49
    /// document.
50
    ///
51
    /// If present, `key` is an authorization key, and we assume that the
52
    /// decryption is nontrivial.
53
    ///
54
    /// A failure may mean either that the encryption was corrupted, or that we
55
    /// didn't have the right key.
56
716
    pub fn decrypt_inner(
57
716
        &self,
58
716
        blinded_id: &HsBlindId,
59
716
        revision: RevisionCounter,
60
716
        subcredential: &Subcredential,
61
716
        key: Option<&HsClientDescEncSecretKey>,
62
716
    ) -> std::result::Result<Vec<u8>, super::HsDescError> {
63
726
        let desc_enc_nonce = key.and_then(|k| self.find_cookie(subcredential, k));
64
716
        let decrypt = HsDescEncryption {
65
716
            blinded_id,
66
716
            desc_enc_nonce: desc_enc_nonce.as_ref(),
67
716
            subcredential,
68
716
            revision,
69
716
            string_const: b"hsdir-encrypted-data",
70
716
        };
71

            
72
716
        match decrypt.decrypt(&self.encrypted) {
73
714
            Ok(mut v) => {
74
                // Work around a bug in an implementation we presume to be
75
                // OnionBalance: it doesn't NL-terminate the final line of the
76
                // inner document.
77
714
                if !v.ends_with(b"\n") {
78
                    v.push(b'\n');
79
714
                }
80
714
                Ok(v)
81
            }
82
2
            Err(_) => match (key, desc_enc_nonce) {
83
                (Some(_), None) => Err(HsDescError::WrongDecryptionKey),
84
                (Some(_), Some(_)) => Err(HsDescError::DecryptionFailed),
85
2
                (None, _) => Err(HsDescError::MissingDecryptionKey),
86
            },
87
        }
88
716
    }
89

            
90
    /// Use a `ClientDescAuthSecretKey` (`KS_hsc_desc_enc`) to see if there is any `auth-client`
91
    /// entry for us (a client who holds that secret key) in this descriptor.
92
    /// If so, decrypt it and return its
93
    /// corresponding "Descriptor Cookie" (`N_hs_desc_enc`)
94
    ///
95
    /// If no such `N_hs_desc_enc` is found, then either we do not have
96
    /// permission to decrypt the encryption layer, OR no permission is required.
97
    ///
98
    /// (The protocol makes it intentionally impossible to distinguish any error
99
    /// conditions here other than "no cookie for you.")
100
436
    fn find_cookie(
101
436
        &self,
102
436
        subcredential: &Subcredential,
103
436
        ks_hsc_desc_enc: &HsClientDescEncSecretKey,
104
436
    ) -> Option<HsDescEncNonce> {
105
        use cipher::{KeyIvInit, StreamCipher};
106
        use tor_llcrypto::cipher::aes::Aes256Ctr as Cipher;
107
        use tor_llcrypto::util::ct::ct_lookup;
108

            
109
436
        let (client_id, cookie_key) = build_descriptor_cookie_key(
110
436
            ks_hsc_desc_enc.as_ref(),
111
436
            &self.svc_desc_enc_key,
112
436
            subcredential,
113
436
        );
114
        // See whether there is any matching client_id in self.auth_ids.
115
6956
        let auth_client = ct_lookup(&self.auth_clients, |c| c.client_id.ct_eq(&client_id))?;
116

            
117
        // We found an auth client entry: Take and decrypt the cookie `N_hs_desc_enc` at last.
118
436
        let mut cookie = auth_client.encrypted_cookie;
119
436
        let mut cipher = Cipher::new(&cookie_key.into(), &auth_client.iv.into());
120
436
        cipher.apply_keystream(&mut cookie);
121
436
        Some(cookie.into())
122
436
    }
123
}
124

            
125
/// Information that a single authorized client can use to decrypt the onion
126
/// service descriptor.
127
#[derive(Debug, Clone)]
128
pub(super) struct AuthClient {
129
    /// A check field that clients can use to see if this [`AuthClient`] entry corresponds
130
    /// to a key they hold.
131
    ///
132
    /// This is the first part of the `auth-client` line.
133
    pub(super) client_id: CtByteArray<HS_DESC_CLIENT_ID_LEN>,
134
    /// An IV used to decrypt `encrypted_cookie`.
135
    ///
136
    /// This is the second item on the `auth-client` line.
137
    pub(super) iv: [u8; HS_DESC_IV_LEN],
138
    /// An encrypted value used to find the descriptor cookie `N_hs_desc_enc`,
139
    /// which in turn is
140
    /// needed to decrypt the [HsDescMiddle]'s `encrypted_body`.
141
    ///
142
    /// This is the third item on the `auth-client` line.  When decrypted, it
143
    /// reveals a `DescEncEncryptionCookie` (`N_hs_desc_enc`, not yet so named
144
    /// in the spec).
145
    pub(super) encrypted_cookie: [u8; HS_DESC_ENC_NONCE_LEN],
146
}
147

            
148
impl AuthClient {
149
    /// Try to extract an AuthClient from a single AuthClient item.
150
11396
    fn from_item(item: &Item<'_, HsMiddleKwd>) -> Result<Self> {
151
        use crate::NetdocErrorKind as EK;
152

            
153
11396
        if item.kwd() != HsMiddleKwd::AUTH_CLIENT {
154
            return Err(EK::Internal.with_msg("called with invalid argument."));
155
11396
        }
156
11396
        let client_id = item.parse_arg::<B64>(0)?.into_array()?.into();
157
11396
        let iv = item.parse_arg::<B64>(1)?.into_array()?;
158
11396
        let encrypted_cookie = item.parse_arg::<B64>(2)?.into_array()?;
159
11396
        Ok(AuthClient {
160
11396
            client_id,
161
11396
            iv,
162
11396
            encrypted_cookie,
163
11396
        })
164
11396
    }
165
}
166

            
167
decl_keyword! {
168
    pub(crate) HsMiddleKwd {
169
        "desc-auth-type" => DESC_AUTH_TYPE,
170
        "desc-auth-ephemeral-key" => DESC_AUTH_EPHEMERAL_KEY,
171
        "auth-client" => AUTH_CLIENT,
172
        "encrypted" => ENCRYPTED,
173
    }
174
}
175

            
176
/// Rules about how keywords appear in the middle document of an onion service
177
/// descriptor.
178
110
static HS_MIDDLE_RULES: LazyLock<SectionRules<HsMiddleKwd>> = LazyLock::new(|| {
179
    use HsMiddleKwd::*;
180

            
181
110
    let mut rules = SectionRules::builder();
182
110
    rules.add(DESC_AUTH_TYPE.rule().required().args(1..));
183
110
    rules.add(DESC_AUTH_EPHEMERAL_KEY.rule().required().args(1..));
184
110
    rules.add(AUTH_CLIENT.rule().required().may_repeat().args(3..));
185
110
    rules.add(ENCRYPTED.rule().required().obj_required());
186
110
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
187

            
188
110
    rules.build()
189
110
});
190

            
191
impl HsDescMiddle {
192
    /// Try to parse the middle document of an onion service descriptor from a provided
193
    /// string.
194
716
    pub fn parse(s: &str) -> Result<HsDescMiddle> {
195
716
        let mut reader = NetDocReader::new(s)?;
196
716
        let result = HsDescMiddle::take_from_reader(&mut reader).map_err(|e| e.within(s))?;
197
716
        Ok(result)
198
716
    }
199

            
200
    /// Extract an HsDescMiddle from a reader.
201
    ///
202
    /// The reader must contain a single HsDescOuter; we return an error if not.
203
716
    fn take_from_reader(reader: &mut NetDocReader<'_, HsMiddleKwd>) -> Result<HsDescMiddle> {
204
        use crate::NetdocErrorKind as EK;
205
        use HsMiddleKwd::*;
206

            
207
716
        let body = HS_MIDDLE_RULES.parse(reader)?;
208

            
209
        // Check for the only currently recognized `desc-auth-type`
210
        {
211
716
            let auth_type = body.required(DESC_AUTH_TYPE)?.required_arg(0)?;
212
716
            if auth_type != HS_DESC_AUTH_TYPE {
213
                return Err(EK::BadDocumentVersion
214
                    .at_pos(Pos::at(auth_type))
215
                    .with_msg(format!("Unrecognized desc-auth-type {auth_type:?}")));
216
716
            }
217
        }
218

            
219
        // Extract `KP_hss_desc_enc` from DESC_AUTH_EPHEMERAL_KEY
220
716
        let ephemeral_key: HsSvcDescEncKey = {
221
716
            let token = body.required(DESC_AUTH_EPHEMERAL_KEY)?;
222
716
            let key = curve25519::PublicKey::from(token.parse_arg::<B64>(0)?.into_array()?);
223
716
            key.into()
224
        };
225

            
226
        // Parse all the auth-client lines.
227
716
        let auth_clients: Vec<AuthClient> = body
228
716
            .slice(AUTH_CLIENT)
229
716
            .iter()
230
716
            .map(AuthClient::from_item)
231
716
            .collect::<Result<Vec<_>>>()?;
232

            
233
        // The encrypted body is taken verbatim.
234
716
        let encrypted_body: Vec<u8> = body.required(ENCRYPTED)?.obj("MESSAGE")?;
235

            
236
716
        Ok(HsDescMiddle {
237
716
            svc_desc_enc_key: ephemeral_key,
238
716
            auth_clients,
239
716
            encrypted: encrypted_body,
240
716
        })
241
716
    }
242
}
243

            
244
#[cfg(test)]
245
mod test {
246
    // @@ begin test lint list maintained by maint/add_warning @@
247
    #![allow(clippy::bool_assert_comparison)]
248
    #![allow(clippy::clone_on_copy)]
249
    #![allow(clippy::dbg_macro)]
250
    #![allow(clippy::mixed_attributes_style)]
251
    #![allow(clippy::print_stderr)]
252
    #![allow(clippy::print_stdout)]
253
    #![allow(clippy::single_char_pattern)]
254
    #![allow(clippy::unwrap_used)]
255
    #![allow(clippy::unchecked_time_subtraction)]
256
    #![allow(clippy::useless_vec)]
257
    #![allow(clippy::needless_pass_by_value)]
258
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
259

            
260
    use hex_literal::hex;
261
    use tor_checkable::{SelfSigned, Timebound};
262

            
263
    use super::*;
264
    use crate::doc::hsdesc::{
265
        outer::HsDescOuter,
266
        test_data::{TEST_DATA, TEST_SUBCREDENTIAL},
267
    };
268

            
269
    #[test]
270
    fn parse_good() -> Result<()> {
271
        let desc = HsDescOuter::parse(TEST_DATA)?
272
            .dangerously_assume_wellsigned()
273
            .dangerously_assume_timely();
274
        let subcred = TEST_SUBCREDENTIAL.into();
275
        let body = desc.decrypt_body(&subcred).unwrap();
276
        let body = std::str::from_utf8(&body[..]).unwrap();
277

            
278
        let middle = HsDescMiddle::parse(body)?;
279
        assert_eq!(
280
            middle.svc_desc_enc_key.as_bytes(),
281
            &hex!("161090571E6DB517C0C8591CE524A56DF17BAE3FF8DCD50735F9AEB89634073E")
282
        );
283
        assert_eq!(middle.auth_clients.len(), 16);
284

            
285
        // Here we make sure that decryption "works" minimally and returns some
286
        // bytes for a descriptor with no HsClientDescEncSecretKey.
287
        //
288
        // We make sure that the actual decrypted value is reasonable elsewhere,
289
        // in the tests in inner.rs.
290
        //
291
        // We test the case where a HsClientDescEncSecretKey is needed
292
        // elsewhere, in `hsdesc::test::parse_desc_auth_good`.
293
        let _inner_body = middle
294
            .decrypt_inner(&desc.blinded_id(), desc.revision_counter(), &subcred, None)
295
            .unwrap();
296

            
297
        Ok(())
298
    }
299
}