1
//! Functionality for encoding the middle document of an onion service descriptor.
2
//!
3
//! NOTE: `HsDescMiddle` is a private helper for building hidden service descriptors, and is
4
//! not meant to be used directly. Hidden services will use `HsDescBuilder` to build and encode
5
//! hidden service descriptors.
6

            
7
use crate::NetdocBuilder;
8
use crate::build::NetdocEncoder;
9
use crate::doc::hsdesc::build::ClientAuth;
10
use crate::doc::hsdesc::desc_enc::{
11
    HS_DESC_CLIENT_ID_LEN, HS_DESC_ENC_NONCE_LEN, HS_DESC_IV_LEN, build_descriptor_cookie_key,
12
};
13
use crate::doc::hsdesc::middle::{AuthClient, HS_DESC_AUTH_TYPE, HsMiddleKwd};
14

            
15
use tor_bytes::EncodeError;
16
use tor_hscrypto::Subcredential;
17
use tor_llcrypto::pk::curve25519::{EphemeralSecret, PublicKey};
18
use tor_llcrypto::util::ct::CtByteArray;
19

            
20
use base64ct::{Base64, Encoding};
21
use rand::{CryptoRng, Rng, RngCore};
22

            
23
/// The representation of the middle document of an onion service descriptor.
24
///
25
/// The plaintext format of this document is described in section 2.5.1.2. of rend-spec-v3.
26
#[derive(Debug)]
27
pub(super) struct HsDescMiddle<'a> {
28
    /// Restricted discovery parameters, if restricted discovery is enabled. If set to `None`,
29
    /// restricted discovery is disabled.
30
    pub(super) client_auth: Option<&'a ClientAuth<'a>>,
31
    /// The "subcredential" of the onion service.
32
    pub(super) subcredential: Subcredential,
33
    /// The (encrypted) inner document of the onion service descriptor.
34
    ///
35
    /// The `encrypted` field is created by encrypting a
36
    /// [`build::inner::HsDescInner`](super::inner::HsDescInner)
37
    /// inner document as described in sections
38
    /// 2.5.2.1. and 2.5.2.2. of rend-spec-v3.
39
    pub(super) encrypted: Vec<u8>,
