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"
|
name = "nettext"
|
||||||
description = "A text-based data format for cryptographic network protocols"
|
description = "A text-based data format for cryptographic network protocols"
|
||||||
authors = ["Alex Auvolat <alex@adnab.me>"]
|
authors = ["Alex Auvolat <alex@adnab.me>"]
|
||||||
version = "0.1.1"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -13,10 +13,9 @@ readme = "README.md"
|
||||||
nom = "7.1"
|
nom = "7.1"
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
|
|
||||||
blake2 = { version = "0.10", optional = true }
|
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
ed25519-dalek = { version = "1.0", optional = true }
|
dryoc = { version = "0.4", optional = true }
|
||||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||||
|
|
||||||
[features]
|
[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
|
//! Helpers to use cryptographic data types in nettext
|
||||||
|
|
||||||
#[cfg(feature = "blake2")]
|
pub use dryoc::*;
|
||||||
mod b2;
|
|
||||||
|
|
||||||
#[cfg(feature = "blake2")]
|
use dryoc::types::{Bytes, StackByteArray};
|
||||||
pub use b2::*;
|
|
||||||
|
|
||||||
#[cfg(feature = "ed25519-dalek")]
|
use crate::enc;
|
||||||
mod ed25519;
|
|
||||||
|
|
||||||
#[cfg(feature = "ed25519-dalek")]
|
pub type SigningKeyPair = sign::SigningKeyPair<sign::PublicKey, sign::SecretKey>;
|
||||||
pub use ed25519::*;
|
|
||||||
|
|
||||||
/// An error corresponding to a cryptographic check that failed
|
impl<const N: usize> enc::Encode for StackByteArray<N> {
|
||||||
pub enum CryptoError {
|
fn term(&self) -> enc::Result<'_> {
|
||||||
/// A hash verification failed
|
Ok(enc::bytes(self.as_slice()))
|
||||||
InvalidHash,
|
}
|
||||||
/// A signature verification failed
|
}
|
||||||
InvalidSignature,
|
|
||||||
|
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;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[cfg(any(feature = "blake2", feature = "ed25519-dalek"))]
|
#[cfg(any(feature = "dryoc"))]
|
||||||
use crate::crypto;
|
use crate::crypto;
|
||||||
|
|
||||||
pub use decode::*;
|
pub use decode::*;
|
||||||
|
@ -442,7 +442,7 @@ impl<'a, 'b> Term<'a, 'b> {
|
||||||
|
|
||||||
// ---- CRYPTO HELPERS ----
|
// ---- CRYPTO HELPERS ----
|
||||||
|
|
||||||
#[cfg(feature = "blake2")]
|
#[cfg(feature = "dryoc")]
|
||||||
impl<'a, 'b> Term<'a, 'b> {
|
impl<'a, 'b> Term<'a, 'b> {
|
||||||
/// Try to interpret this string as a Blake2b512 digest (32-bytes base64 encoded)
|
/// 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::dec::decode;
|
||||||
|
/// use nettext::crypto::generichash::GenericHash;
|
||||||
///
|
///
|
||||||
/// let term = decode(b"{
|
/// let term = decode(b"{
|
||||||
/// message = hello,
|
/// message = hello,
|
||||||
/// hash = 5M-jmj03vjHFlgnoB5cHmcqmihm_qhUTXxZQheAdQaZboeGxRq62vQCStJ6sIUwQPM-jo2WVS7vlL3Sis2IMlA
|
/// hash = Mk3PAn3UowqTLEQfNlol6GsXPe-kuOWJSCU0cbgbcs8,
|
||||||
/// }").unwrap();
|
/// }").unwrap();
|
||||||
/// let [msg, hash] = term.dict_of(["message", "hash"], false).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> {
|
pub fn b2sum(&self) -> Result<crypto::generichash::Hash, TypeError> {
|
||||||
Ok(crypto::Blake2Sum::from_bytes(self.bytes_exact()?))
|
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)
|
/// Try to interpret this string as an ed25519 keypair (64 bytes base64 encoded)
|
||||||
pub fn keypair(&self) -> Result<crypto::Keypair, TypeError> {
|
pub fn keypair(&self) -> Result<crypto::SigningKeyPair, TypeError> {
|
||||||
let bytes = self.bytes_exact::<64>()?;
|
let secret_key = crypto::sign::SecretKey::from(self.bytes_exact()?);
|
||||||
crypto::Keypair::from_bytes(&bytes).map_err(|_| TypeError::WrongType("KEYPAIR"))
|
Ok(crypto::SigningKeyPair::from_secret_key(secret_key))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to interpret this string as an ed25519 public key (32 bytes base64 encoded)
|
/// Try to interpret this string as an ed25519 public key (32 bytes base64 encoded)
|
||||||
pub fn public_key(&self) -> Result<crypto::PublicKey, TypeError> {
|
pub fn public_key(&self) -> Result<crypto::sign::PublicKey, TypeError> {
|
||||||
let bytes = self.bytes_exact::<32>()?;
|
Ok(crypto::sign::PublicKey::from(self.bytes_exact()?))
|
||||||
crypto::PublicKey::from_bytes(&bytes).map_err(|_| TypeError::WrongType("PUBLICKEY"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to interpret this string as an ed25519 secret key (32 bytes base64 encoded)
|
/// Try to interpret this string as an ed25519 secret key (32 bytes base64 encoded)
|
||||||
pub fn secret_key(&self) -> Result<crypto::SecretKey, TypeError> {
|
pub fn secret_key(&self) -> Result<crypto::sign::SecretKey, TypeError> {
|
||||||
let bytes = self.bytes_exact::<32>()?;
|
Ok(crypto::sign::SecretKey::from(self.bytes_exact()?))
|
||||||
crypto::SecretKey::from_bytes(&bytes).map_err(|_| TypeError::WrongType("SECRETKEY"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to interpret this string as an ed25519 signature (64 bytes base64 encoded)
|
/// Try to interpret this string as an ed25519 signature (64 bytes base64 encoded)
|
||||||
pub fn signature(&self) -> Result<crypto::Signature, TypeError> {
|
pub fn signature(&self) -> Result<crypto::sign::Signature, TypeError> {
|
||||||
let bytes = self.bytes_exact::<64>()?;
|
Ok(crypto::sign::Signature::from(self.bytes_exact()?))
|
||||||
crypto::Signature::from_bytes(&bytes).map_err(|_| TypeError::WrongType("SIGNATURE"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
41
src/lib.rs
41
src/lib.rs
|
@ -3,12 +3,13 @@
|
||||||
//! ```
|
//! ```
|
||||||
//! use nettext::enc::*;
|
//! use nettext::enc::*;
|
||||||
//! use nettext::dec::*;
|
//! 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
|
//! // Encode a fist object that represents a payload that will be hashed and signed
|
||||||
//! let text1 = list([
|
//! let signed_payload = list([
|
||||||
//! string("CALL").unwrap(),
|
//! string("CALL").unwrap(),
|
||||||
//! string("myfunction").unwrap(),
|
//! string("myfunction").unwrap(),
|
||||||
//! dict([
|
//! dict([
|
||||||
|
@ -17,36 +18,38 @@
|
||||||
//! ("c", raw(b"{ a = 12, b = 42 }").unwrap()),
|
//! ("c", raw(b"{ a = 12, b = 42 }").unwrap()),
|
||||||
//! ("d", bytes_split(&((0..128u8).collect::<Vec<_>>()))),
|
//! ("d", bytes_split(&((0..128u8).collect::<Vec<_>>()))),
|
||||||
//! ]).unwrap(),
|
//! ]).unwrap(),
|
||||||
//! keypair.public.term().unwrap(),
|
//! keypair.public_key.term().unwrap(),
|
||||||
//! ]).unwrap().encode();
|
//! ]).unwrap().encode();
|
||||||
//! eprintln!("{}", std::str::from_utf8(&text1).unwrap());
|
//! eprintln!("{}", std::str::from_utf8(&signed_payload).unwrap());
|
||||||
//!
|
//!
|
||||||
//! let hash = crypto::Blake2Sum::compute(&text1);
|
//! let hash: Hash = GenericHash::hash_with_defaults(&signed_payload, None::<&Vec<u8>>).unwrap();
|
||||||
//! let sign = keypair.sign(&text1);
|
//! let (sign, _) = keypair.sign_with_defaults(&signed_payload[..]).unwrap().into_parts();
|
||||||
//!
|
//!
|
||||||
//! // Encode a second object that represents the signed and hashed payload
|
//! // Encode a second object that represents the signed and hashed payload
|
||||||
//! let text2 = dict([
|
//! dict([
|
||||||
//! ("hash", hash.term().unwrap()),
|
//! ("hash", hash.term().unwrap()),
|
||||||
//! ("signature", sign.term().unwrap()),
|
//! ("signature", sign.term().unwrap()),
|
||||||
//! ("payload", raw(&text1).unwrap()),
|
//! ("payload", raw(&signed_payload).unwrap()),
|
||||||
//! ]).unwrap().encode();
|
//! ]).unwrap().encode()
|
||||||
//! eprintln!("{}", std::str::from_utf8(&text2).unwrap());
|
//! };
|
||||||
|
//! eprintln!("{}", std::str::from_utf8(&final_payload).unwrap());
|
||||||
//!
|
//!
|
||||||
//! // Decode and check everything is fine
|
//! // Decode and check everything is fine
|
||||||
//! let object1 = decode(&text2).unwrap();
|
//! let signed_object = decode(&final_payload).unwrap();
|
||||||
//! let [hash, signature, payload] = object1.dict_of(["hash", "signature", "payload"], false).unwrap();
|
//! let [hash, signature, payload] = signed_object.dict_of(["hash", "signature", "payload"], false).unwrap();
|
||||||
//! assert!(hash.b2sum().unwrap().verify(payload.raw()).is_ok());
|
//! let hash = hash.b2sum().unwrap();
|
||||||
//! assert_eq!(payload.raw(), text1);
|
//! 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 object2 = decode(payload.raw()).unwrap();
|
||||||
//!
|
//!
|
||||||
//! let [verb, arg1, arg2, pubkey] = object2.list_of().unwrap();
|
//! let [verb, arg1, arg2, pubkey] = object2.list_of().unwrap();
|
||||||
//! let pubkey = pubkey.public_key().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!(verb.string().unwrap(), "CALL");
|
||||||
//! assert_eq!(arg1.string().unwrap(), "myfunction");
|
//! assert_eq!(arg1.string().unwrap(), "myfunction");
|
||||||
//! assert_eq!(pubkey, keypair.public);
|
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! The value of `text1` would be as follows:
|
//! 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
|
//! 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.
|
//! to check the hash and the signature: the raw representation of the term hasn't changed.
|
||||||
|
|
||||||
pub mod crypto;
|
|
||||||
pub mod dec;
|
pub mod dec;
|
||||||
pub mod enc;
|
pub mod enc;
|
||||||
|
|
||||||
|
#[cfg(feature = "dryoc")]
|
||||||
|
pub mod crypto;
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
pub mod serde;
|
pub mod serde;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue