1use crate::crypto::*;
3use anyhow::{ensure, Result};
4use openssl::{memcmp, nid::Nid};
5use serde::{Deserialize, Serialize};
6use std::{
7 io::{Read, Write},
8 net::TcpStream,
9 time::{Duration, Instant},
10};
11use zeroize::Zeroize;
12
13pub const VERSION: u16 = 0xf104;
14pub struct Cursor<'a> {
15 bytes: &'a [u8],
16 offset: usize,
17}
18impl<'a> Cursor<'a> {
19 pub fn new(bytes: &'a [u8]) -> Self {
20 Self { bytes, offset: 0 }
21 }
22 pub fn take(&mut self, n: usize) -> Result<&'a [u8]> {
23 ensure!(n <= self.bytes.len() - self.offset, "Truncated structure");
24 let b = &self.bytes[self.offset..self.offset + n];
25 self.offset += n;
26 Ok(b)
27 }
28 pub fn uint(&mut self, n: usize) -> Result<u64> {
29 ensure!(n <= 8, "Integer too wide");
30 Ok(self.take(n)?.iter().fold(0, |a, &b| (a << 8) | b as u64))
31 }
32 pub fn sized(&mut self, width: usize) -> Result<&'a [u8]> {
33 let n = usize::try_from(self.uint(width)?)?;
34 self.take(n)
35 }
36 pub fn end(&self) -> Result<()> {
37 ensure!(self.offset == self.bytes.len(), "Trailing structure bytes");
38 Ok(())
39 }
40}
41pub fn section(b: &[u8]) -> Vec<u8> {
42 [(b.len() as u32).to_be_bytes().as_slice(), b].concat()
43}
44fn sized(b: &[u8]) -> Result<Vec<u8>> {
45 ensure!(b.len() <= u16::MAX as usize, "Section too large");
46 Ok([(b.len() as u16).to_be_bytes().as_slice(), b].concat())
47}
48#[derive(Clone)]
49pub struct Record {
50 pub header: [u8; 5],
51 pub body: Vec<u8>,
52}
53pub fn frame(kind: u8, body: &[u8]) -> Result<Vec<u8>> {
54 ensure!(
55 (0x15..=0x19).contains(&kind) && body.len() <= 65535,
56 "Invalid MMTLS record"
57 );
58 Ok([
59 vec![kind],
60 VERSION.to_be_bytes().to_vec(),
61 (body.len() as u16).to_be_bytes().to_vec(),
62 body.to_vec(),
63 ]
64 .concat())
65}
66pub fn nonce(iv: &[u8], sequence: u64) -> Result<[u8; 12]> {
67 ensure!(iv.len() == 12, "Invalid IV");
68 let mut b: [u8; 12] = iv.try_into()?;
69 for (i, v) in sequence.to_be_bytes().iter().enumerate() {
70 b[i + 4] ^= v;
71 }
72 Ok(b)
73}
74pub fn seal(kind: u8, plain: &[u8], key: &[u8], iv: &[u8], sequence: u64) -> Result<Vec<u8>> {
75 ensure!(plain.len() <= 65519, "MMTLS plaintext too large");
76 let header = frame(kind, &vec![0; plain.len() + 16])?[..5].to_vec();
77 let aad = [sequence.to_be_bytes().as_slice(), &header].concat();
78 let (cipher, tag) = gcm_seal(key, &nonce(iv, sequence)?, &aad, plain)?;
79 Ok([header, cipher, tag.to_vec()].concat())
80}
81pub fn open(record: &Record, key: &[u8], iv: &[u8], sequence: u64) -> Result<Vec<u8>> {
82 ensure!(record.body.len() >= 16, "Missing authentication tag");
83 let n = record.body.len() - 16;
84 gcm_open(
85 key,
86 &nonce(iv, sequence)?,
87 &[sequence.to_be_bytes().as_slice(), &record.header].concat(),
88 &record.body[..n],
89 &record.body[n..],
90 )
91}
92struct Traffic {
93 client_key: Vec<u8>,
94 server_key: Vec<u8>,
95 client_iv: Vec<u8>,
96 server_iv: Vec<u8>,
97}
98impl Drop for Traffic {
99 fn drop(&mut self) {
100 self.client_key.zeroize();
101 self.server_key.zeroize();
102 self.client_iv.zeroize();
103 self.server_iv.zeroize();
104 }
105}
106fn traffic(secret: &[u8], label: &str, transcript: &[u8]) -> Result<Traffic> {
107 let mut b = expand(
108 secret,
109 &[label.as_bytes(), &sha256(transcript)].concat(),
110 56,
111 )?;
112 let t = Traffic {
113 client_key: b[..16].to_vec(),
114 server_key: b[16..32].to_vec(),
115 client_iv: b[32..44].to_vec(),
116 server_iv: b[44..].to_vec(),
117 };
118 b.zeroize();
119 Ok(t)
120}
121fn signed_payload(bytes: &[u8], kind: u8) -> Result<Vec<u8>> {
122 let mut top = Cursor::new(bytes);
123 let mut body = Cursor::new(top.sized(4)?);
124 top.end()?;
125 ensure!(body.uint(1)? == kind as u64, "Unexpected handshake message");
126 let value = body.sized(2)?.to_vec();
127 body.end()?;
128 Ok(value)
129}
130
131#[derive(Clone, Serialize, Deserialize)]
132pub struct Ticket {
133 pub kind: u8,
134 pub lifetime: u32,
135 pub reserved: u32,
136 pub nonce: Vec<u8>,
137 pub opaque: Vec<u8>,
138}
139#[derive(Clone, Serialize, Deserialize)]
140pub struct ShortSession {
141 pub access_psk: Vec<u8>,
142 pub ticket: Ticket,
143}
144impl Drop for ShortSession {
145 fn drop(&mut self) {
146 self.access_psk.zeroize();
147 self.ticket.opaque.zeroize();
148 self.ticket.nonce.zeroize();
149 }
150}
151pub struct Session {
152 socket: TcpStream,
153 deadline: Instant,
154 received: usize,
155 receive_budget: usize,
156 pub short: ShortSession,
157 pub certificate_region: u32,
158 pub certificate_version: u32,
159 traffic: Traffic,
160 outgoing: u64,
161 incoming: u64,
162 record_pending: Vec<u8>,
163 application_pending: LonglinkDecoder,
164}
165
166fn host_valid(name: &str) -> bool {
167 !name.is_empty()
168 && name.len() <= 253
169 && name.is_ascii()
170 && name.split('.').all(|s| {
171 !s.is_empty()
172 && s.len() <= 63
173 && s.bytes()
174 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == b'-')
175 && !s.starts_with('-')
176 && !s.ends_with('-')
177 })
178 && (name.ends_with(".wechat.com") || name.ends_with(".weixin.qq.com"))
179}
180pub fn validate_host(name: &str) -> Result<()> {
181 ensure!(host_valid(name), "Unsupported native server host");
182 Ok(())
183}
184fn read_exact_deadline(
185 socket: &mut TcpStream,
186 mut bytes: &mut [u8],
187 deadline: Instant,
188) -> Result<()> {
189 while !bytes.is_empty() {
190 let remaining = deadline.saturating_duration_since(Instant::now());
191 ensure!(!remaining.is_zero(), "MMTLS deadline exceeded");
192 socket.set_read_timeout(Some(remaining))?;
193 match socket.read(bytes) {
194 Ok(0) => anyhow::bail!("MMTLS connection closed"),
195 Ok(n) => bytes = &mut bytes[n..],
196 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
197 Err(e) => return Err(e.into()),
198 }
199 }
200 Ok(())
201}
202fn read_record(
203 socket: &mut TcpStream,
204 deadline: Instant,
205 received: &mut usize,
206 budget: usize,
207) -> Result<Record> {
208 let mut h = [0; 5];
209 read_exact_deadline(socket, &mut h, deadline)?;
210 ensure!(
211 (0x15..=0x19).contains(&h[0]) && u16::from_be_bytes([h[1], h[2]]) == VERSION,
212 "Unexpected MMTLS header"
213 );
214 let n = u16::from_be_bytes([h[3], h[4]]) as usize;
215 *received = received
216 .checked_add(n + 5)
217 .ok_or_else(|| anyhow::anyhow!("Receive overflow"))?;
218 ensure!(*received <= budget, "MMTLS receive budget exceeded");
219 let mut body = vec![0; n];
220 read_exact_deadline(socket, &mut body, deadline)?;
221 ensure!(h[0] != 0x15, "Server returned MMTLS alert");
222 Ok(Record { header: h, body })
223}
224
225impl Session {
226 pub fn connect(host: &str, timeout: Duration, receive_budget: usize) -> Result<Self> {
227 validate_host(host)?;
228 ensure!(
229 (1024..=crate::MAX_MESSAGE_BYTES).contains(&receive_budget),
230 "Invalid receive budget"
231 );
232 ensure!(
233 !timeout.is_zero() && timeout <= Duration::from_secs(120),
234 "Invalid MMTLS timeout"
235 );
236 let deadline = Instant::now() + timeout;
237 use std::net::ToSocketAddrs;
240 let addresses = (host, 80).to_socket_addrs()?;
241 let mut socket = None;
242 for addr in addresses {
243 let remaining = deadline.saturating_duration_since(Instant::now());
244 if remaining.is_zero() {
245 break;
246 }
247 if let Ok(s) = TcpStream::connect_timeout(&addr, remaining) {
248 socket = Some(s);
249 break;
250 }
251 }
252 let mut socket = socket.ok_or_else(|| anyhow::anyhow!("Native connection failed"))?;
253 socket.set_write_timeout(Some(timeout))?;
254 let keys = [
255 Ecdh::generate(Nid::X9_62_PRIME256V1)?,
256 Ecdh::generate(Nid::X9_62_PRIME256V1)?,
257 ];
258 let mut shares = vec![];
259 for (i, k) in keys.iter().enumerate() {
260 let point = k.public()?;
261 shares.extend(section(
262 &[((i + 5) as u32).to_be_bytes().to_vec(), sized(&point)?].concat(),
263 ));
264 }
265 let mut extra = vec![0; 4];
266 extra.push(2);
267 extra.extend(5u32.to_be_bytes());
268 extra.extend(1000u32.to_be_bytes());
269 let extension = section(&[0x10u16.to_be_bytes().to_vec(), vec![2], shares, extra].concat());
270 let hello = section(
271 &[
272 vec![1],
273 VERSION.to_le_bytes().to_vec(),
274 vec![1],
275 0xc02bu16.to_be_bytes().to_vec(),
276 random::<32>()?.to_vec(),
277 crate::wire::unix_time()?.to_be_bytes().to_vec(),
278 section(&[vec![1], extension].concat()),
279 ]
280 .concat(),
281 );
282 socket.write_all(&frame(0x16, &hello)?)?;
283 let mut received = 0;
284 let server_record = read_record(&mut socket, deadline, &mut received, receive_budget)?;
285 ensure!(server_record.header[0] == 0x16, "Expected handshake");
286 let server = parse_server_hello(&server_record.body)?;
287 let index = server
288 .key_id
289 .checked_sub(5)
290 .ok_or_else(|| anyhow::anyhow!("Unoffered key"))? as usize;
291 ensure!(index < 2, "Unoffered key");
292 let mut x = keys[index].derive(&server.point)?;
293 let first = x.iter().position(|v| *v != 0).unwrap_or(x.len() - 1);
294 let mut secret = sha256(&x[first..]);
295 x.zeroize();
296 let mut transcript = [hello, server_record.body].concat();
297 let handshake = traffic(&secret, "handshake key expansion", &transcript)?;
298 let r = read_record(&mut socket, deadline, &mut received, receive_budget)?;
299 ensure!(r.header[0] == 0x16, "Expected signature record");
300 let signature_body = open(&r, &handshake.server_key, &handshake.server_iv, 1)?;
301 let signature = signed_payload(&signature_body, 0x0f)?;
302 let pins: serde_json::Value =
303 serde_json::from_str(include_str!("../fixtures/mmtls-public-keys.json"))?;
304 let pin = pins["profiles"]
305 .as_array()
306 .unwrap()
307 .iter()
308 .find(|p| p["version"].as_u64() == Some(server.version as u64))
309 .ok_or_else(|| anyhow::anyhow!("Unsupported MMTLS certificate version"))?;
310 ensure!([5, 1000].contains(&server.version), "Unoffered MMTLS pin");
311 verify_signature(
312 pin["ecdsaPEM"].as_str().unwrap(),
313 &sha256(&transcript),
314 &signature,
315 )?;
316 transcript.extend(signature_body);
317 let r = read_record(&mut socket, deadline, &mut received, receive_budget)?;
318 ensure!(r.header[0] == 0x16, "Expected ticket record");
319 let ticket_body = open(&r, &handshake.server_key, &handshake.server_iv, 2)?;
320 let tickets = parse_tickets(&ticket_body)?;
321 let access_psk = expand(
322 &secret,
323 &[b"PSK_ACCESS".as_slice(), &sha256(&transcript)].concat(),
324 32,
325 )?;
326 transcript.extend(ticket_body);
327 let r = read_record(&mut socket, deadline, &mut received, receive_budget)?;
328 ensure!(r.header[0] == 0x16, "Expected Finished");
329 let finish = signed_payload(
330 &open(&r, &handshake.server_key, &handshake.server_iv, 3)?,
331 0x14,
332 )?;
333 let expected = hmac(
334 &expand(&secret, b"server finished", 32)?,
335 &sha256(&transcript),
336 )?;
337 ensure!(
338 finish.len() == expected.len() && memcmp::eq(&finish, &expected),
339 "Server Finished verification failed"
340 );
341 let client_finish = hmac(
342 &expand(&secret, b"client finished", 32)?,
343 &sha256(&transcript),
344 )?;
345 socket.write_all(&seal(
346 0x16,
347 §ion(&[vec![0x14], sized(&client_finish)?].concat()),
348 &handshake.client_key,
349 &handshake.client_iv,
350 1,
351 )?)?;
352 let mut expanded = expand(
353 &secret,
354 &[b"expanded secret".as_slice(), &sha256(&transcript)].concat(),
355 32,
356 )?;
357 let application = traffic(&expanded, "application data key expansion", &transcript)?;
358 secret.zeroize();
359 expanded.zeroize();
360 let noop = pack_longlink(6, u32::MAX, &[])?;
361 socket.write_all(&seal(
362 0x17,
363 &noop,
364 &application.client_key,
365 &application.client_iv,
366 2,
367 )?)?;
368 let r = read_record(&mut socket, deadline, &mut received, receive_budget)?;
369 ensure!(r.header[0] == 0x17, "Expected NOOP reply");
370 let p = unpack_longlink(&open(
371 &r,
372 &application.server_key,
373 &application.server_iv,
374 4,
375 )?)?;
376 ensure!(
377 p.command == 0x3b9aca06 && p.sequence == u32::MAX && p.payload.is_empty(),
378 "Unexpected NOOP reply"
379 );
380 Ok(Self {
381 socket,
382 deadline,
383 received,
384 receive_budget,
385 short: ShortSession {
386 access_psk,
387 ticket: tickets[0].clone(),
388 },
389 certificate_region: server.region,
390 certificate_version: server.version,
391 traffic: application,
392 outgoing: 3,
393 incoming: 5,
394 record_pending: Vec::new(),
395 application_pending: LonglinkDecoder {
396 pending: Vec::new(),
397 max_payload: receive_budget,
398 },
399 })
400 }
401 pub fn send_application(&mut self, command: u32, sequence: u32, payload: &[u8]) -> Result<()> {
402 let bytes = seal(
403 0x17,
404 &pack_longlink(command, sequence, payload)?,
405 &self.traffic.client_key,
406 &self.traffic.client_iv,
407 self.outgoing,
408 )?;
409 self.socket.write_all(&bytes)?;
410 self.outgoing = self
411 .outgoing
412 .checked_add(1)
413 .ok_or_else(|| anyhow::anyhow!("Sequence exhausted"))?;
414 Ok(())
415 }
416 pub fn receive_application(&mut self) -> Result<Longlink> {
417 let r = read_record(
418 &mut self.socket,
419 self.deadline,
420 &mut self.received,
421 self.receive_budget,
422 )?;
423 ensure!(r.header[0] == 0x17, "Expected application record");
424 let plain = open(
425 &r,
426 &self.traffic.server_key,
427 &self.traffic.server_iv,
428 self.incoming,
429 )?;
430 self.incoming = self
431 .incoming
432 .checked_add(1)
433 .ok_or_else(|| anyhow::anyhow!("Sequence exhausted"))?;
434 unpack_longlink(&plain)
435 }
436
437 pub fn poll_application(&mut self, timeout: Duration) -> Result<Option<Longlink>> {
441 ensure!(
442 !timeout.is_zero() && timeout <= Duration::from_secs(120),
443 "Invalid longlink poll timeout"
444 );
445 let deadline = Instant::now() + timeout;
446 loop {
447 if let Some(packet) = self.application_pending.next_packet()? {
448 return Ok(Some(packet));
449 }
450 if self.record_pending.len() >= 5 {
451 let h: [u8; 5] = self.record_pending[..5].try_into()?;
452 ensure!(
453 (0x15..=0x19).contains(&h[0]) && u16::from_be_bytes([h[1], h[2]]) == VERSION,
454 "Unexpected MMTLS header"
455 );
456 let n = u16::from_be_bytes([h[3], h[4]]) as usize;
457 if self.record_pending.len() >= n + 5 {
458 ensure!(h[0] == 0x17, "Unexpected persistent MMTLS record");
459 let record = Record {
460 header: h,
461 body: self.record_pending[5..n + 5].to_vec(),
462 };
463 let plain = open(
464 &record,
465 &self.traffic.server_key,
466 &self.traffic.server_iv,
467 self.incoming,
468 )?;
469 self.incoming = self
470 .incoming
471 .checked_add(1)
472 .ok_or_else(|| anyhow::anyhow!("Sequence exhausted"))?;
473 self.record_pending.drain(..n + 5);
474 self.application_pending.feed(&plain)?;
475 continue;
476 }
477 }
478 let remaining = deadline.saturating_duration_since(Instant::now());
479 if remaining.is_zero() {
480 return Ok(None);
481 }
482 self.socket.set_read_timeout(Some(remaining))?;
483 let mut bytes = [0u8; 65540];
484 match self.socket.read(&mut bytes) {
485 Ok(0) => anyhow::bail!("MMTLS connection closed"),
486 Ok(n) => {
487 ensure!(
489 self.record_pending.len() + n <= 131080,
490 "MMTLS pending record limit"
491 );
492 self.record_pending.extend_from_slice(&bytes[..n]);
493 }
494 Err(e)
495 if matches!(
496 e.kind(),
497 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
498 ) =>
499 {
500 return Ok(None)
501 }
502 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
503 Err(e) => return Err(e.into()),
504 }
505 }
506 }
507}
508struct ServerHello {
509 key_id: u32,
510 point: Vec<u8>,
511 region: u32,
512 version: u32,
513}
514fn parse_server_hello(bytes: &[u8]) -> Result<ServerHello> {
515 let mut top = Cursor::new(bytes);
516 let mut b = Cursor::new(top.sized(4)?);
517 top.end()?;
518 ensure!(
519 b.uint(1)? == 2 && b.take(2)? == VERSION.to_le_bytes() && b.uint(2)? == 0xc02b,
520 "Unsupported ServerHello"
521 );
522 b.take(32)?;
523 let mut e = Cursor::new(b.sized(4)?);
524 b.end()?;
525 let count = e.uint(1)?;
526 let mut seen = std::collections::HashSet::new();
527 let mut share = None;
528 let mut cert = None;
529 for _ in 0..count {
530 let mut x = Cursor::new(e.sized(4)?);
531 let kind = x.uint(2)?;
532 ensure!(seen.insert(kind), "Duplicate extension");
533 match kind {
534 0x11 => {
535 let id = x.uint(4)? as u32;
536 let point = x.sized(2)?.to_vec();
537 ensure!(point.len() == 65 && point[0] == 4, "Invalid ECDH point");
538 x.end()?;
539 share = Some((id, point));
540 }
541 0x13 => {
542 let region = x.uint(4)? as u32;
543 let version = x.uint(4)? as u32;
544 x.end()?;
545 cert = Some((region, version));
546 }
547 _ => {}
548 }
549 }
550 e.end()?;
551 let (key_id, point) = share.ok_or_else(|| anyhow::anyhow!("Missing ECDH selection"))?;
552 let (region, version) = cert.ok_or_else(|| anyhow::anyhow!("Missing certificate selection"))?;
553 Ok(ServerHello {
554 key_id,
555 point,
556 region,
557 version,
558 })
559}
560fn parse_tickets(input: &[u8]) -> Result<Vec<Ticket>> {
561 let mut top = Cursor::new(input);
562 let mut b = Cursor::new(top.sized(4)?);
563 top.end()?;
564 ensure!(b.uint(1)? == 4, "Expected tickets");
565 let count = b.uint(1)?;
566 ensure!((1..=4).contains(&count), "Invalid ticket count");
567 let mut out = vec![];
568 for _ in 0..count {
569 let mut t = Cursor::new(b.sized(4)?);
570 let kind = t.uint(1)? as u8;
571 let lifetime = t.uint(4)? as u32;
572 t.sized(2)?;
573 let reserved = t.uint(4)? as u32;
574 let nonce = t.sized(2)?.to_vec();
575 let opaque = t.sized(2)?.to_vec();
576 t.end()?;
577 ensure!(
578 nonce.len() == 12 && !opaque.is_empty(),
579 "Invalid shortlink ticket"
580 );
581 out.push(Ticket {
582 kind,
583 lifetime,
584 reserved,
585 nonce,
586 opaque,
587 });
588 }
589 b.end()?;
590 Ok(out)
591}
592pub struct Longlink {
593 pub command: u32,
594 pub sequence: u32,
595 pub payload: Vec<u8>,
596}
597struct LonglinkDecoder {
598 pending: Vec<u8>,
599 max_payload: usize,
600}
601impl Default for LonglinkDecoder {
602 fn default() -> Self {
603 Self {
604 pending: Vec::new(),
605 max_payload: crate::MAX_MESSAGE_BYTES,
606 }
607 }
608}
609impl LonglinkDecoder {
610 fn feed(&mut self, bytes: &[u8]) -> Result<()> {
611 ensure!(
612 self.pending.len().saturating_add(bytes.len()) <= self.max_payload + 65540,
613 "Longlink pending packet limit"
614 );
615 self.pending.extend_from_slice(bytes);
616 Ok(())
617 }
618 fn next_packet(&mut self) -> Result<Option<Longlink>> {
619 if self.pending.len() < 4 {
620 return Ok(None);
621 }
622 let n = u32::from_be_bytes(self.pending[..4].try_into()?) as usize;
623 ensure!(
624 (16..=self.max_payload + 16).contains(&n),
625 "Invalid longlink packet length"
626 );
627 if self.pending.len() >= 8 {
628 ensure!(
629 self.pending[4..8] == [0, 16, 0, 1],
630 "Invalid longlink envelope"
631 );
632 }
633 if self.pending.len() < n {
634 return Ok(None);
635 }
636 let packet = unpack_longlink(&self.pending[..n])?;
637 self.pending.drain(..n);
638 Ok(Some(packet))
639 }
640}
641pub fn pack_longlink(command: u32, sequence: u32, payload: &[u8]) -> Result<Vec<u8>> {
642 ensure!(payload.len() <= 65503, "Longlink body too large");
643 Ok([
644 ((16 + payload.len()) as u32).to_be_bytes().to_vec(),
645 0x10u16.to_be_bytes().to_vec(),
646 1u16.to_be_bytes().to_vec(),
647 command.to_be_bytes().to_vec(),
648 sequence.to_be_bytes().to_vec(),
649 payload.to_vec(),
650 ]
651 .concat())
652}
653pub fn unpack_longlink(input: &[u8]) -> Result<Longlink> {
654 let mut c = Cursor::new(input);
655 ensure!(
656 c.uint(4)? == input.len() as u64 && c.uint(2)? == 0x10 && c.uint(2)? == 1,
657 "Invalid longlink envelope"
658 );
659 let command = c.uint(4)? as u32;
660 let sequence = c.uint(4)? as u32;
661 let payload = c.take(input.len() - 16)?.to_vec();
662 Ok(Longlink {
663 command,
664 sequence,
665 payload,
666 })
667}
668
669#[derive(Debug)]
671pub(crate) struct UnsupportedNativeRoute;
672impl std::fmt::Display for UnsupportedNativeRoute {
673 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
674 f.write_str("Unsupported native route")
675 }
676}
677impl std::error::Error for UnsupportedNativeRoute {}
678pub(crate) fn validate_short_route(path: &str) -> Result<()> {
679 let ordinary = path.starts_with("/cgi-bin/micromsg-bin/")
680 && path
681 .bytes()
682 .all(|b| b.is_ascii_alphanumeric() || b == b'/' || b == b'-');
683 if ordinary || path == "/cgi-bin/micromsg-bin/mm_ilink_cloud_app_auth" {
684 Ok(())
685 } else {
686 Err(UnsupportedNativeRoute.into())
687 }
688}
689
690pub fn short_exchange(
691 session: &ShortSession,
692 host: &str,
693 path: &str,
694 payload: &[u8],
695 timeout: Duration,
696) -> Result<Vec<u8>> {
697 let (hello, extensions, wire) = prepare_short_exchange(session, host, path, payload)?;
698 let client = reqwest::blocking::Client::builder()
699 .timeout(timeout)
700 .connect_timeout(timeout.min(Duration::from_secs(10)))
701 .redirect(reqwest::redirect::Policy::none())
702 .no_proxy()
703 .build()?;
704 let mut response = client
705 .post(format!(
706 "http://{host}/mmtls/{}",
707 hex::encode(random::<4>()?)
708 ))
709 .header("Content-Type", "application/octet-stream")
710 .header("User-Agent", "MicroMessenger Client")
711 .header("Upgrade", "mmtls")
712 .header("Accept", "*/*")
713 .header("Cache-Control", "no-cache")
714 .header("Connection", "close")
715 .body(wire)
716 .send()?;
717 ensure!(
718 response.status() == 200,
719 "Native shortlink HTTP status {}",
720 response.status().as_u16()
721 );
722 let mut bytes = Vec::new();
723 response
724 .by_ref()
725 .take((crate::MAX_MESSAGE_BYTES + 1) as u64)
726 .read_to_end(&mut bytes)?;
727 ensure!(
728 bytes.len() <= crate::MAX_MESSAGE_BYTES,
729 "Shortlink receive budget exceeded"
730 );
731 decode_short_reply(&bytes, session, &hello, &extensions)
732}
733fn prepare_short_exchange(
734 session: &ShortSession,
735 host: &str,
736 path: &str,
737 payload: &[u8],
738) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> {
739 validate_host(host)?;
740 validate_short_route(path)?;
741 ensure!(
742 payload.len() <= crate::MAX_MESSAGE_BYTES,
743 "Payload too large"
744 );
745 let (hello, extensions) = early_hello(&session.ticket)?;
746 let keys = expand(
747 &session.access_psk,
748 &[b"early data key expansion".as_slice(), &sha256(&hello)].concat(),
749 28,
750 )?;
751 let route = section(
752 &[
753 sized(path.as_bytes())?,
754 sized(host.as_bytes())?,
755 section(payload),
756 ]
757 .concat(),
758 );
759 let wire = [
760 frame(0x19, &hello)?,
761 seal(0x19, &extensions, &keys[..16], &keys[16..], 1)?,
762 seal(0x17, &route, &keys[..16], &keys[16..], 2)?,
763 seal(0x15, §ion(&[0, 1, 1]), &keys[..16], &keys[16..], 3)?,
764 ]
765 .concat();
766 Ok((hello, extensions, wire))
767}
768
769pub(crate) async fn short_exchange_interruptible(
771 session: &ShortSession,
772 host: &str,
773 path: &str,
774 payload: &[u8],
775 timeout: Duration,
776) -> Result<Vec<u8>> {
777 let (hello, extensions, wire) = prepare_short_exchange(session, host, path, payload)?;
778 let client = reqwest::Client::builder()
779 .timeout(timeout)
780 .connect_timeout(timeout.min(Duration::from_secs(10)))
781 .redirect(reqwest::redirect::Policy::none())
782 .no_proxy()
783 .build()?;
784 let mut response = client
785 .post(format!(
786 "http://{host}/mmtls/{}",
787 hex::encode(random::<4>()?)
788 ))
789 .header("Content-Type", "application/octet-stream")
790 .header("User-Agent", "MicroMessenger Client")
791 .header("Upgrade", "mmtls")
792 .header("Accept", "*/*")
793 .header("Cache-Control", "no-cache")
794 .header("Connection", "close")
795 .body(wire)
796 .send()
797 .await?;
798 ensure!(
799 response.status() == 200,
800 "Native shortlink HTTP status {}",
801 response.status().as_u16()
802 );
803 let mut bytes = Vec::new();
804 while let Some(chunk) = response.chunk().await? {
805 ensure!(
806 chunk.len() <= crate::MAX_MESSAGE_BYTES - bytes.len(),
807 "Shortlink receive budget exceeded"
808 );
809 bytes.extend_from_slice(&chunk);
810 }
811 decode_short_reply(&bytes, session, &hello, &extensions)
812}
813fn early_hello(ticket: &Ticket) -> Result<(Vec<u8>, Vec<u8>)> {
814 ensure!(
815 ticket.nonce.len() == 12 && !ticket.opaque.is_empty(),
816 "Invalid short ticket"
817 );
818 let now = crate::wire::unix_time()?;
819 let encoded = [
820 vec![ticket.kind],
821 ticket.lifetime.to_be_bytes().to_vec(),
822 sized(&[])?,
823 ticket.reserved.to_be_bytes().to_vec(),
824 sized(&ticket.nonce)?,
825 sized(&ticket.opaque)?,
826 ]
827 .concat();
828 let ext = section(&[0x0fu16.to_be_bytes().to_vec(), vec![1], section(&encoded)].concat());
829 let hello = section(
830 &[
831 vec![1],
832 VERSION.to_le_bytes().to_vec(),
833 vec![1],
834 0xa8u16.to_be_bytes().to_vec(),
835 random::<32>()?.to_vec(),
836 now.to_be_bytes().to_vec(),
837 section(&[vec![1], ext].concat()),
838 ]
839 .concat(),
840 );
841 let extensions = section(
842 &[
843 vec![8],
844 section(
845 &[
846 vec![1],
847 section(&[0x12u16.to_be_bytes().to_vec(), now.to_be_bytes().to_vec()].concat()),
848 ]
849 .concat(),
850 ),
851 ]
852 .concat(),
853 );
854 Ok((hello, extensions))
855}
856fn decode_short_reply(
857 input: &[u8],
858 session: &ShortSession,
859 hello: &[u8],
860 extensions: &[u8],
861) -> Result<Vec<u8>> {
862 let mut c = Cursor::new(input);
863 let mut records = vec![];
864 while c.offset < input.len() {
865 let h: [u8; 5] = c.take(5)?.try_into()?;
866 ensure!(
867 (0x15..=0x19).contains(&h[0]) && u16::from_be_bytes([h[1], h[2]]) == VERSION,
868 "Invalid shortlink header"
869 );
870 records.push(Record {
871 header: h,
872 body: c.take(u16::from_be_bytes([h[3], h[4]]) as usize)?.to_vec(),
873 });
874 }
875 ensure!(
876 records.len() >= 4
877 && records[0].header[0] == 0x16
878 && records[1].header[0] == 0x16
879 && records.last().unwrap().header[0] == 0x15
880 && records[2..records.len() - 1]
881 .iter()
882 .all(|r| r.header[0] == 0x17),
883 "Invalid shortlink sequence"
884 );
885 let server = &records[0];
886 let mut top = Cursor::new(&server.body);
887 let mut b = Cursor::new(top.sized(4)?);
888 top.end()?;
889 ensure!(
890 b.uint(1)? == 2 && b.take(2)? == VERSION.to_le_bytes() && b.uint(2)? == 0xa8,
891 "Unsupported shortlink selection"
892 );
893 b.take(32)?;
894 let mut e = Cursor::new(b.sized(4)?);
895 b.end()?;
896 ensure!(e.uint(1)? == 0, "Unsupported shortlink extensions");
897 e.end()?;
898 let transcript = [hello, extensions, &server.body].concat();
899 let keys = expand(
900 &session.access_psk,
901 &[b"handshake key expansion".as_slice(), &sha256(&transcript)].concat(),
902 28,
903 )?;
904 let finish = signed_payload(&open(&records[1], &keys[..16], &keys[16..], 1)?, 0x14)?;
905 let expected = hmac(
906 &expand(&session.access_psk, b"server finished", 32)?,
907 &sha256(&transcript),
908 )?;
909 ensure!(
910 finish.len() == expected.len() && memcmp::eq(&finish, &expected),
911 "Shortlink Finished failed"
912 );
913 let mut plain = vec![];
914 for (i, r) in records[2..records.len() - 1].iter().enumerate() {
915 plain.extend(open(r, &keys[..16], &keys[16..], i as u64 + 2)?);
916 ensure!(
917 plain.len() <= crate::MAX_MESSAGE_BYTES,
918 "Shortlink plaintext too large"
919 );
920 }
921 ensure!(
922 open(
923 records.last().unwrap(),
924 &keys[..16],
925 &keys[16..],
926 records.len() as u64 - 1
927 )? == section(&[0, 1, 1]),
928 "Unexpected shortlink close"
929 );
930 Ok(plain)
931}
932
933#[cfg(test)]
934mod tests {
935 #[test]
936 fn exact_call_app_auth_route_does_not_admit_arbitrary_route_bytes() {
937 assert!(validate_short_route("/cgi-bin/micromsg-bin/mm_ilink_cloud_app_auth").is_ok());
938 assert!(validate_short_route("/cgi-bin/micromsg-bin/heartbeat").is_ok());
939 assert!(validate_short_route("/cgi-bin/micromsg-bin/voipilinkack").is_ok());
940 for bad in [
941 "/cgi-bin/micromsg-bin/another_route",
942 "/cgi-bin/micromsg-bin/mm_ilink_cloud_app_auth/extra",
943 "/cgi-bin/micromsg-bin/mm_ilink_cloud_app_auth?x=1",
944 "/cgi-bin/micromsg-bin/mm_ilink_cloud_app_auth#x",
945 "/cgi-bin/micromsg-bin/mm_ilink_cloud_app_auth\r\nX: y",
946 "/cgi-bin/micromsg-bin/mm_ilink_cloud_app_auth\0",
947 "/cgi-bin/micromsg-bin/%2e%2e",
948 "/cgi-bin/micromsg-bin/../x",
949 "/cgi-bin/micromsg-bin/\\x",
950 "/cgi-bin/micromsg-bin/音",
951 "http://example.test/cgi-bin/micromsg-bin/heartbeat",
952 ] {
953 assert!(validate_short_route(bad)
954 .unwrap_err()
955 .is::<UnsupportedNativeRoute>());
956 }
957 }
958 use super::*;
959 #[test]
960 fn longlink_frames_can_split_or_share_records_and_reject_bad_lengths_early() {
961 let a = pack_longlink(24, 0, &6u32.to_be_bytes()).unwrap();
962 let b = pack_longlink(1000000238, 23, &[2; 256]).unwrap();
963 let both = [a.as_slice(), b.as_slice()].concat();
964 for cut in 0..a.len() {
965 let mut decoder = LonglinkDecoder::default();
966 decoder.feed(&both[..cut]).unwrap();
967 assert!(decoder.next_packet().unwrap().is_none());
968 decoder.feed(&both[cut..]).unwrap();
969 assert_eq!(decoder.next_packet().unwrap().unwrap().command, 24);
970 assert_eq!(decoder.next_packet().unwrap().unwrap().sequence, 23);
971 assert!(decoder.next_packet().unwrap().is_none());
972 }
973 for n in [0u32, 15, (crate::MAX_MESSAGE_BYTES + 17) as u32, u32::MAX] {
974 let mut decoder = LonglinkDecoder::default();
975 decoder.feed(&n.to_be_bytes()).unwrap();
976 assert!(decoder.next_packet().is_err());
977 }
978 let mut invalid = a.clone();
979 invalid[7] = 2;
980 let mut decoder = LonglinkDecoder::default();
981 decoder.feed(&invalid[..8]).unwrap();
982 assert!(decoder.next_packet().is_err());
983 let mut limited = LonglinkDecoder {
984 pending: Vec::new(),
985 max_payload: 128,
986 };
987 limited.feed(&b[..4]).unwrap();
988 assert!(limited.next_packet().is_err());
989 let payload = vec![7; 200_000];
990 let large = [
991 (payload.len() as u32 + 16).to_be_bytes().to_vec(),
992 vec![0, 16, 0, 1],
993 1000000121u32.to_be_bytes().to_vec(),
994 9u32.to_be_bytes().to_vec(),
995 payload.clone(),
996 ]
997 .concat();
998 let mut decoder = LonglinkDecoder::default();
999 for part in large.chunks(20_000) {
1000 decoder.feed(part).unwrap();
1001 }
1002 assert_eq!(decoder.next_packet().unwrap().unwrap().payload, payload);
1003 }
1004 #[test]
1005 fn encrypted_idle_timeout_keeps_partial_record_and_packet_bytes() {
1006 use std::{net::TcpListener, sync::mpsc, thread};
1007 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
1008 let socket = TcpStream::connect(listener.local_addr().unwrap()).unwrap();
1009 let (mut peer, _) = listener.accept().unwrap();
1010 let first = pack_longlink(24, 0, &6u32.to_be_bytes()).unwrap();
1011 let second = pack_longlink(1000000006, u32::MAX, &[]).unwrap();
1012 let key = vec![1; 16];
1013 let iv = vec![2; 12];
1014 let r1 = seal(0x17, &first[..10], &key, &iv, 5).unwrap();
1015 let r2 = seal(0x17, &[first[10..].to_vec(), second].concat(), &key, &iv, 6).unwrap();
1016 let mut corrupt = seal(0x17, &first, &key, &iv, 7).unwrap();
1017 *corrupt.last_mut().unwrap() ^= 1;
1018 let (sent, observed) = mpsc::channel();
1019 let (resume, waiting) = mpsc::channel();
1020 let writer = thread::spawn(move || {
1021 peer.write_all(&r1[..3]).unwrap();
1022 sent.send(()).unwrap();
1023 waiting.recv().unwrap();
1024 peer.write_all(&r1[3..]).unwrap();
1025 peer.write_all(&r2).unwrap();
1026 peer.write_all(&corrupt).unwrap();
1027 });
1028 let mut session = Session {
1029 socket,
1030 deadline: Instant::now(),
1031 received: 0,
1032 receive_budget: 1024,
1033 short: ShortSession {
1034 access_psk: vec![],
1035 ticket: Ticket {
1036 kind: 1,
1037 lifetime: 0,
1038 reserved: 0,
1039 nonce: vec![],
1040 opaque: vec![],
1041 },
1042 },
1043 certificate_region: 0,
1044 certificate_version: 0,
1045 traffic: Traffic {
1046 client_key: key.clone(),
1047 server_key: key,
1048 client_iv: iv.clone(),
1049 server_iv: iv,
1050 },
1051 outgoing: 3,
1052 incoming: 5,
1053 record_pending: Vec::new(),
1054 application_pending: LonglinkDecoder::default(),
1055 };
1056 observed.recv().unwrap();
1057 assert!(session
1058 .poll_application(Duration::from_millis(10))
1059 .unwrap()
1060 .is_none());
1061 assert_eq!(session.record_pending.len(), 3);
1062 resume.send(()).unwrap();
1063 assert_eq!(
1064 session
1065 .poll_application(Duration::from_secs(1))
1066 .unwrap()
1067 .unwrap()
1068 .command,
1069 24
1070 );
1071 assert_eq!(
1072 session
1073 .poll_application(Duration::from_secs(1))
1074 .unwrap()
1075 .unwrap()
1076 .sequence,
1077 u32::MAX
1078 );
1079 assert!(session.poll_application(Duration::from_secs(1)).is_err());
1080 assert_eq!(session.incoming, 7);
1081 writer.join().unwrap();
1082 }
1083 #[test]
1084 fn published_mmtls_vector() {
1085 let key = hex::decode("29b37affd98238fb0370c564d8fa992e").unwrap();
1086 let wire_nonce = hex::decode("36ee037a258fbb83ca1f9762").unwrap();
1087 let base = nonce(&wire_nonce, 1).unwrap();
1088 let plain = hex::decode(
1089 "0000002314002009d6fc43a65a48dd64e3b6846562d13752f5abc2cb254539906ecd429df71082",
1090 )
1091 .unwrap();
1092 let expected=hex::decode("16f1040037e4bdea652cd80a273fd2ef549df4c87c1083c23f658fcc2d4152d163bc809f35a56fd0f4a62caa138d765a086eec02e21704892a981cf7").unwrap();
1093 assert_eq!(seal(0x16, &plain, &key, &base, 1).unwrap(), expected);
1094 let r = Record {
1095 header: expected[..5].try_into().unwrap(),
1096 body: expected[5..].to_vec(),
1097 };
1098 assert_eq!(open(&r, &key, &base, 1).unwrap(), plain);
1099 assert!(open(&r, &key, &base, 2).is_err());
1100 }
1101 #[test]
1102 fn hosts_restricted() {
1103 for s in [
1104 "localhost",
1105 "127.0.0.1",
1106 "long.weixin.qq.com.attacker.org",
1107 "-x.wechat.com",
1108 "x.wechat.com:80",
1109 ] {
1110 assert!(validate_host(s).is_err());
1111 }
1112 assert!(validate_host("long.weixin.qq.com").is_ok());
1113 }
1114 #[test]
1115 fn malformed_envelopes() {
1116 assert!(unpack_longlink(&[]).is_err());
1117 assert!(parse_server_hello(&[0; 4]).is_err());
1118 let mut v = pack_longlink(6, 123, &[1, 2]).unwrap();
1119 v[3] ^= 1;
1120 assert!(unpack_longlink(&v).is_err());
1121 }
1122}