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