snowstorm/control/
odoh.rs

1use bytes::BytesMut;
2use odoh_rs::*;
3use rand::rngs::StdRng;
4use rand::Rng;
5use rand::SeedableRng;
6use reqwest::Request;
7use serde_json::json;
8
9pub async fn do_odoh_request(request: &Request) -> Result<Vec<u8>, String> {
10    let ips = ["5.42.201.107:443"].map(|ip| ip.parse().unwrap());
11    let cli = reqwest::Client::builder()
12        .resolve_to_addrs("0day.dev", &ips)
13        .build()
14        .expect("client builder");
15
16    let mut rng = StdRng::from_os_rng();
17    let padding_len = rng.random_range(0..10);
18    let path = request.url().path();
19    let query = request
20        .url()
21        .query()
22        .map(|q| format!("?{}", q))
23        .unwrap_or_default();
24    let request = json!({
25        "m": request.method().as_str(),
26        "p": format!("{}{}", path, query),
27        "b": request.body().map(|b| b.as_bytes())
28    });
29    let request = serde_json::to_vec(&request).map_err(|e| e.to_string())?;
30    let query = ObliviousDoHMessagePlaintext::new(request, padding_len);
31
32    // There's no nice way to build a ObliviousDoHConfigContents from a public key, so instead
33    // we manually deserialize a buffer that contains all the necessary fields.
34    let serialized_key = [
35        0x00, 0x20, // kem id
36        0x00, 0x01, // kdf id
37        0x00, 0x01, // aseid
38        0x00, 0x20, // public key length
39
40        // Actual public key bytes
41        0x8b, 0x92, 0x16, 0x80, 0x7d, 0xce, 0xe9, 0xc3, 0xae, 0x5e, 0x42, 0x2b, 0x62, 0xc7, 0xad,
42        0x80, 0x3b, 0x40, 0x60, 0x19, 0x75, 0x6b, 0xd7, 0x82, 0x0c, 0xac, 0x16, 0x1c, 0x68, 0x91,
43        0xcb, 0x39
44    ];
45    let mut buf = BytesMut::new();
46    buf.extend_from_slice(serialized_key.as_slice());
47    let config: ObliviousDoHConfigContents = odoh_rs::Deserialize::deserialize(&mut buf).unwrap();
48
49    let mut buf = BytesMut::new();
50    config.serialize::<BytesMut>(&mut buf).unwrap();
51
52    let (query_enc, cli_secret) =
53        encrypt_query(&query, &config, &mut rng).map_err(|e| e.to_string())?;
54    let query_body = compose(&query_enc).map_err(|e| e.to_string())?;
55
56     let proxy_query = [
57         ("targethost", "dope.snowstorm.love"),
58         ("targetpath", "/dns-query"),
59     ];
60
61    // Fastly doesn't want to proxy to unknown odoh servers
62    // Need to figure this out first and then enable the relay
63    let req_builder = cli
64        .post("https://0day.dev/proxy")
65        //.post("https://dope.snowstorm.love/dns-query")
66        .header("content-type", "application/oblivious-dns-message")
67        .header("accept", "application/oblivious-dns-message")
68        .header("cache-control", "no-cache, no-store")
69        .query(&proxy_query);
70
71    let mut resp_body = req_builder
72        .body(query_body.to_vec())
73        .send()
74        .await
75        .map_err(|e| e.to_string())?
76        .error_for_status()
77        .map_err(|e| e.to_string())?
78        .bytes()
79        .await
80        .map_err(|e| e.to_string())?;
81
82    let response_enc = parse(&mut resp_body).map_err(|e| e.to_string())?;
83    let response_dec =
84        decrypt_response(&query, &response_enc, cli_secret).map_err(|e| e.to_string())?;
85
86    Ok(response_dec.into_msg().to_vec())
87}
88
89#[cfg(test)]
90mod tests {
91    use crate::control::get_backend_url;
92
93    use super::*;
94    use reqwest::Method;
95
96    #[tokio::test]
97    async fn test_do_odoh_request() {
98        let url = format!("{}/{}", get_backend_url(), "auth?method=info");
99        let request = Request::new(Method::GET, url.parse().unwrap());
100        let response = do_odoh_request(&request).await;
101        println!("Response: {:?}", response);
102        assert!(response.is_ok());
103    }
104}