rb_mumble_protocol 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3e1daa7a8599ad332f998313a54453e096eb3cdb0a2e091028bc33e179945168
4
+ data.tar.gz: 24859982a247cdf5f3d9bfc03d163db347995aa9b7642650043737749ba0d43e
5
+ SHA512:
6
+ metadata.gz: d7f2032dd233a3714bca92ca14f2918b65bd89e1891f68881c720dcd741ea16764e589a72ab4fc293257424a6482420f252460cb0ae4092408f02eccc5def763
7
+ data.tar.gz: 31c1e2b93dbb4834023c98272090248b4633486e5a8fe7eeb48e6a6c2d09a207d5ad95cb4bf16588370d1d7da1581b31ffe182b03049b3761c4538881fff30d3
data/Cargo.lock ADDED
@@ -0,0 +1,385 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 3
4
+
5
+ [[package]]
6
+ name = "aho-corasick"
7
+ version = "1.0.2"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
10
+ dependencies = [
11
+ "memchr",
12
+ ]
13
+
14
+ [[package]]
15
+ name = "bindgen"
16
+ version = "0.62.0"
17
+ source = "registry+https://github.com/rust-lang/crates.io-index"
18
+ checksum = "c6720a8b7b2d39dd533285ed438d458f65b31b5c257e6ac7bb3d7e82844dd722"
19
+ dependencies = [
20
+ "bitflags",
21
+ "cexpr",
22
+ "clang-sys",
23
+ "lazy_static",
24
+ "lazycell",
25
+ "peeking_take_while",
26
+ "proc-macro2",
27
+ "quote",
28
+ "regex",
29
+ "rustc-hash",
30
+ "shlex",
31
+ "syn 1.0.109",
32
+ ]
33
+
34
+ [[package]]
35
+ name = "bitflags"
36
+ version = "1.3.2"
37
+ source = "registry+https://github.com/rust-lang/crates.io-index"
38
+ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
39
+
40
+ [[package]]
41
+ name = "bytes"
42
+ version = "1.4.0"
43
+ source = "registry+https://github.com/rust-lang/crates.io-index"
44
+ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
45
+
46
+ [[package]]
47
+ name = "cc"
48
+ version = "1.0.79"
49
+ source = "registry+https://github.com/rust-lang/crates.io-index"
50
+ checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
51
+
52
+ [[package]]
53
+ name = "cexpr"
54
+ version = "0.6.0"
55
+ source = "registry+https://github.com/rust-lang/crates.io-index"
56
+ checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
57
+ dependencies = [
58
+ "nom",
59
+ ]
60
+
61
+ [[package]]
62
+ name = "cfg-if"
63
+ version = "1.0.0"
64
+ source = "registry+https://github.com/rust-lang/crates.io-index"
65
+ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
66
+
67
+ [[package]]
68
+ name = "clang-sys"
69
+ version = "1.6.1"
70
+ source = "registry+https://github.com/rust-lang/crates.io-index"
71
+ checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
72
+ dependencies = [
73
+ "glob",
74
+ "libc",
75
+ "libloading",
76
+ ]
77
+
78
+ [[package]]
79
+ name = "foreign-types"
80
+ version = "0.3.2"
81
+ source = "registry+https://github.com/rust-lang/crates.io-index"
82
+ checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
83
+ dependencies = [
84
+ "foreign-types-shared",
85
+ ]
86
+
87
+ [[package]]
88
+ name = "foreign-types-shared"
89
+ version = "0.1.1"
90
+ source = "registry+https://github.com/rust-lang/crates.io-index"
91
+ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
92
+
93
+ [[package]]
94
+ name = "glob"
95
+ version = "0.3.1"
96
+ source = "registry+https://github.com/rust-lang/crates.io-index"
97
+ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
98
+
99
+ [[package]]
100
+ name = "lazy_static"
101
+ version = "1.4.0"
102
+ source = "registry+https://github.com/rust-lang/crates.io-index"
103
+ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
104
+
105
+ [[package]]
106
+ name = "lazycell"
107
+ version = "1.3.0"
108
+ source = "registry+https://github.com/rust-lang/crates.io-index"
109
+ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
110
+
111
+ [[package]]
112
+ name = "libc"
113
+ version = "0.2.147"
114
+ source = "registry+https://github.com/rust-lang/crates.io-index"
115
+ checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
116
+
117
+ [[package]]
118
+ name = "libloading"
119
+ version = "0.7.4"
120
+ source = "registry+https://github.com/rust-lang/crates.io-index"
121
+ checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
122
+ dependencies = [
123
+ "cfg-if",
124
+ "winapi",
125
+ ]
126
+
127
+ [[package]]
128
+ name = "magnus"
129
+ version = "0.4.4"
130
+ source = "registry+https://github.com/rust-lang/crates.io-index"
131
+ checksum = "fc87660cd7daa49fddbfd524c836de54d5c927d520cd163f43700c5087c57d6c"
132
+ dependencies = [
133
+ "magnus-macros",
134
+ "rb-sys",
135
+ "rb-sys-env",
136
+ ]
137
+
138
+ [[package]]
139
+ name = "magnus-macros"
140
+ version = "0.3.0"
141
+ source = "registry+https://github.com/rust-lang/crates.io-index"
142
+ checksum = "206cb23bfeea05180c97522ef6a3e52a4eb17b0ed2f30ee3ca9c4f994d2378ae"
143
+ dependencies = [
144
+ "proc-macro2",
145
+ "quote",
146
+ "syn 1.0.109",
147
+ ]
148
+
149
+ [[package]]
150
+ name = "memchr"
151
+ version = "2.5.0"
152
+ source = "registry+https://github.com/rust-lang/crates.io-index"
153
+ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
154
+
155
+ [[package]]
156
+ name = "minimal-lexical"
157
+ version = "0.2.1"
158
+ source = "registry+https://github.com/rust-lang/crates.io-index"
159
+ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
160
+
161
+ [[package]]
162
+ name = "nom"
163
+ version = "7.1.3"
164
+ source = "registry+https://github.com/rust-lang/crates.io-index"
165
+ checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
166
+ dependencies = [
167
+ "memchr",
168
+ "minimal-lexical",
169
+ ]
170
+
171
+ [[package]]
172
+ name = "once_cell"
173
+ version = "1.18.0"
174
+ source = "registry+https://github.com/rust-lang/crates.io-index"
175
+ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
176
+
177
+ [[package]]
178
+ name = "openssl"
179
+ version = "0.10.55"
180
+ source = "registry+https://github.com/rust-lang/crates.io-index"
181
+ checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d"
182
+ dependencies = [
183
+ "bitflags",
184
+ "cfg-if",
185
+ "foreign-types",
186
+ "libc",
187
+ "once_cell",
188
+ "openssl-macros",
189
+ "openssl-sys",
190
+ ]
191
+
192
+ [[package]]
193
+ name = "openssl-macros"
194
+ version = "0.1.1"
195
+ source = "registry+https://github.com/rust-lang/crates.io-index"
196
+ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
197
+ dependencies = [
198
+ "proc-macro2",
199
+ "quote",
200
+ "syn 2.0.27",
201
+ ]
202
+
203
+ [[package]]
204
+ name = "openssl-sys"
205
+ version = "0.9.90"
206
+ source = "registry+https://github.com/rust-lang/crates.io-index"
207
+ checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6"
208
+ dependencies = [
209
+ "cc",
210
+ "libc",
211
+ "pkg-config",
212
+ "vcpkg",
213
+ ]
214
+
215
+ [[package]]
216
+ name = "peeking_take_while"
217
+ version = "0.1.2"
218
+ source = "registry+https://github.com/rust-lang/crates.io-index"
219
+ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
220
+
221
+ [[package]]
222
+ name = "pkg-config"
223
+ version = "0.3.27"
224
+ source = "registry+https://github.com/rust-lang/crates.io-index"
225
+ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
226
+
227
+ [[package]]
228
+ name = "proc-macro2"
229
+ version = "1.0.66"
230
+ source = "registry+https://github.com/rust-lang/crates.io-index"
231
+ checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
232
+ dependencies = [
233
+ "unicode-ident",
234
+ ]
235
+
236
+ [[package]]
237
+ name = "quote"
238
+ version = "1.0.31"
239
+ source = "registry+https://github.com/rust-lang/crates.io-index"
240
+ checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
241
+ dependencies = [
242
+ "proc-macro2",
243
+ ]
244
+
245
+ [[package]]
246
+ name = "rb-sys"
247
+ version = "0.9.79"
248
+ source = "registry+https://github.com/rust-lang/crates.io-index"
249
+ checksum = "939fb78db3e4f26665c1d4c7b91ca66d3578335a19aba552d4a6445811d07072"
250
+ dependencies = [
251
+ "rb-sys-build",
252
+ ]
253
+
254
+ [[package]]
255
+ name = "rb-sys-build"
256
+ version = "0.9.79"
257
+ source = "registry+https://github.com/rust-lang/crates.io-index"
258
+ checksum = "335a95eb0420d52fa94ef12019df3c2c250c6b19cbb3c60bd05cb7e9c362072c"
259
+ dependencies = [
260
+ "bindgen",
261
+ "lazy_static",
262
+ "proc-macro2",
263
+ "quote",
264
+ "regex",
265
+ "shell-words",
266
+ "syn 1.0.109",
267
+ ]
268
+
269
+ [[package]]
270
+ name = "rb-sys-env"
271
+ version = "0.1.2"
272
+ source = "registry+https://github.com/rust-lang/crates.io-index"
273
+ checksum = "a35802679f07360454b418a5d1735c89716bde01d35b1560fc953c1415a0b3bb"
274
+
275
+ [[package]]
276
+ name = "rb_mumble_protocol"
277
+ version = "0.1.0"
278
+ dependencies = [
279
+ "bytes",
280
+ "magnus",
281
+ "openssl",
282
+ ]
283
+
284
+ [[package]]
285
+ name = "regex"
286
+ version = "1.9.1"
287
+ source = "registry+https://github.com/rust-lang/crates.io-index"
288
+ checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
289
+ dependencies = [
290
+ "aho-corasick",
291
+ "memchr",
292
+ "regex-automata",
293
+ "regex-syntax",
294
+ ]
295
+
296
+ [[package]]
297
+ name = "regex-automata"
298
+ version = "0.3.3"
299
+ source = "registry+https://github.com/rust-lang/crates.io-index"
300
+ checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
301
+ dependencies = [
302
+ "aho-corasick",
303
+ "memchr",
304
+ "regex-syntax",
305
+ ]
306
+
307
+ [[package]]
308
+ name = "regex-syntax"
309
+ version = "0.7.4"
310
+ source = "registry+https://github.com/rust-lang/crates.io-index"
311
+ checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
312
+
313
+ [[package]]
314
+ name = "rustc-hash"
315
+ version = "1.1.0"
316
+ source = "registry+https://github.com/rust-lang/crates.io-index"
317
+ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
318
+
319
+ [[package]]
320
+ name = "shell-words"
321
+ version = "1.1.0"
322
+ source = "registry+https://github.com/rust-lang/crates.io-index"
323
+ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
324
+
325
+ [[package]]
326
+ name = "shlex"
327
+ version = "1.1.0"
328
+ source = "registry+https://github.com/rust-lang/crates.io-index"
329
+ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
330
+
331
+ [[package]]
332
+ name = "syn"
333
+ version = "1.0.109"
334
+ source = "registry+https://github.com/rust-lang/crates.io-index"
335
+ checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
336
+ dependencies = [
337
+ "proc-macro2",
338
+ "quote",
339
+ "unicode-ident",
340
+ ]
341
+
342
+ [[package]]
343
+ name = "syn"
344
+ version = "2.0.27"
345
+ source = "registry+https://github.com/rust-lang/crates.io-index"
346
+ checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
347
+ dependencies = [
348
+ "proc-macro2",
349
+ "quote",
350
+ "unicode-ident",
351
+ ]
352
+
353
+ [[package]]
354
+ name = "unicode-ident"
355
+ version = "1.0.11"
356
+ source = "registry+https://github.com/rust-lang/crates.io-index"
357
+ checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
358
+
359
+ [[package]]
360
+ name = "vcpkg"
361
+ version = "0.2.15"
362
+ source = "registry+https://github.com/rust-lang/crates.io-index"
363
+ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
364
+
365
+ [[package]]
366
+ name = "winapi"
367
+ version = "0.3.9"
368
+ source = "registry+https://github.com/rust-lang/crates.io-index"
369
+ checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
370
+ dependencies = [
371
+ "winapi-i686-pc-windows-gnu",
372
+ "winapi-x86_64-pc-windows-gnu",
373
+ ]
374
+
375
+ [[package]]
376
+ name = "winapi-i686-pc-windows-gnu"
377
+ version = "0.4.0"
378
+ source = "registry+https://github.com/rust-lang/crates.io-index"
379
+ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
380
+
381
+ [[package]]
382
+ name = "winapi-x86_64-pc-windows-gnu"
383
+ version = "0.4.0"
384
+ source = "registry+https://github.com/rust-lang/crates.io-index"
385
+ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
data/Cargo.toml ADDED
@@ -0,0 +1,7 @@
1
+ # This Cargo.toml is here to let externals tools (IDEs, etc.) know that this is
2
+ # a Rust project. Your extensions dependencies should be added to the Cargo.toml
3
+ # in the ext/ directory.
4
+
5
+ [workspace]
6
+ members = ["./ext/rb_mumble_protocol"]
7
+ # resolver = "2"
@@ -0,0 +1,14 @@
1
+ [package]
2
+ name = "rb_mumble_protocol"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ authors = ["Mikhail Odebe <derpiranha@gmail.com>"]
6
+ publish = false
7
+
8
+ [lib]
9
+ crate-type = ["cdylib"]
10
+
11
+ [dependencies]
12
+ magnus = { version = "0.4" }
13
+ bytes = "1.0"
14
+ openssl = { version = "0.10" }
@@ -0,0 +1,456 @@
1
+ //! Implementation of the cryptography used for Mumble's voice channel
2
+
3
+ use bytes::BytesMut;
4
+ use openssl::memcmp;
5
+ use openssl::rand::rand_bytes;
6
+
7
+ /// Maximum size of an encrypted Mumble packet.
8
+ /// Note that larger packets can be produced if there is sufficient voice data in one packet but
9
+ /// there's no guarantee that the remote end will not just drop it.
10
+ pub const MAX_PACKET_SIZE: usize = 1024;
11
+ /// Size in bytes of the AES key used in `CryptState`.
12
+ pub const KEY_SIZE: usize = 16;
13
+ /// Size in bytes of blocks for the AES primitive.
14
+ pub const BLOCK_SIZE: usize = std::mem::size_of::<u128>();
15
+
16
+ /// Implements OCB2-AES128 for encryption and authentication of the voice packets
17
+ /// when transmitted over UDP.
18
+ /// Also provides statistics about good, late and lost packets.
19
+ ///
20
+ /// Note that OCB is covered by patents, however a license has been granted for use in "most"
21
+ /// software. See: http://web.cs.ucdavis.edu/~rogaway/ocb/license.htm
22
+ ///
23
+ /// Based on https://github.com/mumble-voip/mumble/blob/e31d267a11b4ed0597ad41309a7f6b715837141f/src/CryptState.cpp
24
+ #[derive(Debug)]
25
+ pub struct CryptState {
26
+ key: [u8; KEY_SIZE],
27
+
28
+ // internally as native endianness, externally as little endian and during ocb_* as big endian
29
+ encrypt_nonce: u128,
30
+ decrypt_nonce: u128,
31
+ decrypt_history: [u8; 0x100],
32
+
33
+ good: u32,
34
+ late: u32,
35
+ lost: u32,
36
+ }
37
+
38
+ /// The reason a decrypt operation failed.
39
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
40
+ pub enum DecryptError {
41
+ /// The packet is too short to be decrypted
42
+ Eof,
43
+ /// The packet has already been decrypted previously.
44
+ Repeat,
45
+ /// The packet was far too late.
46
+ Late,
47
+ /// The MAC of the decrypted packet did not match.
48
+ ///
49
+ /// This may also indicate a substantial de-sync of the decryption nonce.
50
+ Mac,
51
+ }
52
+
53
+ impl CryptState {
54
+ /// Creates a new CryptState with randomly generated key and initial encrypt- and decrypt-nonce.
55
+ pub fn generate_new() -> Self {
56
+ let mut key = [0; KEY_SIZE];
57
+ rand_bytes(&mut key).unwrap();
58
+
59
+ CryptState {
60
+ key,
61
+
62
+ encrypt_nonce: 0,
63
+ decrypt_nonce: 1 << 127,
64
+ decrypt_history: [0; 0x100],
65
+
66
+ good: 0,
67
+ late: 0,
68
+ lost: 0,
69
+ }
70
+ }
71
+
72
+ pub fn make_new(&self) -> Self {
73
+ CryptState {
74
+ key: self.key.clone(),
75
+
76
+ encrypt_nonce: self.encrypt_nonce.clone(),
77
+ decrypt_nonce: self.decrypt_nonce.clone(),
78
+ decrypt_history: [0; 0x100],
79
+
80
+ good: 0,
81
+ late: 0,
82
+ lost: 0,
83
+ }
84
+ }
85
+
86
+ /// Creates a new CryptState from previously generated key, encrypt- and decrypt-nonce.
87
+ pub fn new_from(
88
+ key: [u8; KEY_SIZE],
89
+ encrypt_nonce: [u8; BLOCK_SIZE],
90
+ decrypt_nonce: [u8; BLOCK_SIZE],
91
+ ) -> Self {
92
+ CryptState {
93
+ key,
94
+
95
+ encrypt_nonce: u128::from_le_bytes(encrypt_nonce),
96
+ decrypt_nonce: u128::from_le_bytes(decrypt_nonce),
97
+ decrypt_history: [0; 0x100],
98
+
99
+ good: 0,
100
+ late: 0,
101
+ lost: 0,
102
+ }
103
+ }
104
+
105
+ /// Returns the amount of packets transmitted without issues.
106
+ pub fn get_good(&self) -> u32 {
107
+ self.good
108
+ }
109
+
110
+ /// Returns the amount of packets which were transmitted successfully but arrived late.
111
+ pub fn get_late(&self) -> u32 {
112
+ self.late
113
+ }
114
+
115
+ /// Returns the amount of packets which were lost.
116
+ pub fn get_lost(&self) -> u32 {
117
+ self.lost
118
+ }
119
+
120
+ /// Returns the shared, **private** key.
121
+ pub fn get_key(&self) -> &[u8; KEY_SIZE] {
122
+ &self.key
123
+ }
124
+
125
+ /// Returns the nonce used for encrypting.
126
+ pub fn get_encrypt_nonce(&self) -> [u8; BLOCK_SIZE] {
127
+ self.encrypt_nonce.to_le_bytes()
128
+ }
129
+
130
+ /// Returns the nonce used for decrypting.
131
+ pub fn get_decrypt_nonce(&self) -> [u8; BLOCK_SIZE] {
132
+ self.decrypt_nonce.to_le_bytes()
133
+ }
134
+
135
+ /// Updates the nonce used for decrypting.
136
+ pub fn set_decrypt_nonce(&mut self, nonce: &[u8; BLOCK_SIZE]) {
137
+ self.decrypt_nonce = u128::from_le_bytes(*nonce);
138
+ }
139
+
140
+ /// Encrypts an encoded voice packet and returns the resulting bytes.
141
+ pub fn encrypt(&mut self, src: Vec<u8>, dst: &mut BytesMut) {
142
+ self.encrypt_nonce = self.encrypt_nonce.wrapping_add(1);
143
+
144
+ // Leave four bytes for header
145
+ dst.resize(4, 0);
146
+ let mut inner = dst.split_off(4);
147
+
148
+ // Copy source bytes
149
+ inner.extend_from_slice(&src);
150
+
151
+ // Encryption
152
+ let tag = self.ocb_encrypt(inner.as_mut());
153
+
154
+ // Build result
155
+ dst.unsplit(inner);
156
+ dst[0] = self.encrypt_nonce as u8;
157
+ dst[1..4].copy_from_slice(&tag.to_be_bytes()[0..3]);
158
+ }
159
+
160
+ /// Decrypts a voice packet and (if successful) returns the resulting bytes.
161
+ pub fn decrypt(&mut self, buf: &mut BytesMut) -> Result<(), DecryptError> {
162
+ if buf.len() < 4 {
163
+ return Err(DecryptError::Eof);
164
+ }
165
+ let header = buf.split_to(4);
166
+ let nonce_0 = header[0];
167
+
168
+ // If we update our decrypt_nonce and the tag check fails or we've been processing late
169
+ // packets, we need to revert it
170
+ let saved_nonce = self.decrypt_nonce;
171
+ let mut late = false; // will always restore nonce if this is the case
172
+ let mut lost = 0; // for stats only
173
+
174
+ if self.decrypt_nonce.wrapping_add(1) as u8 == nonce_0 {
175
+ // in order
176
+ self.decrypt_nonce = self.decrypt_nonce.wrapping_add(1);
177
+ } else {
178
+ // packet is late or repeated, or we lost a few packets in between
179
+ let diff = nonce_0.wrapping_sub(self.decrypt_nonce as u8) as i8;
180
+
181
+ self.decrypt_nonce = self.decrypt_nonce.wrapping_add(diff as u128);
182
+ if diff > 0 {
183
+ lost = i32::from(diff - 1); // lost a few packets in between this and the last one
184
+ } else if diff > -30 {
185
+ if self.decrypt_history[nonce_0 as usize] == (self.decrypt_nonce >> 8) as u8 {
186
+ self.decrypt_nonce = saved_nonce;
187
+ return Err(DecryptError::Repeat);
188
+ }
189
+ // just late
190
+ late = true;
191
+ lost = -1;
192
+ } else {
193
+ return Err(DecryptError::Late); // late by more than 30 packets
194
+ }
195
+ }
196
+
197
+ let tag = self.ocb_decrypt(buf.as_mut());
198
+
199
+ if !memcmp::eq(&tag.to_be_bytes()[0..3], &header[1..4]) {
200
+ self.decrypt_nonce = saved_nonce;
201
+ return Err(DecryptError::Mac);
202
+ }
203
+
204
+ self.decrypt_history[nonce_0 as usize] = (self.decrypt_nonce >> 8) as u8;
205
+
206
+ self.good += 1;
207
+ if late {
208
+ self.late += 1;
209
+ self.decrypt_nonce = saved_nonce;
210
+ }
211
+ self.lost = (self.lost as i32 + lost as i32) as u32;
212
+
213
+ Ok(())
214
+ }
215
+
216
+ /// Encrypt the provided buffer using AES-OCB, returning the tag.
217
+ fn ocb_encrypt(&self, mut buf: &mut [u8]) -> u128 {
218
+ let mut offset = self.aes_encrypt(self.encrypt_nonce.to_be());
219
+ let mut checksum = 0u128;
220
+
221
+ while buf.len() > BLOCK_SIZE {
222
+ let (chunk, remainder) = buf.split_at_mut(BLOCK_SIZE);
223
+ buf = remainder;
224
+ let chunk: &mut [u8; BLOCK_SIZE] = chunk.try_into().expect("split_at works");
225
+
226
+ offset = s2(offset);
227
+
228
+ let plain = u128::from_be_bytes(*chunk);
229
+ let encrypted = self.aes_encrypt(offset ^ plain) ^ offset;
230
+ chunk.copy_from_slice(&encrypted.to_be_bytes());
231
+
232
+ checksum ^= plain;
233
+ }
234
+
235
+ offset = s2(offset);
236
+
237
+ let len = buf.len();
238
+ assert!(len <= BLOCK_SIZE);
239
+ let pad = self.aes_encrypt((len * 8) as u128 ^ offset);
240
+ let mut block = pad.to_be_bytes();
241
+ block[..len].copy_from_slice(buf);
242
+ let plain = u128::from_be_bytes(block);
243
+ let encrypted = pad ^ plain;
244
+ buf.copy_from_slice(&encrypted.to_be_bytes()[..len]);
245
+
246
+ checksum ^= plain;
247
+
248
+ self.aes_encrypt(offset ^ s2(offset) ^ checksum)
249
+ }
250
+
251
+ /// Decrypt the provided buffer using AES-OCB, returning the tag.
252
+ /// **Make sure to verify that the tag matches!**
253
+ fn ocb_decrypt(&self, mut buf: &mut [u8]) -> u128 {
254
+ let mut offset = self.aes_encrypt(self.decrypt_nonce.to_be());
255
+ let mut checksum = 0u128;
256
+
257
+ while buf.len() > BLOCK_SIZE {
258
+ let (chunk, remainder) = buf.split_at_mut(BLOCK_SIZE);
259
+ buf = remainder;
260
+ let chunk: &mut [u8; BLOCK_SIZE] = chunk.try_into().expect("split_at works");
261
+
262
+ offset = s2(offset);
263
+
264
+ let encrypted = u128::from_be_bytes(*chunk);
265
+ let plain = self.aes_decrypt(offset ^ encrypted) ^ offset;
266
+ chunk.copy_from_slice(&plain.to_be_bytes());
267
+
268
+ checksum ^= plain;
269
+ }
270
+
271
+ offset = s2(offset);
272
+
273
+ let len = buf.len();
274
+ assert!(len <= BLOCK_SIZE);
275
+ let pad = self.aes_encrypt((len * 8) as u128 ^ offset);
276
+ let mut block = [0; BLOCK_SIZE];
277
+ block[..len].copy_from_slice(buf);
278
+ let plain = u128::from_be_bytes(block) ^ pad;
279
+ buf.copy_from_slice(&plain.to_be_bytes()[..len]);
280
+
281
+ checksum ^= plain;
282
+
283
+ self.aes_encrypt(offset ^ s2(offset) ^ checksum)
284
+ }
285
+
286
+ /// AES-128 encryption primitive.
287
+ fn aes_encrypt(&self, block: u128) -> u128 {
288
+ // TODO is there no better way to do this (and aes_decrypt)?
289
+ let mut result = [0u8; BLOCK_SIZE * 2];
290
+ let mut crypter = openssl::symm::Crypter::new(
291
+ openssl::symm::Cipher::aes_128_ecb(),
292
+ openssl::symm::Mode::Encrypt,
293
+ &self.key,
294
+ None,
295
+ )
296
+ .unwrap();
297
+ crypter.pad(false);
298
+ crypter.update(&block.to_be_bytes(), &mut result).unwrap();
299
+ crypter.finalize(&mut result).unwrap();
300
+ u128::from_be_bytes((&result[..BLOCK_SIZE]).try_into().unwrap())
301
+ }
302
+
303
+ /// AES-128 decryption primitive.
304
+ fn aes_decrypt(&self, block: u128) -> u128 {
305
+ let mut result = [0u8; BLOCK_SIZE * 2];
306
+ let mut crypter = openssl::symm::Crypter::new(
307
+ openssl::symm::Cipher::aes_128_ecb(),
308
+ openssl::symm::Mode::Decrypt,
309
+ &self.key,
310
+ None,
311
+ )
312
+ .unwrap();
313
+ crypter.pad(false);
314
+ crypter.update(&block.to_be_bytes(), &mut result).unwrap();
315
+ crypter.finalize(&mut result).unwrap();
316
+ u128::from_be_bytes((&result[..BLOCK_SIZE]).try_into().unwrap())
317
+ }
318
+ }
319
+
320
+ fn s2(block: u128) -> u128 {
321
+ let rot = block.rotate_left(1);
322
+ let carry = rot & 1;
323
+ rot ^ (carry * 0x86)
324
+ }
325
+
326
+ #[cfg(test)]
327
+ mod test {
328
+ use bytes::BufMut;
329
+
330
+ use super::*;
331
+
332
+ fn u128hex(src: &str) -> u128 {
333
+ u128::from_str_radix(src, 16).unwrap()
334
+ }
335
+
336
+ fn bytes_from_hex(src: &str) -> BytesMut {
337
+ let mut buf = BytesMut::new();
338
+ hex_to_bytes(src, &mut buf);
339
+ buf
340
+ }
341
+
342
+ fn hex_to_bytes(src: &str, dst: &mut BytesMut) {
343
+ dst.clear();
344
+ dst.reserve(src.len() / 2);
345
+ let mut iter = src.chars();
346
+ while !iter.as_str().is_empty() {
347
+ dst.put_u8(u8::from_str_radix(&iter.as_str()[..2], 16).unwrap());
348
+ iter.next();
349
+ iter.next();
350
+ }
351
+ }
352
+
353
+ #[test]
354
+ fn encrypt_and_decrypt_are_inverse() {
355
+ let mut server_state = CryptState::generate_new();
356
+ // swap nonce vectors side to side
357
+ let mut client_state =
358
+ CryptState::new_from(
359
+ *server_state.get_key(),
360
+ server_state.get_decrypt_nonce(),
361
+ server_state.get_encrypt_nonce(),
362
+ );
363
+
364
+ let mut buffer = BytesMut::new();
365
+ let src = "test".as_bytes().to_vec();
366
+ server_state.encrypt(src.clone(), &mut buffer);
367
+
368
+ let mut buffer2 = BytesMut::new();
369
+ buffer2.extend_from_slice(&(buffer.to_vec()));
370
+
371
+ client_state.decrypt(&mut buffer2).unwrap();
372
+
373
+ assert_eq!(src, buffer2.to_vec());
374
+ }
375
+
376
+ #[test]
377
+ fn aes_test_vectors() {
378
+ let key = u128hex("E8E9EAEBEDEEEFF0F2F3F4F5F7F8F9FA");
379
+ let state =
380
+ CryptState::new_from(key.to_be_bytes(), Default::default(), Default::default());
381
+ assert_eq!(
382
+ u128hex("6743C3D1519AB4F2CD9A78AB09A511BD"),
383
+ state.aes_encrypt(u128hex("014BAF2278A69D331D5180103643E99A"))
384
+ );
385
+ assert_eq!(
386
+ u128hex("014BAF2278A69D331D5180103643E99A"),
387
+ state.aes_decrypt(u128hex("6743C3D1519AB4F2CD9A78AB09A511BD"))
388
+ );
389
+ }
390
+
391
+ // Test vectors from http://web.cs.ucdavis.edu/~rogaway/papers/draft-krovetz-ocb-00.txt
392
+ // (excluding ones with headers since those aren't implemented here)
393
+ #[test]
394
+ #[allow(clippy::cognitive_complexity)] // all macro-generated
395
+ fn ocb_test_vectors() {
396
+ macro_rules! test_cases {
397
+ ($(
398
+ T : $name:expr,
399
+ M : $plain:expr,
400
+ C : $cipher:expr,
401
+ T : $tag:expr,
402
+ )*) => {$(
403
+ let key = u128hex("000102030405060708090a0b0c0d0e0f");
404
+ let nonce = u128hex("000102030405060708090a0b0c0d0e0f");
405
+ let state = CryptState::new_from(
406
+ key.to_be_bytes(),
407
+ nonce.to_be_bytes(),
408
+ nonce.to_be_bytes(),
409
+ );
410
+
411
+ let mut result = BytesMut::new();
412
+ hex_to_bytes($plain.as_ref(), &mut result);
413
+ let tag = state.ocb_encrypt(&mut result);
414
+ assert_eq!(bytes_from_hex($cipher), result, concat!("ENCRYPT-RESULT-", $name));
415
+ assert_eq!(u128hex($tag), tag, concat!("ENCRYPT-TAG-", $name));
416
+
417
+ hex_to_bytes($cipher.as_ref(), &mut result);
418
+ let tag = state.ocb_decrypt(&mut result);
419
+ assert_eq!(bytes_from_hex($plain), result, concat!("DECRYPT-RESULT-", $name));
420
+ assert_eq!(u128hex($tag), tag, concat!("DECRYPT-TAG-", $name));
421
+ )*};
422
+ }
423
+
424
+ test_cases! {
425
+ T : "OCB-AES-128-0B",
426
+ M : "",
427
+ C : "",
428
+ T : "BF3108130773AD5EC70EC69E7875A7B0",
429
+
430
+ T : "OCB-AES-128-8B",
431
+ M : "0001020304050607",
432
+ C : "C636B3A868F429BB",
433
+ T : "A45F5FDEA5C088D1D7C8BE37CABC8C5C",
434
+
435
+ T : "OCB-AES-128-16B",
436
+ M : "000102030405060708090A0B0C0D0E0F",
437
+ C : "52E48F5D19FE2D9869F0C4A4B3D2BE57",
438
+ T : "F7EE49AE7AA5B5E6645DB6B3966136F9",
439
+
440
+ T : "OCB-AES-128-24B",
441
+ M : "000102030405060708090A0B0C0D0E0F1011121314151617",
442
+ C : "F75D6BC8B4DC8D66B836A2B08B32A636CC579E145D323BEB",
443
+ T : "A1A50F822819D6E0A216784AC24AC84C",
444
+
445
+ T : "OCB-AES-128-32B",
446
+ M : "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
447
+ C : "F75D6BC8B4DC8D66B836A2B08B32A636CEC3C555037571709DA25E1BB0421A27",
448
+ T : "09CA6C73F0B5C6C5FD587122D75F2AA3",
449
+
450
+ T : "OCB-AES-128-40B",
451
+ M : "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627",
452
+ C : "F75D6BC8B4DC8D66B836A2B08B32A6369F1CD3C5228D79FD6C267F5F6AA7B231C7DFB9D59951AE9C",
453
+ T : "9DB0CDF880F73E3E10D4EB3217766688",
454
+ }
455
+ }
456
+ }
@@ -0,0 +1,113 @@
1
+ use std::cell::RefCell;
2
+
3
+ use magnus::{class, define_module, method, function, prelude::*, Error, RHash};
4
+
5
+ use bytes::BytesMut;
6
+
7
+ pub mod crypt_state;
8
+
9
+ #[magnus::wrap(class = "RbMumbleProtocol::CryptState", free_immediatly, size)]
10
+ struct CryptStateRef(RefCell<crypt_state::CryptState>);
11
+
12
+ impl CryptStateRef {
13
+ pub fn new() -> Self {
14
+ Self(RefCell::new(crypt_state::CryptState::generate_new()))
15
+ }
16
+
17
+ pub fn new_from(
18
+ key: Vec<u8>,
19
+ encrypt_nonce: Vec<u8>,
20
+ decrypt_nonce: Vec<u8>,
21
+ ) -> Self {
22
+ let new_key =
23
+ key
24
+ .try_into()
25
+ .unwrap_or_else(|v: Vec<u8>| panic!("Expected a Key of length {} but it was {}", 16, v.len()));
26
+
27
+ let new_encrypt_nonce =
28
+ encrypt_nonce
29
+ .try_into()
30
+ .unwrap_or_else(|v: Vec<u8>| panic!("Expected a Encrypt nonce of length {} but it was {}", 16, v.len()));
31
+
32
+ let new_decrypt_nonce =
33
+ decrypt_nonce
34
+ .try_into()
35
+ .unwrap_or_else(|v: Vec<u8>| panic!("Expected a Decrypt nonce of length {} but it was {}", 16, v.len()));
36
+
37
+ let new_state = crypt_state::CryptState::new_from(
38
+ new_key,
39
+ new_encrypt_nonce,
40
+ new_decrypt_nonce
41
+ );
42
+
43
+ Self(RefCell::new(new_state))
44
+ }
45
+
46
+ pub fn key(&self) -> Vec<u8> { self.0.try_borrow_mut().unwrap().get_key().to_vec() }
47
+ pub fn encrypt_nonce(&self) -> Vec<u8> { self.0.try_borrow_mut().unwrap().get_encrypt_nonce().to_vec() }
48
+ pub fn decrypt_nonce(&self) -> Vec<u8> { self.0.try_borrow_mut().unwrap().get_decrypt_nonce().to_vec() }
49
+
50
+ pub fn stats(&self) -> RHash {
51
+ let hash = RHash::new();
52
+ let state = self.0.try_borrow_mut().unwrap();
53
+
54
+ let _ = hash.aset("good", state.get_good());
55
+ let _ = hash.aset("late", state.get_late());
56
+ let _ = hash.aset("lost", state.get_lost());
57
+
58
+ hash
59
+ }
60
+
61
+ pub fn encrypt(&self, src: Vec<u8>) -> Vec<u8> {
62
+ let mut buffer = BytesMut::new();
63
+
64
+ self.0.try_borrow_mut().unwrap().encrypt(src, &mut buffer);
65
+
66
+ buffer.to_vec()
67
+ }
68
+
69
+ pub fn decrypt(&self, encrypted: Vec<u8>) -> Vec<u8> {
70
+ let mut buffer = BytesMut::new();
71
+ buffer.extend_from_slice(&encrypted);
72
+
73
+ self.0.try_borrow_mut().unwrap().decrypt(&mut buffer).unwrap();
74
+
75
+ buffer.to_vec()
76
+ }
77
+ }
78
+
79
+ #[magnus::init]
80
+ fn init() -> Result<(), Error> {
81
+ let module = define_module("RbMumbleProtocol")?;
82
+ let class = module.define_class("CryptState", class::object())?;
83
+
84
+ class.define_singleton_method("new", function!(CryptStateRef::new, 0))?;
85
+ class.define_singleton_method("new_from", function!(CryptStateRef::new_from, 3))?;
86
+
87
+ class.define_method("key", method!(CryptStateRef::key, 0))?;
88
+ class.define_method("encrypt_nonce", method!(CryptStateRef::encrypt_nonce, 0))?;
89
+ class.define_method("decrypt_nonce", method!(CryptStateRef::decrypt_nonce, 0))?;
90
+ class.define_method("stats", method!(CryptStateRef::stats, 0))?;
91
+
92
+ class.define_method("encrypt", method!(CryptStateRef::encrypt, 1))?;
93
+ class.define_method("decrypt", method!(CryptStateRef::decrypt, 1))?;
94
+
95
+ Ok(())
96
+ }
97
+
98
+ #[test]
99
+ fn encrypt_and_decrypt_are_inverse() {
100
+ let server_state = CryptStateRef::new();
101
+ // swap nonce vectors side to side
102
+ let client_state = CryptStateRef::new_from(
103
+ server_state.key(),
104
+ server_state.decrypt_nonce(),
105
+ server_state.encrypt_nonce()
106
+ );
107
+
108
+ let src= "test".as_bytes().to_vec();
109
+ let encrypted= server_state.encrypt(src.clone());
110
+ let result= client_state.decrypt(encrypted.clone());
111
+
112
+ assert_eq!(src, result);
113
+ }
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RbMumbleProtocol
4
+ class CryptState
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RbMumbleProtocol
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rb_mumble_protocol/version"
4
+ require_relative "rb_mumble_protocol/crypt_state"
5
+ require_relative "rb_mumble_protocol/rb_mumble_protocol"
6
+
7
+ module RbMumbleProtocol
8
+ class Error < StandardError; end
9
+ # Your code goes here...
10
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rb_mumble_protocol
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mikhail Odebe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-07-23 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Gem that providing classes for implementing Mumble-related projects.
14
+ email:
15
+ - derpiranha@gmail.com
16
+ executables: []
17
+ extensions:
18
+ - ext/rb_mumble_protocol/Cargo.toml
19
+ extra_rdoc_files: []
20
+ files:
21
+ - Cargo.lock
22
+ - Cargo.toml
23
+ - ext/rb_mumble_protocol/Cargo.toml
24
+ - ext/rb_mumble_protocol/src/crypt_state.rs
25
+ - ext/rb_mumble_protocol/src/lib.rs
26
+ - lib/rb_mumble_protocol.rb
27
+ - lib/rb_mumble_protocol/crypt_state.rb
28
+ - lib/rb_mumble_protocol/version.rb
29
+ homepage: https://github.com/Odebe/rb_mumble_protocol
30
+ licenses: []
31
+ metadata:
32
+ homepage_uri: https://github.com/Odebe/rb_mumble_protocol
33
+ source_code_uri: https://github.com/Odebe/rb_mumble_protocol
34
+ changelog_uri: https://github.com/Odebe/rb_mumble_protocol
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 2.6.0
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: 3.3.11
49
+ requirements: []
50
+ rubygems_version: 3.4.10
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: Gem that providing classes for implementing Mumble-related projects.
54
+ test_files: []