1
//! Key derivation functions
2
//!
3
//! Tor has three relevant key derivation functions that we use for
4
//! deriving keys used for relay encryption.
5
//!
6
//! The *KDF-TOR* KDF (implemented by `LegacyKdf`) is used with the old
7
//! TAP handshake.  It is ugly, it is based on SHA-1, and it should be
8
//! avoided for new uses.
9
//!
10
//! The *HKDF-SHA256* KDF (implemented by `Ntor1Kdf`) is used with the
11
//! Ntor handshake.  It is based on RFC5869 and SHA256.
12
//!
13
//! The *SHAKE* KDF (implemented by `ShakeKdf` is used with v3 onion
14
//! services, and is likely to be used by other places in the future.
15
//! It is based on SHAKE-256.
16

            
17
use crate::{Error, Result, SecretBytes};
18
use digest::{ExtendableOutput, Update, XofReader};
19
use tor_llcrypto::d::{Sha1, Sha256, Shake256};
20

            
21
use zeroize::Zeroizing;
22

            
23
/// A trait for a key derivation function.
24
pub(crate) trait Kdf {
25
    /// Derive `n_bytes` of key data from some secret `seed`.
26
    fn derive(&self, seed: &[u8], n_bytes: usize) -> Result<SecretBytes>;
27
}
28

            
29
/// A legacy KDF, for use with TAP.
30
///
31
/// This KDF is based on SHA1.  Don't use this for anything new.
32
pub(crate) struct LegacyKdf {
33
    /// Starting index value for the TAP kdf.  should always be 1.
34
    idx: u8,
35
}
36

            
37
/// A parameterized KDF, for use with ntor.
38
///
39
/// This KDF is based on HKDF-SHA256.
40
pub(crate) struct Ntor1Kdf<'a, 'b> {
41
    /// A constant for parameterizing the kdf, during the key extraction
42
    /// phase.
43
    t_key: &'a [u8],
44
    /// Another constant for parameterizing the kdf, during the key
45
    /// expansion phase.
46
    m_expand: &'b [u8],
47
}
48

            
49
/// A modern KDF, for use with v3 onion services.
50
///
51
/// This KDF is based on SHAKE256
52
pub(crate) struct ShakeKdf();
53

            
54
impl LegacyKdf {
55
    /// Instantiate a LegacyKdf.
56
31
    pub(crate) fn new(idx: u8) -> Self {
57
31
        LegacyKdf { idx }
58
31
    }
59
}
60
impl Kdf for LegacyKdf {
61
31
    fn derive(&self, seed: &[u8], n_bytes: usize) -> Result<SecretBytes> {
62
31
        use digest::Digest;
63
31

            
64
31
        let mut result = Zeroizing::new(Vec::with_capacity(n_bytes + Sha1::output_size()));
65
31
        let mut k = self.idx;
66
31
        if n_bytes > Sha1::output_size() * (256 - (k as usize)) {
67
1
            return Err(Error::InvalidOutputLength);
68
30
        }
69

            
70
122
        while result.len() < n_bytes {
71
92
            let mut d = Sha1::new();
72
92
            Digest::update(&mut d, seed);
73
92
            Digest::update(&mut d, &[k]);
74
92
            result.extend(d.finalize());
75
92
            k += 1;
76
92
        }
77

            
78
30
        result.truncate(n_bytes);
79
30
        Ok(result)
80
31
    }
81
}
82

            
83
impl<'a, 'b> Ntor1Kdf<'a, 'b> {
84
    /// Instantiate an Ntor1Kdf, with given values for t_key and m_expand.
85
15
    pub(crate) fn new(t_key: &'a [u8], m_expand: &'b [u8]) -> Self {
86
15
        Ntor1Kdf { t_key, m_expand }
87
15
    }
88
}
89

            
90
impl Kdf for Ntor1Kdf<'_, '_> {
91
15
    fn derive(&self, seed: &[u8], n_bytes: usize) -> Result<SecretBytes> {
92
15
        let hkdf = hkdf::Hkdf::<Sha256>::new(Some(self.t_key), seed);
93
15

            
94
15
        let mut result = Zeroizing::new(vec![0; n_bytes]);
95
15
        hkdf.expand(self.m_expand, &mut result[..])
96
15
            .map_err(|_| Error::InvalidOutputLength)?;
97
15
        Ok(result)
98
15
    }
99
}
100

            
101
impl ShakeKdf {
102
    /// Instantiate a ShakeKdf.
103
11
    pub(crate) fn new() -> Self {
104
11
        ShakeKdf()
105
11
    }
106
}
107
impl Kdf for ShakeKdf {
108
11
    fn derive(&self, seed: &[u8], n_bytes: usize) -> Result<SecretBytes> {
109
11
        let mut xof = Shake256::default();
110
11
        xof.update(seed);
111
11
        let mut result = Zeroizing::new(vec![0; n_bytes]);
112
11
        xof.finalize_xof().read(&mut result);
113
11
        Ok(result)
114
11
    }
115
}
116

            
117
#[cfg(test)]
118
mod test {
119
    #![allow(clippy::unwrap_used)]
120
    use super::*;
121
    use hex_literal::hex;
122

            
123
    #[test]
124
    fn clearbox_tap_kdf() {
125
        // Calculate an instance of the TAP KDF, based on its spec in
126
        // tor-spec.txt.
127
        use digest::Digest;
128
        let input = b"here is an example key seed that we will expand";
129
        let result = LegacyKdf::new(6).derive(input, 99).unwrap();
130

            
131
        let mut expect_result = Vec::new();
132
        let mut k0: Vec<u8> = Vec::new();
133
        k0.extend(&input[..]);
134
        for x in 6..11 {
135
            k0.push(x);
136
            expect_result.extend(Sha1::digest(&k0));
137
            k0.pop();
138
        }
139
        expect_result.truncate(99);
140

            
141
        assert_eq!(&result[..], &expect_result[..]);
142
    }
143

            
144
    #[test]
145
    fn testvec_tap_kdf() {
146
        // Taken from test_crypto.c in Tor, generated by a python script.
147
        fn expand(b: &[u8]) -> SecretBytes {
148
            LegacyKdf::new(0).derive(b, 100).unwrap()
149
        }
150

            
151
        let expect = hex!(
152
            "5ba93c9db0cff93f52b521d7420e43f6eda2784fbf8b4530d8
153
             d246dd74ac53a13471bba17941dff7c4ea21bb365bbeeaf5f2
154
             c654883e56d11e43c44e9842926af7ca0a8cca12604f945414
155
             f07b01e13da42c6cf1de3abfdea9b95f34687cbbe92b9a7383"
156
        );
157
        assert_eq!(&expand(&b""[..])[..], &expect[..]);
158

            
159
        let expect = hex!(
160
            "776c6214fc647aaa5f683c737ee66ec44f03d0372e1cce6922
161
             7950f236ddf1e329a7ce7c227903303f525a8c6662426e8034
162
             870642a6dabbd41b5d97ec9bf2312ea729992f48f8ea2d0ba8
163
             3f45dfda1a80bdc8b80de01b23e3e0ffae099b3e4ccf28dc28"
164
        );
165
        assert_eq!(&expand(&b"Tor"[..])[..], &expect[..]);
166

            
167
        let brunner_quote = b"AN ALARMING ITEM TO FIND ON A MONTHLY AUTO-DEBIT NOTICE";
168
        let expect = hex!(
169
            "a340b5d126086c3ab29c2af4179196dbf95e1c72431419d331
170
             4844bf8f6afb6098db952b95581fb6c33625709d6f4400b8e7
171
             ace18a70579fad83c0982ef73f89395bcc39493ad53a685854
172
             daf2ba9b78733b805d9a6824c907ee1dba5ac27a1e466d4d10"
173
        );
174
        assert_eq!(&expand(&brunner_quote[..])[..], &expect[..]);
175
    }
176

            
177
    #[test]
178
    fn fail_tap_kdf() {
179
        let result = LegacyKdf::new(6).derive(&b"x"[..], 10000);
180
        assert!(result.is_err());
181
    }
182

            
183
    #[test]
184
    fn clearbox_ntor1_kdf() {
185
        // Calculate Ntor1Kdf, and make sure we get the same result by
186
        // following the calculation in the spec.
187
        let input = b"another example key seed that we will expand";
188
        let result = Ntor1Kdf::new(&b"key"[..], &b"expand"[..])
189
            .derive(input, 99)
190
            .unwrap();
191

            
192
        let kdf = hkdf::Hkdf::<Sha256>::new(Some(&b"key"[..]), &input[..]);
193
        let mut expect_result = vec![0_u8; 99];
194
        kdf.expand(&b"expand"[..], &mut expect_result[..]).unwrap();
195

            
196
        assert_eq!(&expect_result[..], &result[..]);
197
    }
198

            
199
    #[test]
200
    fn testvec_ntor1_kdf() {
201
        // From Tor's test_crypto.c; generated with ntor_ref.py
202
        fn expand(b: &[u8]) -> SecretBytes {
203
            let t_key = b"ntor-curve25519-sha256-1:key_extract";
204
            let m_expand = b"ntor-curve25519-sha256-1:key_expand";
205
            Ntor1Kdf::new(&t_key[..], &m_expand[..])
206
                .derive(b, 100)
207
                .unwrap()
208
        }
209

            
210
        let expect = hex!(
211
            "5521492a85139a8d9107a2d5c0d9c91610d0f95989975ebee6
212
             c02a4f8d622a6cfdf9b7c7edd3832e2760ded1eac309b76f8d
213
             66c4a3c4d6225429b3a016e3c3d45911152fc87bc2de9630c3
214
             961be9fdb9f93197ea8e5977180801926d3321fa21513e59ac"
215
        );
216
        assert_eq!(&expand(&b"Tor"[..])[..], &expect[..]);
217

            
218
        let brunner_quote = b"AN ALARMING ITEM TO FIND ON YOUR CREDIT-RATING STATEMENT";
219
        let expect = hex!(
220
            "a2aa9b50da7e481d30463adb8f233ff06e9571a0ca6ab6df0f
221
             b206fa34e5bc78d063fc291501beec53b36e5a0e434561200c
222
             5f8bd13e0f88b3459600b4dc21d69363e2895321c06184879d
223
             94b18f078411be70b767c7fc40679a9440a0c95ea83a23efbf"
224
        );
225
        assert_eq!(&expand(&brunner_quote[..])[..], &expect[..]);
226
    }
227

            
228
    #[test]
229
    fn testvec_shake_kdf() {
230
        // This is just one of the shake test vectors from tor-llcrypto
231
        let input = hex!(
232
            "76891a7bcc6c04490035b743152f64a8dd2ea18ab472b8d36ecf45
233
             858d0b0046"
234
        );
235
        let expected = hex!(
236
            "e8447df87d01beeb724c9a2a38ab00fcc24e9bd17860e673b02122
237
             2d621a7810e5d3"
238
        );
239

            
240
        let result = ShakeKdf::new().derive(&input[..], expected.len());
241
        assert_eq!(&result.unwrap()[..], &expected[..]);
242
    }
243
}