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 core::ops::Deref;
20 use crypto_provider::p256::{P256PublicKey, PointCompression, P256};
21 use crypto_provider::{
22 elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey},
23 CryptoRng,
24 };
25 use hex_literal::hex;
26 use rstest_reuse::template;
27
28 /// An ECDH provider that provides associated types for testing purposes. This can be mostly
29 /// considered "aliases" for the otherwise long fully-qualified associated types.
30 pub trait EcdhProviderForP256Test {
31 /// The ECDH Provider that is "wrapped" by this type.
32 type EcdhProvider: EcdhProvider<
33 P256,
34 PublicKey = <Self as EcdhProviderForP256Test>::PublicKey,
35 EphemeralSecret = <Self as EcdhProviderForP256Test>::EphemeralSecret,
36 SharedSecret = <Self as EcdhProviderForP256Test>::SharedSecret,
37 >;
38 /// The public key type.
39 type PublicKey: P256PublicKey;
40 /// The ephemeral secret type.
41 type EphemeralSecret: EphemeralSecretForTesting<P256, Impl = Self::EcdhProvider>;
42 /// The shared secret type.
43 type SharedSecret: Into<[u8; 32]>;
44 }
45
46 impl<E> EcdhProviderForP256Test for E
47 where
48 E: EcdhProvider<P256>,
49 E::PublicKey: P256PublicKey,
50 E::EphemeralSecret: EphemeralSecretForTesting<P256>,
51 {
52 type EcdhProvider = E;
53 type PublicKey = E::PublicKey;
54 type EphemeralSecret = E::EphemeralSecret;
55 type SharedSecret = E::SharedSecret;
56 }
57
58 /// Test for P256PublicKey::to_bytes
to_bytes_test<E: EcdhProviderForP256Test>(_: PhantomData<E>)59 pub fn to_bytes_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
60 let sec1_bytes = hex!(
61 "04756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09f0b6da270d2a58a06022
62 8bbe76c6dc1643088107636deff8aa79e8002a157b92"
63 );
64 let key = E::PublicKey::from_sec1_bytes(&sec1_bytes).unwrap();
65 // Not part of the API contract, but `to_bytes()` should prefer to use uncompressed
66 // representation since support for compressed point is optional.
67 let key_bytes = key.to_bytes();
68 assert_eq!(sec1_bytes.to_vec(), key_bytes.deref());
69 }
70
71 /// Test for P256PublicKey::to_sec1_bytes(Compressed). Support for compressed representation is
72 /// optional.
to_bytes_compressed_test<E: EcdhProviderForP256Test>(_: PhantomData<E>)73 pub fn to_bytes_compressed_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
74 let sec1_bytes = hex!(
75 "04756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09f0b6da270d2a58a06022
76 8bbe76c6dc1643088107636deff8aa79e8002a157b92"
77 );
78 let key = E::PublicKey::from_sec1_bytes(&sec1_bytes).unwrap();
79 let key_bytes = key.to_sec1_bytes(PointCompression::Compressed);
80 let sec1_bytes_compressed =
81 hex!("02756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09");
82 assert_eq!(sec1_bytes_compressed.to_vec(), key_bytes.deref());
83 }
84
85 /// Test for P256PublicKey::to_sec1_bytes(Uncompressed)
to_bytes_uncompressed_test<E: EcdhProviderForP256Test>(_: PhantomData<E>)86 pub fn to_bytes_uncompressed_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
87 let sec1_bytes = hex!(
88 "04756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09f0b6da270d2a58a06022
89 8bbe76c6dc1643088107636deff8aa79e8002a157b92"
90 );
91 let key = E::PublicKey::from_sec1_bytes(&sec1_bytes).unwrap();
92 let key_bytes = key.to_sec1_bytes(PointCompression::Uncompressed);
93 assert_eq!(sec1_bytes.to_vec(), key_bytes.deref());
94 }
95
96 /// Random test for P256PublicKey::to_bytes
to_bytes_random_test<E: EcdhProviderForP256Test>(_: PhantomData<E>)97 pub fn to_bytes_random_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
98 for _ in 1..100 {
99 let public_key_bytes =
100 E::EphemeralSecret::generate_random(&mut <E::EphemeralSecret as EphemeralSecret<
101 P256,
102 >>::Rng::new())
103 .public_key_bytes();
104 let public_key = E::PublicKey::from_bytes(public_key_bytes.as_ref()).unwrap();
105 assert_eq!(
106 E::PublicKey::from_bytes(&public_key.to_bytes()).unwrap(),
107 public_key,
108 "from_bytes should return the same key for `{public_key_bytes:?}`",
109 );
110 }
111 }
112
113 /// Test for P256PublicKey::from_affine_coordinates
from_affine_coordinates_test<E: EcdhProviderForP256Test>(_: PhantomData<E>)114 pub fn from_affine_coordinates_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
115 // https://www.secg.org/sec1-v2.pdf, section 2.3.3
116 let x = hex!("756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09");
117 let y = hex!("f0b6da270d2a58a060228bbe76c6dc1643088107636deff8aa79e8002a157b92");
118 let sec1 = hex!(
119 "04756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09f0b6da270d2a58a06022
120 8bbe76c6dc1643088107636deff8aa79e8002a157b92"
121 );
122 let expected_key = E::PublicKey::from_sec1_bytes(&sec1).unwrap();
123 assert!(
124 E::PublicKey::from_affine_coordinates(&x, &y).unwrap() == expected_key,
125 "Public key does not match"
126 );
127 }
128
129 /// Test for P256PublicKey::from_affine_coordinates
from_affine_coordinates_not_on_curve_test<E: EcdhProviderForP256Test>(_: PhantomData<E>)130 pub fn from_affine_coordinates_not_on_curve_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
131 // (Invalid) coordinate from wycheproof ecdh_secp256r1_ecpoint_test.json, tcId 193
132 let x = hex!("0000000000000000000000000000000000000000000000000000000000000000");
133 let y = hex!("0000000000000000000000000000000000000000000000000000000000000000");
134 let result = E::PublicKey::from_affine_coordinates(&x, &y);
135 assert!(result.is_err(), "Creating public key from invalid affine coordinate should fail");
136 }
137
138 /// Test for P256PublicKey::from_sec1_bytes
from_sec1_bytes_not_on_curve_test<E: EcdhProviderForP256Test>(_: PhantomData<E>)139 pub fn from_sec1_bytes_not_on_curve_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
140 // (Invalid) sec1 encoding from wycheproof ecdh_secp256r1_ecpoint_test.json, tcId 193
141 let sec1 = hex!(
142 "04000000000000000000000000000000000000000000000000000000000000000000000000000000000000
143 00000000000000000000000000000000000000000000"
144 );
145 let result = E::PublicKey::from_sec1_bytes(&sec1);
146 assert!(result.is_err(), "Creating public key from point not on curve should fail");
147 }
148
149 /// Test for P256PublicKey::from_sec1_bytes
from_sec1_bytes_at_infinity_test<E: EcdhProviderForP256Test>(_: PhantomData<E>)150 pub fn from_sec1_bytes_at_infinity_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
151 // A single [0] byte represents a point at infinity.
152 let sec1 = hex!("00");
153 let result = E::PublicKey::from_sec1_bytes(&sec1);
154 assert!(result.is_err(), "Creating public key from point at infinity should fail");
155 }
156
157 /// Test for P256PublicKey::to_affine_coordinates
public_key_to_affine_coordinates_test<E: EcdhProviderForP256Test>(_: PhantomData<E>)158 pub fn public_key_to_affine_coordinates_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
159 // https://www.secg.org/sec1-v2.pdf, section 2.3.3
160 let expected_x = hex!("756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09");
161 let expected_y = hex!("f0b6da270d2a58a060228bbe76c6dc1643088107636deff8aa79e8002a157b92");
162 let sec1 = hex!(
163 "04756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09f0b6da270d2a58a06022
164 8bbe76c6dc1643088107636deff8aa79e8002a157b92"
165 );
166 let public_key = E::PublicKey::from_sec1_bytes(&sec1).unwrap();
167 let (actual_x, actual_y) = public_key.to_affine_coordinates().unwrap();
168 assert_eq!(actual_x, expected_x);
169 assert_eq!(actual_y, expected_y);
170 }
171
172 /// Test for P256PublicKey::to_affine_coordinates with compressed point with 0x02 octet prefix.
173 /// Support for compressed points is optional according to the specs, but both openssl and
174 /// rustcrypto implementations support it.
public_key_to_affine_coordinates_compressed02_test<E: EcdhProviderForP256Test>( _: PhantomData<E>, )175 pub fn public_key_to_affine_coordinates_compressed02_test<E: EcdhProviderForP256Test>(
176 _: PhantomData<E>,
177 ) {
178 // https://www.secg.org/sec1-v2.pdf, section 2.3.3
179 let expected_x = hex!("21238e877c2400f15f9ea7d4353ac0a63dcb5d13168a96fcfc93bdc66031ed1c");
180 let expected_y = hex!("fa339bd0886602e91b9d2aa9b43213f660b680b1c97ef09cb1cacdc14e9d85ee");
181 let sec1 = hex!("0221238e877c2400f15f9ea7d4353ac0a63dcb5d13168a96fcfc93bdc66031ed1c");
182 let public_key = E::PublicKey::from_sec1_bytes(&sec1).unwrap();
183 let (actual_x, actual_y) = public_key.to_affine_coordinates().unwrap();
184 assert_eq!(actual_x, expected_x);
185 assert_eq!(actual_y, expected_y);
186 }
187
188 /// Test for P256PublicKey::to_affine_coordinates with compressed point with 0x03 octet prefix
189 /// Support for compressed points is optional according to the specs, but both openssl and
190 /// rustcrypto implementations support it.
public_key_to_affine_coordinates_compressed03_test<E: EcdhProviderForP256Test>( _: PhantomData<E>, )191 pub fn public_key_to_affine_coordinates_compressed03_test<E: EcdhProviderForP256Test>(
192 _: PhantomData<E>,
193 ) {
194 // https://www.secg.org/sec1-v2.pdf, section 2.3.3
195 let expected_x = hex!("f557ef33d52e540e6aa4e6fcbb62a314adcb051cc8a1fefc69d004c282af81ff");
196 let expected_y = hex!("96cd4c6ed5cbf00bb3184e5cd983c3442160310c8519b4c4d16292be83eec539");
197 let sec1 = hex!("03f557ef33d52e540e6aa4e6fcbb62a314adcb051cc8a1fefc69d004c282af81ff");
198 let public_key = E::PublicKey::from_sec1_bytes(&sec1).unwrap();
199 let (actual_x, actual_y) = public_key.to_affine_coordinates().unwrap();
200 assert_eq!(actual_x, expected_x);
201 assert_eq!(actual_y, expected_y);
202 }
203
204 /// Test for P256PublicKey::to_affine_coordinates with the top byte being zero
public_key_to_affine_coordinates_zero_top_byte_test<E: EcdhProviderForP256Test>( _: PhantomData<E>, )205 pub fn public_key_to_affine_coordinates_zero_top_byte_test<E: EcdhProviderForP256Test>(
206 _: PhantomData<E>,
207 ) {
208 // https://www.secg.org/sec1-v2.pdf, section 2.3.3
209 let expected_x = hex!("00f24fe76679c57bc6c2f025af92e6c0b2058fb15fa41014775987587400ed48");
210 let expected_y = hex!("e09f6fa9979a60f578a62dca805ad75b9e6b89403f2ebb934869e3697ac590e9");
211 let sec1 = hex!("0400f24fe76679c57bc6c2f025af92e6c0b2058fb15fa41014775987587400ed48e09f6fa9979a60f578a62dca805ad75b9e6b89403f2ebb934869e3697ac590e9");
212 let public_key = E::PublicKey::from_sec1_bytes(&sec1).unwrap();
213 let (actual_x, actual_y) = public_key.to_affine_coordinates().unwrap();
214 assert_eq!(actual_x, expected_x);
215 assert_eq!(actual_y, expected_y);
216 }
217
218 /// Test for P256 Diffie-Hellman key exchange.
p256_ecdh_test<E: EcdhProviderForP256Test>(_: PhantomData<E>)219 pub fn p256_ecdh_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
220 // From wycheproof ecdh_secp256r1_ecpoint_test.json, tcId 1
221 // https://github.com/google/wycheproof/blob/b063b4a/testvectors/ecdh_secp256r1_ecpoint_test.json#L22
222 // sec1 public key manually extracted from the ASN encoded test data
223 let public_key_sec1 = hex!(
224 "0462d5bd3372af75fe85a040715d0f502428e07046868b0bfdfa61d731afe44f
225 26ac333a93a9e70a81cd5a95b5bf8d13990eb741c8c38872b4a07d275a014e30cf"
226 );
227 let private = hex!("0612465c89a023ab17855b0a6bcebfd3febb53aef84138647b5352e02c10c346");
228 let expected_shared_secret =
229 hex!("53020d908b0219328b658b525f26780e3ae12bcd952bb25a93bc0895e1714285");
230 let actual_shared_secret = p256_ecdh_test_impl::<E>(&public_key_sec1, &private).unwrap();
231 assert_eq!(actual_shared_secret.into(), expected_shared_secret);
232 }
233
p256_ecdh_test_impl<E: EcdhProviderForP256Test>( public_key_sec1: &[u8], private: &[u8; 32], ) -> Result<E::SharedSecret, TestError>234 fn p256_ecdh_test_impl<E: EcdhProviderForP256Test>(
235 public_key_sec1: &[u8],
236 private: &[u8; 32],
237 ) -> Result<E::SharedSecret, TestError> {
238 let public_key = E::PublicKey::from_sec1_bytes(public_key_sec1).map_err(TestError::new)?;
239 let ephemeral_secret = E::EphemeralSecret::from_private_components(private, &public_key)
240 .map_err(TestError::new)?;
241 ephemeral_secret.diffie_hellman(&public_key).map_err(TestError::new)
242 }
243
244 /// Wycheproof test for P256 Diffie-Hellman.
wycheproof_p256_test<E: EcdhProviderForP256Test>(_: PhantomData<E>)245 pub fn wycheproof_p256_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
246 // Test cases from https://github.com/randombit/wycheproof-rs/blob/master/src/data/ecdh_secp256r1_ecpoint_test.json
247 let test_set =
248 wycheproof::ecdh::TestSet::load(wycheproof::ecdh::TestName::EcdhSecp256r1Ecpoint).unwrap();
249 for test_group in test_set.test_groups {
250 for test in test_group.tests {
251 if test.private_key.len() != 32 {
252 // Some Wycheproof test cases have private key length that are not 32 bytes, but
253 // the RustCrypto implementation doesn't support that (it always take 32 bytes
254 // from the given RNG when generating a new key).
255 continue;
256 };
257 let result = p256_ecdh_test_impl::<E>(
258 &test.public_key,
259 &test
260 .private_key
261 .as_slice()
262 .try_into()
263 .expect("Private key should be 32 bytes long"),
264 );
265 match test.result {
266 wycheproof::TestResult::Valid => {
267 let shared_secret =
268 result.unwrap_or_else(|_| panic!("Test {} should succeed", test.tc_id));
269 assert_eq!(test.shared_secret.as_slice(), shared_secret.into());
270 }
271 wycheproof::TestResult::Invalid => {
272 result.err().unwrap_or_else(|| panic!("Test {} should fail", test.tc_id));
273 }
274 wycheproof::TestResult::Acceptable => {
275 if let Ok(shared_secret) = result {
276 assert_eq!(test.shared_secret.as_slice(), shared_secret.into());
277 }
278 // Test passes if `result` is an error because this test is "acceptable"
279 }
280 }
281 }
282 }
283 }
284
285 /// Generates the test cases to validate the P256 implementation.
286 /// For example, to test `MyCryptoProvider`:
287 ///
288 /// ```
289 /// use crypto_provider::p256::testing::*;
290 ///
291 /// mod tests {
292 /// #[apply(p256_test_cases)]
293 /// fn p256_tests(testcase: CryptoProviderTestCase<MyCryptoProvider> {
294 /// testcase(PhantomData::<MyCryptoProvider>);
295 /// }
296 /// }
297 /// ```
298 #[template]
299 #[export]
300 #[rstest]
301 #[case::to_bytes(to_bytes_test, "to_bytes")]
302 #[case::to_bytes_compressed(to_bytes_compressed_test, "to_bytes_compressed")]
303 #[case::to_bytes_uncompressed(to_bytes_uncompressed_test, "to_bytes_uncompressed")]
304 #[case::to_bytes_random(to_bytes_random_test, "to_bytes_random")]
305 #[case::from_sec1_bytes_not_on_curve(
306 from_sec1_bytes_not_on_curve_test,
307 "from_sec1_bytes_not_on_curve"
308 )]
309 #[case::from_sec1_bytes_not_on_infinity(
310 from_sec1_bytes_at_infinity_test,
311 "from_sec1_bytes_not_on_infinity"
312 )]
313 #[case::from_affine_coordinates(from_affine_coordinates_test, "from_affine_coordinates")]
314 #[case::from_affine_coordinates_not_on_curve(
315 from_affine_coordinates_not_on_curve_test,
316 "from_affine_coordinates_not_on_curve"
317 )]
318 #[case::public_key_to_affine_coordinates(
319 public_key_to_affine_coordinates_test,
320 "public_key_to_affine_coordinates"
321 )]
322 #[case::public_key_to_affine_coordinates_compressed02(
323 public_key_to_affine_coordinates_compressed02_test,
324 "public_key_to_affine_coordinates_compressed02"
325 )]
326 #[case::public_key_to_affine_coordinates_compressed03(
327 public_key_to_affine_coordinates_compressed03_test,
328 "public_key_to_affine_coordinates_compressed03"
329 )]
330 #[case::public_key_to_affine_coordinates_zero_top_byte(
331 public_key_to_affine_coordinates_zero_top_byte_test,
332 "public_key_to_affine_coordinates_zero_top_byte"
333 )]
334 #[case::p256_ecdh(p256_ecdh_test, "p256_ecdh")]
335 #[case::wycheproof_p256(wycheproof_p256_test, "wycheproof_p256")]
p256_test_cases<C: CryptoProvider>( #[case] testcase: CryptoProviderTestCase<C>, #[case] name: &str, )336 fn p256_test_cases<C: CryptoProvider>(
337 #[case] testcase: CryptoProviderTestCase<C>,
338 #[case] name: &str,
339 ) {
340 }
341