Move to dryoc crate for crypto primitives
This commit is contained in:
parent
5651210a83
commit
fd957e7ef1
6 changed files with 71 additions and 148 deletions
|
@ -2,7 +2,7 @@
|
|||
name = "nettext"
|
||||
description = "A text-based data format for cryptographic network protocols"
|
||||
authors = ["Alex Auvolat <alex@adnab.me>"]
|
||||
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" ]
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
|
@ -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()))
|
||||
}
|
||||
}
|
|
@ -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<sign::PublicKey, sign::SecretKey>;
|
||||
|
||||
/// An error corresponding to a cryptographic check that failed
|
||||
pub enum CryptoError {
|
||||
/// A hash verification failed
|
||||
InvalidHash,
|
||||
/// A signature verification failed
|
||||
InvalidSignature,
|
||||
impl<const N: usize> enc::Encode for StackByteArray<N> {
|
||||
fn term(&self) -> enc::Result<'_> {
|
||||
Ok(enc::bytes(self.as_slice()))
|
||||
}
|
||||
}
|
||||
|
||||
impl enc::Encode for sign::SigningKeyPair<sign::PublicKey, sign::SecretKey> {
|
||||
fn term(&self) -> enc::Result<'_> {
|
||||
Ok(enc::bytes(self.secret_key.as_slice()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<u8>>).unwrap();
|
||||
/// assert_eq!(hash.b2sum().unwrap(), expected_hash);
|
||||
/// ```
|
||||
pub fn b2sum(&self) -> Result<crypto::Blake2Sum, TypeError> {
|
||||
Ok(crypto::Blake2Sum::from_bytes(self.bytes_exact()?))
|
||||
pub fn b2sum(&self) -> Result<crypto::generichash::Hash, TypeError> {
|
||||
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<crypto::Keypair, TypeError> {
|
||||
let bytes = self.bytes_exact::<64>()?;
|
||||
crypto::Keypair::from_bytes(&bytes).map_err(|_| TypeError::WrongType("KEYPAIR"))
|
||||
pub fn keypair(&self) -> Result<crypto::SigningKeyPair, TypeError> {
|
||||
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<crypto::PublicKey, TypeError> {
|
||||
let bytes = self.bytes_exact::<32>()?;
|
||||
crypto::PublicKey::from_bytes(&bytes).map_err(|_| TypeError::WrongType("PUBLICKEY"))
|
||||
pub fn public_key(&self) -> Result<crypto::sign::PublicKey, TypeError> {
|
||||
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<crypto::SecretKey, TypeError> {
|
||||
let bytes = self.bytes_exact::<32>()?;
|
||||
crypto::SecretKey::from_bytes(&bytes).map_err(|_| TypeError::WrongType("SECRETKEY"))
|
||||
pub fn secret_key(&self) -> Result<crypto::sign::SecretKey, TypeError> {
|
||||
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<crypto::Signature, TypeError> {
|
||||
let bytes = self.bytes_exact::<64>()?;
|
||||
crypto::Signature::from_bytes(&bytes).map_err(|_| TypeError::WrongType("SIGNATURE"))
|
||||
pub fn signature(&self) -> Result<crypto::sign::Signature, TypeError> {
|
||||
Ok(crypto::sign::Signature::from(self.bytes_exact()?))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
69
src/lib.rs
69
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::<Vec<_>>()))),
|
||||
//! ]).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<u8>>).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::<Vec<_>>()))),
|
||||
//! ]).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<u8>>).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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue