1
//! Functionality for encoding the outer document of an onion service descriptor.
2
//!
3
//! NOTE: `HsDescOuter` 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::{NetdocBuilder, NetdocEncoder};
8
use crate::doc::hsdesc::outer::{HsOuterKwd, HS_DESC_SIGNATURE_PREFIX, HS_DESC_VERSION_CURRENT};
9

            
10
use rand::{CryptoRng, RngCore};
11
use tor_bytes::EncodeError;
12
use tor_cert::EncodedEd25519Cert;
13
use tor_hscrypto::RevisionCounter;
14
use tor_llcrypto::pk::ed25519;
15
use tor_units::IntegerMinutes;
16

            
17
use base64ct::{Base64Unpadded, Encoding};
18

            
19
/// The representation of the outer wrapper of an onion service descriptor.
20
///
21
/// The format of this document is described in section 2.4. of rend-spec-v3.
22
#[derive(Debug)]
23
pub(super) struct HsDescOuter<'a> {
24
    /// The short-term descriptor signing key.
25
    pub(super) hs_desc_sign: &'a ed25519::Keypair,
26
    /// The descriptor signing key certificate.
27
    pub(super) hs_desc_sign_cert: EncodedEd25519Cert,
28
    /// The lifetime of this descriptor, in minutes.
29
    ///
30
    /// This doesn't actually list the starting time or the end time for the
31
    /// descriptor: presumably, because we didn't want to leak the onion
32
    /// service's view of the wallclock.
33
    pub(super) lifetime: IntegerMinutes<u16>,
34
    /// A revision counter to tell whether this descriptor is more or less recent
35
    /// than another one for the same blinded ID.
36
    pub(super) revision_counter: RevisionCounter,
37
    /// The (superencrypted) middle document of the onion service descriptor.
38
    ///
39
    /// The `superencrypted` field is created by encrypting an
40
    /// [`build::middle::HsDescMiddle`](super::middle::HsDescMiddle)
41
    /// middle document as described in
42
    /// sections 2.5.1.1. and 2.5.1.2. of rend-spec-v3.
43
    pub(super) superencrypted: Vec<u8>,
