aboutsummaryrefslogtreecommitdiff
path: root/nearby/crypto/crypto_provider/src/x25519.rs
diff options
context:
space:
mode:
Diffstat (limited to 'nearby/crypto/crypto_provider/src/x25519.rs')
-rw-r--r--nearby/crypto/crypto_provider/src/x25519.rs168
1 files changed, 168 insertions, 0 deletions
diff --git a/nearby/crypto/crypto_provider/src/x25519.rs b/nearby/crypto/crypto_provider/src/x25519.rs
new file mode 100644
index 0000000..641dbbe
--- /dev/null
+++ b/nearby/crypto/crypto_provider/src/x25519.rs
@@ -0,0 +1,168 @@
+// 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 crate::elliptic_curve::Curve;
+
+/// Marker type for X25519 implementation. This is used by EcdhProvider as its type parameter.
+pub enum X25519 {}
+impl Curve for X25519 {}
+
+/// Utilities for testing. Implementations can use the test cases and functions provided to test
+/// their implementation.
+#[cfg(feature = "testing")]
+pub mod testing {
+ use super::X25519;
+ pub use crate::testing::prelude::*;
+ use crate::{
+ elliptic_curve::{EcdhProvider, EphemeralSecret, EphemeralSecretForTesting, PublicKey},
+ testing::TestError,
+ };
+ use core::marker::PhantomData;
+ use hex_literal::hex;
+ use rstest_reuse::template;
+
+ /// An ECDH provider that provides associated types for testing purposes. This can be mostly
+ /// considered "aliases" for the otherwise long fully-qualified associated types.
+ pub trait EcdhProviderForX25519Test {
+ /// The ECDH Provider that is "wrapped" by this type.
+ type EcdhProvider: EcdhProvider<
+ X25519,
+ PublicKey = <Self as EcdhProviderForX25519Test>::PublicKey,
+ EphemeralSecret = <Self as EcdhProviderForX25519Test>::EphemeralSecret,
+ SharedSecret = <Self as EcdhProviderForX25519Test>::SharedSecret,
+ >;
+ /// The public key type.
+ type PublicKey: PublicKey<X25519>;
+ /// The ephemeral secret type.
+ type EphemeralSecret: EphemeralSecretForTesting<X25519, Impl = Self::EcdhProvider>;
+ /// The shared secret type.
+ type SharedSecret: Into<[u8; 32]>;
+ }
+
+ impl<E> EcdhProviderForX25519Test for E
+ where
+ E: EcdhProvider<X25519>,
+ E::PublicKey: PublicKey<X25519>,
+ E::EphemeralSecret: EphemeralSecretForTesting<X25519>,
+ {
+ type EcdhProvider = E;
+ type PublicKey = E::PublicKey;
+ type EphemeralSecret = E::EphemeralSecret;
+ type SharedSecret = E::SharedSecret;
+ }
+
+ /// Test for `PublicKey<X25519>::to_bytes`
+ pub fn x25519_to_bytes_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
+ let public_key_bytes =
+ hex!("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829");
+ let public_key = E::PublicKey::from_bytes(&public_key_bytes).unwrap();
+ assert_eq!(public_key_bytes.to_vec(), public_key.to_bytes());
+ }
+
+ /// Random test for `PublicKey<X25519>::to_bytes`
+ pub fn x25519_to_bytes_random_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
+ let mut rng = rand::thread_rng();
+ for _ in 1..100 {
+ let public_key_bytes = E::EphemeralSecret::generate_random(&mut rng).public_key_bytes();
+ let public_key = E::PublicKey::from_bytes(&public_key_bytes).unwrap();
+ assert_eq!(
+ E::PublicKey::from_bytes(&public_key.to_bytes()).unwrap(),
+ public_key,
+ "from_bytes should return the same key for `{public_key_bytes:?}`",
+ );
+ }
+ }
+
+ /// Test for X25519 Diffie-Hellman key exchange.
+ pub fn x25519_ecdh_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
+ // From wycheproof ecdh_secx25519r1_ecpoint_test.json, tcId 1
+ // http://google3/third_party/wycheproof/testvectors/ecdh_secx25519r1_ecpoint_test.json;l=22;rcl=375894991
+ // sec1 public key manually extracted from the ASN encoded test data
+ let public_key = hex!("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829");
+ let private = hex!("c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475");
+ let expected_shared_secret =
+ hex!("436a2c040cf45fea9b29a0cb81b1f41458f863d0d61b453d0a982720d6d61320");
+ let result = x25519_ecdh_test_impl::<E>(&public_key, &private).unwrap();
+ assert_eq!(expected_shared_secret, result.into());
+ }
+
+ fn x25519_ecdh_test_impl<E: EcdhProviderForX25519Test>(
+ public_key: &[u8],
+ private: &[u8; 32],
+ ) -> Result<E::SharedSecret, TestError> {
+ let public_key = E::PublicKey::from_bytes(public_key).map_err(TestError::new)?;
+ let ephemeral_secret = E::EphemeralSecret::from_private_components(private, &public_key)
+ .map_err(TestError::new)?;
+ ephemeral_secret
+ .diffie_hellman(&public_key)
+ .map_err(TestError::new)
+ }
+
+ /// Wycheproof test for X25519 Diffie-Hellman.
+ pub fn wycheproof_x25519_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
+ // Test cases from https://github.com/randombit/wycheproof-rs/blob/master/src/data/x25519_test.json
+ let test_set = wycheproof::xdh::TestSet::load(wycheproof::xdh::TestName::X25519).unwrap();
+ for test_group in test_set.test_groups {
+ for test in test_group.tests {
+ let result = x25519_ecdh_test_impl::<E>(
+ &test.public_key,
+ &test
+ .private_key
+ .try_into()
+ .expect("Private keys should be 32 bytes long"),
+ );
+ match test.result {
+ wycheproof::TestResult::Valid => {
+ let shared_secret =
+ result.unwrap_or_else(|_| panic!("Test {} should succeed", test.tc_id));
+ assert_eq!(&test.shared_secret, &shared_secret.into());
+ }
+ wycheproof::TestResult::Invalid => {
+ result
+ .err()
+ .unwrap_or_else(|| panic!("Test {} should fail", test.tc_id));
+ }
+ wycheproof::TestResult::Acceptable => {
+ if let Ok(shared_secret) = result {
+ assert_eq!(test.shared_secret, shared_secret.into());
+ }
+ // Test passes if `result` is an error because this test is "acceptable"
+ }
+ }
+ }
+ }
+ }
+
+ /// Generates the test cases to validate the x25519 implementation.
+ /// For example, to test `MyCryptoProvider`:
+ ///
+ /// ```
+ /// use crypto_provider::x25519::testing::*;
+ ///
+ /// mod tests {
+ /// #[apply(x25519_test_cases)]
+ /// fn x25519_tests(testcase: CryptoProviderTestCase<MyCryptoProvider>) {
+ /// testcase(PhantomData::<MyCryptoProvider>);
+ /// }
+ /// }
+ /// ```
+ #[template]
+ #[export]
+ #[rstest]
+ #[case::x25519_to_bytes(x25519_to_bytes_test)]
+ #[case::x25519_to_bytes_random(x25519_to_bytes_random_test)]
+ #[case::x25519_ecdh(x25519_ecdh_test)]
+ #[case::wycheproof_x25519(wycheproof_x25519_test)]
+ fn x25519_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {}
+}