• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 use crate::elliptic_curve::EphemeralSecretForTesting;
16 pub use crate::prelude::*;
17 use crate::TestError;
18 use core::marker::PhantomData;
19 use crypto_provider::x25519::X25519;
20 use crypto_provider::{
21     elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey},
22     CryptoRng,
23 };
24 use hex_literal::hex;
25 use rstest_reuse::template;
26 
27 /// An ECDH provider that provides associated types for testing purposes. This can be mostly
28 /// considered "aliases" for the otherwise long fully-qualified associated types.
29 pub trait EcdhProviderForX25519Test {
30     /// The ECDH Provider that is "wrapped" by this type.
31     type EcdhProvider: EcdhProvider<
32         X25519,
33         PublicKey = <Self as EcdhProviderForX25519Test>::PublicKey,
34         EphemeralSecret = <Self as EcdhProviderForX25519Test>::EphemeralSecret,
35         SharedSecret = <Self as EcdhProviderForX25519Test>::SharedSecret,
36     >;
37     /// The public key type.
38     type PublicKey: PublicKey<X25519>;
39     /// The ephemeral secret type.
40     type EphemeralSecret: EphemeralSecretForTesting<X25519, Impl = Self::EcdhProvider>;
41     /// The shared secret type.
42     type SharedSecret: Into<[u8; 32]>;
43 }
44 
45 impl<E> EcdhProviderForX25519Test for E
46 where
47     E: EcdhProvider<X25519>,
48     E::PublicKey: PublicKey<X25519>,
49     E::EphemeralSecret: EphemeralSecretForTesting<X25519>,
50 {
51     type EcdhProvider = E;
52     type PublicKey = E::PublicKey;
53     type EphemeralSecret = E::EphemeralSecret;
54     type SharedSecret = E::SharedSecret;
55 }
56 
57 /// Test for `PublicKey<X25519>::to_bytes`
x25519_to_bytes_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>)58 pub fn x25519_to_bytes_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
59     let public_key_bytes = hex!("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829");
60     let public_key = E::PublicKey::from_bytes(&public_key_bytes).unwrap();
61     assert_eq!(public_key_bytes.to_vec(), public_key.to_bytes().as_ref());
62 }
63 
64 /// Random test for `PublicKey<X25519>::to_bytes`
x25519_to_bytes_random_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>)65 pub fn x25519_to_bytes_random_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
66     for _ in 1..100 {
67         let public_key_bytes =
68             E::EphemeralSecret::generate_random(&mut <E::EphemeralSecret as EphemeralSecret<
69                 X25519,
70             >>::Rng::new())
71             .public_key_bytes();
72         let public_key = E::PublicKey::from_bytes(public_key_bytes.as_ref()).unwrap();
73         assert_eq!(
74             E::PublicKey::from_bytes(public_key.to_bytes().as_ref()).unwrap(),
75             public_key,
76             "from_bytes should return the same key for `{public_key_bytes:?}`",
77         );
78     }
79 }
80 
81 /// Test for X25519 Diffie-Hellman key exchange.
x25519_ecdh_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>)82 pub fn x25519_ecdh_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
83     // From wycheproof ecdh_secx25519r1_ecpoint_test.json, tcId 1
84     // https://github.com/google/wycheproof/blob/b063b4a/testvectors/x25519_test.json#L23
85     // sec1 public key manually extracted from the ASN encoded test data
86     let public_key = hex!("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829");
87     let private = hex!("c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475");
88     let expected_shared_secret =
89         hex!("436a2c040cf45fea9b29a0cb81b1f41458f863d0d61b453d0a982720d6d61320");
90     let result = x25519_ecdh_test_impl::<E>(&public_key, &private).unwrap();
91     assert_eq!(expected_shared_secret, result.into());
92 }
93 
x25519_ecdh_test_impl<E: EcdhProviderForX25519Test>( public_key: &[u8], private: &[u8; 32], ) -> Result<E::SharedSecret, TestError>94 fn x25519_ecdh_test_impl<E: EcdhProviderForX25519Test>(
95     public_key: &[u8],
96     private: &[u8; 32],
97 ) -> Result<E::SharedSecret, TestError> {
98     let public_key = E::PublicKey::from_bytes(public_key).map_err(TestError::new)?;
99     let ephemeral_secret = E::EphemeralSecret::from_private_components(private, &public_key)
100         .map_err(TestError::new)?;
101     ephemeral_secret.diffie_hellman(&public_key).map_err(TestError::new)
102 }
103 
104 /// Wycheproof test for X25519 Diffie-Hellman.
wycheproof_x25519_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>)105 pub fn wycheproof_x25519_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
106     // Test cases from https://github.com/randombit/wycheproof-rs/blob/master/src/data/x25519_test.json
107     let test_set = wycheproof::xdh::TestSet::load(wycheproof::xdh::TestName::X25519).unwrap();
108     for test_group in test_set.test_groups {
109         for test in test_group.tests {
110             let result = x25519_ecdh_test_impl::<E>(
111                 &test.public_key,
112                 &test
113                     .private_key
114                     .as_slice()
115                     .try_into()
116                     .expect("Private keys should be 32 bytes long"),
117             );
118             match test.result {
119                 wycheproof::TestResult::Valid => {
120                     let shared_secret =
121                         result.unwrap_or_else(|_| panic!("Test {} should succeed", test.tc_id));
122                     assert_eq!(&test.shared_secret.as_slice(), &shared_secret.into());
123                 }
124                 wycheproof::TestResult::Invalid => {
125                     result.err().unwrap_or_else(|| panic!("Test {} should fail", test.tc_id));
126                 }
127                 wycheproof::TestResult::Acceptable => {
128                     if let Ok(shared_secret) = result {
129                         assert_eq!(test.shared_secret.as_slice(), shared_secret.into());
130                     }
131                     // Test passes if `result` is an error because this test is "acceptable"
132                 }
133             }
134         }
135     }
136 }
137 
138 /// Generates the test cases to validate the x25519 implementation.
139 /// For example, to test `MyCryptoProvider`:
140 ///
141 /// ```
142 /// use crypto_provider::x25519::testing::*;
143 ///
144 /// mod tests {
145 ///     #[apply(x25519_test_cases)]
146 ///     fn x25519_tests(testcase: CryptoProviderTestCase<MyCryptoProvider>) {
147 ///         testcase(PhantomData::<MyCryptoProvider>);
148 ///     }
149 /// }
150 /// ```
151 #[template]
152 #[export]
153 #[rstest]
154 #[case::x25519_to_bytes(x25519_to_bytes_test)]
155 #[case::x25519_to_bytes_random(x25519_to_bytes_random_test)]
156 #[case::x25519_ecdh(x25519_ecdh_test)]
157 #[case::wycheproof_x25519(wycheproof_x25519_test)]
x25519_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>)158 fn x25519_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {}
159