44
}
45

            
46
impl<'a> NetdocBuilder for HsDescOuter<'a> {
47
154
    fn build_sign<R: RngCore + CryptoRng>(self, _: &mut R) -> Result<String, EncodeError> {
48
        use tor_llcrypto::pk::ed25519::Signer as _;
49
        use HsOuterKwd::*;
50

            
51
        let HsDescOuter {
52
154
            hs_desc_sign,
53
154
            hs_desc_sign_cert,
54
154
            lifetime,
55
154
            revision_counter,
56
154
            superencrypted,
57
154
        } = self;
58
154

            
59
154
        let mut encoder = NetdocEncoder::new();
60
154
        let beginning = encoder.cursor();
61
154
        encoder.item(HS_DESCRIPTOR).arg(&HS_DESC_VERSION_CURRENT);
62
154
        encoder.item(DESCRIPTOR_LIFETIME).arg(&lifetime.to_string());
63
154

            
64
154
        encoder
65
154
            .item(DESCRIPTOR_SIGNING_KEY_CERT)
66
154
            .object("ED25519 CERT", hs_desc_sign_cert.as_ref());
67
154
        encoder.item(REVISION_COUNTER).arg(&*revision_counter);
68
154
        encoder
69
154
            .item(SUPERENCRYPTED)
70
154
            .object("MESSAGE", superencrypted);
71
154
        let end = encoder.cursor();
72
154

            
73
154
        let mut text = HS_DESC_SIGNATURE_PREFIX.to_vec();
74
154
        text.extend_from_slice(encoder.slice(beginning, end)?.as_bytes());
75
154
        let signature = hs_desc_sign.sign(&text);
76
154

            
77
154
        // TODO SPEC encoding of this signature is completely unspecified (rend-spec-v3 2.4)
78
154
        // TODO SPEC base64 is sometimes padded, sometimes unpadded, but NONE of the specs ever say!
79
154
        encoder
80
154
            .item(SIGNATURE)
81
154
            .arg(&Base64Unpadded::encode_string(&signature.to_bytes()));
82
154

            
83
154
        encoder.finish().map_err(|e| e.into())
84
154
    }
85
}
86

            
87
#[cfg(test)]
88
mod test {
89
    // @@ begin test lint list maintained by maint/add_warning @@
90
    #![allow(clippy::bool_assert_comparison)]
91
    #![allow(clippy::clone_on_copy)]
92
    #![allow(clippy::dbg_macro)]
93
    #![allow(clippy::mixed_attributes_style)]
94
    #![allow(clippy::print_stderr)]
95
    #![allow(clippy::print_stdout)]
96
    #![allow(clippy::single_char_pattern)]
97
    #![allow(clippy::unwrap_used)]
98
    #![allow(clippy::unchecked_duration_subtraction)]
99
    #![allow(clippy::useless_vec)]
100
    #![allow(clippy::needless_pass_by_value)]
101
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
102

            
103
    use std::time::UNIX_EPOCH;
104

            
105
    use crate::doc::hsdesc::create_desc_sign_key_cert;
106

            
107
    use super::*;
108
    use tor_basic_utils::test_rng::Config;
109
    use tor_hscrypto::pk::HsIdKeypair;
110
    use tor_hscrypto::time::TimePeriod;
111
    use tor_llcrypto::pk::ed25519::ExpandedKeypair;
112
    use tor_units::IntegerMinutes;
113

            
114
    // Some dummy bytes, not actually encrypted.
115
    const TEST_SUPERENCRYPTED_VALUE: &[u8] = &[1, 2, 3, 4];
116

            
117
    #[test]
118
    fn outer_hsdesc() {
119
        let mut rng = Config::Deterministic.into_rng();
120
        let hs_id = ed25519::Keypair::generate(&mut rng);
121
        let hs_desc_sign = ed25519::Keypair::generate(&mut rng);
122
        let period = TimePeriod::new(
123
            humantime::parse_duration("24 hours").unwrap(),
124
            humantime::parse_rfc3339("2023-02-09T12:00:00Z").unwrap(),
125
            humantime::parse_duration("12 hours").unwrap(),
126
        )
127
        .unwrap();
128
        let (_public, blinded_id, _) = HsIdKeypair::from(ExpandedKeypair::from(&hs_id))
129
            .compute_blinded_key(period)
130
            .unwrap();
131

            
132
        let hs_desc_sign_cert =
133
            create_desc_sign_key_cert(&hs_desc_sign.verifying_key(), &blinded_id, UNIX_EPOCH)
134
                .unwrap();
135

            
136
        let hs_desc = HsDescOuter {
137
            hs_desc_sign: &hs_desc_sign,
138
            hs_desc_sign_cert,
139
            lifetime: IntegerMinutes::new(20),
140
            revision_counter: 9001.into(),
141
            superencrypted: TEST_SUPERENCRYPTED_VALUE.into(),
142
        }
143
        .build_sign(&mut Config::Deterministic.into_rng())
144
        .unwrap();
145

            
146
        assert_eq!(
147
            hs_desc,
148
            r#"hs-descriptor 3
149
descriptor-lifetime 20
150
descriptor-signing-key-cert
151
-----BEGIN ED25519 CERT-----
152
AQgAAAAAAZZVJwNlzVw1ZQGO7MTzC5MsySASd+fswAcjdTJJOifXAQAgBACI78JJ
153
/MuWPH0T5rQziVMJK/yETbYCVycypjsytCmeA4eiWhcVBG4r6AY/fXqHZnI3ApID
154
fsb92Bs45IrOrkQdATb5mk1dlFb0X6+0wIF0P0gCVuAEkGv1kvcR/zpvhww=
155
-----END ED25519 CERT-----
156
revision-counter 9001
157
superencrypted
158
-----BEGIN MESSAGE-----
159
AQIDBA==
160
-----END MESSAGE-----
161
signature g6wu776AYYD+BXPBocToRXPF9xob3TB34hkR1/h8tDBGjGMnBWZw03INbiX6Z8FaOXCulccQ309fYEO/BmwyDQ
162
"#
163
        );
164
    }
165
}