40
}
41

            
42
impl<'a> NetdocBuilder for HsDescMiddle<'a> {
43
174
    fn build_sign<R: RngCore + CryptoRng>(self, rng: &mut R) -> Result<String, EncodeError> {
44
        use HsMiddleKwd::*;
45
        use cipher::{KeyIvInit, StreamCipher};
46
        use tor_llcrypto::cipher::aes::Aes256Ctr as Cipher;
47

            
48
        let HsDescMiddle {
49
174
            client_auth,
50
174
            subcredential,
51
174
            encrypted,
52
174
        } = self;
53

            
54
174
        let mut encoder = NetdocEncoder::new();
55

            
56
172
        let (ephemeral_key, auth_clients): (_, Box<dyn std::iter::Iterator<Item = AuthClient>>) =
57
8
            match client_auth {
58
8
                Some(client_auth) if client_auth.auth_clients.is_empty() => {
59
2
                    return Err(tor_error::bad_api_usage!(
60
2
                        "restricted discovery is enabled, but there are no authorized clients"
61
2
                    )
62
2
                    .into());
63
                }
64
6
                Some(client_auth) => {
65
                    // Restricted discovery is enabled.
66
8
                    let auth_clients = client_auth.auth_clients.iter().map(|client| {
67
8
                        let (client_id, cookie_key) = build_descriptor_cookie_key(
68
8
                            client_auth.ephemeral_key.secret.as_ref(),
69
8
                            client,
70
8
                            &subcredential,
71
8
                        );
72

            
73
                        // Encrypt the descriptor cookie with the public key of the client.
74
8
                        let mut encrypted_cookie = client_auth.descriptor_cookie;
75
8
                        let iv = rng.random::<[u8; HS_DESC_IV_LEN]>();
76
8
                        let mut cipher = Cipher::new(&cookie_key.into(), &iv.into());
77
8
                        cipher.apply_keystream(&mut encrypted_cookie);
78

            
79
8
                        AuthClient {
80
8
                            client_id,
81
8
                            iv,
82
8
                            encrypted_cookie,
83
8
                        }
84
8
                    });
85

            
86
6
                    (*client_auth.ephemeral_key.public, Box::new(auth_clients))
87
                }
88
                None => {
89
                    // Generate a single client-auth line filled with random values for client-id,
90
                    // iv, and encrypted-cookie.
91
166
                    let dummy_auth_client = AuthClient {
92
166
                        client_id: CtByteArray::from(rng.random::<[u8; HS_DESC_CLIENT_ID_LEN]>()),
93
166
                        iv: rng.random::<[u8; HS_DESC_IV_LEN]>(),
94
166
                        encrypted_cookie: rng.random::<[u8; HS_DESC_ENC_NONCE_LEN]>(),
95
166
                    };
96

            
97
                    // As per section 2.5.1.2. of rend-spec-v3, if restricted discovery is disabled,
98
                    // we need to generate some fake data for the desc-auth-ephemeral-key
99
                    // and auth-client fields.
100
166
                    let secret = EphemeralSecret::random_from_rng(rng);
101
166
                    let dummy_ephemeral_key = PublicKey::from(&secret);
102

            
103
166
                    (
104
166
                        dummy_ephemeral_key,
105
166
                        Box::new(std::iter::once(dummy_auth_client)),
106
166
                    )
107
                }
108
            };
109

            
110
172
        encoder.item(DESC_AUTH_TYPE).arg(&HS_DESC_AUTH_TYPE);
111
172
        encoder
112
172
            .item(DESC_AUTH_EPHEMERAL_KEY)
113
172
            .arg(&Base64::encode_string(ephemeral_key.as_bytes()));
114

            
115
346
        for auth_client in auth_clients {
116
174
            encoder
117
174
                .item(AUTH_CLIENT)
118
174
                .arg(&Base64::encode_string(&*auth_client.client_id))
119
174
                .arg(&Base64::encode_string(&auth_client.iv))
120
174
                .arg(&Base64::encode_string(&auth_client.encrypted_cookie));
121
174
        }
122

            
123
172
        encoder.item(ENCRYPTED).object("MESSAGE", encrypted);
124
172
        encoder.finish().map_err(|e| e.into())
125
174
    }
126
}
127

            
128
#[cfg(test)]
129
mod test {
130
    // @@ begin test lint list maintained by maint/add_warning @@
131
    #![allow(clippy::bool_assert_comparison)]
132
    #![allow(clippy::clone_on_copy)]
133
    #![allow(clippy::dbg_macro)]
134
    #![allow(clippy::mixed_attributes_style)]
135
    #![allow(clippy::print_stderr)]
136
    #![allow(clippy::print_stdout)]
137
    #![allow(clippy::single_char_pattern)]
138
    #![allow(clippy::unwrap_used)]
139
    #![allow(clippy::unchecked_time_subtraction)]
140
    #![allow(clippy::useless_vec)]
141
    #![allow(clippy::needless_pass_by_value)]
142
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
143

            
144
    use super::*;
145
    use crate::doc::hsdesc::build::ClientAuth;
146
    use crate::doc::hsdesc::build::test::{create_curve25519_pk, expect_bug};
147
    use crate::doc::hsdesc::test_data::TEST_SUBCREDENTIAL;
148
    use tor_basic_utils::test_rng::Config;
149
    use tor_hscrypto::pk::HsSvcDescEncKeypair;
150
    use tor_llcrypto::pk::curve25519;
151

            
152
    // Some dummy bytes, not actually encrypted.
153
    const TEST_ENCRYPTED_VALUE: &[u8] = &[1, 2, 3, 4];
154

            
155
    #[test]
156
    fn middle_hsdesc_encoding_no_client_auth() {
157
        let hs_desc = HsDescMiddle {
158
            client_auth: None,
159
            subcredential: TEST_SUBCREDENTIAL.into(),
160
            encrypted: TEST_ENCRYPTED_VALUE.into(),
161
        }
162
        .build_sign(&mut Config::Deterministic.into_rng())
163
        .unwrap();
164

            
165
        assert_eq!(
166
            hs_desc,
167
            r#"desc-auth-type x25519
168
desc-auth-ephemeral-key XI/a9NGh/7ClaFcKqtdI9DoP8da5ovwPDdgCHUr3xX0=
169
auth-client F+Z6EDfG7oc= 7EIXRtlSozVtGAs6+mNujQ== pNtSIyiCahSvUVg+7s71Ow==
170
encrypted
171
-----BEGIN MESSAGE-----
172
AQIDBA==
173
-----END MESSAGE-----
174
"#
175
        );
176
    }
177

            
178
    #[test]
179
    fn middle_hsdesc_encoding_with_bad_client_auth() {
180
        let mut rng = Config::Deterministic.into_rng();
181
        let secret = curve25519::StaticSecret::random_from_rng(&mut rng);
182
        let public = curve25519::PublicKey::from(&secret).into();
183

            
184
        let client_auth = ClientAuth {
185
            ephemeral_key: HsSvcDescEncKeypair {
186
                public,
187
                secret: secret.into(),
188
            },
189
            auth_clients: &[],
190
            descriptor_cookie: rand::Rng::random::<[u8; HS_DESC_ENC_NONCE_LEN]>(&mut rng),
191
        };
192

            
193
        let err = HsDescMiddle {
194
            client_auth: Some(&client_auth),
195
            subcredential: TEST_SUBCREDENTIAL.into(),
196
            encrypted: TEST_ENCRYPTED_VALUE.into(),
197
        }
198
        .build_sign(&mut rng)
199
        .unwrap_err();
200

            
201
        assert!(
202
            expect_bug(err)
203
                .contains("restricted discovery is enabled, but there are no authorized clients")
204
        );
205
    }
206

            
207
    #[test]
208
    fn middle_hsdesc_encoding_client_auth() {
209
        let mut rng = Config::Deterministic.into_rng();
210
        // 2 authorized clients
211
        let auth_clients = vec![
212
            create_curve25519_pk(&mut rng),
213
            create_curve25519_pk(&mut rng),
214
        ];
215

            
216
        let secret = curve25519::StaticSecret::random_from_rng(&mut rng);
217
        let public = curve25519::PublicKey::from(&secret).into();
218

            
219
        let client_auth = ClientAuth {
220
            ephemeral_key: HsSvcDescEncKeypair {
221
                public,
222
                secret: secret.into(),
223
            },
224
            auth_clients: &auth_clients,
225
            descriptor_cookie: rand::Rng::random::<[u8; HS_DESC_ENC_NONCE_LEN]>(&mut rng),
226
        };
227

            
228
        let hs_desc = HsDescMiddle {
229
            client_auth: Some(&client_auth),
230
            subcredential: TEST_SUBCREDENTIAL.into(),
231
            encrypted: TEST_ENCRYPTED_VALUE.into(),
232
        }
233
        .build_sign(&mut Config::Deterministic.into_rng())
234
        .unwrap();
235

            
236
        assert_eq!(
237
            hs_desc,
238
            r#"desc-auth-type x25519
239
desc-auth-ephemeral-key 9Upi9XNWyqx3ZwHeQ5r3+Dh116k+C4yHeE9BcM68HDc=
240
auth-client pxfSbhBMPw0= F+Z6EDfG7ofsQhdG2VKjNQ== fEursUD9Bj5Q9mFP8sIddA==
241
auth-client DV7nt+CDOno= bRgLOvpjbo2k21IjKIJqFA== 2yVT+Lpm/WL4JAU64zlGpQ==
242
encrypted
243
-----BEGIN MESSAGE-----
244
AQIDBA==
245
-----END MESSAGE-----
246
"#
247
        );
248
    }
249
}