From 05604ca9b95f002629bb4a832b092147ca0b7da1 Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 17 Nov 2023 09:43:57 +0100 Subject: [PATCH] feat: Add rustcrypto backend --- .github/workflows/build-and-test.yml | 4 +- Cargo.toml | 1 + crypto/Cargo.toml | 9 ++ crypto/edhoc-crypto-rustcrypto/Cargo.toml | 18 +++ crypto/edhoc-crypto-rustcrypto/src/lib.rs | 140 ++++++++++++++++++++++ crypto/src/lib.rs | 8 ++ 6 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 crypto/edhoc-crypto-rustcrypto/Cargo.toml create mode 100644 crypto/edhoc-crypto-rustcrypto/src/lib.rs diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 402a4da1..7a205393 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -28,7 +28,7 @@ jobs: strategy: fail-fast: false matrix: - crypto_backend: [edhoc-crypto/hacspec, edhoc-crypto/psa] + crypto_backend: [edhoc-crypto/hacspec, edhoc-crypto/psa, edhoc-crypto/rustcrypto] ead: [ead-none, ead-zeroconf] steps: @@ -46,7 +46,7 @@ jobs: strategy: fail-fast: false matrix: - crypto_backend: [edhoc-crypto/hacspec, edhoc-crypto/psa, edhoc-crypto/psa-baremetal, edhoc-crypto/cryptocell310] + crypto_backend: [edhoc-crypto/hacspec, edhoc-crypto/psa, edhoc-crypto/psa-baremetal, edhoc-crypto/cryptocell310, edhoc-crypto/rustcrypto] ead: [ead-none, ead-zeroconf] steps: diff --git a/Cargo.toml b/Cargo.toml index 0e16567f..98fd7fa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "crypto/edhoc-crypto-cc2538", "crypto/edhoc-crypto-hacspec", "crypto/edhoc-crypto-psa", + "crypto/edhoc-crypto-rustcrypto", "crypto/edhoc-crypto-cryptocell310-sys", "crypto/edhoc-crypto-trait", "examples/coap", diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index 2856c2fa..3351c7bc 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -23,6 +23,10 @@ edhoc-crypto-psa = { path = "./edhoc-crypto-psa", default-features = false, opti # cryptocell for nrf52840 edhoc-crypto-cryptocell310 = { path = "./edhoc-crypto-cryptocell310-sys", optional = true } +# software implementations from rustcrypto +edhoc-crypto-rustcrypto = { path = "./edhoc-crypto-rustcrypto", optional = true } +rand_core = { version = "0.6.4", optional = true, default-features = false } + [features] default = [ ] hacspec = [ "edhoc-crypto-hacspec" ] @@ -30,3 +34,8 @@ cc2538 = [ "edhoc-crypto-cc2538" ] psa = [ "edhoc-crypto-psa" ] psa-baremetal = [ "psa", "edhoc-crypto-psa/baremetal" ] cryptocell310 = [ "edhoc-crypto-cryptocell310" ] +# This requires std because we need to conjure randomness from thin air; +# embedded systems can still use rustcrypto but need to provide a crypto from +# edhoc-crypto-rustcrypto on their own, and combine it with an entropy choice +# of their avail. +rustcrypto = [ "edhoc-crypto-rustcrypto", "rand_core/getrandom" ] diff --git a/crypto/edhoc-crypto-rustcrypto/Cargo.toml b/crypto/edhoc-crypto-rustcrypto/Cargo.toml new file mode 100644 index 00000000..05abcf79 --- /dev/null +++ b/crypto/edhoc-crypto-rustcrypto/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "edhoc-crypto-rustcrypto" +version = "0.1.0" +edition = "2021" +license = "BSD" +description = "EDHOC crypto library backend based on the RustCrypto crates" + +[dependencies] +edhoc-consts = { path = "../../consts" } +edhoc-crypto-trait.path = "../edhoc-crypto-trait" + +aead = { version = "0.5.2", default-features = false } +aes = { version = "0.8.3", default-features = false } +ccm = { version = "0.5.0", default-features = false } +hkdf = { version = "0.12.3", default-features = false } +p256 = { version = "0.13.2", default-features = false, features = [ "ecdh" ] } +sha2 = { version = "0.10.8", default-features = false } +rand_core = { version = "0.6.4", default-features = false } diff --git a/crypto/edhoc-crypto-rustcrypto/src/lib.rs b/crypto/edhoc-crypto-rustcrypto/src/lib.rs new file mode 100644 index 00000000..32f3360e --- /dev/null +++ b/crypto/edhoc-crypto-rustcrypto/src/lib.rs @@ -0,0 +1,140 @@ +#![no_std] + +use edhoc_consts::{ + BufferCiphertext3, BufferPlaintext3, BytesCcmIvLen, BytesCcmKeyLen, BytesHashLen, + BytesMaxBuffer, BytesMaxInfoBuffer, BytesP256ElemLen, EDHOCError, MessageBufferTrait, + AES_CCM_TAG_LEN, MAX_BUFFER_LEN, +}; +use edhoc_crypto_trait::Crypto as CryptoTrait; + +use ccm::AeadInPlace; +use ccm::KeyInit; +use p256::elliptic_curve::point::AffineCoordinates; +use p256::elliptic_curve::point::DecompressPoint; +use sha2::Digest; + +type AesCcm16_64_128 = ccm::Ccm; + +/// A type representing cryptographic operations through various RustCrypto crates (eg. [aes], +/// [ccm], [p256]). +/// +/// Its size depends on the implementation of Rng passed in at creation. +pub struct Crypto { + rng: Rng, +} + +impl Crypto { + pub const fn new(rng: Rng) -> Self { + Self { rng } + } +} + +impl core::fmt::Debug for Crypto { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + f.debug_struct("edhoc_crypto_rustcrypto::Crypto") + .field("rng", &core::any::type_name::()) + .finish() + } +} + +impl CryptoTrait for Crypto { + fn sha256_digest(&mut self, message: &BytesMaxBuffer, message_len: usize) -> BytesHashLen { + let mut hasher = sha2::Sha256::new(); + hasher.update(&message[..message_len]); + hasher.finalize().into() + } + + fn hkdf_expand( + &mut self, + prk: &BytesHashLen, + info: &BytesMaxInfoBuffer, + info_len: usize, + length: usize, + ) -> BytesMaxBuffer { + let hkdf = + hkdf::Hkdf::::from_prk(prk).expect("Static size was checked at extract"); + let mut output: BytesMaxBuffer = [0; MAX_BUFFER_LEN]; + hkdf.expand(&info[..info_len], &mut output[..length]) + .expect("Static lengths match the algorithm"); + output + } + + fn hkdf_extract(&mut self, salt: &BytesHashLen, ikm: &BytesP256ElemLen) -> BytesHashLen { + // While it'd be nice to just pass around an Hkdf, the extract output is not a type generic + // of this trait (yet?). + let mut extracted = hkdf::HkdfExtract::::new(Some(salt)); + extracted.input_ikm(ikm); + extracted.finalize().0.into() + } + + fn aes_ccm_encrypt_tag_8( + &mut self, + key: &BytesCcmKeyLen, + iv: &BytesCcmIvLen, + ad: &[u8], + plaintext: &BufferPlaintext3, + ) -> BufferCiphertext3 { + let key = AesCcm16_64_128::new(key.into()); + let mut outbuffer = BufferCiphertext3::new(); + outbuffer.content[..plaintext.len].copy_from_slice(&plaintext.content[..plaintext.len]); + if let Ok(tag) = + key.encrypt_in_place_detached(iv.into(), ad, &mut outbuffer.content[..plaintext.len]) + { + outbuffer.content[plaintext.len..][..AES_CCM_TAG_LEN].copy_from_slice(&tag); + } else { + panic!("Preconfigured sizes should not allow encryption to fail") + } + outbuffer.len = plaintext.len + AES_CCM_TAG_LEN; + outbuffer + } + + fn aes_ccm_decrypt_tag_8( + &mut self, + key: &BytesCcmKeyLen, + iv: &BytesCcmIvLen, + ad: &[u8], + ciphertext: &BufferCiphertext3, + ) -> Result { + let key = AesCcm16_64_128::new(key.into()); + let mut buffer = BufferPlaintext3::new(); + buffer.len = ciphertext.len - AES_CCM_TAG_LEN; + buffer.content[..buffer.len].copy_from_slice(&ciphertext.content[..buffer.len]); + let tag = &ciphertext.content[buffer.len..][..AES_CCM_TAG_LEN]; + key.decrypt_in_place_detached(iv.into(), ad, &mut buffer.content[..buffer.len], tag.into()) + .map_err(|_| EDHOCError::MacVerificationFailed)?; + Ok(buffer) + } + + fn p256_ecdh( + &mut self, + private_key: &BytesP256ElemLen, + public_key: &BytesP256ElemLen, + ) -> BytesP256ElemLen { + let secret = p256::SecretKey::from_bytes(private_key.as_slice().into()) + .expect("Invalid secret key generated"); + let public = p256::AffinePoint::decompress( + public_key.into(), + 1.into(), /* Y coordinate choice does not matter for ECDH operation */ + ) + // While this can actually panic so far, the proper fix is in + // https://github.com/openwsn-berkeley/edhoc-rs/issues/93 which will justify this to be a + // panic (because after that, public key validity will be an invariant of the public key + // type) + .expect("Public key is not a good point"); + + (*p256::ecdh::diffie_hellman(secret.to_nonzero_scalar(), public).raw_secret_bytes()).into() + } + + fn get_random_byte(&mut self) -> u8 { + self.rng.next_u32() as _ + } + + fn p256_generate_key_pair(&mut self) -> (BytesP256ElemLen, BytesP256ElemLen) { + let secret = p256::SecretKey::random(&mut self.rng); + + let public_key = secret.public_key().as_affine().x(); + let private_key = secret.to_bytes(); + + (private_key.into(), public_key.into()) + } +} diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 09e0f6b6..801712c3 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -29,6 +29,14 @@ pub const fn default_crypto() -> Crypto { edhoc_crypto_psa::Crypto } +#[cfg(feature = "rustcrypto")] +pub type Crypto = edhoc_crypto_rustcrypto::Crypto; + +#[cfg(feature = "rustcrypto")] +pub const fn default_crypto() -> Crypto { + edhoc_crypto_rustcrypto::Crypto::new(rand_core::OsRng) +} + #[cfg(any(feature = "cryptocell310", feature = "cryptocell310-rust"))] pub type Crypto = edhoc_crypto_cryptocell310::Crypto;