Skip to content

Commit

Permalink
WebSocket proxy OHTTP KeyConfig fetch
Browse files Browse the repository at this point in the history
Bootstrap Oblivious HTTP without revealing a client IP to the directory.
  • Loading branch information
DanGould committed Apr 20, 2024
1 parent 7855489 commit 144c81e
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 10 deletions.
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions mutiny-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ aes = { version = "0.8" }
jwt-compact = { version = "0.8.0-beta.1", features = ["es256k"] }
argon2 = { version = "0.5.0", features = ["password-hash", "alloc"] }
once_cell = "1.18.0"
gloo-net = { version = "0.5.0", features = ["io-util"] }
payjoin = { version = "0.15.0", features = ["v2", "send", "receive", "base64"] }
futures-rustls = { version = "0.25.1" }
rustls-pki-types = { version = "1.4.0", features = ["web"] }
webpki-roots = "0.26.1"
bincode = "1.3.3"
hex-conservative = "0.1.1"
async-lock = "3.2.0"
Expand Down Expand Up @@ -85,6 +89,8 @@ wasm-bindgen-futures = { version = "0.4.38" }
gloo-net = { version = "0.4.0" }
web-time = "1.1"
gloo-timers = { version = "0.3.0", features = ["futures"] }
web-sys = { version = "0.3.65", features = ["console"] }
js-sys = "0.3.65"
getrandom = { version = "0.2", features = ["js"] }
# add nip07 feature for wasm32
nostr = { version = "0.29.0", default-features = false, features = ["nip04", "nip05", "nip07", "nip47", "nip57"] }
Expand Down
4 changes: 3 additions & 1 deletion mutiny-core/src/nodemanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,9 @@ impl<S: MutinyStorage> NodeManager<S> {
) -> Result<(Enrolled, payjoin::OhttpKeys), PayjoinError> {
use crate::payjoin::{fetch_ohttp_keys, random_ohttp_relay, PAYJOIN_DIR};

let ohttp_keys = fetch_ohttp_keys(PAYJOIN_DIR.to_owned()).await?;
log_info!(self.logger, "Starting payjoin session");

let ohttp_keys = fetch_ohttp_keys().await?;
let http_client = reqwest::Client::builder().build()?;

let mut enroller = payjoin::receive::v2::Enroller::from_directory_config(
Expand Down
79 changes: 70 additions & 9 deletions mutiny-core/src/payjoin.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::collections::HashMap;
use std::sync::Arc;

use crate::error::MutinyError;
use crate::storage::MutinyStorage;
use bitcoin::Transaction;
use core::time::Duration;
use gloo_net::websocket::futures::WebSocket;
use hex_conservative::DisplayHex;
use once_cell::sync::Lazy;
use payjoin::receive::v2::Enrolled;
Expand Down Expand Up @@ -77,16 +79,75 @@ impl<S: MutinyStorage> PayjoinStorage for S {
}
}

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

let ohttp_keys_res = http_client
.get(format!("{}/ohttp-keys", directory.as_ref()))
.send()
.await?
.bytes()
.await?;
OhttpKeys::decode(ohttp_keys_res.as_ref()).map_err(|_| Error::OhttpDecodeFailed)
let tls_connector = {
let root_store = futures_rustls::rustls::RootCertStore {
roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
};
let config = futures_rustls::rustls::ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth();
futures_rustls::TlsConnector::from(Arc::new(config))
};
let directory_host = PAYJOIN_DIR.host_str().ok_or(Error::BadDirectoryHost)?;
let domain = futures_rustls::rustls::pki_types::ServerName::try_from(directory_host)
.map_err(|_| Error::BadDirectoryHost)?
.to_owned();

let ws = WebSocket::open(&format!(
"wss://{}:443",
random_ohttp_relay()
.host_str()
.ok_or(Error::BadOhttpWsHost)?
))
.map_err(|_| Error::BadOhttpWsHost)?;

let mut tls_stream = tls_connector
.connect(domain, ws)
.await
.map_err(|e| Error::RequestFailed(e.to_string()))?;
let ohttp_keys_req = format!(
"GET /ohttp-keys HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n",
directory_host
);
tls_stream
.write_all(ohttp_keys_req.as_bytes())
.await
.map_err(|e| Error::RequestFailed(e.to_string()))?;
tls_stream
.flush()
.await
.map_err(|e| Error::RequestFailed(e.to_string()))?;
let mut response_bytes = Vec::new();
tls_stream
.read_to_end(&mut response_bytes)
.await
.map_err(|e| Error::RequestFailed(e.to_string()))?;
let (_headers, res_body) = separate_headers_and_body(&response_bytes)?;
payjoin::OhttpKeys::decode(res_body).map_err(|_| Error::OhttpDecodeFailed)
}

fn separate_headers_and_body(response_bytes: &[u8]) -> Result<(&[u8], &[u8]), Error> {
let separator = b"\r\n\r\n";

// Search for the separator
if let Some(position) = response_bytes
.windows(separator.len())
.position(|window| window == separator)
{
// The body starts immediately after the separator
let body_start_index = position + separator.len();
let headers = &response_bytes[..position];
let body = &response_bytes[body_start_index..];

Ok((headers, body))
} else {
Err(Error::RequestFailed(
"No header-body separator found in the response".to_string(),
))
}
}

#[derive(Debug)]
Expand Down

0 comments on commit 144c81e

Please sign in to comment.