General Upgrade, NEW: WebServers, Better Docs
Some checks failed
CI / checks (push) Failing after 4m20s
Some checks failed
CI / checks (push) Failing after 4m20s
This commit is contained in:
parent
5f11d476b6
commit
cf52b22537
122 changed files with 10308 additions and 5205 deletions
714
example/Cargo.lock
generated
714
example/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"server",
|
||||
"client", "keygen",
|
||||
"client", "keygen", "webserver",
|
||||
]
|
||||
resolver = "3"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "client"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
|
|
@ -8,6 +8,6 @@ name = "client"
|
|||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
mtp = { version = "0.1.0", path = "../../", features = ["client", "crypto", "files", "pipes"] }
|
||||
mtp = { version = "0.2.0", path = "../../", features = ["client", "crypto", "files", "pipes"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
rand = "0.8"
|
||||
rand = "0.10.1"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use mtp::client::{ClientConfig, MTPClient, MTPConnection};
|
|||
use mtp::crypto::{
|
||||
Ed25519Signer, KemPrivateKey, KemPublicKey, Keyring, MlDsaSigner, PublicKeyBundle,
|
||||
};
|
||||
use mtp::files::{load_keyring, save_keyring};
|
||||
use mtp::files::{load_keyring_raw, save_keyring_raw};
|
||||
|
||||
pub async fn connect_or_register(
|
||||
mut config: ClientConfig,
|
||||
|
|
@ -14,7 +14,10 @@ pub async fn connect_or_register(
|
|||
let keyring_path = format!("{key_prefix}.mk");
|
||||
let id_path = format!("{key_prefix}.id");
|
||||
|
||||
if let (Ok(keyring), Ok(id)) = (load_keyring(&keyring_path), fs::read_to_string(&id_path)) {
|
||||
if let (Ok(keyring), Ok(id)) = (
|
||||
load_keyring_raw(&keyring_path),
|
||||
fs::read_to_string(&id_path),
|
||||
) {
|
||||
let client_id: u64 = id.trim().parse()?;
|
||||
println!("Loaded client keys (ID: {client_id})");
|
||||
|
||||
|
|
@ -41,7 +44,7 @@ pub async fn connect_or_register(
|
|||
let conn = MTPClient::auth_register(config, &keyring, &host_public_key).await?;
|
||||
println!("Registered with ID: {}", conn.client_id);
|
||||
|
||||
save_keyring(&keyring, &keyring_path)?;
|
||||
save_keyring_raw(&keyring, &keyring_path)?;
|
||||
fs::write(&id_path, conn.client_id.to_string())?;
|
||||
println!("Saved client keys -> {keyring_path}");
|
||||
|
||||
|
|
|
|||
|
|
@ -13,33 +13,33 @@ pub fn build_demo_message(
|
|||
let signer = Ed25519Signer::new(&keyring.sig_cl_secret_key)?;
|
||||
|
||||
let tm = TypeMap::latest();
|
||||
let version_id = DataType::Version
|
||||
.try_to_id(&tm)
|
||||
.ok_or_else(|| std::io::Error::other("Version is absent from the type map"))?;
|
||||
let id_id = DataType::Id
|
||||
.try_to_id(&tm)
|
||||
.ok_or_else(|| std::io::Error::other("Id is absent from the type map"))?;
|
||||
|
||||
let inner_enc = DataValue::Container(vec![
|
||||
(
|
||||
DataType::Version.to_id(&tm),
|
||||
DataValue::Str("secret inner data".into()),
|
||||
),
|
||||
(DataType::Id.to_id(&tm), DataValue::UnsignedNumber(42)),
|
||||
(version_id, DataValue::Str("secret inner data".into())),
|
||||
(id_id, DataValue::UnsignedNumber(42)),
|
||||
]);
|
||||
let mut dv_enc = inner_enc;
|
||||
dv_enc.encrypt_container(enc_type, server_bundle, b"demo-aad");
|
||||
|
||||
let inner_sig = DataValue::Container(vec![
|
||||
(
|
||||
DataType::Version.to_id(&tm),
|
||||
DataValue::Str("signed by client".into()),
|
||||
),
|
||||
(DataType::Id.to_id(&tm), DataValue::UnsignedNumber(99)),
|
||||
(version_id, DataValue::Str("signed by client".into())),
|
||||
(id_id, DataValue::UnsignedNumber(99)),
|
||||
]);
|
||||
let mut dv_sig = inner_sig;
|
||||
dv_sig.sign_container(SigAlgorithm::ED25519, &signer);
|
||||
|
||||
let inner_sec = DataValue::Container(vec![
|
||||
(
|
||||
DataType::Version.to_id(&tm),
|
||||
version_id,
|
||||
DataValue::Str("signed+encrypted payload".into()),
|
||||
),
|
||||
(DataType::Id.to_id(&tm), DataValue::UnsignedNumber(7)),
|
||||
(id_id, DataValue::UnsignedNumber(7)),
|
||||
]);
|
||||
let mut dv_sec = inner_sec;
|
||||
dv_sec.sign_and_encrypt_container(
|
||||
|
|
@ -65,7 +65,7 @@ pub fn build_demo_message(
|
|||
)
|
||||
.add_typed_default(DataType::Data, DataValue::Str("Hello, MTP!".into()))
|
||||
.add_typed_default(DataType::Flags, DataValue::BoolTrue)
|
||||
.add_typed_default(DataType::Value, DataValue::Float(2, 12345))
|
||||
.add_typed_default(DataType::Value, DataValue::Float(1_234_500.0))
|
||||
.add_typed_default(
|
||||
DataType::BinaryData,
|
||||
DataValue::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF, 0x42]),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "keygen"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
mtp = { version = "0.1.0", path = "../../", features = ["files"] }
|
||||
mtp = { version = "0.2.0", path = "../../", features = ["files"] }
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ use std::path::PathBuf;
|
|||
|
||||
use mtp::crypto::Keyring;
|
||||
use mtp::files::{
|
||||
self, BUNDLE_EXTENSION, KEYRING_EXTENSION, load_keyring, load_public_key_bundle, save_keyring,
|
||||
save_public_key_bundle,
|
||||
self, BUNDLE_EXTENSION, KEYRING_EXTENSION, load_keyring_raw, load_public_key_bundle,
|
||||
save_keyring_raw, save_public_key_bundle,
|
||||
};
|
||||
|
||||
fn main() -> Result<(), files::FileError> {
|
||||
|
|
@ -11,11 +11,11 @@ fn main() -> Result<(), files::FileError> {
|
|||
let bundle_path = PathBuf::from(format!("bundle.{BUNDLE_EXTENSION}"));
|
||||
|
||||
let keyring = Keyring::generate();
|
||||
save_keyring(&keyring, &keyring_path)?;
|
||||
save_keyring_raw(&keyring, &keyring_path)?;
|
||||
save_public_key_bundle(&keyring.public_key_bundle(), &bundle_path)?;
|
||||
|
||||
/* Read both back to confirm the files round-trip through the on-disk format. */
|
||||
let loaded_keyring = load_keyring(&keyring_path)?;
|
||||
let loaded_keyring = load_keyring_raw(&keyring_path)?;
|
||||
let loaded_bundle = load_public_key_bundle(&bundle_path)?;
|
||||
assert_eq!(keyring.to_bytes(), loaded_keyring.to_bytes());
|
||||
assert_eq!(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "server"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
|
|
@ -8,10 +8,8 @@ name = "server"
|
|||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
mtp = { version = "0.1.0", path = "../../", features = ["crypto", "host", "files", "pipes"] }
|
||||
rcgen = "0.14"
|
||||
mtp = { version = "0.2.0", path = "../../", features = ["crypto", "tls", "host", "files", "pipes"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
serde_json = { version = "1" }
|
||||
hex = "0.4"
|
||||
base64 = "0.22"
|
||||
time = "0.3"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::fs;
|
||||
|
||||
use mtp::crypto::Keyring;
|
||||
use mtp::files::{load_keyring, save_keyring, save_public_key_bundle};
|
||||
use mtp::files::{load_keyring_raw, save_keyring_raw, save_public_key_bundle};
|
||||
|
||||
/* Host id is fixed for the example; only the keyring itself is persisted. */
|
||||
const HOST_ID: u64 = 1;
|
||||
|
|
@ -9,13 +9,13 @@ const HOST_ID: u64 = 1;
|
|||
pub fn load_or_generate_host_keys(
|
||||
keyring_path: &str,
|
||||
) -> Result<(u64, Keyring), Box<dyn std::error::Error>> {
|
||||
if let Ok(keyring) = load_keyring(keyring_path) {
|
||||
if let Ok(keyring) = load_keyring_raw(keyring_path) {
|
||||
println!("Loaded host keyring from {keyring_path}");
|
||||
return Ok((HOST_ID, keyring));
|
||||
}
|
||||
|
||||
let keyring = Keyring::generate();
|
||||
save_keyring(&keyring, keyring_path)?;
|
||||
save_keyring_raw(&keyring, keyring_path)?;
|
||||
println!("Generated host keyring -> {keyring_path}");
|
||||
Ok((HOST_ID, keyring))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,13 +86,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let (_host_id, host_keyring) = keys::load_or_generate_host_keys("host.mk")?;
|
||||
keys::export_host_public_keys(&host_keyring)?;
|
||||
|
||||
let decrypt_keyring = Arc::new(match mtp::crypto::Keyring::from_bytes(&host_keyring.to_bytes())
|
||||
{
|
||||
Ok(keyring) => keyring,
|
||||
Err(e) => {
|
||||
return Err(format!("failed to re-load host keyring for decryption: {e}").into());
|
||||
}
|
||||
});
|
||||
let decrypt_keyring = Arc::new(
|
||||
match mtp::crypto::Keyring::from_bytes(&host_keyring.to_bytes()) {
|
||||
Ok(keyring) => keyring,
|
||||
Err(e) => {
|
||||
return Err(format!("failed to re-load host keyring for decryption: {e}").into());
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let (clients, next_id) = clients::load_client_db("clients.json")?;
|
||||
|
||||
|
|
@ -100,7 +101,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let get_existing_user = move |id: u64, _description: Option<String>| {
|
||||
let clients = clients_for_get.clone();
|
||||
Box::pin(async move {
|
||||
let result = clients.lock()?.get(&id).cloned();
|
||||
let result = clients.lock().unwrap().get(&id).cloned();
|
||||
if result.is_some() {
|
||||
println!("Auth lookup: client ID {id} found");
|
||||
} else {
|
||||
|
|
@ -119,8 +120,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let nid_arc = next_id_for_register.clone();
|
||||
let path = clients_path.clone();
|
||||
Box::pin(async move {
|
||||
let mut db = db_arc.lock()?;
|
||||
let mut nid = nid_arc.lock()?;
|
||||
let mut db = db_arc.lock().unwrap();
|
||||
let mut nid = nid_arc.lock().unwrap();
|
||||
let id = *nid;
|
||||
*nid += 1;
|
||||
db.insert(id, bundle);
|
||||
|
|
@ -166,7 +167,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
);
|
||||
println!("Client ID: {}", conn.client_id);
|
||||
|
||||
let tm: &TypeMap = conn.codec.registry().get(&conn.version)?;
|
||||
let tm: &TypeMap = conn.codec.registry().get(&conn.version).unwrap();
|
||||
|
||||
println!("Waiting for messages / pipe requests ...");
|
||||
loop {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
use base64::Engine;
|
||||
use rcgen::{CertificateParams, ExtendedKeyUsagePurpose, IsCa, KeyPair, KeyUsagePurpose, SanType};
|
||||
use std::fs;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::path::Path;
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
pub fn load_or_generate_tls(
|
||||
cert_path: &str,
|
||||
|
|
@ -22,35 +19,13 @@ pub fn load_or_generate_tls(
|
|||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)?;
|
||||
let (cert_pem, key_pem) = mtp::crypto::tls::generate_self_signed_cert("localhost")?;
|
||||
|
||||
let mut params = CertificateParams::new(vec!["localhost".into()])?;
|
||||
params.not_before = OffsetDateTime::now_utc() - Duration::minutes(5);
|
||||
params.not_after = OffsetDateTime::now_utc() + Duration::days(13);
|
||||
|
||||
params
|
||||
.subject_alt_names
|
||||
.push(SanType::IpAddress(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))));
|
||||
params
|
||||
.subject_alt_names
|
||||
.push(SanType::IpAddress(IpAddr::V6(Ipv6Addr::new(
|
||||
0, 0, 0, 0, 0, 0, 0, 1,
|
||||
))));
|
||||
|
||||
params.key_usages = vec![KeyUsagePurpose::DigitalSignature];
|
||||
params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ServerAuth];
|
||||
params.is_ca = IsCa::NoCa;
|
||||
|
||||
let cert = params.self_signed(&key_pair)?;
|
||||
|
||||
let cert_str = cert.pem();
|
||||
let key_str = key_pair.serialize_pem();
|
||||
|
||||
fs::write(cert_path, cert_str.as_bytes())?;
|
||||
fs::write(key_path, key_str.as_bytes())?;
|
||||
fs::write(cert_path, &cert_pem)?;
|
||||
fs::write(key_path, &key_pem)?;
|
||||
println!("Wrote {cert_path} and {key_path}");
|
||||
|
||||
Ok((cert_str.into_bytes(), key_str.into_bytes()))
|
||||
Ok((cert_pem, key_pem))
|
||||
}
|
||||
|
||||
pub fn certificate_sha256_hex(cert: &[u8]) -> Result<String, Box<dyn std::error::Error>> {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "mtp-web-client",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@11.8.0",
|
||||
"scripts": {
|
||||
|
|
|
|||
10
example/webserver/Cargo.toml
Normal file
10
example/webserver/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "mtp-webserver-example"
|
||||
version = "0.2.0"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
http = "1"
|
||||
mtp = { path = "../..", features = ["web-server"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
38
example/webserver/src/main.rs
Normal file
38
example/webserver/src/main.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use http::StatusCode;
|
||||
use mtp::{
|
||||
host::HostConfig,
|
||||
webserver::{Http3Request, Http3Response, MTPWebServer, WebServerConfig},
|
||||
};
|
||||
|
||||
async fn health(_request: Http3Request, response: Http3Response) -> Http3Response {
|
||||
response
|
||||
.status(StatusCode::OK)
|
||||
.header("content-type", "application/json")
|
||||
.body(r#"{"status":"ok"}"#)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = HostConfig::new(
|
||||
"0.0.0.0".parse()?,
|
||||
4433,
|
||||
std::fs::read("cert.pem")?,
|
||||
std::fs::read("key.pem")?,
|
||||
);
|
||||
let web = WebServerConfig::new().route("/health", health)?.mtp_path("/mtp");
|
||||
let mut server = MTPWebServer::new(config, web).await?;
|
||||
println!("listening on {}", server.local_addr());
|
||||
|
||||
while let Some(connection) = server.accept().await? {
|
||||
println!(
|
||||
"MTP client connected: path={}, version={}, description={:?}",
|
||||
connection.path, connection.version, connection.description
|
||||
);
|
||||
tokio::spawn(async move {
|
||||
while let Ok(message) = connection.receiver.receive().await {
|
||||
println!("received MTP message {}", message.get_id());
|
||||
}
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Reference in a new issue