diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6800bb9..7d02f5c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -8,27 +8,26 @@ on: name: Rust jobs: - # TODO: fix no_std support (see #36) - # build: - # name: Build - # strategy: - # matrix: - # target: - # - thumbv7em-none-eabihf - # - wasm32-unknown-unknown - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v1 - # - uses: actions-rs/toolchain@v1 - # with: - # toolchain: stable - # override: true - # - name: Install target - # run: rustup target add ${{ matrix.target }} - # - uses: actions-rs/cargo@v1 - # with: - # command: build - # args: --no-default-features --lib --target ${{ matrix.target }} + build: + name: Build + strategy: + matrix: + target: + - thumbv7em-none-eabihf + - wasm32-unknown-unknown + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - name: Install target + run: rustup target add ${{ matrix.target }} + - uses: actions-rs/cargo@v1 + with: + command: build + args: --no-default-features --lib --target ${{ matrix.target }} check: name: Check diff --git a/Cargo.toml b/Cargo.toml index 49655ad..46fedaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,19 @@ [package] -name = "miscreant" +name = "miscreant" description = """ - Symmetric encryption library providing misuse-resistant - authenticated encryption (MRAE) including AES-SIV (RFC 5297), - AES-PMAC-SIV, and the STREAM segmented encryption construction. - """ -version = "0.4.2" # Also update html_root_url in lib.rs when bumping this -license = "Apache-2.0 OR MIT" -authors = ["Tony Arcieri "] -homepage = "https://miscreant.io" -repository = "https://github.com/miscreant/miscreant.rs" -readme = "README.md" -categories = ["cryptography", "no-std"] -keywords = ["aes", "cryptography", "encryption", "security", "streaming"] -edition = "2018" +Symmetric encryption library providing misuse-resistant +authenticated encryption (MRAE) including AES-SIV (RFC 5297), +AES-PMAC-SIV, and the STREAM segmented encryption construction. +""" +version = "0.4.2" # Also update html_root_url in lib.rs when bumping this +license = "Apache-2.0 OR MIT" +authors = ["Tony Arcieri "] +homepage = "https://miscreant.io" +repository = "https://github.com/miscreant/miscreant.rs" +readme = "README.md" +categories = ["cryptography", "no-std"] +keywords = ["aes", "cryptography", "encryption", "security", "streaming"] +edition = "2018" [lib] crate-type = ["rlib", "staticlib"] @@ -23,7 +23,7 @@ travis-ci = { repository = "miscreant/miscreant.rs" } [dependencies] aes = { version = "0.3", default-features = false } -aes-siv = { version = "0.1", default-features = false, features = ["alloc"] } +aes-siv = { version = "0.2", default-features = false } cmac = { version = "0.2", default-features = false } crypto-mac = { version = "0.7", default-features = false } ctr = { version = "0.3", default-features = false } @@ -35,9 +35,33 @@ subtle-encoding = "0.5" serde_json = "1" [features] -default = ["pmac", "stream"] +default = ["std", "pmac", "stream"] +alloc = ["aes-siv/alloc"] pmac = ["pmac_crate", "aes-siv/pmac"] +std = ["alloc"] stream = [] [workspace] members = [".", "benches"] + +[profile.dev] +panic = "abort" + +[profile.release] +codegen-units = 1 +debug = false +debug-assertions = false +lto = false +opt-level = 3 +overflow-checks = true +panic = "abort" +rpath = false + +[profile.bench] +codegen-units = 1 +debug = false +debug-assertions = false +lto = false +opt-level = 3 +overflow-checks = false +rpath = false diff --git a/src/aead.rs b/src/aead.rs index 8c7eea7..25636b0 100644 --- a/src/aead.rs +++ b/src/aead.rs @@ -3,17 +3,26 @@ //! and authenticity. use crate::{ - generic_array::{typenum::U16, ArrayLength}, + generic_array::{typenum::U16, ArrayLength, GenericArray}, Error, }; use aes::{Aes128, Aes256}; use aes_siv::siv::{Siv, IV_SIZE}; use cmac::Cmac; +use core::ops::Add; use crypto_mac::Mac; use ctr::Ctr128; -use pmac_crate::Pmac; use stream_cipher::{NewStreamCipher, SyncStreamCipher}; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +#[cfg(feature = "pmac")] +use pmac_crate::Pmac; + +/// AES-SIV tags (which have a dual role as the synthetic IV) +pub type Tag = GenericArray; + /// An Authenticated Encryption with Associated Data (AEAD) algorithm. pub trait Aead { /// Size of a key associated with this AEAD algorithm @@ -71,6 +80,7 @@ pub trait Aead { ) -> Result<&'a [u8], Error>; /// Encrypt the given plaintext, allocating and returning a Vec for the ciphertext + #[cfg(feature = "alloc")] fn encrypt(&mut self, nonce: &[u8], associated_data: &[u8], plaintext: &[u8]) -> Vec { let mut buffer = vec![0; IV_SIZE + plaintext.len()]; buffer[IV_SIZE..].copy_from_slice(plaintext); @@ -79,6 +89,7 @@ pub trait Aead { } /// Decrypt the given ciphertext, allocating and returning a Vec for the plaintext + #[cfg(feature = "alloc")] fn decrypt( &mut self, nonce: &[u8], @@ -103,38 +114,59 @@ where siv: Siv, } +// +// AES-CMAC-SIV +// + /// SIV AEAD modes based on CMAC pub type CmacSivAead = SivAead, Cmac>; -/// SIV AEAD modes based on PMAC -pub type PmacSivAead = SivAead, Pmac>; - /// AES-CMAC-SIV in AEAD mode with 256-bit key size (128-bit security) pub type Aes128SivAead = CmacSivAead; /// AES-CMAC-SIV in AEAD mode with 512-bit key size (256-bit security) pub type Aes256SivAead = CmacSivAead; +// +// AES-PMAC-SIV +// + +/// SIV AEAD modes based on PMAC +#[cfg(feature = "pmac")] +pub type PmacSivAead = SivAead, Pmac>; + /// AES-PMAC-SIV in AEAD mode with 256-bit key size (128-bit security) +#[cfg(feature = "pmac")] pub type Aes128PmacSivAead = PmacSivAead; /// AES-PMAC-SIV in AEAD mode with 512-bit key size (256-bit security) +#[cfg(feature = "pmac")] pub type Aes256PmacSivAead = PmacSivAead; impl Aead for SivAead where C: NewStreamCipher + SyncStreamCipher, M: Mac, + C::KeySize: Add, + ::Output: ArrayLength, { type KeySize = ::KeySize; type TagSize = U16; fn new(key: &[u8]) -> Self { - Self { siv: Siv::new(key) } + Self { + siv: Siv::new(GenericArray::clone_from_slice(key)), + } } fn encrypt_in_place(&mut self, nonce: &[u8], associated_data: &[u8], buffer: &mut [u8]) { - self.siv.encrypt_in_place(&[associated_data, nonce], buffer) + assert!(buffer.len() >= IV_SIZE, "no space for IV in buffer"); + let tag = self + .siv + .encrypt_in_place_detached(&[associated_data, nonce], &mut buffer[IV_SIZE..]) + .expect("encryption failure!"); + + buffer[..IV_SIZE].copy_from_slice(&tag); } fn decrypt_in_place<'a>( @@ -143,6 +175,16 @@ where associated_data: &[u8], buffer: &'a mut [u8], ) -> Result<&'a [u8], Error> { - self.siv.decrypt_in_place(&[associated_data, nonce], buffer) + if buffer.len() < IV_SIZE { + return Err(Error); + } + + let tag = Tag::clone_from_slice(&buffer[..IV_SIZE]); + self.siv.decrypt_in_place_detached( + &[associated_data, nonce], + &mut buffer[IV_SIZE..], + &tag, + )?; + Ok(&buffer[IV_SIZE..]) } } diff --git a/src/ffi.rs b/src/ffi.rs index b594b9a..1b46cba 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -7,11 +7,14 @@ #![allow(clippy::missing_safety_doc, clippy::too_many_arguments)] use crate::generic_array::typenum::marker_traits::Unsigned; -use crate::{Aead, Aes128PmacSivAead, Aes128SivAead, Aes256PmacSivAead, Aes256SivAead}; +use crate::{Aead, Aes128SivAead, Aes256SivAead}; use core::{ptr, slice}; +#[cfg(feature = "pmac")] +use crate::{Aes128PmacSivAead, Aes256PmacSivAead}; + // -// AES-128-SIV AEAD +// AES-128-CMAC-SIV AEAD // /// AES-128-SIV AEAD: authenticated encryption @@ -55,7 +58,7 @@ pub static crypto_aead_aes128siv_KEYBYTES: u32 = 32; pub static crypto_aead_aes128siv_TAGBYTES: u32 = 16; // -// AES-256-SIV AEAD +// AES-256-CMAC-SIV AEAD // /// AES-256-SIV AEAD: authenticated encryption @@ -103,6 +106,7 @@ pub static crypto_aead_aes256siv_TAGBYTES: u32 = 16; // /// AES-128-PMAC-SIV AEAD: authenticated encryption +#[cfg(feature = "pmac")] #[no_mangle] pub unsafe extern "C" fn crypto_aead_aes128pmacsiv_encrypt( ct: *mut u8, @@ -119,6 +123,7 @@ pub unsafe extern "C" fn crypto_aead_aes128pmacsiv_encrypt( } /// AES-128-PMAC-SIV AEAD: authenticated decryption +#[cfg(feature = "pmac")] #[no_mangle] pub unsafe extern "C" fn crypto_aead_aes128pmacsiv_decrypt( msg: *mut u8, @@ -135,10 +140,12 @@ pub unsafe extern "C" fn crypto_aead_aes128pmacsiv_decrypt( } /// AES-128-PMAC-SIV key size +#[cfg(feature = "pmac")] #[no_mangle] pub static crypto_aead_aes128pmacsiv_KEYBYTES: u32 = 32; /// AES-128-PMAC-SIV authenticator tag size +#[cfg(feature = "pmac")] #[no_mangle] pub static crypto_aead_aes128pmacsiv_TAGBYTES: u32 = 16; @@ -147,6 +154,7 @@ pub static crypto_aead_aes128pmacsiv_TAGBYTES: u32 = 16; // /// AES-256-PMAC-SIV AEAD: authenticated encryption +#[cfg(feature = "pmac")] #[no_mangle] pub unsafe extern "C" fn crypto_aead_aes256pmacsiv_encrypt( ct: *mut u8, @@ -163,6 +171,7 @@ pub unsafe extern "C" fn crypto_aead_aes256pmacsiv_encrypt( } /// AES-256-PMAC-SIV AEAD: authenticated decryption +#[cfg(feature = "pmac")] #[no_mangle] pub unsafe extern "C" fn crypto_aead_aes256pmacsiv_decrypt( msg: *mut u8, @@ -179,10 +188,12 @@ pub unsafe extern "C" fn crypto_aead_aes256pmacsiv_decrypt( } /// AES-128-SIV key size +#[cfg(feature = "pmac")] #[no_mangle] pub static crypto_aead_aes256pmacsiv_KEYBYTES: u32 = 64; /// AES-128-SIV authenticator tag size +#[cfg(feature = "pmac")] #[no_mangle] pub static crypto_aead_aes256pmacsiv_TAGBYTES: u32 = 16; diff --git a/src/lib.rs b/src/lib.rs index 65d132c..2d6adfa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,7 @@ //! //! [AES-NI]: https://en.wikipedia.org/wiki/AES_instruction_set#x86_architecture_processors +#![no_std] #![doc(html_root_url = "https://docs.rs/miscreant/0.4.2")] #![warn( missing_docs, @@ -31,14 +32,31 @@ unused_qualifications )] +#[cfg(feature = "alloc")] +#[macro_use] +extern crate alloc; + +#[cfg(feature = "std")] +extern crate std; + mod aead; pub mod ffi; #[cfg(feature = "stream")] pub mod stream; -pub use crate::aead::{Aead, Aes128PmacSivAead, Aes128SivAead, Aes256PmacSivAead, Aes256SivAead}; +pub use crate::aead::{Aead, Aes128SivAead, Aes256SivAead}; pub use aes_siv::{ - aead::generic_array, - aead::Error, - siv::{Aes128PmacSiv, Aes128Siv, Aes256PmacSiv, Aes256Siv}, + aead::{generic_array, Error}, + siv::{Aes128Siv, Aes256Siv}, }; + +#[cfg(feature = "pmac")] +pub use crate::aead::{Aes128PmacSivAead, Aes256PmacSivAead}; +#[cfg(feature = "pmac")] +pub use aes_siv::siv::{Aes128PmacSiv, Aes256PmacSiv}; + +#[cfg(not(any(feature = "std", test)))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} diff --git a/src/stream.rs b/src/stream.rs index 0b1c2d2..fecae37 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -3,6 +3,9 @@ use crate::{Aead, Aes128PmacSivAead, Aes128SivAead, Aes256PmacSivAead, Aes256SivAead, Error}; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + /// Size of a nonce required by STREAM in bytes pub const NONCE_SIZE: usize = 8; @@ -58,6 +61,7 @@ impl Encryptor { /// Encrypt the next message in the stream, allocating and returning a /// `Vec` for the ciphertext + #[cfg(feature = "alloc")] pub fn encrypt_next(&mut self, ad: &[u8], plaintext: &[u8]) -> Vec { let ciphertext = self.alg.encrypt(self.nonce.as_slice(), ad, plaintext); self.nonce.increment(); @@ -66,6 +70,7 @@ impl Encryptor { /// Encrypt the final message in the stream, allocating and returning a /// `Vec` for the ciphertext + #[cfg(feature = "alloc")] pub fn encrypt_last(mut self, ad: &[u8], plaintext: &[u8]) -> Vec { self.alg.encrypt(&self.nonce.finish(), ad, plaintext) } @@ -131,6 +136,7 @@ impl Decryptor { /// Decrypt the next message in the stream, allocating and returning a /// `Vec` for the plaintext + #[cfg(feature = "alloc")] pub fn decrypt_next(&mut self, ad: &[u8], ciphertext: &[u8]) -> Result, Error> { let plaintext = self.alg.decrypt(self.nonce.as_slice(), ad, ciphertext)?; self.nonce.increment(); @@ -139,6 +145,7 @@ impl Decryptor { /// Decrypt the next message in the stream, allocating and returning a /// `Vec` for the plaintext + #[cfg(feature = "alloc")] pub fn decrypt_last(mut self, ad: &[u8], ciphertext: &[u8]) -> Result, Error> { self.alg.decrypt(&self.nonce.finish(), ad, ciphertext) } diff --git a/tests/siv_test.rs b/tests/siv_test.rs index aff3508..f9e5117 100644 --- a/tests/siv_test.rs +++ b/tests/siv_test.rs @@ -1,7 +1,7 @@ mod siv_vectors; use self::siv_vectors::{AesPmacSivExample, AesSivExample}; -use miscreant::{Aes128PmacSiv, Aes128Siv, Aes256PmacSiv, Aes256Siv}; +use miscreant::{generic_array::GenericArray, Aes128PmacSiv, Aes128Siv, Aes256PmacSiv, Aes256Siv}; #[test] fn aes_siv_examples_encrypt() { @@ -9,10 +9,13 @@ fn aes_siv_examples_encrypt() { for example in examples { let ciphertext = match example.key.len() { - 32 => Aes128Siv::new(&example.key).encrypt(&example.ad, &example.plaintext), - 64 => Aes256Siv::new(&example.key).encrypt(&example.ad, &example.plaintext), + 32 => Aes128Siv::new(GenericArray::clone_from_slice(&example.key)) + .encrypt(&example.ad, &example.plaintext), + 64 => Aes256Siv::new(GenericArray::clone_from_slice(&example.key)) + .encrypt(&example.ad, &example.plaintext), _ => panic!("unexpected key size: {}", example.key.len()), - }; + } + .unwrap(); assert_eq!(ciphertext, example.ciphertext); } @@ -24,8 +27,10 @@ fn aes_siv_examples_decrypt() { for example in examples { let plaintext = match example.key.len() { - 32 => Aes128Siv::new(&example.key).decrypt(&example.ad, &example.ciphertext), - 64 => Aes256Siv::new(&example.key).decrypt(&example.ad, &example.ciphertext), + 32 => Aes128Siv::new(GenericArray::clone_from_slice(&example.key)) + .decrypt(&example.ad, &example.ciphertext), + 64 => Aes256Siv::new(GenericArray::clone_from_slice(&example.key)) + .decrypt(&example.ad, &example.ciphertext), _ => panic!("unexpected key size: {}", example.key.len()), } .expect("decrypt failure"); @@ -40,10 +45,13 @@ fn aes_pmac_siv_examples_encrypt() { for example in examples { let ciphertext = match example.key.len() { - 32 => Aes128PmacSiv::new(&example.key).encrypt(&example.ad, &example.plaintext), - 64 => Aes256PmacSiv::new(&example.key).encrypt(&example.ad, &example.plaintext), + 32 => Aes128PmacSiv::new(GenericArray::clone_from_slice(&example.key)) + .encrypt(&example.ad, &example.plaintext), + 64 => Aes256PmacSiv::new(GenericArray::clone_from_slice(&example.key)) + .encrypt(&example.ad, &example.plaintext), _ => panic!("unexpected key size: {}", example.key.len()), - }; + } + .unwrap(); assert_eq!(ciphertext, example.ciphertext); } @@ -55,8 +63,10 @@ fn aes_pmac_siv_examples_decrypt() { for example in examples { let plaintext = match example.key.len() { - 32 => Aes128PmacSiv::new(&example.key).decrypt(&example.ad, &example.ciphertext), - 64 => Aes256PmacSiv::new(&example.key).decrypt(&example.ad, &example.ciphertext), + 32 => Aes128PmacSiv::new(GenericArray::clone_from_slice(&example.key)) + .decrypt(&example.ad, &example.ciphertext), + 64 => Aes256PmacSiv::new(GenericArray::clone_from_slice(&example.key)) + .decrypt(&example.ad, &example.ciphertext), _ => panic!("unexpected key size: {}", example.key.len()), } .expect("decrypt failure");