Skip to content

Commit

Permalink
Lints and doc updates
Browse files Browse the repository at this point in the history
* Added important lints and fixed documentation accordingly
  • Loading branch information
jmlepisto committed Nov 12, 2024
1 parent d0314ef commit 6e8cb39
Show file tree
Hide file tree
Showing 13 changed files with 119 additions and 50 deletions.
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

⚠️ **Work in progress** ⚠️

`no_std` compatible, pure Rust implementation of the [Noise protocol framework](https://noiseprotocol.org/noise.html)
`no_std` compatible, pure Rust implementation of the [**Noise protocol framework**](https://noiseprotocol.org/noise.html)
with support for [**Post Quantum (PQ) extensions**](https://doi.org/10.1145/3548606.3560577) as presented by
Yawning Angel, Benjamin Dowling, Andreas Hülsing, Peter Schwabe, and Fiona Johanna Weber.

Main targets of this crate are correctness, extensibility, and strict `no_std` compatibility
Main targets of this crate are **correctness**, extensibility, and strict `no_std` compatibility
and those come with the small drawback of more verbose user experience with some boilerplate.
If you don't need PQ functionality and are developing for a regular target, you probably are better
off using these instead:
Expand All @@ -22,15 +22,18 @@ off using these instead:
Basis of this implementation relies heavily on the abovementioned crates and I'm extending
huge thanks to the developers for their effort!

⚠️ **Warning** ⚠️ This library has not received any formal audit and is still in early phase
⚠️ **Warning** ⚠️

* This library has not received any formal audit
* While we enable some cryptographic providers by default, it is up to **you** to get familiar with those and decide if they meet your security and integrity requirements

## Basics

From user perspective, everything in this crate is built around three types:

* [`NqHandshake`](https://docs.rs/clatter/latest/clatter/struct.NqHandshake.html) - Classical, non-post-quantum Noise handshake
* [`PqHandshake`](https://docs.rs/clatter/latest/clatter/struct.PqHandshake.html) - Post-quantum Noise handshake
* [`DualLayerHandshake`](https://docs.rs/clatter/latest/clatter/struct.DualLayerHandshake.html) - Dual layer handshake, which combines two Noise handshakes
* [`DualLayerHandshake`](https://docs.rs/clatter/latest/clatter/struct.DualLayerHandshake.html) - Dual layer handshake, which combines two Noise handshakes and allows a naive hybrid encryption approach

Users will pick and instantiate the desired handshake state machine with the crypto primitives
they wish to use (supplied as generic parameters) and complete the handshake using the methods
Expand Down Expand Up @@ -168,3 +171,13 @@ does not currently implement *SEEC*.
* Better documentation
* Intuitive Noise pattern parser with `alloc`feature

## Some Coding Quidelines..

Let's be the cleanest Noise Rust implementation there is :)

* `no_std` compatibility required at all times
* No Clippy warnings allowed - ignores allowed only when absolutely justified
* Good hygiene expected:
* Check for integer overflows
* Zeroize sensitive assets on drop

11 changes: 11 additions & 0 deletions src/bytearray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,22 @@ use zeroize::{Zeroize, ZeroizeOnDrop};
/// Simple trait used throughout the codebase to provide
/// portable array operations
pub trait ByteArray: Sized + Zeroize + PartialEq + Debug {
/// Initialize a new array with zeros
fn new_zero() -> Self;
/// Initialize a new array by filling it with the given element
fn new_with(_: u8) -> Self;
/// Initialize a new array by copying it from the given slice
///
/// # Panics
/// * Panics if the slice length does not match this array length
fn from_slice(_: &[u8]) -> Self;
/// Array length
fn len() -> usize;
/// Borrow this array as a slice
fn as_slice(&self) -> &[u8];
/// Borrow this array as mutable
fn as_mut(&mut self) -> &mut [u8];
/// Clone this array
fn clone(&self) -> Self {
Self::from_slice(self.as_slice())
}
Expand All @@ -21,6 +31,7 @@ pub trait ByteArray: Sized + Zeroize + PartialEq + Debug {
pub struct SensitiveByteArray<A: ByteArray>(A);

impl<A: ByteArray> SensitiveByteArray<A> {
/// Encapsulate the given [`ByteArray`]
pub fn new(a: A) -> SensitiveByteArray<A> {
Self(a)
}
Expand Down
2 changes: 2 additions & 0 deletions src/cipherstate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use crate::traits::{Cipher, CryptoComponent};

/// Pair of [`CipherState`] instances for sending and receiving transport messages
pub struct CipherStates<C: Cipher> {
/// Cipher for initiator -> responder communication
pub initiator_to_responder: CipherState<C>,
/// Cipher for responder -> initiator communication
pub responder_to_initiator: CipherState<C>,
}

Expand Down
8 changes: 4 additions & 4 deletions src/crypto_impl/x25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,23 @@ impl CryptoComponent for X25519 {
}

impl Dh for X25519 {
type Key = SensitiveByteArray<[u8; 32]>;
type PrivateKey = SensitiveByteArray<[u8; 32]>;
type PubKey = [u8; 32];
type Output = SensitiveByteArray<[u8; 32]>;

fn genkey<R: rand_core::RngCore + rand_core::CryptoRng>(
rng: &mut R,
) -> crate::error::DhResult<KeyPair<Self::PubKey, Self::Key>> {
) -> crate::error::DhResult<KeyPair<Self::PubKey, Self::PrivateKey>> {
let s = StaticSecret::random_from_rng(rng);
let p = PublicKey::from(&s);

Ok(KeyPair {
public: Self::PubKey::from_slice(p.as_bytes()),
secret: Self::Key::from_slice(s.as_bytes()),
secret: Self::PrivateKey::from_slice(s.as_bytes()),
})
}

fn dh(k: &Self::Key, pk: &Self::PubKey) -> crate::error::DhResult<Self::Output> {
fn dh(k: &Self::PrivateKey, pk: &Self::PubKey) -> crate::error::DhResult<Self::Output> {
let k = StaticSecret::from(**k);
let pk = PublicKey::from(*pk);
Ok(Self::Output::from_slice(k.diffie_hellman(&pk).as_bytes()))
Expand Down
5 changes: 5 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub enum HandshakeError {
Transport(#[from] TransportError),
}

/// Handshake operation result type
pub type HandshakeResult<T> = Result<T, HandshakeError>;

/// Errors that can happen during transport operations
Expand All @@ -41,6 +42,7 @@ pub enum TransportError {
Cipher(#[from] CipherError),
}

/// Transport operation result type
pub type TransportResult<T> = Result<T, TransportError>;

/// Errors that can happen during KEM operations
Expand All @@ -56,6 +58,7 @@ pub enum KemError {
KeyGeneration,
}

/// KEM operation result type
pub type KemResult<T> = Result<T, KemError>;

/// Errors that can happen during DH operations
Expand All @@ -65,6 +68,7 @@ pub enum DhError {
KeyGeneration,
}

/// DH operation result type
pub type DhResult<T> = Result<T, DhError>;

/// Errors that can happen during Cipher operations
Expand All @@ -76,4 +80,5 @@ pub enum CipherError {
Decrypt,
}

/// Cipher operation result type
pub type CipherResult<T> = Result<T, CipherError>;
17 changes: 16 additions & 1 deletion src/handshakepattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,23 @@ use arrayvec::ArrayVec;
/// Handshake tokens as defined by the Noise spec and PQNoise paper.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Token {
/// Initiator ephemeral key
E,
/// Initiator static key
S,
/// Ephemeral-ephemeral DH
EE,
/// Ephemeral-static DH
ES,
/// Static-ephemeral DH
SE,
/// Static-static DH
SS,
/// Ephemeral KEM
Ekem,
/// Static KEM
Skem,
/// Pre-shared key
Psk,
}

Expand All @@ -30,13 +39,19 @@ pub struct HandshakePattern {
has_psk: bool,
}

/// Handshake message pattern
///
/// Does not include pre-message patterns
#[derive(Clone, Debug)]
pub struct MessagePattern {
/// Messages sent by the initiator
pub initiator: ArrayVec<ArrayVec<Token, 8>, 8>,
/// Messages sent by the responder
pub responder: ArrayVec<ArrayVec<Token, 8>, 8>,
}

impl MessagePattern {
/// Check if the pattern includes pre-shared keys
pub fn has_psk(&self) -> bool {
self.initiator.iter().flatten().any(|t| *t == Token::Psk)
|| self.responder.iter().flatten().any(|t| *t == Token::Psk)
Expand All @@ -46,7 +61,7 @@ impl MessagePattern {
impl HandshakePattern {
/// Initialize a new handshake pattern
///
/// # Arguments:
/// # Arguments
/// * `name` - Pattern name
/// * `pre_initiator` - Tokens shared by initiator pre handshake
/// * `pre_responder` - Tokens shared by responder pre handshake
Expand Down
4 changes: 2 additions & 2 deletions src/handshakestate/dual_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ where
{
/// Initialize a new dual layer handshake
///
/// # Arguments:
/// # Arguments
/// * `outer` - Outer handshake, which is completed first
/// * `innter` - Inner handshake, which benefits from the security of the outer layer
///
/// # Generic parameters:
/// # Generic parameters
/// * `const BUF` - Intermediate decrypt buffer size - Must be large enough to fit all inner handshake messages
///
/// # Panics
Expand Down
16 changes: 10 additions & 6 deletions src/handshakestate/nq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ where
{
// Internal, we can live with this
#[allow(clippy::type_complexity)]
internals: HandshakeInternals<'a, C, H, RNG, DH::Key, DH::PubKey, DH::Key, DH::PubKey>,
internals:
HandshakeInternals<'a, C, H, RNG, DH::PrivateKey, DH::PubKey, DH::PrivateKey, DH::PubKey>,
}

impl<'a, DH, CIPHER, HASH, RNG> NqHandshake<'a, DH, CIPHER, HASH, RNG>
Expand All @@ -37,7 +38,7 @@ where
{
/// Initialize new non-post-quantum handshake
///
/// # Arguments:
/// # Arguments
/// * `pattern` - Handshake pattern
/// * `prologue` - Optional prologue data for the handshake
/// * `initiator` - True if we are the initiator
Expand All @@ -47,17 +48,20 @@ where
/// * `re` - Peer public ephemeral key - Shouldn't usually be provided manually
/// * `rng` - RNG to use during the handshake
///
/// # Generic parameters:
/// # Generic parameters
/// * `DH` - DH algorithm to use
/// * `CIPHER` - Cipher algorithm to use
/// * `HASH` - Hashing algorithm to use
///
/// # Panics
/// * Panics if initialized with a PQ pattern
#[allow(clippy::too_many_arguments)] // Okay for now
pub fn new(
pattern: HandshakePattern,
prologue: &[u8],
initiator: bool,
s: Option<KeyPair<DH::PubKey, DH::Key>>,
e: Option<KeyPair<DH::PubKey, DH::Key>>,
s: Option<KeyPair<DH::PubKey, DH::PrivateKey>>,
e: Option<KeyPair<DH::PubKey, DH::PrivateKey>>,
rs: Option<DH::PubKey>,
re: Option<DH::PubKey>,
rng: &'a mut RNG,
Expand Down Expand Up @@ -168,7 +172,7 @@ where
}

fn dh(
a: Option<&KeyPair<DH::PubKey, DH::Key>>,
a: Option<&KeyPair<DH::PubKey, DH::PrivateKey>>,
b: Option<&DH::PubKey>,
) -> HandshakeResult<DH::Output> {
let a = a.ok_or(HandshakeError::MissingMaterial)?;
Expand Down
7 changes: 5 additions & 2 deletions src/handshakestate/pq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ where
{
/// Initialize new post-quantum handshake
///
/// # Arguments:
/// # Arguments
/// * `pattern` - Handshake pattern
/// * `prologue` - Optional prologue data for the handshake
/// * `initiator` - True if we are the initiator
Expand All @@ -59,11 +59,14 @@ where
/// * `re` - Peer public ephemeral key - Shouldn't usually be provided manually
/// * `rng` - RNG to use during the handshake
///
/// # Generic parameters:
/// # Generic parameters
/// * `EKEM` - KEM algorithm to use for ephemeral key encapsulation
/// * `SKEM` - KEM algorithm to use for static key encapsulation
/// * `CIPHER` - Cipher algorithm to use
/// * `HASH` - Hashing algorithm to use
///
/// # Panics
/// * Panics if initialized with a NQ pattern
#[allow(clippy::too_many_arguments)] // Okay for now
pub fn new(
pattern: HandshakePattern,
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]
#![warn(clippy::missing_panics_doc)]
//! # Clatter 🔊
//!
//! ⚠️ **Work in progress** ⚠️
Expand Down Expand Up @@ -186,6 +188,8 @@ pub mod crypto {
/// A zeroize-on-drop container for keys
#[derive(ZeroizeOnDrop, Clone)]
pub struct KeyPair<P: Zeroize, S: Zeroize> {
/// Public key
pub public: P,
/// Secret (private) key
pub secret: S,
}
8 changes: 4 additions & 4 deletions src/symmetricstate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ where
}
}

/// # Protocol:
/// # Protocol
/// ```text
/// Sets h = HASH(h || data)
/// ```
Expand All @@ -52,7 +52,7 @@ where
self.h = h.result();
}

/// # Protocol:
/// # Protocol
/// ```text
/// * Sets ck, temp_k = HKDF(ck, input_key_material, 2).
/// * If HASHLEN is 64, then truncates temp_k to 32 bytes.
Expand All @@ -64,7 +64,7 @@ where
self.cipherstate = Some(CipherState::new(&temp_k.as_slice()[..C::key_len()], 0));
}

/// # Protocol:
/// # Protocol
/// ```text
/// * Sets ck, temp_h, temp_k = HKDF(ck, input_key_material, 3).
/// * Calls MixHash(temp_h).
Expand Down Expand Up @@ -112,7 +112,7 @@ where

/// Return [`CipherStates`] for encrypting transport messages
///
/// # Panics:
/// # Panics
/// * If no key material has been stablished
pub(crate) fn split(&self) -> CipherStates<C> {
// This means that we are still in the initial state
Expand Down
Loading

0 comments on commit 6e8cb39

Please sign in to comment.