Browse Source

WIP: minimal web server, starting on file downloads

Justin Tracey 11 months ago
parent
commit
5bcebc6c18
4 changed files with 153 additions and 8 deletions
  1. 3 0
      Cargo.toml
  2. 1 1
      src/bin/mgen-client.rs
  3. 128 0
      src/bin/mgen-web.rs
  4. 21 7
      src/lib.rs

+ 3 - 0
Cargo.toml

@@ -7,6 +7,8 @@ edition = "2021"
 chrono = "0.4.24"
 futures = "0.3.28"
 glob = "0.3.1"
+hyper = { version = "0.14", default-features = false, features = ["client"] }
+hyper-rustls = "0.24.1"
 rand = "0.8.5"
 rand_distr = { version = "0.4.3", features = ["serde1"] }
 rand_xoshiro = "0.6.0"
@@ -16,6 +18,7 @@ serde_yaml = "0.9.21"
 tokio = { version = "1", features = ["full"] }
 tokio-rustls = { version = "0.24.1", features = ["dangerous_configuration"] }
 tokio-socks = "0.5.1"
+url = "2.4.0"
 
 [profile.release]
 lto = true

+ 1 - 1
src/bin/mgen-client.rs

@@ -42,7 +42,7 @@ type ErrorChannelIn = mpsc::UnboundedSender<MessengerError>;
 type ErrorChannelOut = mpsc::UnboundedReceiver<MessengerError>;
 
 // we gain a (very) tiny performance win by not bothering to validate the cert
