diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a097085..d857938 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Cargo Build & Test +name: Build & Test on: push: @@ -7,8 +7,7 @@ on: pull_request: jobs: - build: - name: Rust - latest + build-and-test: runs-on: ubuntu-latest strategy: matrix: @@ -19,7 +18,11 @@ jobs: steps: - uses: actions/checkout@v3 - name: Update Toolchain - run: rustup default ${{ matrix.toolchain }} && rustup component add --toolchain ${{ matrix.toolchain }} rustfmt && rustup component add --toolchain ${{ matrix.toolchain }} clippy && rustup update ${{ matrix.toolchain }} + run: | + rustup default ${{ matrix.toolchain }} + rustup component add --toolchain ${{ matrix.toolchain }} rustfmt + rustup component add --toolchain ${{ matrix.toolchain }} clippy + rustup update ${{ matrix.toolchain }} - name: Build run: cargo build --verbose - name: Lint @@ -28,5 +31,9 @@ jobs: run: cargo fmt --all -- --check - name: Test run: cargo test --verbose - + - name: Check No Standard Library Support + run: | + rustup target add --toolchain ${{ matrix.toolchain }} thumbv7m-none-eabi + cargo install cross + cross build --target thumbv7m-none-eabi --no-default-features diff --git a/Cargo.toml b/Cargo.toml index 7dd8044..122b38b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,14 @@ repository = "https://github.com/rustaceanrob/bip324" readme = "README.md" rust-version = "1.56.1" +[features] +default = ["std"] +std = ["secp256k1/std", "rand/std", "rand/std_rng"] + [dependencies] -secp256k1 = { version="0.28.2" } -rand = "0.8.4" -bitcoin_hashes = "0.13.0" +secp256k1 = { version="0.28.2", default-features = false} +rand = { version = "0.8.4", default-features = false } +bitcoin_hashes = { version = "0.13.0", default-features = false } [dev-dependencies] hex = "0.4.3" diff --git a/src/chachapoly.rs b/src/chachapoly.rs index d504911..038b3e1 100644 --- a/src/chachapoly.rs +++ b/src/chachapoly.rs @@ -1,9 +1,10 @@ use crate::chacha::ChaCha20; use crate::error; use crate::poly1305::Poly1305; -use error::ChaCha20Poly1305EncryptionError; -extern crate alloc; pub use error::ChaCha20Poly1305DecryptionError; +use error::ChaCha20Poly1305EncryptionError; + +use alloc::string::ToString; #[derive(Debug)] pub struct ChaCha20Poly1305 { diff --git a/src/error.rs b/src/error.rs index f33e576..edd0e7f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,6 @@ -use std::error::Error; -extern crate alloc; use core::fmt; -// use alloc::string::String; + +use alloc::string::String; /// An error occured responding to an inbound handshake. #[derive(Debug)] @@ -12,8 +11,8 @@ pub enum ResponderHandshakeError { EncryptionError(String), } -impl std::fmt::Display for ResponderHandshakeError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for ResponderHandshakeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ResponderHandshakeError::ECC(e) => write!(f, "ECC error: {}", e), ResponderHandshakeError::IncorrectMessage(s) => write!(f, "Version error: {}", s), @@ -22,8 +21,9 @@ impl std::fmt::Display for ResponderHandshakeError { } } -impl Error for ResponderHandshakeError { - fn source(&self) -> Option<&(dyn Error + 'static)> { +#[cfg(feature = "std")] +impl std::error::Error for ResponderHandshakeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { ResponderHandshakeError::ECC(e) => Some(e), ResponderHandshakeError::IncorrectMessage(_) => None, @@ -41,8 +41,8 @@ pub enum HandshakeCompletionError { DecryptionError(String), } -impl std::fmt::Display for HandshakeCompletionError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for HandshakeCompletionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { HandshakeCompletionError::MessageTooShort(s) => write!(f, "Handshake error: {}", s), HandshakeCompletionError::TooMuchGarbage(s) => write!(f, "Handshake error: {}", s), @@ -52,8 +52,9 @@ impl std::fmt::Display for HandshakeCompletionError { } } -impl Error for HandshakeCompletionError { - fn source(&self) -> Option<&(dyn Error + 'static)> { +#[cfg(feature = "std")] +impl std::error::Error for HandshakeCompletionError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { HandshakeCompletionError::MessageTooShort(_s) => None, HandshakeCompletionError::TooMuchGarbage(_s) => None, @@ -71,8 +72,8 @@ pub enum FSChaChaError { Poly1305Decryption(String), } -impl std::fmt::Display for FSChaChaError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for FSChaChaError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { FSChaChaError::StreamEncryption(s) => write!(f, "Cipher error: {}", s), FSChaChaError::StreamDecryption(s) => write!(f, "Cipher error: {}", s), @@ -82,8 +83,9 @@ impl std::fmt::Display for FSChaChaError { } } -impl Error for FSChaChaError { - fn source(&self) -> Option<&(dyn Error + 'static)> { +#[cfg(feature = "std")] +impl std::error::Error for FSChaChaError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { FSChaChaError::StreamEncryption(_s) => None, FSChaChaError::StreamDecryption(_s) => None, diff --git a/src/hkdf.rs b/src/hkdf.rs index 8baa223..c94aa63 100644 --- a/src/hkdf.rs +++ b/src/hkdf.rs @@ -21,6 +21,7 @@ impl fmt::Display for InvalidLength { } } +#[cfg(feature = "std")] impl std::error::Error for InvalidLength {} /// HMAC-based Extract-and-Expand Key Derivation Function (HKDF). diff --git a/src/lib.rs b/src/lib.rs index d0402a8..b614ef3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,6 +36,10 @@ //! assert_eq!(message, secret_message); //! ``` +#![cfg_attr(all(not(feature = "std"), not(test)), no_std)] + +extern crate alloc; + mod chacha; mod chachapoly; mod error; @@ -45,12 +49,19 @@ mod types; use chacha::ChaCha20; use chachapoly::ChaCha20Poly1305; + +use alloc::vec; +use alloc::vec::Vec; + +use alloc::string::ToString; + use error::FSChaChaError; pub use error::{HandshakeCompletionError, ResponderHandshakeError}; use hkdf::Hkdf; use rand::Rng; use secp256k1::{ ellswift::{ElligatorSwift, ElligatorSwiftParty}, + ffi::types::AlignedType, PublicKey, Secp256k1, SecretKey, }; pub use types::SessionKeyMaterial; @@ -360,22 +371,21 @@ impl FSChaCha20 { } } -fn gen_key() -> Result { - let mut rnd = rand::thread_rng(); +fn gen_key(rng: &mut impl Rng) -> Result { let mut buffer: Vec = vec![0; 32]; - rnd.fill(&mut buffer[..]); + rng.fill(&mut buffer[..]); let sk = SecretKey::from_slice(&buffer)?; Ok(sk) } fn new_elligator_swift(sk: SecretKey) -> ElligatorSwift { - let curve = Secp256k1::new(); + let mut buf_ful = vec![AlignedType::zeroed(); Secp256k1::preallocate_size()]; + let curve = Secp256k1::preallocated_new(&mut buf_ful).unwrap(); let pk = PublicKey::from_secret_key(&curve, &sk); ElligatorSwift::from_pubkey(pk) } -fn gen_garbage(garbage_len: u32) -> Vec { - let mut rng = rand::thread_rng(); +fn gen_garbage(garbage_len: u32, rng: &mut impl Rng) -> Vec { let buffer: Vec = (0..garbage_len).map(|_| rng.gen()).collect(); buffer } @@ -449,13 +459,37 @@ fn initialize_session_key_material(ikm: &[u8]) -> SessionKeyMaterial { /// # Errors /// /// Fails if their was an error generating the keypair. +#[cfg(feature = "std")] pub fn initialize_v2_handshake( garbage_len: Option, ) -> Result { - let sk = gen_key()?; + let mut rng = rand::thread_rng(); + initialize_v2_handshake_with_rng(garbage_len, &mut rng) +} + +/// Initialize a V2 transport handshake with a peer. The `InitiatorHandshake` contains a message ready to be sent over the wire, +/// and the information necessary for completing ECDH when the peer responds. +/// +/// # Arguments +/// +/// `garbage_len` - The length of the additional garbage to be sent along with the encoded public key. +/// `rng` - supplied Random Number Generator. +/// +/// # Returns +/// +/// A partial handshake. +/// +/// # Errors +/// +/// Fails if their was an error generating the keypair. +pub fn initialize_v2_handshake_with_rng( + garbage_len: Option, + rng: &mut impl Rng, +) -> Result { + let sk = gen_key(rng)?; let es = new_elligator_swift(sk); let garbage_len = garbage_len.unwrap_or(MAX_GARBAGE_LEN); - let garbage = gen_garbage(garbage_len); + let garbage = gen_garbage(garbage_len, rng); let point = EcdhPoint { secret_key: sk, elligator_swift: es, @@ -482,8 +516,31 @@ pub fn initialize_v2_handshake( /// # Errors /// /// Fails if the packet was not prepared properly. +#[cfg(feature = "std")] pub fn receive_v2_handshake( message: Vec, +) -> Result { + let mut rng = rand::thread_rng(); + receive_v2_handshake_with_rng(message, &mut rng) +} + +/// Receive a V2 handshake over the wire. The `ResponderHandshake` contains the message ready to be sent over the wire and a struct for parsing packets. +/// +/// # Arguments +/// +/// `message` - The message received over the wire. +/// `rng` - Supplied Random Number Generator. +/// +/// # Returns +/// +/// A completed handshake containing a `PacketHandler`. +/// +/// # Errors +/// +/// Fails if the packet was not prepared properly. +pub fn receive_v2_handshake_with_rng( + message: Vec, + rng: &mut impl Rng, ) -> Result { let mut network_magic = NETWORK_MAGIC.to_vec(); let mut version_bytes = "version".as_bytes().to_vec(); @@ -496,7 +553,7 @@ pub fn receive_v2_handshake( )) } else { let mut response: Vec = Vec::new(); - let sk = gen_key().map_err(ResponderHandshakeError::ECC)?; + let sk = gen_key(rng).map_err(ResponderHandshakeError::ECC)?; let es = new_elligator_swift(sk); response.extend(&es.to_array()); let elliswift_message = &message[..64]; @@ -507,7 +564,7 @@ pub fn receive_v2_handshake( let session_keys = get_shared_secrets(theirs, es, sk, ElligatorSwiftParty::B); let initiator_garbage = message[64..].to_vec(); let initiator_garbage_len = initiator_garbage.len() as u32; - let response_garbage = gen_garbage(initiator_garbage_len); + let response_garbage = gen_garbage(initiator_garbage_len, rng); if initiator_garbage_len > MAX_GARBAGE_LEN { return Err(ResponderHandshakeError::IncorrectMessage( "Garbage length is too large.".to_string(), @@ -649,7 +706,8 @@ mod tests { #[test] fn test_sec_keygen() { - gen_key().unwrap(); + let mut rng = rand::thread_rng(); + gen_key(&mut rng).unwrap(); } #[test] @@ -765,6 +823,7 @@ mod tests { #[test] fn test_fuzz_packets() { + let mut rng = rand::thread_rng(); let alice = SecretKey::from_str("61062ea5071d800bbfd59e2e8b53d47d194b095ae5a4df04936b49772ef0d4d7") .unwrap(); @@ -780,7 +839,7 @@ mod tests { PacketHandler::new(session_keys.clone(), HandshakeRole::Initiator); let mut bob_packet_handler = PacketHandler::new(session_keys, HandshakeRole::Responder); for _ in 0..REKEY_INTERVAL + 100 { - let message = gen_garbage(4095); + let message = gen_garbage(4095, &mut rng); let enc_packet = alice_packet_handler .prepare_v2_packet(message.clone(), None, false) .unwrap(); @@ -789,7 +848,7 @@ mod tests { .unwrap(); let secret_message = dec_packet.first().unwrap().message.clone().unwrap(); assert_eq!(message, secret_message); - let message = gen_garbage(420); + let message = gen_garbage(420, &mut rng); let enc_packet = bob_packet_handler .prepare_v2_packet(message.clone(), None, false) .unwrap(); @@ -803,6 +862,7 @@ mod tests { #[test] fn test_authenticated_garbage() { + let mut rng = rand::thread_rng(); let alice = SecretKey::from_str("61062ea5071d800bbfd59e2e8b53d47d194b095ae5a4df04936b49772ef0d4d7") .unwrap(); @@ -817,7 +877,7 @@ mod tests { let mut alice_packet_handler = PacketHandler::new(session_keys.clone(), HandshakeRole::Initiator); let mut bob_packet_handler = PacketHandler::new(session_keys, HandshakeRole::Responder); - let auth_garbage = gen_garbage(200); + let auth_garbage = gen_garbage(200, &mut rng); let enc_packet = alice_packet_handler .prepare_v2_packet(Vec::new(), Some(auth_garbage.clone()), false) .unwrap(); @@ -885,6 +945,7 @@ mod tests { #[test] fn test_fuzz_decode_multiple_messages() { + let mut rng = rand::thread_rng(); let handshake_init = initialize_v2_handshake(None).unwrap(); let mut handshake_response = receive_v2_handshake(handshake_init.message.clone()).unwrap(); let alice_completion = @@ -899,7 +960,7 @@ mod tests { let mut bob = handshake_response.packet_handler; let mut message_to_bob = Vec::new(); for _ in 0..REKEY_INTERVAL + 100 { - let message = gen_garbage(420); + let message = gen_garbage(420, &mut rng); let enc_packet = alice .prepare_v2_packet(message.clone(), None, false) .unwrap(); @@ -912,6 +973,7 @@ mod tests { #[test] fn test_vector_1() { + let mut rng = rand::thread_rng(); let alice = SecretKey::from_str("61062ea5071d800bbfd59e2e8b53d47d194b095ae5a4df04936b49772ef0d4d7") .unwrap(); @@ -926,7 +988,7 @@ mod tests { let mut alice_packet_handler = PacketHandler::new(session_keys.clone(), HandshakeRole::Initiator); let mut bob_packet_handler = PacketHandler::new(session_keys, HandshakeRole::Responder); - let first = gen_garbage(100); + let first = gen_garbage(100, &mut rng); let enc = alice_packet_handler .prepare_v2_packet(first.clone(), None, false) .unwrap(); diff --git a/src/types.rs b/src/types.rs index 32d33c8..a104369 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,5 @@ use crate::PacketHandler; +use alloc::vec::Vec; use secp256k1::{ellswift::ElligatorSwift, SecretKey}; /// A point on the curve used to complete the handshake.