diff --git a/Cargo.lock b/Cargo.lock index 43b5b29..2e8d29d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1029,6 +1029,7 @@ name = "mtp-crypto" version = "0.1.0" dependencies = [ "aes-gcm", + "base64", "chacha20poly1305", "ed25519-dalek", "getrandom 0.4.3", diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index 26031c7..cc98dc8 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -17,6 +17,7 @@ hkdf = { version = "0.13", optional = true } sha2 = { version = "0.11", optional = true } zeroize = { version = "1.9", features = ["derive"] } thiserror = "1" +base64 = "0.22" rand_core = { version = "0.6", features = ["getrandom"] } getrandom = "0.4.3" mlkem-tls = { version = "0.2", optional = true } diff --git a/crypto/src/error.rs b/crypto/src/error.rs index c1a535f..4c5cef0 100644 --- a/crypto/src/error.rs +++ b/crypto/src/error.rs @@ -28,4 +28,6 @@ pub enum CryptoError { UnknownAlgorithm, #[error("invalid hex encoding")] InvalidHex, + #[error("invalid base64 encoding")] + InvalidBase64, } diff --git a/crypto/src/keypair.rs b/crypto/src/keypair.rs index 169eaa8..3454cc9 100644 --- a/crypto/src/keypair.rs +++ b/crypto/src/keypair.rs @@ -1,4 +1,7 @@ use std::fmt; + +use base64::Engine; +use base64::engine::general_purpose; use zeroize::{Zeroize, ZeroizeOnDrop}; // --- Private key types --- @@ -197,6 +200,16 @@ fn hex_to_bytes(s: &str) -> Result, crate::error::CryptoError> { .collect() } +fn bytes_to_base64(bytes: &[u8]) -> String { + general_purpose::STANDARD.encode(bytes) +} + +fn base64_to_bytes(s: &str) -> Result, crate::error::CryptoError> { + general_purpose::STANDARD + .decode(s) + .map_err(|_| crate::error::CryptoError::InvalidBase64) +} + // --- #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -510,6 +523,22 @@ impl Keyring { sig_cl_secret_key: SignaturePrivateKey::new(read_key(&mut offset)?), }) } + + pub fn to_hex(&self) -> String { + bytes_to_hex(&self.to_bytes()) + } + + pub fn from_hex(s: &str) -> Result { + Self::from_bytes(&hex_to_bytes(s)?) + } + + pub fn to_base64(&self) -> String { + bytes_to_base64(&self.to_bytes()) + } + + pub fn from_base64(s: &str) -> Result { + Self::from_bytes(&base64_to_bytes(s)?) + } } impl TryFrom<&[u8]> for Keyring { @@ -645,6 +674,14 @@ impl PublicKeyBundle { sig_cl_public_key: cl, }) } + + pub fn to_base64(&self) -> String { + bytes_to_base64(&self.as_bytes()) + } + + pub fn from_base64(s: &str) -> Result { + Self::from_bytes(&base64_to_bytes(s)?) + } } impl TryFrom<&[u8]> for PublicKeyBundle { @@ -760,6 +797,48 @@ mod tests { assert_eq!(key.as_bytes(), recovered.as_bytes()); } + #[test] + fn keyring_hex_roundtrip() { + let keyring = Keyring::new( + KemPublicKey::new(vec![1u8; 16]), + KemPrivateKey::new(vec![2u8; 16]), + SignaturePqPublicKey::new(vec![3u8; 16]), + SignaturePqPrivateKey::new(vec![4u8; 16]), + SignaturePublicKey::new(vec![5u8; 16]), + SignaturePrivateKey::new(vec![6u8; 16]), + ); + let hex = keyring.to_hex(); + let recovered = Keyring::from_hex(&hex).unwrap(); + assert_eq!(keyring.to_bytes(), recovered.to_bytes()); + } + + #[test] + fn keyring_base64_roundtrip() { + let keyring = Keyring::new( + KemPublicKey::new(vec![1u8; 16]), + KemPrivateKey::new(vec![2u8; 16]), + SignaturePqPublicKey::new(vec![3u8; 16]), + SignaturePqPrivateKey::new(vec![4u8; 16]), + SignaturePublicKey::new(vec![5u8; 16]), + SignaturePrivateKey::new(vec![6u8; 16]), + ); + let b64 = keyring.to_base64(); + let recovered = Keyring::from_base64(&b64).unwrap(); + assert_eq!(keyring.to_bytes(), recovered.to_bytes()); + } + + #[test] + fn public_key_bundle_base64_roundtrip() { + let bundle = PublicKeyBundle::new( + KemPublicKey::new(vec![1u8; 32]), + SignaturePqPublicKey::new(vec![2u8; 64]), + SignaturePublicKey::new(vec![3u8; 32]), + ); + let b64 = bundle.to_base64(); + let recovered = PublicKeyBundle::from_base64(&b64).unwrap(); + assert_eq!(bundle.as_bytes(), recovered.as_bytes()); + } + #[test] fn hex_invalid_returns_error() { assert!(KemPublicKey::from_hex("xyz").is_err()); diff --git a/example/Cargo.lock b/example/Cargo.lock index 49da683..e07c673 100644 --- a/example/Cargo.lock +++ b/example/Cargo.lock @@ -59,9 +59,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -70,14 +70,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" dependencies = [ "cc", "cmake", "dunce", "fs_extra", + "pkg-config", ] [[package]] @@ -595,9 +596,9 @@ checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" [[package]] name = "hybrid-array" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ "ctutils", "typenum", @@ -743,9 +744,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.102" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", @@ -771,6 +772,13 @@ dependencies = [ "cpufeatures 0.3.0", ] +[[package]] +name = "keygen" +version = "0.1.0" +dependencies = [ + "mtp", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -937,6 +945,7 @@ dependencies = [ name = "mtp-crypto" version = "0.1.0" dependencies = [ + "base64", "chacha20poly1305", "ed25519-dalek", "getrandom 0.4.3", @@ -995,9 +1004,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c863e9ab5e7bf9c99ba75e1050f1e4d624ae87ed3532d6238ffbdc7b585dbbe6" dependencies = [ "num-integer", "num-traits", @@ -1134,6 +1143,12 @@ dependencies = [ "spki 0.8.0", ] +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + [[package]] name = "poly1305" version = "0.8.0" @@ -1360,9 +1375,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -1412,9 +1427,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" dependencies = [ "web-time", "zeroize", @@ -1758,9 +1773,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.51" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", "num-conv", @@ -1778,9 +1793,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.30" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -1951,9 +1966,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -1964,9 +1979,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1974,9 +1989,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", @@ -1987,9 +2002,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] diff --git a/example/Cargo.toml b/example/Cargo.toml index 9ec3f02..48506d1 100644 --- a/example/Cargo.toml +++ b/example/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ "server", - "client", + "client", "keygen", ] resolver = "3" diff --git a/example/keygen/Cargo.toml b/example/keygen/Cargo.toml new file mode 100644 index 0000000..ddfadad --- /dev/null +++ b/example/keygen/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "keygen" +version = "0.1.0" +edition = "2024" + +[dependencies] +mtp = { version = "0.1.0", path = "../../", features = ["crypto"] } diff --git a/example/keygen/src/main.rs b/example/keygen/src/main.rs new file mode 100644 index 0000000..938a3e0 --- /dev/null +++ b/example/keygen/src/main.rs @@ -0,0 +1,13 @@ +use mtp::crypto::Keyring; + +fn main() { + let keyring = Keyring::generate(); + let bundle = keyring.public_key_bundle(); + + // 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()); + + println!("PublicKeyBundle (base64):\n{}", bundle.to_base64()); +} diff --git a/example/server/src/main.rs b/example/server/src/main.rs index 3de4a97..776b2b6 100644 --- a/example/server/src/main.rs +++ b/example/server/src/main.rs @@ -3,7 +3,8 @@ mod handlers; mod keys; mod tls; -use mtp::host::{HostConfig, MTPHost}; +use mtp::host::{AuthenticationPolicy, HostConfig, MTPHost}; + use mtp::type_map::TypeMap; use std::future::Future; use std::path::Path; @@ -77,7 +78,9 @@ async fn main() -> Result<(), Box> { Ok(()) => {} Err(e) => eprintln!("Failed to persist client database to {path}: {e}"), }, - Err(e) => eprintln!("Failed to serialize client database after registering {id}: {e}"), + Err(e) => { + eprintln!("Failed to serialize client database after registering {id}: {e}") + } } println!("Registered new client with ID: {}", id); id @@ -92,16 +95,14 @@ async fn main() -> Result<(), Box> { cert_pem, key_pem, ) - .with_authentication(host_keyring, get_existing_user, complete_register); + .with_authentication(host_keyring, get_existing_user, complete_register) + .with_authentication_policy(AuthenticationPolicy::ForceAuthentication); let mut host = MTPHost::new(config).await?; println!("Server listening on {}", host.local_addr()); while let Some(conn) = host.accept().await? { - let desc = conn - .description - .as_deref() - .unwrap_or("(no description)"); + let desc = conn.description.as_deref().unwrap_or("(no description)"); println!( "\n--- New connection (version {}, description: {desc}) ---", conn.version diff --git a/src/lib.rs b/src/lib.rs index e1325bc..03314f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,5 @@ pub use mtp_codec as codec; pub use mtp_common as common; -pub use mtp_transport as transport; pub use mtp_type_map as type_map; #[cfg(feature = "crypto")]