-pub struct NoCertificateVerification {}
+struct NoCertificateVerification {}
 
 impl tokio_rustls::rustls::client::ServerCertVerifier for NoCertificateVerification {
     fn verify_server_cert(

+ 128 - 0
src/bin/mgen-web.rs

@@ -0,0 +1,128 @@
+// An extremely minimal http server.
+// Accepts https GET requests with a "size" parameter, and responds with that many bytes.
+// Accepts all other https requests, and ignores whatever was given.
+
+use hyper::server::conn::AddrIncoming;
+use hyper::service::{make_service_fn, service_fn};
+use hyper::{Body, Method, Request, Response, Server, StatusCode};
+use hyper_rustls::TlsAcceptor;
+use mgen::log;
+use std::collections::HashMap;
+use std::io::BufReader;
+use std::str::FromStr;
+use tokio_rustls::rustls::PrivateKey;
+
+static MISSING: &[u8] = b"Missing field";
+static NOTNUMERIC: &[u8] = b"Number field is not numeric";
+
+async fn process(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
+    let Some(query) = req.uri().query()
+    else {
+        return Ok(Response::builder()
+                  .status(StatusCode::UNPROCESSABLE_ENTITY)
+                  .body(Body::from(MISSING))
+                  .unwrap());
+    };
+
+    let params = url::form_urlencoded::parse(query.as_bytes())
+        .into_owned()
+        .collect::<HashMap<String, String>>();
+
+    let Some(from) = params.get("user")
+    else {
+        return Ok(Response::builder()
+                  .status(StatusCode::UNPROCESSABLE_ENTITY)
+                  .body(Body::from(MISSING))
+                  .unwrap());
+    };
+
+    match req.method() {
+        &Method::GET => {
+            let Ok(size) = params
+                .get("size")
+                .map_or(Ok(0), |s| usize::from_str(s.as_str()))
+            else {
+                return Ok(Response::builder()
+                          .status(StatusCode::UNPROCESSABLE_ENTITY)
+                          .body(Body::from(NOTNUMERIC))
+                          .unwrap());
+            };
+            log!("sending,{},{}", from, size);
+
+            let body = Body::from(vec![0; size]);
+            Ok(Response::new(body))
+        }
+
+        _ => {
+            log!("receiving,{}", from);
+            Ok(Response::new(Body::empty()))
+        }
+    }
+}
+
+// FIXME: move this out into module to share with server (along with other shared functionality)
+fn load_private_key(filename: &str) -> PrivateKey {
+    let keyfile = std::fs::File::open(filename).expect("cannot open private key file");
+    let mut reader = BufReader::new(keyfile);
+
+    loop {
+        match rustls_pemfile::read_one(&mut reader).expect("cannot parse private key .pem file") {
+            Some(rustls_pemfile::Item::RSAKey(key)) => return PrivateKey(key),
+            Some(rustls_pemfile::Item::PKCS8Key(key)) => return PrivateKey(key),
+            Some(rustls_pemfile::Item::ECKey(key)) => return PrivateKey(key),
+            None => break,
+            _ => {}
+        }
+    }
+
+    panic!(
+        "no keys found in {:?} (encrypted keys not supported)",
+        filename
+    );
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
+    let mut args = std::env::args();
+    let _arg0 = args.next().unwrap();
+
+    let cert_filename = args
+        .next()
+        .unwrap_or_else(|| panic!("no cert file provided"));
+    let key_filename = args
+        .next()
+        .unwrap_or_else(|| panic!("no key file provided"));
+
+    let listen_addr = args.next().unwrap_or("127.0.0.1:6398".to_string());
+
+    let certfile = std::fs::File::open(cert_filename).expect("cannot open certificate file");
+    let mut reader = BufReader::new(certfile);
+    let certs: Vec<tokio_rustls::rustls::Certificate> = rustls_pemfile::certs(&mut reader)
+        .unwrap()
+        .iter()
+        .map(|v| tokio_rustls::rustls::Certificate(v.clone()))
+        .collect();
+    let key = load_private_key(&key_filename);
+
+    let config = tokio_rustls::rustls::ServerConfig::builder()
+        .with_safe_default_cipher_suites()
+        .with_safe_default_kx_groups()
+        .with_safe_default_protocol_versions()
+        .unwrap()
+        .with_no_client_auth()
+        .with_single_cert(certs, key)?;
+
+    let incoming = AddrIncoming::bind(&listen_addr.parse()?)?;
+
+    let acceptor = TlsAcceptor::builder()
+        .with_tls_config(config)
+        .with_http11_alpn()
+        .with_incoming(incoming);
+
+    let service = make_service_fn(|_| async { Ok::<_, std::io::Error>(service_fn(process)) });
+    let server = Server::builder(acceptor).serve(service);
+
+    log!("listening,{}", &listen_addr);
+    server.await?;
+    Ok(())
+}

+ 21 - 7
src/lib.rs

@@ -4,13 +4,19 @@ use tokio::io::{copy, sink, AsyncReadExt, AsyncWriteExt};
 
 pub mod updater;
 
-/// The padding interval. All message bodies are a size of some multiple of this.
+/// The padding interval in bytes. All message bodies are a size of some multiple of this.
 /// All messages bodies are a minimum  of this size.
-// FIXME: double check what this should be
-pub const PADDING_BLOCK_SIZE: u32 = 256;
+// from https://github.com/signalapp/libsignal/blob/af7bb8567c812aa13625fc90076bf71a59d64ff5/rust/protocol/src/crypto.rs#L92C41-L92C41
+pub const PADDING_BLOCK_SIZE: u32 = 10 * 128 / 8;
 /// The most blocks a message body can contain.
 // from https://github.com/signalapp/Signal-Android/blob/36a8c4d8ba9fdb62905ecb9a20e3eeba4d2f9022/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java
 pub const MAX_BLOCKS_IN_BODY: u32 = (100 * 1024 * 1024) / PADDING_BLOCK_SIZE;
+/// The maxmimum number of bytes that can be sent inline; larger values use the HTTP server.
+// FIXME: should only apply to client-server, not p2p
+// In actuality, this is 2000 for Signal:
+// https://github.com/signalapp/Signal-Android/blob/244902ecfc30e21287a35bb1680e2dbe6366975b/app/src/main/java/org/thoughtcrime/securesms/util/PushCharacterCalculator.java#L23
+// but we align to a close block count since in practice we sample from block counts
+pub const INLINE_MAX_SIZE: u32 = 14 * PADDING_BLOCK_SIZE;
 
 #[macro_export]
 macro_rules! log {
@@ -59,10 +65,18 @@ pub enum MessageBody {
 }
 
 impl MessageBody {
-    fn size(&self) -> u32 {
+    /// Size on the wire of the message's body
+    fn size(&self) -> usize {
         match self {
-            MessageBody::Receipt => PADDING_BLOCK_SIZE,
-            MessageBody::Size(size) => size.get(),
+            MessageBody::Receipt => PADDING_BLOCK_SIZE as usize,
+            MessageBody::Size(size) => {
+                let size = size.get();
+                if size <= INLINE_MAX_SIZE {
+                    size as usize
+                } else {
+                    INLINE_MAX_SIZE as usize
+                }
+            }
         }
     }
 }
@@ -275,7 +289,7 @@ impl SerializedMessage {
         &self,
         writer: &mut T,
     ) -> std::io::Result<()> {
-        let body_buf = vec![0; self.body.size() as usize];
+        let body_buf = vec![0; self.body.size()];
 
         // write_all_vectored is not yet stable x_x
         // https://github.com/rust-lang/rust/issues/70436