Skip to content
This repository was archived by the owner on Feb 3, 2025. It is now read-only.

Commit 84a1c6e

Browse files
committed
WebSocket proxy OHTTP KeyConfig fetch
Bootstrap Oblivious HTTP without revealing a client IP to the directory.
1 parent eb70217 commit 84a1c6e

File tree

4 files changed

+131
-12
lines changed

4 files changed

+131
-12
lines changed

Diff for: Cargo.lock

+51-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: mutiny-core/Cargo.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ aes = { version = "0.8" }
4545
jwt-compact = { version = "0.8.0-beta.1", features = ["es256k"] }
4646
argon2 = { version = "0.5.0", features = ["password-hash", "alloc"] }
4747
once_cell = "1.18.0"
48+
gloo-net = { version = "0.5.0", features = ["io-util"] }
4849
payjoin = { version = "0.15.0", features = ["v2", "send", "receive", "base64"] }
50+
futures-rustls = { version = "0.25.1" }
51+
rustls-pki-types = { version = "1.4.0", features = ["web"] }
52+
webpki-roots = "0.26.1"
4953
bincode = "1.3.3"
5054
hex-conservative = "0.1.1"
5155
async-lock = "3.2.0"
@@ -81,7 +85,6 @@ ignored_tests = []
8185
wasm-bindgen-futures = { version = "0.4.38" }
8286
web-sys = { version = "0.3.65", features = ["console"] }
8387
js-sys = "0.3.65"
84-
gloo-net = { version = "0.4.0" }
8588
instant = { version = "0.1", features = ["wasm-bindgen"] }
8689
getrandom = { version = "0.2", features = ["js"] }
8790
# add nip07 feature for wasm32

Diff for: mutiny-core/src/nodemanager.rs

+2
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,8 @@ impl<S: MutinyStorage> NodeManager<S> {
730730
pub async fn start_payjoin_session(&self) -> Result<(Enrolled, OhttpKeys), PayjoinError> {
731731
use crate::payjoin::{OHTTP_RELAYS, PAYJOIN_DIR};
732732

733+
log_info!(self.logger, "Starting payjoin session");
734+
733735
let ohttp_keys =
734736
crate::payjoin::fetch_ohttp_keys(OHTTP_RELAYS[0].to_owned(), PAYJOIN_DIR.to_owned())
735737
.await?;

Diff for: mutiny-core/src/payjoin.rs

+74-9
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use std::collections::HashMap;
2+
use std::sync::Arc;
23

34
use crate::error::MutinyError;
45
use crate::storage::MutinyStorage;
56
use core::time::Duration;
7+
use gloo_net::websocket::futures::WebSocket;
68
use hex_conservative::DisplayHex;
79
use once_cell::sync::Lazy;
810
use payjoin::receive::v2::Enrolled;
@@ -69,16 +71,73 @@ impl<S: MutinyStorage> PayjoinStorage for S {
6971
}
7072
}
7173

72-
pub async fn fetch_ohttp_keys(_ohttp_relay: Url, directory: Url) -> Result<OhttpKeys, Error> {
73-
let http_client = reqwest::Client::builder().build()?;
74+
pub async fn fetch_ohttp_keys(ohttp_relay: Url, directory: Url) -> Result<OhttpKeys, Error> {
75+
use futures_util::{AsyncReadExt, AsyncWriteExt};
7476

75-
let ohttp_keys_res = http_client
76-
.get(format!("{}/ohttp-keys", directory.as_ref()))
77-
.send()
78-
.await?
79-
.bytes()
80-
.await?;
81-
Ok(OhttpKeys::decode(ohttp_keys_res.as_ref()).map_err(|_| Error::OhttpDecodeFailed)?)
77+
let tls_connector = {
78+
let root_store = futures_rustls::rustls::RootCertStore {
79+
roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
80+
};
81+
let config = futures_rustls::rustls::ClientConfig::builder()
82+
.with_root_certificates(root_store)
83+
.with_no_client_auth();
84+
futures_rustls::TlsConnector::from(Arc::new(config))
85+
};
86+
let directory_host = directory.host_str().ok_or(Error::BadDirectoryHost)?;
87+
let domain = futures_rustls::rustls::pki_types::ServerName::try_from(directory_host)
88+
.map_err(|_| Error::BadDirectoryHost)?
89+
.to_owned();
90+
91+
let ws = WebSocket::open(&format!(
92+
"wss://{}:443",
93+
ohttp_relay.host_str().ok_or(Error::BadOhttpWsHost)?
94+
))
95+
.map_err(|_| Error::BadOhttpWsHost)?;
96+
97+
let mut tls_stream = tls_connector
98+
.connect(domain, ws)
99+
.await
100+
.map_err(|e| Error::RequestFailed(e.to_string()))?;
101+
let ohttp_keys_req = format!(
102+
"GET /ohttp-keys HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n",
103+
directory_host
104+
);
105+
tls_stream
106+
.write_all(ohttp_keys_req.as_bytes())
107+
.await
108+
.map_err(|e| Error::RequestFailed(e.to_string()))?;
109+
tls_stream
110+
.flush()
111+
.await
112+
.map_err(|e| Error::RequestFailed(e.to_string()))?;
113+
let mut response_bytes = Vec::new();
114+
tls_stream
115+
.read_to_end(&mut response_bytes)
116+
.await
117+
.map_err(|e| Error::RequestFailed(e.to_string()))?;
118+
let (_headers, res_body) = separate_headers_and_body(&response_bytes)?;
119+
payjoin::OhttpKeys::decode(res_body).map_err(|_| Error::OhttpDecodeFailed)
120+
}
121+
122+
fn separate_headers_and_body(response_bytes: &[u8]) -> Result<(&[u8], &[u8]), Error> {
123+
let separator = b"\r\n\r\n";
124+
125+
// Search for the separator
126+
if let Some(position) = response_bytes
127+
.windows(separator.len())
128+
.position(|window| window == separator)
129+
{
130+
// The body starts immediately after the separator
131+
let body_start_index = position + separator.len();
132+
let headers = &response_bytes[..position];
133+
let body = &response_bytes[body_start_index..];
134+
135+
Ok((headers, body))
136+
} else {
137+
Err(Error::RequestFailed(
138+
"No header-body separator found in the response".to_string(),
139+
))
140+
}
82141
}
83142

84143
#[derive(Debug)]
@@ -89,6 +148,9 @@ pub enum Error {
89148
OhttpDecodeFailed,
90149
Shutdown,
91150
SessionExpired,
151+
BadDirectoryHost,
152+
BadOhttpWsHost,
153+
RequestFailed(String),
92154
}
93155

94156
impl std::error::Error for Error {}
@@ -102,6 +164,9 @@ impl std::fmt::Display for Error {
102164
Error::OhttpDecodeFailed => write!(f, "Failed to decode ohttp keys"),
103165
Error::Shutdown => write!(f, "Payjoin stopped by application shutdown"),
104166
Error::SessionExpired => write!(f, "Payjoin session expired. Create a new payment request and have the sender try again."),
167+
Error::BadDirectoryHost => write!(f, "Bad directory host"),
168+
Error::BadOhttpWsHost => write!(f, "Bad ohttp ws host"),
169+
Error::RequestFailed(e) => write!(f, "Request failed: {}", e),
105170
}
106171
}
107172
}

0 commit comments

Comments
 (0)