Project import generated by Copybara. GitOrigin-RevId: 8e45d682af0c4329d479cb7a1e0b83d70c9a94f3 Change-Id: I81aa3256a4bd578d47e2743b4e67c74cb24823f9
diff --git a/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml b/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml new file mode 100644 index 0000000..ad5a4b8 --- /dev/null +++ b/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml
@@ -0,0 +1,37 @@ +[package] +name = "crypto_provider_rustcrypto" +version.workspace = true +edition.workspace = true +publish.workspace = true + +[dependencies] +aead = "0.5.1" +aes-gcm-siv = { version = "0.11.1", features = ["aes"], optional = true } +crypto_provider.workspace = true +hmac.workspace = true +hkdf.workspace = true +sha2.workspace = true +x25519-dalek.workspace = true +p256 = { workspace = true, features = ["ecdh"], default-features = false } +sec1.workspace = true +ed25519-dalek = { workspace = true, default-features = false } +rand = { workspace = true, default-features = false } +rand_core_05_adapter.workspace = true +rand_core.workspace = true +subtle.workspace = true +aes.workspace = true +ctr.workspace = true +cbc.workspace = true +cfg-if.workspace = true +rand_chacha = { workspace = true, default-features = false, optional = true } + +[dev-dependencies] +hex.workspace = true +crypto_provider = { workspace = true, features = ["testing"] } +crypto_provider_rustcrypto = { path = ".", features = ["std"] } + +[features] +default = ["alloc", "gcm_siv", "rand_chacha"] +std = ["ed25519-dalek/default", "rand/std", "rand/std_rng", "crypto_provider/std", "crypto_provider/alloc"] +alloc = ["aead/bytes"] +gcm_siv = ["crypto_provider/gcm_siv", "dep:aes-gcm-siv"]
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes/cbc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes/cbc.rs new file mode 100644 index 0000000..b43876c --- /dev/null +++ b/nearby/crypto/crypto_provider_rustcrypto/src/aes/cbc.rs
@@ -0,0 +1,53 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate alloc; +use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit}; +use aes::Aes256; +use alloc::vec::Vec; +use crypto_provider::aes::{ + cbc::{AesCbcIv, DecryptionError}, + Aes256Key, AesKey, +}; + +/// RustCrypto implementation of AES-CBC with PKCS7 padding +pub enum AesCbcPkcs7Padded {} +impl crypto_provider::aes::cbc::AesCbcPkcs7Padded for AesCbcPkcs7Padded { + fn encrypt(key: &Aes256Key, iv: &AesCbcIv, message: &[u8]) -> Vec<u8> { + let encryptor = cbc::Encryptor::<Aes256>::new(key.as_array().into(), iv.into()); + encryptor.encrypt_padded_vec_mut::<Pkcs7>(message) + } + + fn decrypt( + key: &Aes256Key, + iv: &AesCbcIv, + ciphertext: &[u8], + ) -> Result<Vec<u8>, DecryptionError> { + cbc::Decryptor::<Aes256>::new(key.as_array().into(), iv.into()) + .decrypt_padded_vec_mut::<Pkcs7>(ciphertext) + .map_err(|_| DecryptionError::BadPadding) + } +} + +#[cfg(test)] +mod tests { + use super::AesCbcPkcs7Padded; + use core::marker::PhantomData; + use crypto_provider::aes::cbc::testing::*; + + #[apply(aes_256_cbc_test_cases)] + fn aes_256_cbc_test(testcase: CryptoProviderTestCase<AesCbcPkcs7Padded>) { + testcase(PhantomData); + } +}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes/gcm_siv.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes/gcm_siv.rs new file mode 100644 index 0000000..d5c655b --- /dev/null +++ b/nearby/crypto/crypto_provider_rustcrypto/src/aes/gcm_siv.rs
@@ -0,0 +1,102 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use aes_gcm_siv::{AeadInPlace, Aes128GcmSiv, Aes256GcmSiv, KeyInit, Nonce}; + +use crypto_provider::aes::gcm_siv::{bytes, GcmSivError}; +use crypto_provider::aes::{Aes128Key, Aes256Key, AesKey}; + +pub struct AesGcmSiv128(Aes128GcmSiv); + +impl crypto_provider::aes::gcm_siv::AesGcmSiv for AesGcmSiv128 { + type Key = Aes128Key; + + fn new(key: &Self::Key) -> Self { + Self(Aes128GcmSiv::new(key.as_slice().into())) + } + + fn encrypt( + &self, + data: &mut bytes::BytesMut, + aad: &[u8], + nonce: &[u8], + ) -> Result<(), GcmSivError> { + self.0 + .encrypt_in_place(Nonce::from_slice(nonce), aad, data) + .map_err(|_| GcmSivError::EncryptOutBufferTooSmall) + } + + fn decrypt( + &self, + data: &mut bytes::BytesMut, + aad: &[u8], + nonce: &[u8], + ) -> Result<(), GcmSivError> { + self.0 + .decrypt_in_place(Nonce::from_slice(nonce), aad, data) + .map_err(|_| GcmSivError::DecryptTagDoesNotMatch) + } +} + +pub struct AesGcmSiv256(Aes256GcmSiv); + +impl crypto_provider::aes::gcm_siv::AesGcmSiv for AesGcmSiv256 { + type Key = Aes256Key; + + fn new(key: &Self::Key) -> Self { + Self(Aes256GcmSiv::new(key.as_slice().into())) + } + + fn encrypt( + &self, + data: &mut bytes::BytesMut, + aad: &[u8], + nonce: &[u8], + ) -> Result<(), GcmSivError> { + self.0 + .encrypt_in_place(Nonce::from_slice(nonce), aad, data) + .map_err(|_| GcmSivError::EncryptOutBufferTooSmall) + } + + fn decrypt( + &self, + data: &mut bytes::BytesMut, + aad: &[u8], + nonce: &[u8], + ) -> Result<(), GcmSivError> { + self.0 + .decrypt_in_place(Nonce::from_slice(nonce), aad, data) + .map_err(|_| GcmSivError::DecryptTagDoesNotMatch) + } +} + +#[cfg(test)] +mod tests { + use core::marker::PhantomData; + + use crypto_provider::aes::gcm_siv::testing::*; + use crypto_provider::aes::testing::*; + + use super::*; + + #[apply(aes_128_gcm_siv_test_cases)] + fn aes_gcm_siv_128_test(testcase: CryptoProviderTestCase<AesGcmSiv128>) { + testcase(PhantomData); + } + + #[apply(aes_256_gcm_siv_test_cases)] + fn aes_gcm_siv_256_test(testcase: CryptoProviderTestCase<AesGcmSiv256>) { + testcase(PhantomData); + } +}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes/mod.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes/mod.rs new file mode 100644 index 0000000..a351b81 --- /dev/null +++ b/nearby/crypto/crypto_provider_rustcrypto/src/aes/mod.rs
@@ -0,0 +1,191 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Implementation of `crypto_provider::aes` types using RustCrypto's `aes`. +#![forbid(unsafe_code)] + +use aes::cipher::{ + generic_array, BlockDecrypt as _, BlockEncrypt as _, KeyInit as _, KeyIvInit as _, + StreamCipher as _, +}; + +use crypto_provider::aes::{ + Aes, Aes128Key, Aes256Key, AesBlock, AesCipher, AesDecryptCipher, AesEncryptCipher, AesKey, +}; + +/// Module implementing AES-CBC. +#[cfg(feature = "alloc")] +pub(crate) mod cbc; +#[cfg(feature = "gcm_siv")] +pub(crate) mod gcm_siv; + +/// Rust crypto implementation of AES-128 +pub struct Aes128; +impl Aes for Aes128 { + type Key = Aes128Key; + type EncryptCipher = Aes128Cipher; + type DecryptCipher = Aes128Cipher; +} + +/// Rust crypto implementation of AES-256 +pub struct Aes256; +impl Aes for Aes256 { + type Key = Aes256Key; + type EncryptCipher = Aes256Cipher; + type DecryptCipher = Aes256Cipher; +} + +/// A Rust Crypto AES-128 cipher used for encryption and decryption +pub struct Aes128Cipher(aes::Aes128); + +impl AesCipher for Aes128Cipher { + type Key = Aes128Key; + + fn new(key: &Self::Key) -> Self { + Self(aes::Aes128::new(key.as_array().into())) + } +} + +impl AesEncryptCipher for Aes128Cipher { + fn encrypt(&self, block: &mut AesBlock) { + self.0 + .encrypt_block(generic_array::GenericArray::from_mut_slice( + block.as_mut_slice(), + )); + } +} + +impl AesDecryptCipher for Aes128Cipher { + fn decrypt(&self, block: &mut AesBlock) { + self.0 + .decrypt_block(generic_array::GenericArray::from_mut_slice( + block.as_mut_slice(), + )) + } +} + +/// A Rust Crypto AES-256 cipher used for encryption and decryption +pub struct Aes256Cipher(aes::Aes256); + +impl AesCipher for Aes256Cipher { + type Key = Aes256Key; + + fn new(key: &Self::Key) -> Self { + Self(aes::Aes256::new(key.as_array().into())) + } +} + +impl AesEncryptCipher for Aes256Cipher { + fn encrypt(&self, block: &mut AesBlock) { + self.0 + .encrypt_block(generic_array::GenericArray::from_mut_slice( + block.as_mut_slice(), + )); + } +} + +impl AesDecryptCipher for Aes256Cipher { + fn decrypt(&self, block: &mut AesBlock) { + self.0 + .decrypt_block(generic_array::GenericArray::from_mut_slice( + block.as_mut_slice(), + )) + } +} + +/// RustCrypto implementation of AES-CTR 128. +pub struct AesCtr128 { + cipher: ctr::Ctr128BE<aes::Aes128>, +} + +impl crypto_provider::aes::ctr::AesCtr for AesCtr128 { + type Key = crypto_provider::aes::Aes128Key; + + fn new(key: &Self::Key, iv: [u8; 16]) -> Self { + Self { + cipher: ctr::Ctr128BE::new(key.as_array().into(), &iv.into()), + } + } + + fn encrypt(&mut self, data: &mut [u8]) { + self.cipher.apply_keystream(data); + } + + fn decrypt(&mut self, data: &mut [u8]) { + self.cipher.apply_keystream(data); + } +} + +/// RustCrypto implementation of AES-CTR 256. +pub struct AesCtr256 { + cipher: ctr::Ctr128BE<aes::Aes256>, +} + +impl crypto_provider::aes::ctr::AesCtr for AesCtr256 { + type Key = crypto_provider::aes::Aes256Key; + + fn new(key: &Self::Key, iv: [u8; 16]) -> Self { + Self { + cipher: ctr::Ctr128BE::new(key.as_array().into(), &iv.into()), + } + } + + fn encrypt(&mut self, data: &mut [u8]) { + self.cipher.apply_keystream(data); + } + + fn decrypt(&mut self, data: &mut [u8]) { + self.cipher.apply_keystream(data); + } +} + +#[cfg(test)] +mod tests { + use core::marker::PhantomData; + + use crypto_provider::aes::ctr::testing::*; + use crypto_provider::aes::testing::*; + + use super::*; + + #[apply(aes_128_ctr_test_cases)] + fn aes_128_ctr_test(testcase: CryptoProviderTestCase<AesCtr128>) { + testcase(PhantomData); + } + + #[apply(aes_256_ctr_test_cases)] + fn aes_256_ctr_test(testcase: CryptoProviderTestCase<AesCtr256>) { + testcase(PhantomData); + } + + #[apply(aes_128_encrypt_test_cases)] + fn aes_128_encrypt_test(testcase: CryptoProviderTestCase<Aes128Cipher>) { + testcase(PhantomData); + } + + #[apply(aes_128_decrypt_test_cases)] + fn aes_128_decrypt_test(testcase: CryptoProviderTestCase<Aes128Cipher>) { + testcase(PhantomData); + } + + #[apply(aes_256_encrypt_test_cases)] + fn aes_256_encrypt_test(testcase: CryptoProviderTestCase<Aes256Cipher>) { + testcase(PhantomData); + } + + #[apply(aes_256_decrypt_test_cases)] + fn aes_256_decrypt_test(testcase: CryptoProviderTestCase<Aes256Cipher>) { + testcase(PhantomData); + } +}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs b/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs new file mode 100644 index 0000000..874aa82 --- /dev/null +++ b/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs
@@ -0,0 +1,122 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crypto_provider::ed25519::{ + InvalidBytes, InvalidSignature, Signature as _, SignatureError, KEY_LENGTH, KEY_PAIR_LENGTH, + SIGNATURE_LENGTH, +}; +use ed25519_dalek::Signer; + +pub struct Ed25519; +impl crypto_provider::ed25519::Ed25519Provider for Ed25519 { + type KeyPair = KeyPair; + type PublicKey = PublicKey; + type Signature = Signature; +} + +pub struct KeyPair(ed25519_dalek::Keypair); +impl crypto_provider::ed25519::KeyPair for KeyPair { + type PublicKey = PublicKey; + type Signature = Signature; + + fn to_bytes(&self) -> [u8; KEY_PAIR_LENGTH] { + self.0.to_bytes() + } + + fn from_bytes(bytes: [u8; KEY_PAIR_LENGTH]) -> Result<Self, InvalidBytes> { + ed25519_dalek::Keypair::from_bytes(&bytes) + .map(Self) + .map_err(|_| InvalidBytes) + } + + #[allow(clippy::expect_used)] + fn sign(&self, msg: &[u8]) -> Self::Signature { + Self::Signature::from_bytes(&self.0.sign(msg).to_bytes()) + .expect("a signature will always produce valid bytes for creating a Signature") + } + + //TODO: allow providing a crypto rng and make it a no-op for openssl if the need arises to + // improve perf of keypair generation + #[cfg(feature = "std")] + fn generate() -> Self { + let mut csprng = rand::rngs::ThreadRng::default(); + Self(ed25519_dalek::Keypair::generate( + &mut rand_core_05_adapter::RandWrapper::from(&mut csprng), + )) + } + + fn public(&self) -> Self::PublicKey { + PublicKey(self.0.public) + } +} + +pub struct Signature(ed25519_dalek::Signature); +impl crypto_provider::ed25519::Signature for Signature { + fn from_bytes(bytes: &[u8]) -> Result<Self, InvalidSignature> { + if bytes.len() != SIGNATURE_LENGTH { + return Err(InvalidSignature); + } + ed25519_dalek::Signature::from_bytes(bytes) + .map(Self) + .map_err(|_| InvalidSignature) + } + + fn to_bytes(&self) -> [u8; SIGNATURE_LENGTH] { + self.0.to_bytes() + } +} + +pub struct PublicKey(ed25519_dalek::PublicKey); +impl crypto_provider::ed25519::PublicKey for PublicKey { + type Signature = Signature; + + fn from_bytes(bytes: [u8; KEY_LENGTH]) -> Result<Self, InvalidBytes> + where + Self: Sized, + { + ed25519_dalek::PublicKey::from_bytes(&bytes) + .map(PublicKey) + .map_err(|_| InvalidBytes) + } + + fn to_bytes(&self) -> [u8; KEY_LENGTH] { + self.0.to_bytes() + } + + fn verify_strict( + &self, + message: &[u8], + signature: &Self::Signature, + ) -> Result<(), SignatureError> { + self.0 + .verify_strict(message, &signature.0) + .map_err(|_| SignatureError) + } +} + +#[cfg(test)] +mod tests { + use crate::ed25519::Ed25519; + use crypto_provider::ed25519::testing::{run_rfc_test_vectors, run_wycheproof_test_vectors}; + + #[test] + fn wycheproof_test_ed25519_rustcrypto() { + run_wycheproof_test_vectors::<Ed25519>() + } + + #[test] + fn rfc_test_ed25519_rustcrypto() { + run_rfc_test_vectors::<Ed25519>() + } +}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_rc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_rc.rs new file mode 100644 index 0000000..d6ca687 --- /dev/null +++ b/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_rc.rs
@@ -0,0 +1,74 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crypto_provider::hkdf::InvalidLength; +use hmac::digest::block_buffer::Eager; +use hmac::digest::consts::U256; +use hmac::digest::core_api::{ + BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore, UpdateCore, +}; +use hmac::digest::typenum::{IsLess, Le, NonZero}; +use hmac::digest::{HashMarker, OutputSizeUser}; + +/// RustCrypto based hkdf implementation +#[derive(Clone)] +pub struct Hkdf<D> +where + D: OutputSizeUser, + D: CoreProxy, + D::Core: HashMarker + + UpdateCore + + FixedOutputCore + + BufferKindUser<BufferKind = Eager> + + Default + + Clone, + <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>, + Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero, +{ + hkdf_impl: hkdf::Hkdf<D>, +} + +impl<D> crypto_provider::hkdf::Hkdf for Hkdf<D> +where + D: OutputSizeUser, + D: CoreProxy, + D::Core: HashMarker + + UpdateCore + + FixedOutputCore + + BufferKindUser<BufferKind = Eager> + + Default + + Clone, + <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>, + Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero, +{ + fn new(salt: Option<&[u8]>, ikm: &[u8]) -> Self { + Hkdf { + hkdf_impl: hkdf::Hkdf::new(salt, ikm), + } + } + + fn expand_multi_info( + &self, + info_components: &[&[u8]], + okm: &mut [u8], + ) -> Result<(), InvalidLength> { + self.hkdf_impl + .expand_multi_info(info_components, okm) + .map_err(|_| InvalidLength) + } + + fn expand(&self, info: &[u8], okm: &mut [u8]) -> Result<(), InvalidLength> { + self.hkdf_impl.expand(info, okm).map_err(|_| InvalidLength) + } +}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/hmac_rc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/hmac_rc.rs new file mode 100644 index 0000000..e31b815 --- /dev/null +++ b/nearby/crypto/crypto_provider_rustcrypto/src/hmac_rc.rs
@@ -0,0 +1,114 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crypto_provider::hmac::{InvalidLength, MacError}; +use hmac::digest::block_buffer::Eager; +use hmac::digest::consts::U256; +use hmac::digest::core_api::{ + BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore, UpdateCore, +}; +use hmac::digest::typenum::{IsLess, Le, NonZero}; +use hmac::digest::{HashMarker, OutputSizeUser}; +use hmac::Mac; + +/// RustCrypto based hmac implementation +pub struct Hmac<D> +where + D: OutputSizeUser, + D: CoreProxy, + D::Core: HashMarker + + UpdateCore + + FixedOutputCore + + BufferKindUser<BufferKind = Eager> + + Default + + Clone, + <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>, + Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero, +{ + hmac_impl: hmac::Hmac<D>, +} + +impl crypto_provider::hmac::Hmac<32> for Hmac<sha2::Sha256> { + #[allow(clippy::expect_used)] + fn new_from_key(key: [u8; 32]) -> Self { + hmac::Hmac::new_from_slice(&key) + .map(|hmac| Self { hmac_impl: hmac }) + .expect("length will always be valid because input key is of fixed size") + } + + fn new_from_slice(key: &[u8]) -> Result<Self, InvalidLength> { + hmac::Hmac::new_from_slice(key) + .map(|hmac| Self { hmac_impl: hmac }) + .map_err(|_| InvalidLength) + } + + fn update(&mut self, data: &[u8]) { + self.hmac_impl.update(data); + } + + fn finalize(self) -> [u8; 32] { + self.hmac_impl.finalize().into_bytes().into() + } + + fn verify_slice(self, tag: &[u8]) -> Result<(), MacError> { + self.hmac_impl.verify_slice(tag).map_err(|_| MacError) + } + + fn verify(self, tag: [u8; 32]) -> Result<(), MacError> { + self.hmac_impl.verify(&tag.into()).map_err(|_| MacError) + } + + fn verify_truncated_left(self, tag: &[u8]) -> Result<(), MacError> { + self.hmac_impl + .verify_truncated_left(tag) + .map_err(|_| MacError) + } +} + +impl crypto_provider::hmac::Hmac<64> for Hmac<sha2::Sha512> { + #[allow(clippy::expect_used)] + fn new_from_key(key: [u8; 64]) -> Self { + hmac::Hmac::new_from_slice(&key) + .map(|hmac| Self { hmac_impl: hmac }) + .expect("length will always be valid because input key is of fixed size") + } + + fn new_from_slice(key: &[u8]) -> Result<Self, InvalidLength> { + hmac::Hmac::new_from_slice(key) + .map(|hmac| Self { hmac_impl: hmac }) + .map_err(|_| InvalidLength) + } + + fn update(&mut self, data: &[u8]) { + self.hmac_impl.update(data); + } + + fn finalize(self) -> [u8; 64] { + self.hmac_impl.finalize().into_bytes().into() + } + + fn verify_slice(self, tag: &[u8]) -> Result<(), MacError> { + self.hmac_impl.verify_slice(tag).map_err(|_| MacError) + } + + fn verify(self, tag: [u8; 64]) -> Result<(), MacError> { + self.hmac_impl.verify(&tag.into()).map_err(|_| MacError) + } + + fn verify_truncated_left(self, tag: &[u8]) -> Result<(), MacError> { + self.hmac_impl + .verify_truncated_left(tag) + .map_err(|_| MacError) + } +}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs b/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs new file mode 100644 index 0000000..9807f81 --- /dev/null +++ b/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs
@@ -0,0 +1,127 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#![no_std] +#![forbid(unsafe_code)] +#![deny( + missing_docs, + clippy::indexing_slicing, + clippy::unwrap_used, + clippy::panic, + clippy::expect_used +)] + +//! Crate which provides impls for CryptoProvider backed by RustCrypto crates + +/// Contains the RustCrypto backed AES impl for CryptoProvider +pub mod aes; +/// Contains the RustCrypto backed impl for ed25519 key generation, signing, and verification +mod ed25519; +/// Contains the RustCrypto backed hkdf impl for CryptoProvider +mod hkdf_rc; +/// Contains the RustCrypto backed hmac impl for CryptoProvider +mod hmac_rc; +/// Contains the RustCrypto backed P256 impl for CryptoProvider +mod p256; +/// Contains the RustCrypto backed SHA2 impl for CryptoProvider +mod sha2_rc; +/// Contains the RustCrypto backed X25519 impl for CryptoProvider +mod x25519; + +pub use hkdf; +pub use hmac; + +use cfg_if::cfg_if; +use core::{fmt::Debug, marker::PhantomData}; +use rand::{RngCore, SeedableRng}; +use rand_core::CryptoRng; +use subtle::ConstantTimeEq; + +cfg_if! { + if #[cfg(feature = "std")] { + /// Providing a type alias for compatibility with existing usage of RustCrypto + /// by default we use StdRng for the underlying csprng + pub type RustCrypto = RustCryptoImpl<rand::rngs::StdRng>; + } else { + /// A no_std compatible implementation of CryptoProvider backed by RustCrypto crates + pub type RustCrypto = RustCryptoImpl<rand_chacha::ChaCha20Rng>; + } +} + +/// The the RustCrypto backed struct which implements CryptoProvider +#[derive(Default, Clone, Debug, PartialEq, Eq)] +pub struct RustCryptoImpl<R: CryptoRng + SeedableRng + RngCore> { + _marker: PhantomData<R>, +} + +impl<R: CryptoRng + SeedableRng + RngCore> RustCryptoImpl<R> { + /// + pub fn new() -> Self { + Self { + _marker: Default::default(), + } + } +} + +impl<R: CryptoRng + SeedableRng + RngCore + Eq + PartialEq + Debug + Clone + Send> + crypto_provider::CryptoProvider for RustCryptoImpl<R> +{ + type HkdfSha256 = hkdf_rc::Hkdf<sha2::Sha256>; + type HmacSha256 = hmac_rc::Hmac<sha2::Sha256>; + type HkdfSha512 = hkdf_rc::Hkdf<sha2::Sha512>; + type HmacSha512 = hmac_rc::Hmac<sha2::Sha512>; + #[cfg(feature = "alloc")] + type AesCbcPkcs7Padded = aes::cbc::AesCbcPkcs7Padded; + type X25519 = x25519::X25519Ecdh<R>; + type P256 = p256::P256Ecdh<R>; + type Sha256 = sha2_rc::RustCryptoSha256; + type Sha512 = sha2_rc::RustCryptoSha512; + type Aes128 = aes::Aes128; + type Aes256 = aes::Aes256; + type AesCtr128 = aes::AesCtr128; + type AesCtr256 = aes::AesCtr256; + type Ed25519 = ed25519::Ed25519; + type CryptoRng = RcRng<R>; + + fn constant_time_eq(a: &[u8], b: &[u8]) -> bool { + a.ct_eq(b).into() + } +} + +/// A RustCrypto wrapper for RNG +pub struct RcRng<R>(R); + +impl<R: rand_core::CryptoRng + RngCore + SeedableRng> crypto_provider::CryptoRng for RcRng<R> { + fn new() -> Self { + Self(R::from_entropy()) + } + + fn next_u64(&mut self) -> u64 { + self.0.next_u64() + } +} + +#[cfg(test)] +mod testing; + +#[cfg(test)] +mod tests { + use crate::RustCrypto; + use core::marker::PhantomData; + use crypto_provider::sha2::testing::*; + + #[apply(sha2_test_cases)] + fn sha2_tests(testcase: CryptoProviderTestCase<RustCrypto>) { + testcase(PhantomData::<RustCrypto>); + } +}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/p256.rs b/nearby/crypto/crypto_provider_rustcrypto/src/p256.rs new file mode 100644 index 0000000..a50bec8 --- /dev/null +++ b/nearby/crypto/crypto_provider_rustcrypto/src/p256.rs
@@ -0,0 +1,149 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate alloc; + +use crate::RcRng; +use alloc::vec::Vec; +use core::marker::PhantomData; +use crypto_provider::{ + elliptic_curve::{EcdhProvider, EphemeralSecret}, + p256::P256, +}; +use p256::{ + elliptic_curve, + elliptic_curve::{ + generic_array::GenericArray, + sec1::{FromEncodedPoint, ToEncodedPoint}, + }, +}; +use rand::{RngCore, SeedableRng}; +use rand_core::CryptoRng; + +/// Implementation of NIST-P256 using RustCrypto crates. +pub struct P256Ecdh<R> { + _marker: PhantomData<R>, +} + +impl<R: CryptoRng + SeedableRng + RngCore + Send> EcdhProvider<P256> for P256Ecdh<R> { + type PublicKey = P256PublicKey; + type EphemeralSecret = P256EphemeralSecret<R>; + type SharedSecret = [u8; 32]; +} + +/// A NIST-P256 public key. +#[derive(Debug, PartialEq, Eq)] +pub struct P256PublicKey(p256::PublicKey); +impl crypto_provider::p256::P256PublicKey for P256PublicKey { + type Error = elliptic_curve::Error; + + fn from_sec1_bytes(bytes: &[u8]) -> Result<Self, Self::Error> { + p256::PublicKey::from_sec1_bytes(bytes).map(Self) + } + + fn to_sec1_bytes(&self) -> Vec<u8> { + self.0.to_encoded_point(true).as_bytes().to_vec() + } + + #[allow(clippy::expect_used)] + fn to_affine_coordinates(&self) -> Result<([u8; 32], [u8; 32]), Self::Error> { + let p256_key = self.0.to_encoded_point(false); + let x: &[u8; 32] = p256_key + .x() + .expect("Generated key should not be on identity point") + .as_ref(); + let y: &[u8; 32] = p256_key + .y() + .expect("Generated key should not be on identity point") + .as_ref(); + Ok((*x, *y)) + } + fn from_affine_coordinates(x: &[u8; 32], y: &[u8; 32]) -> Result<Self, Self::Error> { + let key_option: Option<p256::PublicKey> = + p256::PublicKey::from_encoded_point(&p256::EncodedPoint::from_affine_coordinates( + &GenericArray::clone_from_slice(x), + &GenericArray::clone_from_slice(y), + false, + )) + .into(); + key_option.map(Self).ok_or(elliptic_curve::Error) + } +} + +/// Ephemeral secrect for use in a P256 Diffie-Hellman +pub struct P256EphemeralSecret<R: CryptoRng + SeedableRng + RngCore> { + secret: p256::ecdh::EphemeralSecret, + _marker: PhantomData<R>, +} + +impl<R: CryptoRng + SeedableRng + RngCore + Send> EphemeralSecret<P256> for P256EphemeralSecret<R> { + type Impl = P256Ecdh<R>; + type Error = sec1::Error; + type Rng = RcRng<R>; + + fn generate_random(rng: &mut Self::Rng) -> Self { + Self { + secret: p256::ecdh::EphemeralSecret::random(&mut rng.0), + _marker: Default::default(), + } + } + + fn public_key_bytes(&self) -> Vec<u8> { + self.secret + .public_key() + .to_encoded_point(false) + .as_bytes() + .into() + } + + fn diffie_hellman( + self, + other_pub: &P256PublicKey, + ) -> Result<<Self::Impl as EcdhProvider<P256>>::SharedSecret, Self::Error> { + let shared_secret = p256::ecdh::EphemeralSecret::diffie_hellman(&self.secret, &other_pub.0); + let bytes: <Self::Impl as EcdhProvider<P256>>::SharedSecret = + (*shared_secret.raw_secret_bytes()).into(); + Ok(bytes) + } +} + +#[cfg(test)] +impl<R: CryptoRng + SeedableRng + RngCore + Send> + crypto_provider::elliptic_curve::EphemeralSecretForTesting<P256> for P256EphemeralSecret<R> +{ + fn from_private_components( + private_bytes: &[u8; 32], + _public_key: &P256PublicKey, + ) -> Result<Self, Self::Error> { + Ok(Self { + secret: p256::ecdh::EphemeralSecret::random(&mut crate::testing::MockCryptoRng { + values: private_bytes.iter(), + }), + _marker: Default::default(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::P256Ecdh; + use core::marker::PhantomData; + use crypto_provider::p256::testing::*; + use rand::rngs::StdRng; + + #[apply(p256_test_cases)] + fn p256_tests(testcase: CryptoProviderTestCase<P256Ecdh<StdRng>>) { + testcase(PhantomData::<P256Ecdh<StdRng>>) + } +}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/sha2_rc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/sha2_rc.rs new file mode 100644 index 0000000..977eb83 --- /dev/null +++ b/nearby/crypto/crypto_provider_rustcrypto/src/sha2_rc.rs
@@ -0,0 +1,37 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use sha2::Digest; + +/// RustCrypto implementation for SHA256 +pub struct RustCryptoSha256; + +impl crypto_provider::sha2::Sha256 for RustCryptoSha256 { + fn sha256(input: &[u8]) -> [u8; 32] { + let mut digest = sha2::Sha256::new(); + digest.update(input); + digest.finalize().into() + } +} + +/// RustCrypto implementation for SHA512 +pub struct RustCryptoSha512; + +impl crypto_provider::sha2::Sha512 for RustCryptoSha512 { + fn sha512(input: &[u8]) -> [u8; 64] { + let mut digest = sha2::Sha512::new(); + digest.update(input); + digest.finalize().into() + } +}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/testing.rs b/nearby/crypto/crypto_provider_rustcrypto/src/testing.rs new file mode 100644 index 0000000..f606492 --- /dev/null +++ b/nearby/crypto/crypto_provider_rustcrypto/src/testing.rs
@@ -0,0 +1,51 @@ +#![allow(clippy::expect_used)] +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(test)] +/// A mock implementation of a CryptoRng that returns the given bytes in `values` in +/// sequence. +pub(crate) struct MockCryptoRng<'a, I: Iterator<Item = &'a u8>> { + pub(crate) values: I, +} + +impl<'a, I: Iterator<Item = &'a u8>> rand::CryptoRng for MockCryptoRng<'a, I> {} + +impl<'a, I: Iterator<Item = &'a u8>> rand::RngCore for MockCryptoRng<'a, I> { + fn fill_bytes(&mut self, dest: &mut [u8]) { + for i in dest { + *i = *self + .values + .next() + .expect("Expecting more data in MockCryptoRng input"); + } + } + + fn next_u32(&mut self) -> u32 { + let mut bytes = [0; 4]; + self.fill_bytes(&mut bytes); + u32::from_le_bytes(bytes) + } + + fn next_u64(&mut self) -> u64 { + let mut bytes = [0; 8]; + self.fill_bytes(&mut bytes); + u64::from_le_bytes(bytes) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { + self.fill_bytes(dest); + Ok(()) + } +}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs b/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs new file mode 100644 index 0000000..a184d44 --- /dev/null +++ b/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs
@@ -0,0 +1,122 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate alloc; + +use crate::RcRng; +use alloc::vec::Vec; +use core::marker::PhantomData; +use crypto_provider::elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey}; +use crypto_provider::x25519::X25519; +use rand::RngCore; +use rand_core::{CryptoRng, SeedableRng}; + +/// The RustCrypto implementation of X25519 ECDH. +pub struct X25519Ecdh<R> { + _marker: PhantomData<R>, +} + +impl<R: CryptoRng + RngCore + SeedableRng + Send> EcdhProvider<X25519> for X25519Ecdh<R> { + type PublicKey = X25519PublicKey; + type EphemeralSecret = X25519EphemeralSecret<R>; + type SharedSecret = [u8; 32]; +} + +/// A X25519 ephemeral secret used for Diffie-Hellman. +pub struct X25519EphemeralSecret<R: CryptoRng + RngCore + SeedableRng> { + secret: x25519_dalek::EphemeralSecret, + marker: PhantomData<R>, +} + +impl<R: CryptoRng + RngCore + SeedableRng + Send> EphemeralSecret<X25519> + for X25519EphemeralSecret<R> +{ + type Impl = X25519Ecdh<R>; + type Error = Error; + type Rng = RcRng<R>; + + fn generate_random(rng: &mut Self::Rng) -> Self { + Self { + secret: x25519_dalek::EphemeralSecret::new(&mut rng.0), + marker: Default::default(), + } + } + + fn public_key_bytes(&self) -> Vec<u8> { + let pubkey: x25519_dalek::PublicKey = (&self.secret).into(); + pubkey.to_bytes().into() + } + + fn diffie_hellman( + self, + other_pub: &<X25519Ecdh<R> as EcdhProvider<X25519>>::PublicKey, + ) -> Result<<X25519Ecdh<R> as EcdhProvider<X25519>>::SharedSecret, Self::Error> { + Ok(x25519_dalek::EphemeralSecret::diffie_hellman(self.secret, &other_pub.0).to_bytes()) + } +} + +#[cfg(test)] +impl<R: CryptoRng + RngCore + SeedableRng + Send> + crypto_provider::elliptic_curve::EphemeralSecretForTesting<X25519> + for X25519EphemeralSecret<R> +{ + fn from_private_components( + private_bytes: &[u8; 32], + _public_key: &X25519PublicKey, + ) -> Result<Self, Self::Error> { + Ok(Self { + secret: x25519_dalek::EphemeralSecret::new(&mut crate::testing::MockCryptoRng { + values: private_bytes.iter(), + }), + marker: Default::default(), + }) + } +} + +/// A X25519 public key. +#[derive(Debug, PartialEq, Eq)] +pub struct X25519PublicKey(x25519_dalek::PublicKey); + +impl PublicKey<X25519> for X25519PublicKey { + type Error = Error; + + fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> { + let byte_sized: [u8; 32] = bytes.try_into().map_err(|_| Error::WrongSize)?; + Ok(Self(byte_sized.into())) + } + + fn to_bytes(&self) -> Vec<u8> { + self.0.as_bytes().to_vec() + } +} + +/// Error type for the RustCrypto implementation of x25519. +#[derive(Debug)] +pub enum Error { + /// Unexpected size for the given input. + WrongSize, +} + +#[cfg(test)] +mod tests { + use super::X25519Ecdh; + use core::marker::PhantomData; + use crypto_provider::x25519::testing::*; + use rand::rngs::StdRng; + + #[apply(x25519_test_cases)] + fn x25519_tests(testcase: CryptoProviderTestCase<X25519Ecdh<StdRng>>) { + testcase(PhantomData::<X25519Ecdh<StdRng>>) + } +}