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::build::NetdocEncoder;
8
use crate::doc::hsdesc::build::ClientAuth;
9
use crate::doc::hsdesc::desc_enc::{
10
    build_descriptor_cookie_key, HS_DESC_CLIENT_ID_LEN, HS_DESC_ENC_NONCE_LEN, HS_DESC_IV_LEN,
11
};
12
use crate::doc::hsdesc::middle::{AuthClient, HsMiddleKwd, HS_DESC_AUTH_TYPE};
13
use crate::NetdocBuilder;
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
158
    fn build_sign<R: RngCore + CryptoRng>(self, rng: &mut R) -> Result<String, EncodeError> {
44
        use cipher::{KeyIvInit, StreamCipher};
45
        use tor_llcrypto::cipher::aes::Aes256Ctr as Cipher;
46
        use HsMiddleKwd::*;
47

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

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

            
56
156
        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
6
                    // 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
8

            
73
8
                        // 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.gen::<[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
8

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

            
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
150
                    let dummy_auth_client = AuthClient {
92
150
                        client_id: CtByteArray::from(rng.gen::<[u8; HS_DESC_CLIENT_ID_LEN]>()),
93
150
                        iv: rng.gen::<[u8; HS_DESC_IV_LEN]>(),
94
150
                        encrypted_cookie: rng.gen::<[u8; HS_DESC_ENC_NONCE_LEN]>(),
95
150
                    };
96
150

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

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

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

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

            
123
156
        encoder.item(ENCRYPTED).object("MESSAGE", encrypted);
124
156
        encoder.finish().map_err(|e| e.into())
125
158
    }
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_duration_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::test::{create_curve25519_pk, expect_bug};
146
    use crate::doc::hsdesc::build::ClientAuth;
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::gen::<[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!(expect_bug(err)
202
            .contains("restricted discovery is enabled, but there are no authorized clients"));
203
    }
204

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

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

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

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

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