diff --git a/Cargo.toml b/Cargo.toml index 13cdaa6..322bb53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "nettext" description = "A text-based data format for cryptographic network protocols" authors = ["Alex Auvolat "] -version = "0.1.1" +version = "0.2.0" edition = "2021" license = "AGPL-3.0" readme = "README.md" @@ -13,10 +13,9 @@ readme = "README.md" nom = "7.1" base64 = "0.13" -blake2 = { version = "0.10", optional = true } rand = "0.7" -ed25519-dalek = { version = "1.0", optional = true } +dryoc = { version = "0.4", optional = true } serde = { version = "1.0", optional = true, features = ["derive"] } [features] -default = [ "blake2", "ed25519-dalek", "serde" ] +default = [ "dryoc", "serde" ] diff --git a/src/crypto/b2.rs b/src/crypto/b2.rs deleted file mode 100644 index 120af38..0000000 --- a/src/crypto/b2.rs +++ /dev/null @@ -1,42 +0,0 @@ -use blake2::{Blake2b512, Digest}; - -use crate::crypto::CryptoError; -use crate::enc; - -/// A Blake2b512 digest -#[derive(Clone, Copy, Eq, PartialEq)] -pub struct Blake2Sum([u8; 64]); - -impl Blake2Sum { - /// Create a Blake2Sum object by passing the digest as bytes directly - pub fn from_bytes(bytes: [u8; 64]) -> Self { - Self(bytes) - } - - /// Compute the Blake2b512 digest of a byte slice - pub fn compute(buf: &[u8]) -> Self { - let mut hasher = Blake2b512::new(); - hasher.update(buf); - Self(hasher.finalize()[..].try_into().unwrap()) - } - - /// Check that this digest corresponds to a given slice - pub fn verify(&self, buf: &[u8]) -> Result<(), CryptoError> { - if Self::compute(buf) == *self { - Ok(()) - } else { - Err(CryptoError::InvalidHash) - } - } - - /// Return a reference to the inner byte slice - pub fn as_bytes(&self) -> &[u8] { - &self.0[..] - } -} - -impl enc::Encode for Blake2Sum { - fn term(&self) -> enc::Result<'_> { - Ok(enc::bytes(self.as_bytes())) - } -} diff --git a/src/crypto/ed25519.rs b/src/crypto/ed25519.rs deleted file mode 100644 index f0ccd4f..0000000 --- a/src/crypto/ed25519.rs +++ /dev/null @@ -1,35 +0,0 @@ -use rand::prelude::*; - -use crate::enc; - -pub use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signature, Signer, Verifier}; - -/// Generate a public/secret Ed25519 keypair -pub fn generate_keypair() -> Keypair { - let mut csprng = thread_rng(); - Keypair::generate(&mut csprng) -} - -impl enc::Encode for Keypair { - fn term(&self) -> enc::Result<'_> { - Ok(enc::bytes(&self.to_bytes())) - } -} - -impl enc::Encode for PublicKey { - fn term(&self) -> enc::Result<'_> { - Ok(enc::bytes(self.as_bytes())) - } -} - -impl enc::Encode for SecretKey { - fn term(&self) -> enc::Result<'_> { - Ok(enc::bytes(self.as_bytes())) - } -} - -impl enc::Encode for Signature { - fn term(&self) -> enc::Result<'_> { - Ok(enc::bytes(&self.to_bytes())) - } -} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 6294b39..01d9afa 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,21 +1,21 @@ //! Helpers to use cryptographic data types in nettext -#[cfg(feature = "blake2")] -mod b2; +pub use dryoc::*; -#[cfg(feature = "blake2")] -pub use b2::*; +use dryoc::types::{Bytes, StackByteArray}; -#[cfg(feature = "ed25519-dalek")] -mod ed25519; +use crate::enc; -#[cfg(feature = "ed25519-dalek")] -pub use ed25519::*; +pub type SigningKeyPair = sign::SigningKeyPair; -/// An error corresponding to a cryptographic check that failed -pub enum CryptoError { - /// A hash verification failed - InvalidHash, - /// A signature verification failed - InvalidSignature, +impl enc::Encode for StackByteArray { + fn term(&self) -> enc::Result<'_> { + Ok(enc::bytes(self.as_slice())) + } +} + +impl enc::Encode for sign::SigningKeyPair { + fn term(&self) -> enc::Result<'_> { + Ok(enc::bytes(self.secret_key.as_slice())) + } } diff --git a/src/dec/mod.rs b/src/dec/mod.rs index 6982cb0..a45793a 100644 --- a/src/dec/mod.rs +++ b/src/dec/mod.rs @@ -5,7 +5,7 @@ mod error; use std::collections::HashMap; -#[cfg(any(feature = "blake2", feature = "ed25519-dalek"))] +#[cfg(any(feature = "dryoc"))] use crate::crypto; pub use decode::*; @@ -442,7 +442,7 @@ impl<'a, 'b> Term<'a, 'b> { // ---- CRYPTO HELPERS ---- -#[cfg(feature = "blake2")] +#[cfg(feature = "dryoc")] impl<'a, 'b> Term<'a, 'b> { /// Try to interpret this string as a Blake2b512 digest (32-bytes base64 encoded) /// @@ -450,43 +450,39 @@ impl<'a, 'b> Term<'a, 'b> { /// /// ``` /// use nettext::dec::decode; + /// use nettext::crypto::generichash::GenericHash; /// /// let term = decode(b"{ /// message = hello, - /// hash = 5M-jmj03vjHFlgnoB5cHmcqmihm_qhUTXxZQheAdQaZboeGxRq62vQCStJ6sIUwQPM-jo2WVS7vlL3Sis2IMlA + /// hash = Mk3PAn3UowqTLEQfNlol6GsXPe-kuOWJSCU0cbgbcs8, /// }").unwrap(); /// let [msg, hash] = term.dict_of(["message", "hash"], false).unwrap(); - /// assert!(hash.b2sum().unwrap().verify(msg.raw()).is_ok()); + /// let expected_hash = GenericHash::hash_with_defaults(msg.raw(), None::<&Vec>).unwrap(); + /// assert_eq!(hash.b2sum().unwrap(), expected_hash); /// ``` - pub fn b2sum(&self) -> Result { - Ok(crypto::Blake2Sum::from_bytes(self.bytes_exact()?)) + pub fn b2sum(&self) -> Result { + Ok(crypto::generichash::Hash::from(self.bytes_exact()?)) } -} -#[cfg(feature = "ed25519-dalek")] -impl<'a, 'b> Term<'a, 'b> { /// Try to interpret this string as an ed25519 keypair (64 bytes base64 encoded) - pub fn keypair(&self) -> Result { - let bytes = self.bytes_exact::<64>()?; - crypto::Keypair::from_bytes(&bytes).map_err(|_| TypeError::WrongType("KEYPAIR")) + pub fn keypair(&self) -> Result { + let secret_key = crypto::sign::SecretKey::from(self.bytes_exact()?); + Ok(crypto::SigningKeyPair::from_secret_key(secret_key)) } /// Try to interpret this string as an ed25519 public key (32 bytes base64 encoded) - pub fn public_key(&self) -> Result { - let bytes = self.bytes_exact::<32>()?; - crypto::PublicKey::from_bytes(&bytes).map_err(|_| TypeError::WrongType("PUBLICKEY")) + pub fn public_key(&self) -> Result { + Ok(crypto::sign::PublicKey::from(self.bytes_exact()?)) } /// Try to interpret this string as an ed25519 secret key (32 bytes base64 encoded) - pub fn secret_key(&self) -> Result { - let bytes = self.bytes_exact::<32>()?; - crypto::SecretKey::from_bytes(&bytes).map_err(|_| TypeError::WrongType("SECRETKEY")) + pub fn secret_key(&self) -> Result { + Ok(crypto::sign::SecretKey::from(self.bytes_exact()?)) } /// Try to interpret this string as an ed25519 signature (64 bytes base64 encoded) - pub fn signature(&self) -> Result { - let bytes = self.bytes_exact::<64>()?; - crypto::Signature::from_bytes(&bytes).map_err(|_| TypeError::WrongType("SIGNATURE")) + pub fn signature(&self) -> Result { + Ok(crypto::sign::Signature::from(self.bytes_exact()?)) } } diff --git a/src/lib.rs b/src/lib.rs index 1c96dac..0fed495 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,50 +3,53 @@ //! ``` //! use nettext::enc::*; //! use nettext::dec::*; -//! use nettext::crypto::{self, Signer, Verifier}; +//! use nettext::crypto::{SigningKeyPair, generichash::{Hash, GenericHash}, sign::{Signature, SignedMessage}}; //! -//! let keypair = crypto::generate_keypair(); +//! let final_payload = { +//! let keypair = SigningKeyPair::gen_with_defaults(); //! -//! // Encode a fist object that represents a payload that will be hashed and signed -//! let text1 = list([ -//! string("CALL").unwrap(), -//! string("myfunction").unwrap(), +//! // Encode a fist object that represents a payload that will be hashed and signed +//! let signed_payload = list([ +//! string("CALL").unwrap(), +//! string("myfunction").unwrap(), +//! dict([ +//! ("a", string("hello").unwrap()), +//! ("b", string("world").unwrap()), +//! ("c", raw(b"{ a = 12, b = 42 }").unwrap()), +//! ("d", bytes_split(&((0..128u8).collect::>()))), +//! ]).unwrap(), +//! keypair.public_key.term().unwrap(), +//! ]).unwrap().encode(); +//! eprintln!("{}", std::str::from_utf8(&signed_payload).unwrap()); +//! +//! let hash: Hash = GenericHash::hash_with_defaults(&signed_payload, None::<&Vec>).unwrap(); +//! let (sign, _) = keypair.sign_with_defaults(&signed_payload[..]).unwrap().into_parts(); +//! +//! // Encode a second object that represents the signed and hashed payload //! dict([ -//! ("a", string("hello").unwrap()), -//! ("b", string("world").unwrap()), -//! ("c", raw(b"{ a = 12, b = 42 }").unwrap()), -//! ("d", bytes_split(&((0..128u8).collect::>()))), -//! ]).unwrap(), -//! keypair.public.term().unwrap(), -//! ]).unwrap().encode(); -//! eprintln!("{}", std::str::from_utf8(&text1).unwrap()); -//! -//! let hash = crypto::Blake2Sum::compute(&text1); -//! let sign = keypair.sign(&text1); -//! -//! // Encode a second object that represents the signed and hashed payload -//! let text2 = dict([ -//! ("hash", hash.term().unwrap()), -//! ("signature", sign.term().unwrap()), -//! ("payload", raw(&text1).unwrap()), -//! ]).unwrap().encode(); -//! eprintln!("{}", std::str::from_utf8(&text2).unwrap()); +//! ("hash", hash.term().unwrap()), +//! ("signature", sign.term().unwrap()), +//! ("payload", raw(&signed_payload).unwrap()), +//! ]).unwrap().encode() +//! }; +//! eprintln!("{}", std::str::from_utf8(&final_payload).unwrap()); //! //! // Decode and check everything is fine -//! let object1 = decode(&text2).unwrap(); -//! let [hash, signature, payload] = object1.dict_of(["hash", "signature", "payload"], false).unwrap(); -//! assert!(hash.b2sum().unwrap().verify(payload.raw()).is_ok()); -//! assert_eq!(payload.raw(), text1); +//! let signed_object = decode(&final_payload).unwrap(); +//! let [hash, signature, payload] = signed_object.dict_of(["hash", "signature", "payload"], false).unwrap(); +//! let hash = hash.b2sum().unwrap(); +//! let signature = signature.signature().unwrap(); +//! let expected_hash = GenericHash::hash_with_defaults(payload.raw(), None::<&Vec>).unwrap(); +//! assert_eq!(hash, expected_hash); //! //! let object2 = decode(payload.raw()).unwrap(); //! //! let [verb, arg1, arg2, pubkey] = object2.list_of().unwrap(); //! let pubkey = pubkey.public_key().unwrap(); -//! assert!(pubkey.verify(payload.raw(), &signature.signature().unwrap()).is_ok()); +//! assert!(SignedMessage::from_parts(signature, payload.raw()).verify(&pubkey).is_ok()); //! //! assert_eq!(verb.string().unwrap(), "CALL"); //! assert_eq!(arg1.string().unwrap(), "myfunction"); -//! assert_eq!(pubkey, keypair.public); //! ``` //! //! The value of `text1` would be as follows: @@ -81,10 +84,12 @@ //! Note that the value of `text1` is embedded as-is inside `text2`. This is what allows us //! to check the hash and the signature: the raw representation of the term hasn't changed. -pub mod crypto; pub mod dec; pub mod enc; +#[cfg(feature = "dryoc")] +pub mod crypto; + #[cfg(feature = "serde")] pub mod serde;