rb_mumble_protocol 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []