xref: /freebsd/crypto/libecc/src/ecdh/x25519_448.c (revision f0865ec9906d5a18fa2a3b61381f22ce16e606ad)
1 /*
2  *  Copyright (C) 2021 - This file is part of libecc project
3  *
4  *  Authors:
5  *      Ryad BENADJILA <ryadbenadjila@gmail.com>
6  *      Arnaud EBALARD <arnaud.ebalard@ssi.gouv.fr>
7  *
8  *  This software is licensed under a dual BSD and GPL v2 license.
9  *  See LICENSE file at the root folder of the project.
10  */
11 #include <libecc/lib_ecc_config.h>
12 #if defined(WITH_X25519) || defined(WITH_X448)
13 
14 /*
15  * XXX: X25519 and X448 are incompatible with small stack devices for now ...
16  */
17 #if defined(USE_SMALL_STACK)
18 #error "Error: X25519 and X448 are incompatible with USE_SMALL_STACK (devices low on memory)"
19 #endif
20 
21 #if defined(WITH_X25519) && !defined(WITH_CURVE_WEI25519)
22 #error "Error: X25519 needs curve WEI25519 to be defined! Please define it in libecc config file"
23 #endif
24 #if defined(WITH_X448) && !defined(WITH_CURVE_WEI448)
25 #error "Error: X448 needs curve WEI448 to be defined! Please define it in libecc config file"
26 #endif
27 
28 #include <libecc/ecdh/x25519_448.h>
29 
30 #include <libecc/curves/curves.h>
31 #include <libecc/sig/ec_key.h>
32 #include <libecc/utils/utils.h>
33 #include <libecc/fp/fp_sqrt.h>
34 
35 /* For randomness source */
36 #include <libecc/external_deps/rand.h>
37 
38 /* This module mainly implements the X25519 and X448 functions strictly as defined in
39  * RFC7748.
40  */
41 
42 
43 /* Scalar clamping/decoding
44  *
45  * NOTE: the scalar encoding is mainly here to ensure that it is of the form
46  * 2^254 plus eight times a value between 0 and 2^251 - 1 inclusive for X25519
47  * (2^447 plus four times a value between 0 and 2^445 - 1 inclusive for X448).
48  *
49  * This ensures "clearing the cofactor" to avoid small subgroup attacks as well
50  * as setting the scalar MSB to avoid timing/SCA attacks on scalar multiplication.
51  * These two desirable properties are part of the rationale behind X25519/X448).
52  */
decode_scalar(u8 * scalar_decoded,const u8 * scalar,u8 len)53 ATTRIBUTE_WARN_UNUSED_RET static int decode_scalar(u8 *scalar_decoded, const u8 *scalar, u8 len)
54 {
55         int ret;
56         u8 i;
57 
58         /* Aliasing is not supported */
59         MUST_HAVE((scalar != scalar_decoded), ret, err);
60 	/* Zero length is not accepted */
61 	MUST_HAVE((len > 0), ret, err);
62 
63 	/* Endianness swapping */
64         for(i = 0; i < len; i++){
65                 scalar_decoded[len - 1 - i] = scalar[i];
66         }
67 	if(len == X25519_SIZE){
68 		/* Curve25519 */
69 		scalar_decoded[len - 1] &= 248;
70         	scalar_decoded[0]       &= 127;
71 		scalar_decoded[0]       |= 64;
72 	}
73 	else if(len == X448_SIZE){
74 		/* Curve448 */
75 		scalar_decoded[len - 1] &= 252;
76 		scalar_decoded[0]       |= 128;
77 	}
78 	else{
79 		/* Error, unknown type */
80 		ret = -1;
81 		goto err;
82 	}
83 
84         ret = 0;
85 
86 err:
87         return ret;
88 }
89 
90 /* U coordinate decoding, mainly endianness swapping  */
decode_u_coordinate(u8 * u_decoded,const u8 * u,u8 len)91 ATTRIBUTE_WARN_UNUSED_RET static int decode_u_coordinate(u8 *u_decoded, const u8 *u, u8 len)
92 {
93 	int ret;
94 	u8 i;
95 
96 	/* Aliasing is not supported */
97 	MUST_HAVE((u != u_decoded), ret, err);
98 	/* Zero length is not accepted */
99 	MUST_HAVE((len > 0), ret, err);
100 
101 	for(i = 0; i < len; i++){
102 		u_decoded[len - 1 - i] = u[i];
103         }
104 
105 	ret = 0;
106 
107 err:
108 	return ret;
109 }
110 
111 /* U coordinate encoding, mainly endianness swapping */
encode_u_coordinate(u8 * u_encoded,const u8 * u,u8 len)112 ATTRIBUTE_WARN_UNUSED_RET static int encode_u_coordinate(u8 *u_encoded, const u8 *u, u8 len)
113 {
114 	return decode_u_coordinate(u_encoded, u, len);
115 }
116 
117 
118 /* Find V coordinate from U coordinate on Curve25519 or Curve448 */
compute_v_from_u(fp_src_t u,fp_t v,ec_montgomery_crv_src_t crv)119 ATTRIBUTE_WARN_UNUSED_RET static int compute_v_from_u(fp_src_t u, fp_t v, ec_montgomery_crv_src_t crv)
120 {
121 	int ret;
122 	fp tmp;
123 
124 	tmp.magic = 0;
125 
126 	ret = aff_pt_montgomery_v_from_u(v, &tmp, u, crv);
127 	/* NOTE: this square root is possibly non-existing if the
128 	 * u coordinate is on the quadratic twist of the curve.
129 	 * An error is returned in this case.
130 	 */
131 
132 	fp_uninit(&tmp);
133 
134 	return ret;
135 }
136 
137 
138 /*
139  * This is the core computation of X25519 and X448.
140  *
141  * NOTE: the user of this primitive should be warned and aware that is is not fully compliant with the
142  * RFC7748 description as u coordinates on the quadratic twist of the curve are rejected as well
143  * as non canonical u.
144  * See the explanations in the implementation of the function for more context and explanations.
145  */
x25519_448_core(const u8 * k,const u8 * u,u8 * res,u8 len)146 ATTRIBUTE_WARN_UNUSED_RET static int x25519_448_core(const u8 *k, const u8 *u, u8 *res, u8 len)
147 {
148 	int ret, iszero, loaded, cmp;
149 	/* Note: our local variables holding scalar and coordinate have the maximum size
150 	 * (X448 size if it is defined, X25519 else).
151 	 */
152 #if defined(WITH_X448)
153 	u8 k_[X448_SIZE], u_[X448_SIZE];
154 #else
155 	u8 k_[X25519_SIZE], u_[X25519_SIZE];
156 #endif
157 	aff_pt_montgomery _Tmp;
158 	prj_pt Q;
159 	ec_montgomery_crv montgomery_curve;
160 	ec_params shortw_curve_params;
161 	ec_shortw_crv_src_t shortw_curve;
162 	fp_src_t alpha_montgomery;
163 	fp_src_t gamma_montgomery;
164 	nn scalar;
165 	nn_t v_coord_nn;
166 	fp_t u_coord, v_coord;
167 	nn_t cofactor;
168 
169 	_Tmp.magic = montgomery_curve.magic = Q.magic = WORD(0);
170 	scalar.magic = WORD(0);
171 
172 	MUST_HAVE((k != NULL) && (u != NULL) && (res != NULL), ret, err);
173 	/* Sanity check on sizes */
174 	MUST_HAVE(((len == X25519_SIZE) || (len == X448_SIZE)), ret, err);
175 	MUST_HAVE(((sizeof(k_) >= len) && (sizeof(u_) >= len)), ret, err);
176 
177 	/* First of all, we clamp and decode the scalar and u */
178 	ret = decode_scalar(k_, k, len); EG(ret, err);
179 	ret = decode_u_coordinate(u_, u, len); EG(ret, err);
180 
181 	/* Import curve parameters */
182 	loaded = 0;
183 #if defined(WITH_X25519)
184 	if(len == X25519_SIZE){
185 		ret = import_params(&shortw_curve_params, &wei25519_str_params); EG(ret, err);
186 		loaded = 1;
187 	}
188 #endif
189 #if defined(WITH_X448)
190 	if(len == X448_SIZE){
191 		ret = import_params(&shortw_curve_params, &wei448_str_params); EG(ret, err);
192 		loaded = 1;
193 	}
194 #endif
195 	/* Sanity check that we have loaded our curve parameters */
196 	MUST_HAVE((loaded == 1), ret, err);
197 
198 	/* Make things more readable */
199 	shortw_curve = &(shortw_curve_params.ec_curve);
200 	cofactor = &(shortw_curve_params.ec_gen_cofactor);
201 	alpha_montgomery = &(shortw_curve_params.ec_alpha_montgomery);
202 	gamma_montgomery = &(shortw_curve_params.ec_gamma_montgomery);
203 	/* NOTE: we use the projective point Q Fp values as temporary
204 	 * space to save some stack here.
205 	 */
206 	u_coord = &(Q.X);
207 	v_coord = &(Q.Y);
208 	v_coord_nn = &(v_coord->fp_val);
209 
210 	/* Get the isogenic Montgomery curve */
211 	ret = curve_shortw_to_montgomery(shortw_curve, &montgomery_curve, alpha_montgomery,
212 			gamma_montgomery); EG(ret, err);
213 
214 	/* Import the u coordinate as a big integer and Fp element */
215 	ret = nn_init_from_buf(v_coord_nn, u_, len); EG(ret, err);
216 	/* Reject non canonical u values.
217 	 * NOTE: we use v here as a temporary value.
218 	 */
219 	ret = nn_cmp(v_coord_nn, &(montgomery_curve.A.ctx->p), &cmp); EG(ret, err);
220 	MUST_HAVE((cmp < 0), ret, err);
221 	/* Now initialize u as Fp element with the reduced value */
222 	ret = fp_init(u_coord, montgomery_curve.A.ctx); EG(ret, err);
223 	ret = fp_set_nn(u_coord, v_coord_nn); EG(ret, err);
224 
225 	/* Compute the v coordinate from u */
226 	ret = compute_v_from_u(u_coord, v_coord, &montgomery_curve); EG(ret, err);
227 	/* NOTE: since we use isogenies of the Curve25519/448, we must stick to points
228 	 * belonging to this curve. Since not all u coordinates provide a v coordinate
229 	 * (i.e. a square residue from the curve formula), the computation above can trigger an error.
230 	 * When this is the case, this means that the u coordinate is on the quadtratic twist of
231 	 * the Montgomery curve (which is a secure curve by design here). We could perform computations
232 	 * on an isogenic curve of this twist, however we choose to return an error instead.
233 	 *
234 	 * Although this is not compliant with the Curve2551/448 original spirit (that accepts any u
235 	 * coordinate thanks to the x-coordinate only computations with the Montgomery Ladder),
236 	 * we emphasize here that in the key exchange defined in RFC7748 all the exchanged points
237 	 * (i.e. public keys) are derived from base points that are on the curve and not on its twist, meaning
238 	 * that all the exchanged u coordinates should belong to the curve. Diverging from this behavior would
239 	 * suggest that an attacker is trying to inject other values, and we are safe to reject them in the
240 	 * context of Diffie-Hellman based key exchange as defined in RFC7748.
241 	 *
242 	 * On the other hand, the drawback of rejecting u coordinates on the quadratic twist is that
243 	 * using the current X25519/448 primitive in other contexts than RFC7748 Diffie-Hellman could be
244 	 * limited and non interoperable with other implementations of this primive. Another issue is that
245 	 * this specific divergence exposes our implementation to be distinguishable from other standard ones
246 	 * in a "black box" analysis context, which is generally not very desirable even if no real security
247 	 * issue is induced.
248 	 */
249 
250 	/* Get the affine point in Montgomery */
251 	ret = aff_pt_montgomery_init_from_coords(&_Tmp, &montgomery_curve, u_coord, v_coord); EG(ret, err);
252 	/* Transfer from Montgomery to short Weierstrass using the isogeny */
253 	ret = aff_pt_montgomery_to_prj_pt_shortw(&_Tmp, shortw_curve, &Q); EG(ret, err);
254 
255 	/*
256 	 * Reject small order public keys: while this is not a strict requirement of RFC7748, there is no
257 	 * good reason to accept these weak values!
258 	 */
259 	ret = check_prj_pt_order(&Q, cofactor, PUBLIC_PT, &cmp); EG(ret, err);
260 	MUST_HAVE((!cmp), ret, err);
261 
262 	/* Import the scalar as big number NN value */
263 	ret = nn_init_from_buf(&scalar, k_, len); EG(ret, err);
264 	/* Now proceed with the scalar multiplication */
265 #ifdef USE_SIG_BLINDING
266 	ret = prj_pt_mul_blind(&Q, &scalar, &Q); EG(ret, err);
267 #else
268 	ret = prj_pt_mul(&Q, &scalar, &Q); EG(ret, err);
269 #endif
270 
271 	/* Transfer back from short Weierstrass to Montgomery using the isogeny */
272 	ret = prj_pt_shortw_to_aff_pt_montgomery(&Q, &montgomery_curve, &_Tmp); EG(ret, err);
273 
274 	/* Reject the all zero output */
275 	ret = fp_iszero(&(_Tmp.u), &iszero); EG(ret, err);
276 	MUST_HAVE((!iszero), ret, err);
277 
278 	/* Now export the resulting u coordinate ... */
279 	ret = fp_export_to_buf(u_, len, &(_Tmp.u)); EG(ret, err);
280 	/* ... and encode it in the output */
281 	ret = encode_u_coordinate(res, u_, len);
282 
283 err:
284 	IGNORE_RET_VAL(local_memset(u_, 0, sizeof(u_)));
285 	IGNORE_RET_VAL(local_memset(k_, 0, sizeof(k_)));
286 	IGNORE_RET_VAL(local_memset(&shortw_curve_params, 0, sizeof(shortw_curve_params)));
287 
288 	nn_uninit(&scalar);
289 	aff_pt_montgomery_uninit(&_Tmp);
290 	prj_pt_uninit(&Q);
291 	ec_montgomery_crv_uninit(&montgomery_curve);
292 
293 	PTR_NULLIFY(shortw_curve);
294 	PTR_NULLIFY(alpha_montgomery);
295 	PTR_NULLIFY(gamma_montgomery);
296 	PTR_NULLIFY(u_coord);
297 	PTR_NULLIFY(v_coord);
298 	PTR_NULLIFY(v_coord_nn);
299 	PTR_NULLIFY(cofactor);
300 
301         return ret;
302 }
303 
x25519_448_gen_priv_key(u8 * priv_key,u8 len)304 ATTRIBUTE_WARN_UNUSED_RET static int x25519_448_gen_priv_key(u8 *priv_key, u8 len)
305 {
306 	int ret;
307 
308 	MUST_HAVE((priv_key != NULL), ret, err);
309 	MUST_HAVE(((len == X25519_SIZE) || (len == X448_SIZE)), ret, err);
310 
311 	/* Generating a private key consists in generating a random byte string */
312 	ret = get_random(priv_key, len);
313 
314 err:
315 	return ret;
316 }
317 
318 
x25519_448_init_pub_key(const u8 * priv_key,u8 * pub_key,u8 len)319 ATTRIBUTE_WARN_UNUSED_RET static int x25519_448_init_pub_key(const u8 *priv_key, u8 *pub_key, u8 len)
320 {
321 	int ret;
322 
323 	MUST_HAVE((priv_key != NULL) && (pub_key != NULL), ret, err);
324 	MUST_HAVE(((len == X25519_SIZE) || (len == X448_SIZE)), ret, err);
325 
326 	/* Computing the public key is x25519(priv_key, 9) or x448(priv_key, 5)
327 	 *
328 	 * NOTE: although we could optimize and accelerate the computation of the public
329 	 * key by skipping the Montgomery to Weierstrass mapping (as the base point on the two
330 	 * isomorphic curves are known), we rather use the regular x25519_448_core primitive
331 	 * as it has the advantages of keeping the code clean and simple (and the performance
332 	 * cost is not so expensive as the core scalar multiplication will take most of the
333 	 * cycles ...).
334 	 *
335 	 */
336 	if(len == X25519_SIZE){
337 		u8 u[X25519_SIZE];
338 
339 		ret = local_memset(u, 0, sizeof(u)); EG(ret, err);
340 		/* X25519 uses the base point with x-coordinate = 0x09 */
341 		u[0] = 0x09;
342 		ret = x25519_448_core(priv_key, u, pub_key, len);
343 	}
344 	else if(len == X448_SIZE){
345 		u8 u[X448_SIZE];
346 
347 		ret = local_memset(u, 0, sizeof(u)); EG(ret, err);
348 		/* X448 uses the base point with x-coordinate = 0x05 */
349 		u[0] = 0x05;
350 		ret = x25519_448_core(priv_key, u, pub_key, len);
351 	}
352 	else{
353 		ret = -1;
354 	}
355 err:
356 	return ret;
357 }
358 
x25519_448_derive_secret(const u8 * priv_key,const u8 * peer_pub_key,u8 * shared_secret,u8 len)359 ATTRIBUTE_WARN_UNUSED_RET static int x25519_448_derive_secret(const u8 *priv_key, const u8 *peer_pub_key, u8 *shared_secret, u8 len)
360 {
361 	int ret;
362 
363 	MUST_HAVE((priv_key != NULL) && (peer_pub_key != NULL) && (shared_secret != NULL), ret, err);
364 	MUST_HAVE(((len == X25519_SIZE) || (len == X448_SIZE)), ret, err);
365 
366 	/* Computing the shared secret is x25519(priv_key, peer_pub_key) or x448(priv_key, peer_pub_key) */
367 	ret = x25519_448_core(priv_key, peer_pub_key, shared_secret, len);
368 
369 err:
370 	return ret;
371 }
372 
373 
374 #if defined(WITH_X25519)
375 /* The X25519 function as specified in RFC7748.
376  *
377  * NOTE: we use isogenies between Montgomery Curve25519 and short Weierstrass
378  * WEI25519 to perform the Elliptic Curves computations.
379  */
x25519(const u8 k[X25519_SIZE],const u8 u[X25519_SIZE],u8 res[X25519_SIZE])380 int x25519(const u8 k[X25519_SIZE], const u8 u[X25519_SIZE], u8 res[X25519_SIZE])
381 {
382 	return x25519_448_core(k, u, res, X25519_SIZE);
383 }
384 
x25519_gen_priv_key(u8 priv_key[X25519_SIZE])385 int x25519_gen_priv_key(u8 priv_key[X25519_SIZE])
386 {
387 	return x25519_448_gen_priv_key(priv_key, X25519_SIZE);
388 }
389 
x25519_init_pub_key(const u8 priv_key[X25519_SIZE],u8 pub_key[X25519_SIZE])390 int x25519_init_pub_key(const u8 priv_key[X25519_SIZE], u8 pub_key[X25519_SIZE])
391 {
392 	return x25519_448_init_pub_key(priv_key, pub_key, X25519_SIZE);
393 }
394 
x25519_derive_secret(const u8 priv_key[X25519_SIZE],const u8 peer_pub_key[X25519_SIZE],u8 shared_secret[X25519_SIZE])395 int x25519_derive_secret(const u8 priv_key[X25519_SIZE], const u8 peer_pub_key[X25519_SIZE], u8 shared_secret[X25519_SIZE])
396 {
397 	return x25519_448_derive_secret(priv_key, peer_pub_key, shared_secret, X25519_SIZE);
398 }
399 #endif
400 
401 #if defined(WITH_X448)
402 /* The X448 function as specified in RFC7748.
403  *
404  * NOTE: we use isogenies between Montgomery Curve448 and short Weierstrass
405  * WEI448 to perform the Elliptic Curves computations.
406  */
x448(const u8 k[X448_SIZE],const u8 u[X448_SIZE],u8 res[X448_SIZE])407 int x448(const u8 k[X448_SIZE], const u8 u[X448_SIZE], u8 res[X448_SIZE])
408 {
409 	return x25519_448_core(k, u, res, X448_SIZE);
410 }
411 
x448_gen_priv_key(u8 priv_key[X448_SIZE])412 int x448_gen_priv_key(u8 priv_key[X448_SIZE])
413 {
414 	return x25519_448_gen_priv_key(priv_key, X448_SIZE);
415 }
416 
x448_init_pub_key(const u8 priv_key[X448_SIZE],u8 pub_key[X448_SIZE])417 int x448_init_pub_key(const u8 priv_key[X448_SIZE], u8 pub_key[X448_SIZE])
418 {
419 	return x25519_448_init_pub_key(priv_key, pub_key, X448_SIZE);
420 }
421 
x448_derive_secret(const u8 priv_key[X448_SIZE],const u8 peer_pub_key[X448_SIZE],u8 shared_secret[X448_SIZE])422 int x448_derive_secret(const u8 priv_key[X448_SIZE], const u8 peer_pub_key[X448_SIZE], u8 shared_secret[X448_SIZE])
423 {
424 	return x25519_448_derive_secret(priv_key, peer_pub_key, shared_secret, X448_SIZE);
425 }
426 #endif
427 
428 #else /* !(defined(WITH_X25519) || defined(WITH_X448)) */
429 
430 /*
431  * Dummy definition to avoid the empty translation unit ISO C warning
432  */
433 typedef int dummy;
434 
435 #endif /* defined(WITH_X25519) || defined(WITH_X448) */
436