mk & mpkb files
Some checks failed
CI / checks (push) Failing after 2m24s

This commit is contained in:
Alex Emmet 2026-07-03 18:56:54 +02:00
commit 75f4139dea
17 changed files with 382 additions and 131 deletions

3
example/.gitignore vendored
View file

@ -10,3 +10,6 @@ web-client/node_modules
web-client/public/host_public_key_bundle.hex
web-client/public/mtp_dev_cert_hash.txt
web-client/dist/
*.mk
*.mpkb

11
example/Cargo.lock generated
View file

@ -207,9 +207,7 @@ dependencies = [
name = "client"
version = "0.1.0"
dependencies = [
"hex",
"mtp",
"serde_json",
"tokio",
]
@ -902,6 +900,7 @@ dependencies = [
"mtp-codec",
"mtp-common",
"mtp-crypto",
"mtp-files",
"mtp-host",
"mtp-transport",
"mtp-type-map",
@ -959,6 +958,14 @@ dependencies = [
"zeroize",
]
[[package]]
name = "mtp-files"
version = "0.1.0"
dependencies = [
"mtp-crypto",
"thiserror 1.0.69",
]
[[package]]
name = "mtp-host"
version = "0.1.0"

View file

@ -8,7 +8,5 @@ name = "client"
path = "src/main.rs"
[dependencies]
mtp = { version = "0.1.0", path = "../../", features = ["client", "crypto"] }
mtp = { version = "0.1.0", path = "../../", features = ["client", "crypto", "files"] }
tokio = { version = "1", features = ["full"] }
serde_json = "1"
hex = "0.4"

View file

@ -1,52 +1,49 @@
use std::fs;
use mtp::client::{ClientConfig, MTPClient, MTPConnection};
use mtp::crypto::{Ed25519Signer, Keyring, MlDsaSigner, PublicKeyBundle};
use mtp::crypto::{
Ed25519Signer, KemPrivateKey, KemPublicKey, Keyring, MlDsaSigner, PublicKeyBundle,
};
use mtp::files::{load_keyring, save_keyring};
pub async fn connect_or_register(
mut config: ClientConfig,
host_public_key: PublicKeyBundle,
client_key_path: &str,
key_prefix: &str,
) -> Result<(MTPConnection, Keyring), Box<dyn std::error::Error>> {
if let Ok(data) = fs::read_to_string(client_key_path) {
let json: serde_json::Value = serde_json::from_str(&data)?;
let client_id = json["client_id"].as_u64().expect("Invalid client_id");
let keyring = Keyring::from_bytes(&hex::decode(
json["keyring"].as_str().expect("Missing keyring"),
)?)?;
let keyring_path = format!("{key_prefix}.mk");
let id_path = format!("{key_prefix}.id");
println!("Loaded client keys (ID: {})", client_id);
if let (Ok(keyring), Ok(id)) = (load_keyring(&keyring_path), fs::read_to_string(&id_path)) {
let client_id: u64 = id.trim().parse()?;
println!("Loaded client keys (ID: {client_id})");
config.client_id = client_id;
let conn = MTPClient::auth_connect(config, &keyring, &host_public_key).await?;
println!("Authenticated (version {})", conn.version);
Ok((conn, keyring))
} else {
println!("No existing keys found: registering new client");
let (_ed_signer, sig_sk, sig_pk) = Ed25519Signer::generate();
let (_pq_signer, sig_pq_sk, sig_pq_pk) = MlDsaSigner::generate();
let keyring = Keyring::new(
mtp::crypto::KemPublicKey::new(vec![]),
mtp::crypto::KemPrivateKey::new(vec![]),
sig_pq_pk,
sig_pq_sk,
sig_pk,
sig_sk,
);
let keyring_bytes = keyring.to_bytes();
let conn = MTPClient::auth_register(config, &keyring, &host_public_key).await?;
println!("Registered with ID: {}", conn.client_id);
let json = serde_json::json!({
"client_id": conn.client_id,
"keyring": hex::encode(&keyring_bytes),
});
fs::write(client_key_path, serde_json::to_string_pretty(&json)?)?;
println!("Saved client keys -> {client_key_path}");
let keyring = Keyring::from_bytes(&keyring_bytes)?;
Ok((conn, keyring))
return Ok((conn, keyring));
}
println!("No existing keys found: registering new client");
/* The client authenticates with signatures only, so the KEM slot is empty. */
let (_ed_signer, sig_sk, sig_pk) = Ed25519Signer::generate();
let (_pq_signer, sig_pq_sk, sig_pq_pk) = MlDsaSigner::generate();
let keyring = Keyring::new(
KemPublicKey::new(vec![]),
KemPrivateKey::new(vec![]),
sig_pq_pk,
sig_pq_sk,
sig_pk,
sig_sk,
);
let conn = MTPClient::auth_register(config, &keyring, &host_public_key).await?;
println!("Registered with ID: {}", conn.client_id);
save_keyring(&keyring, &keyring_path)?;
fs::write(&id_path, conn.client_id.to_string())?;
println!("Saved client keys -> {keyring_path}");
Ok((conn, keyring))
}

View file

@ -5,7 +5,7 @@ use std::fs;
use std::path::Path;
use mtp::client::ClientConfig;
use mtp::crypto::{KemPublicKey, PublicKeyBundle, SignaturePqPublicKey, SignaturePublicKey};
use mtp::files::load_public_key_bundle;
fn dev_cert_path() -> String {
std::env::var("MTP_DEV_CERT").unwrap_or_else(|_| {
@ -25,19 +25,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
"Missing TLS certificate at {cert_path}: enter the Nix shell first or run the server to generate it: {e}"
)
});
let host_public_key = PublicKeyBundle::new(
KemPublicKey::new(
fs::read("host_enc_kem_pk.bin")
.expect("Missing host_enc_kem_pk.bin: run server first"),
),
SignaturePqPublicKey::new(
fs::read("host_sig_pq_pk.bin")
.expect("Missing host_sig_pq_pk.bin: run server first"),
),
SignaturePublicKey::new(
fs::read("host_sig_pk.bin").expect("Missing host_sig_pk.bin: run server first"),
),
);
let host_public_key = load_public_key_bundle("host.mpkb")
.expect("Missing host.mpkb: run the server first to export it");
println!("Connecting to 127.0.0.1:8080 ...");
@ -46,8 +35,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.with_description("MTP example client");
let server_bundle = host_public_key.clone();
let (conn, keyring) =
auth::connect_or_register(config, host_public_key, "client_keys.json").await?;
let (conn, keyring) = auth::connect_or_register(config, host_public_key, "client").await?;
messages::send_and_receive(&conn, &keyring, &server_bundle).await?;
println!("\nDone");

View file

@ -10,20 +10,26 @@ pub fn build_demo_message(
) -> CommunicationValue {
// Encrypt to the server's KEM public key; the server decrypts with its keyring.
let enc_type = EncryptionType::MlKemChaCha20Poly1305;
let signer = Ed25519Signer::new(&keyring.sig_cl_secret_key)
.expect("Ed25519 signer from keyring");
let signer =
Ed25519Signer::new(&keyring.sig_cl_secret_key).expect("Ed25519 signer from keyring");
let tm = TypeMap::latest();
let inner_enc = DataValue::Container(vec![
(DataType::Version.to_id(&tm), DataValue::Str("secret inner data".into())),
(
DataType::Version.to_id(&tm),
DataValue::Str("secret inner data".into()),
),
(DataType::Id.to_id(&tm), 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::Version.to_id(&tm),
DataValue::Str("signed by client".into()),
),
(DataType::Id.to_id(&tm), DataValue::UnsignedNumber(99)),
]);
let mut dv_sig = inner_sig;
@ -37,7 +43,13 @@ pub fn build_demo_message(
(DataType::Id.to_id(&tm), DataValue::UnsignedNumber(7)),
]);
let mut dv_sec = inner_sec;
dv_sec.sign_and_encrypt_container(SigAlgorithm::ED25519, &signer, enc_type, server_bundle, b"demo-aad");
dv_sec.sign_and_encrypt_container(
SigAlgorithm::ED25519,
&signer,
enc_type,
server_bundle,
b"demo-aad",
);
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)

View file

@ -4,4 +4,4 @@ version = "0.1.0"
edition = "2024"
[dependencies]
mtp = { version = "0.1.0", path = "../../", features = ["crypto"] }
mtp = { version = "0.1.0", path = "../../", features = ["files"] }

View file

@ -1,13 +1,35 @@
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,
};
fn main() -> Result<(), files::FileError> {
let prefix = std::env::args()
.nth(1)
.unwrap_or_else(|| "keyring".to_string());
let keyring_path = PathBuf::from(format!("{prefix}.{KEYRING_EXTENSION}"));
let bundle_path = PathBuf::from(format!("{prefix}.{BUNDLE_EXTENSION}"));
fn main() {
let keyring = Keyring::generate();
let bundle = keyring.public_key_bundle();
save_keyring(&keyring, &keyring_path)?;
save_public_key_bundle(&keyring.public_key_bundle(), &bundle_path)?;
// The `Debug` impl redacts private keys by design, so use the encoding
// methods to emit the full keyring (public + secret keys) instead.
println!("Keyring (hex):\n{}\n", keyring.to_hex());
println!("Keyring (base64):\n{}\n", keyring.to_base64());
/* Read both back to confirm the files round-trip through the on-disk format. */
let loaded_keyring = load_keyring(&keyring_path)?;
let loaded_bundle = load_public_key_bundle(&bundle_path)?;
assert_eq!(keyring.to_bytes(), loaded_keyring.to_bytes());
assert_eq!(
keyring.public_key_bundle().as_bytes(),
loaded_bundle.as_bytes()
);
println!("\nPrivateKeyRing (base64):\n{}", keyring.to_base64());
println!("PublicKeyBundle (base64):\n{}", bundle.to_base64());
println!("\nPublicKeyBundle (base64):\n{}", loaded_bundle.to_base64());
println!("Wrote keyring -> {}", keyring_path.display());
println!("Wrote bundle -> {}", bundle_path.display());
Ok(())
}

View file

@ -8,7 +8,7 @@ name = "server"
path = "src/main.rs"
[dependencies]
mtp = { version = "0.1.0", path = "../../", features = ["crypto", "host"] }
mtp = { version = "0.1.0", path = "../../", features = ["crypto", "host", "files"] }
rcgen = "0.14"
tokio = { version = "1", features = ["full"] }
serde_json = { version = "1" }

View file

@ -1,7 +1,5 @@
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataTypeId, DataValue, TypeMap};
use mtp::crypto::{
CryptoError, Keyring, SignaturePublicKey, SignatureScheme, verify_ed25519,
};
use mtp::crypto::{CryptoError, Keyring, SignaturePublicKey, SignatureScheme, verify_ed25519};
struct Ed25519Verifier(SignaturePublicKey);
@ -57,7 +55,10 @@ pub fn process_and_respond(
let enc = msg.get_data(DataType::EncryptedPayload);
if matches!(enc, DataValue::EncryptedContainer(_)) {
let mut dv = enc.clone();
if dv.decrypt_into_container(host_keyring, b"demo-aad").is_some() {
if dv
.decrypt_into_container(host_keyring, b"demo-aad")
.is_some()
{
if let Some(entries) = dv.as_container() {
println!(" Decrypted EncryptedPayload: {:?}", entries);
enc_status = format!("EncryptedPayload decrypted OK ({} entries)", entries.len());
@ -93,7 +94,9 @@ pub fn process_and_respond(
if let Some(pk_bundle) = client_pk {
let verifier = Ed25519Verifier(pk_bundle.sig_cl_public_key.clone());
let mut dv = secure.clone();
if dv.decrypt_signed_encrypted_container(host_keyring, b"demo-aad").is_some()
if dv
.decrypt_signed_encrypted_container(host_keyring, b"demo-aad")
.is_some()
&& dv.verify_into_container(&verifier).is_some()
{
if let Some(entries) = dv.as_container() {

View file

@ -1,50 +1,33 @@
use std::fs;
use mtp::crypto::kem::HybridKem;
use mtp::crypto::{Ed25519Signer, Keyring, MlDsaSigner};
use mtp::crypto::Keyring;
use mtp::files::{load_keyring, save_keyring, save_public_key_bundle};
/* Host id is fixed for the example; only the keyring itself is persisted. */
const HOST_ID: u64 = 1;
pub fn load_or_generate_host_keys(
path: &str,
keyring_path: &str,
) -> Result<(u64, Keyring), Box<dyn std::error::Error>> {
if let Ok(data) = fs::read_to_string(path) {
let json: serde_json::Value = serde_json::from_str(&data)?;
let hid = json["host_id"].as_u64().unwrap_or(1);
let keyring = Keyring::from_bytes(&hex::decode(json["keyring"].as_str().unwrap())?)?;
println!("Loaded host keys (ID: {})", hid);
return Ok((hid, keyring));
if let Ok(keyring) = load_keyring(keyring_path) {
println!("Loaded host keyring from {keyring_path}");
return Ok((HOST_ID, keyring));
}
let (_ed_signer, sig_sk, sig_pk) = Ed25519Signer::generate();
let (_pq_signer, sig_pq_sk, sig_pq_pk) = MlDsaSigner::generate();
let (kem_sk, kem_pk) = HybridKem::generate_keypair();
let keyring = Keyring::new(kem_pk, kem_sk, sig_pq_pk, sig_pq_sk, sig_pk, sig_sk);
let json = serde_json::json!({
"host_id": 1,
"keyring": hex::encode(keyring.to_bytes()),
});
fs::write(path, serde_json::to_string_pretty(&json)?)?;
println!("Generated host keys -> {path}");
Ok((1u64, keyring))
let keyring = Keyring::generate();
save_keyring(&keyring, keyring_path)?;
println!("Generated host keyring -> {keyring_path}");
Ok((HOST_ID, keyring))
}
pub fn export_host_public_keys(host_keyring: &Keyring) -> Result<(), Box<dyn std::error::Error>> {
let public_key_bundle_hex = hex::encode(host_keyring.public_key_bundle().as_bytes());
let bundle = host_keyring.public_key_bundle();
save_public_key_bundle(&bundle, "host.mpkb")?;
fs::write("host_public_key_bundle.hex", &public_key_bundle_hex)?;
/* The web client fetches the bundle as hex over HTTP. */
let bundle_hex = hex::encode(bundle.as_bytes());
fs::write("host_public_key_bundle.hex", &bundle_hex)?;
fs::create_dir_all("web-client/public")?;
fs::write(
"web-client/public/host_public_key_bundle.hex",
&public_key_bundle_hex,
)?;
fs::write(
"host_enc_kem_pk.bin",
host_keyring.kem_public_key.as_bytes(),
)?;
fs::write("host_sig_pk.bin", host_keyring.sig_cl_public_key.as_bytes())?;
fs::write(
"host_sig_pq_pk.bin",
host_keyring.sig_pq_public_key.as_bytes(),
)?;
fs::write("web-client/public/host_public_key_bundle.hex", &bundle_hex)?;
Ok(())
}

View file

@ -36,7 +36,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
tls::export_webtransport_cert_hash(&cert_hash)?;
println!("WebTransport certificate sha256: {cert_hash}");
let (_host_id, host_keyring) = keys::load_or_generate_host_keys("host_keys.json")?;
let (_host_id, host_keyring) = keys::load_or_generate_host_keys("host.mk")?;
keys::export_host_public_keys(&host_keyring)?;
// The keyring is moved into the host config; keep a copy for decrypting the
@ -47,7 +47,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (clients, next_id) = clients::load_client_db("clients.json")?;
let clients_for_get = clients.clone();
let get_existing_user = move |id: u64| {
let get_existing_user = move |id: u64, _description: Option<String>| {
let clients = clients_for_get.clone();
Box::pin(async move {
let result = clients.lock().unwrap().get(&id).cloned();
@ -63,7 +63,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let clients_for_register = clients.clone();
let next_id_for_register = next_id.clone();
let clients_path = "clients.json".to_string();
let complete_register = move |bundle: mtp::crypto::PublicKeyBundle| {
let complete_register = move |bundle: mtp::crypto::PublicKeyBundle,
_description: Option<String>| {
let db_arc = clients_for_register.clone();
let nid_arc = next_id_for_register.clone();
let path = clients_path.clone();
@ -95,7 +96,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
cert_pem,
key_pem,
)
.with_authentication(host_keyring, get_existing_user, complete_register)
.with_authentication(
host_keyring,
Box::new(get_existing_user),
Box::new(complete_register),
)
.with_authentication_policy(AuthenticationPolicy::ForceAuthentication);
let mut host = MTPHost::new(config).await?;