Path: blob/main/crypto/libecc/src/ecdh/x25519_448.c
34869 views
/*1* Copyright (C) 2021 - This file is part of libecc project2*3* Authors:4* Ryad BENADJILA <[email protected]>5* Arnaud EBALARD <[email protected]>6*7* This software is licensed under a dual BSD and GPL v2 license.8* See LICENSE file at the root folder of the project.9*/10#include <libecc/lib_ecc_config.h>11#if defined(WITH_X25519) || defined(WITH_X448)1213/*14* XXX: X25519 and X448 are incompatible with small stack devices for now ...15*/16#if defined(USE_SMALL_STACK)17#error "Error: X25519 and X448 are incompatible with USE_SMALL_STACK (devices low on memory)"18#endif1920#if defined(WITH_X25519) && !defined(WITH_CURVE_WEI25519)21#error "Error: X25519 needs curve WEI25519 to be defined! Please define it in libecc config file"22#endif23#if defined(WITH_X448) && !defined(WITH_CURVE_WEI448)24#error "Error: X448 needs curve WEI448 to be defined! Please define it in libecc config file"25#endif2627#include <libecc/ecdh/x25519_448.h>2829#include <libecc/curves/curves.h>30#include <libecc/sig/ec_key.h>31#include <libecc/utils/utils.h>32#include <libecc/fp/fp_sqrt.h>3334/* For randomness source */35#include <libecc/external_deps/rand.h>3637/* This module mainly implements the X25519 and X448 functions strictly as defined in38* RFC7748.39*/404142/* Scalar clamping/decoding43*44* NOTE: the scalar encoding is mainly here to ensure that it is of the form45* 2^254 plus eight times a value between 0 and 2^251 - 1 inclusive for X2551946* (2^447 plus four times a value between 0 and 2^445 - 1 inclusive for X448).47*48* This ensures "clearing the cofactor" to avoid small subgroup attacks as well49* as setting the scalar MSB to avoid timing/SCA attacks on scalar multiplication.50* These two desirable properties are part of the rationale behind X25519/X448).51*/52ATTRIBUTE_WARN_UNUSED_RET static int decode_scalar(u8 *scalar_decoded, const u8 *scalar, u8 len)53{54int ret;55u8 i;5657/* Aliasing is not supported */58MUST_HAVE((scalar != scalar_decoded), ret, err);59/* Zero length is not accepted */60MUST_HAVE((len > 0), ret, err);6162/* Endianness swapping */63for(i = 0; i < len; i++){64scalar_decoded[len - 1 - i] = scalar[i];65}66if(len == X25519_SIZE){67/* Curve25519 */68scalar_decoded[len - 1] &= 248;69scalar_decoded[0] &= 127;70scalar_decoded[0] |= 64;71}72else if(len == X448_SIZE){73/* Curve448 */74scalar_decoded[len - 1] &= 252;75scalar_decoded[0] |= 128;76}77else{78/* Error, unknown type */79ret = -1;80goto err;81}8283ret = 0;8485err:86return ret;87}8889/* U coordinate decoding, mainly endianness swapping */90ATTRIBUTE_WARN_UNUSED_RET static int decode_u_coordinate(u8 *u_decoded, const u8 *u, u8 len)91{92int ret;93u8 i;9495/* Aliasing is not supported */96MUST_HAVE((u != u_decoded), ret, err);97/* Zero length is not accepted */98MUST_HAVE((len > 0), ret, err);99100for(i = 0; i < len; i++){101u_decoded[len - 1 - i] = u[i];102}103104ret = 0;105106err:107return ret;108}109110/* U coordinate encoding, mainly endianness swapping */111ATTRIBUTE_WARN_UNUSED_RET static int encode_u_coordinate(u8 *u_encoded, const u8 *u, u8 len)112{113return decode_u_coordinate(u_encoded, u, len);114}115116117/* Find V coordinate from U coordinate on Curve25519 or Curve448 */118ATTRIBUTE_WARN_UNUSED_RET static int compute_v_from_u(fp_src_t u, fp_t v, ec_montgomery_crv_src_t crv)119{120int ret;121fp tmp;122123tmp.magic = 0;124125ret = aff_pt_montgomery_v_from_u(v, &tmp, u, crv);126/* NOTE: this square root is possibly non-existing if the127* u coordinate is on the quadratic twist of the curve.128* An error is returned in this case.129*/130131fp_uninit(&tmp);132133return ret;134}135136137/*138* This is the core computation of X25519 and X448.139*140* NOTE: the user of this primitive should be warned and aware that is is not fully compliant with the141* RFC7748 description as u coordinates on the quadratic twist of the curve are rejected as well142* as non canonical u.143* See the explanations in the implementation of the function for more context and explanations.144*/145ATTRIBUTE_WARN_UNUSED_RET static int x25519_448_core(const u8 *k, const u8 *u, u8 *res, u8 len)146{147int ret, iszero, loaded, cmp;148/* Note: our local variables holding scalar and coordinate have the maximum size149* (X448 size if it is defined, X25519 else).150*/151#if defined(WITH_X448)152u8 k_[X448_SIZE], u_[X448_SIZE];153#else154u8 k_[X25519_SIZE], u_[X25519_SIZE];155#endif156aff_pt_montgomery _Tmp;157prj_pt Q;158ec_montgomery_crv montgomery_curve;159ec_params shortw_curve_params;160ec_shortw_crv_src_t shortw_curve;161fp_src_t alpha_montgomery;162fp_src_t gamma_montgomery;163nn scalar;164nn_t v_coord_nn;165fp_t u_coord, v_coord;166nn_t cofactor;167168_Tmp.magic = montgomery_curve.magic = Q.magic = WORD(0);169scalar.magic = WORD(0);170171MUST_HAVE((k != NULL) && (u != NULL) && (res != NULL), ret, err);172/* Sanity check on sizes */173MUST_HAVE(((len == X25519_SIZE) || (len == X448_SIZE)), ret, err);174MUST_HAVE(((sizeof(k_) >= len) && (sizeof(u_) >= len)), ret, err);175176/* First of all, we clamp and decode the scalar and u */177ret = decode_scalar(k_, k, len); EG(ret, err);178ret = decode_u_coordinate(u_, u, len); EG(ret, err);179180/* Import curve parameters */181loaded = 0;182#if defined(WITH_X25519)183if(len == X25519_SIZE){184ret = import_params(&shortw_curve_params, &wei25519_str_params); EG(ret, err);185loaded = 1;186}187#endif188#if defined(WITH_X448)189if(len == X448_SIZE){190ret = import_params(&shortw_curve_params, &wei448_str_params); EG(ret, err);191loaded = 1;192}193#endif194/* Sanity check that we have loaded our curve parameters */195MUST_HAVE((loaded == 1), ret, err);196197/* Make things more readable */198shortw_curve = &(shortw_curve_params.ec_curve);199cofactor = &(shortw_curve_params.ec_gen_cofactor);200alpha_montgomery = &(shortw_curve_params.ec_alpha_montgomery);201gamma_montgomery = &(shortw_curve_params.ec_gamma_montgomery);202/* NOTE: we use the projective point Q Fp values as temporary203* space to save some stack here.204*/205u_coord = &(Q.X);206v_coord = &(Q.Y);207v_coord_nn = &(v_coord->fp_val);208209/* Get the isogenic Montgomery curve */210ret = curve_shortw_to_montgomery(shortw_curve, &montgomery_curve, alpha_montgomery,211gamma_montgomery); EG(ret, err);212213/* Import the u coordinate as a big integer and Fp element */214ret = nn_init_from_buf(v_coord_nn, u_, len); EG(ret, err);215/* Reject non canonical u values.216* NOTE: we use v here as a temporary value.217*/218ret = nn_cmp(v_coord_nn, &(montgomery_curve.A.ctx->p), &cmp); EG(ret, err);219MUST_HAVE((cmp < 0), ret, err);220/* Now initialize u as Fp element with the reduced value */221ret = fp_init(u_coord, montgomery_curve.A.ctx); EG(ret, err);222ret = fp_set_nn(u_coord, v_coord_nn); EG(ret, err);223224/* Compute the v coordinate from u */225ret = compute_v_from_u(u_coord, v_coord, &montgomery_curve); EG(ret, err);226/* NOTE: since we use isogenies of the Curve25519/448, we must stick to points227* belonging to this curve. Since not all u coordinates provide a v coordinate228* (i.e. a square residue from the curve formula), the computation above can trigger an error.229* When this is the case, this means that the u coordinate is on the quadtratic twist of230* the Montgomery curve (which is a secure curve by design here). We could perform computations231* on an isogenic curve of this twist, however we choose to return an error instead.232*233* Although this is not compliant with the Curve2551/448 original spirit (that accepts any u234* coordinate thanks to the x-coordinate only computations with the Montgomery Ladder),235* we emphasize here that in the key exchange defined in RFC7748 all the exchanged points236* (i.e. public keys) are derived from base points that are on the curve and not on its twist, meaning237* that all the exchanged u coordinates should belong to the curve. Diverging from this behavior would238* suggest that an attacker is trying to inject other values, and we are safe to reject them in the239* context of Diffie-Hellman based key exchange as defined in RFC7748.240*241* On the other hand, the drawback of rejecting u coordinates on the quadratic twist is that242* using the current X25519/448 primitive in other contexts than RFC7748 Diffie-Hellman could be243* limited and non interoperable with other implementations of this primive. Another issue is that244* this specific divergence exposes our implementation to be distinguishable from other standard ones245* in a "black box" analysis context, which is generally not very desirable even if no real security246* issue is induced.247*/248249/* Get the affine point in Montgomery */250ret = aff_pt_montgomery_init_from_coords(&_Tmp, &montgomery_curve, u_coord, v_coord); EG(ret, err);251/* Transfer from Montgomery to short Weierstrass using the isogeny */252ret = aff_pt_montgomery_to_prj_pt_shortw(&_Tmp, shortw_curve, &Q); EG(ret, err);253254/*255* Reject small order public keys: while this is not a strict requirement of RFC7748, there is no256* good reason to accept these weak values!257*/258ret = check_prj_pt_order(&Q, cofactor, PUBLIC_PT, &cmp); EG(ret, err);259MUST_HAVE((!cmp), ret, err);260261/* Import the scalar as big number NN value */262ret = nn_init_from_buf(&scalar, k_, len); EG(ret, err);263/* Now proceed with the scalar multiplication */264#ifdef USE_SIG_BLINDING265ret = prj_pt_mul_blind(&Q, &scalar, &Q); EG(ret, err);266#else267ret = prj_pt_mul(&Q, &scalar, &Q); EG(ret, err);268#endif269270/* Transfer back from short Weierstrass to Montgomery using the isogeny */271ret = prj_pt_shortw_to_aff_pt_montgomery(&Q, &montgomery_curve, &_Tmp); EG(ret, err);272273/* Reject the all zero output */274ret = fp_iszero(&(_Tmp.u), &iszero); EG(ret, err);275MUST_HAVE((!iszero), ret, err);276277/* Now export the resulting u coordinate ... */278ret = fp_export_to_buf(u_, len, &(_Tmp.u)); EG(ret, err);279/* ... and encode it in the output */280ret = encode_u_coordinate(res, u_, len);281282err:283IGNORE_RET_VAL(local_memset(u_, 0, sizeof(u_)));284IGNORE_RET_VAL(local_memset(k_, 0, sizeof(k_)));285IGNORE_RET_VAL(local_memset(&shortw_curve_params, 0, sizeof(shortw_curve_params)));286287nn_uninit(&scalar);288aff_pt_montgomery_uninit(&_Tmp);289prj_pt_uninit(&Q);290ec_montgomery_crv_uninit(&montgomery_curve);291292PTR_NULLIFY(shortw_curve);293PTR_NULLIFY(alpha_montgomery);294PTR_NULLIFY(gamma_montgomery);295PTR_NULLIFY(u_coord);296PTR_NULLIFY(v_coord);297PTR_NULLIFY(v_coord_nn);298PTR_NULLIFY(cofactor);299300return ret;301}302303ATTRIBUTE_WARN_UNUSED_RET static int x25519_448_gen_priv_key(u8 *priv_key, u8 len)304{305int ret;306307MUST_HAVE((priv_key != NULL), ret, err);308MUST_HAVE(((len == X25519_SIZE) || (len == X448_SIZE)), ret, err);309310/* Generating a private key consists in generating a random byte string */311ret = get_random(priv_key, len);312313err:314return ret;315}316317318ATTRIBUTE_WARN_UNUSED_RET static int x25519_448_init_pub_key(const u8 *priv_key, u8 *pub_key, u8 len)319{320int ret;321322MUST_HAVE((priv_key != NULL) && (pub_key != NULL), ret, err);323MUST_HAVE(((len == X25519_SIZE) || (len == X448_SIZE)), ret, err);324325/* Computing the public key is x25519(priv_key, 9) or x448(priv_key, 5)326*327* NOTE: although we could optimize and accelerate the computation of the public328* key by skipping the Montgomery to Weierstrass mapping (as the base point on the two329* isomorphic curves are known), we rather use the regular x25519_448_core primitive330* as it has the advantages of keeping the code clean and simple (and the performance331* cost is not so expensive as the core scalar multiplication will take most of the332* cycles ...).333*334*/335if(len == X25519_SIZE){336u8 u[X25519_SIZE];337338ret = local_memset(u, 0, sizeof(u)); EG(ret, err);339/* X25519 uses the base point with x-coordinate = 0x09 */340u[0] = 0x09;341ret = x25519_448_core(priv_key, u, pub_key, len);342}343else if(len == X448_SIZE){344u8 u[X448_SIZE];345346ret = local_memset(u, 0, sizeof(u)); EG(ret, err);347/* X448 uses the base point with x-coordinate = 0x05 */348u[0] = 0x05;349ret = x25519_448_core(priv_key, u, pub_key, len);350}351else{352ret = -1;353}354err:355return ret;356}357358ATTRIBUTE_WARN_UNUSED_RET static int x25519_448_derive_secret(const u8 *priv_key, const u8 *peer_pub_key, u8 *shared_secret, u8 len)359{360int ret;361362MUST_HAVE((priv_key != NULL) && (peer_pub_key != NULL) && (shared_secret != NULL), ret, err);363MUST_HAVE(((len == X25519_SIZE) || (len == X448_SIZE)), ret, err);364365/* Computing the shared secret is x25519(priv_key, peer_pub_key) or x448(priv_key, peer_pub_key) */366ret = x25519_448_core(priv_key, peer_pub_key, shared_secret, len);367368err:369return ret;370}371372373#if defined(WITH_X25519)374/* The X25519 function as specified in RFC7748.375*376* NOTE: we use isogenies between Montgomery Curve25519 and short Weierstrass377* WEI25519 to perform the Elliptic Curves computations.378*/379int x25519(const u8 k[X25519_SIZE], const u8 u[X25519_SIZE], u8 res[X25519_SIZE])380{381return x25519_448_core(k, u, res, X25519_SIZE);382}383384int x25519_gen_priv_key(u8 priv_key[X25519_SIZE])385{386return x25519_448_gen_priv_key(priv_key, X25519_SIZE);387}388389int x25519_init_pub_key(const u8 priv_key[X25519_SIZE], u8 pub_key[X25519_SIZE])390{391return x25519_448_init_pub_key(priv_key, pub_key, X25519_SIZE);392}393394int x25519_derive_secret(const u8 priv_key[X25519_SIZE], const u8 peer_pub_key[X25519_SIZE], u8 shared_secret[X25519_SIZE])395{396return x25519_448_derive_secret(priv_key, peer_pub_key, shared_secret, X25519_SIZE);397}398#endif399400#if defined(WITH_X448)401/* The X448 function as specified in RFC7748.402*403* NOTE: we use isogenies between Montgomery Curve448 and short Weierstrass404* WEI448 to perform the Elliptic Curves computations.405*/406int x448(const u8 k[X448_SIZE], const u8 u[X448_SIZE], u8 res[X448_SIZE])407{408return x25519_448_core(k, u, res, X448_SIZE);409}410411int x448_gen_priv_key(u8 priv_key[X448_SIZE])412{413return x25519_448_gen_priv_key(priv_key, X448_SIZE);414}415416int x448_init_pub_key(const u8 priv_key[X448_SIZE], u8 pub_key[X448_SIZE])417{418return x25519_448_init_pub_key(priv_key, pub_key, X448_SIZE);419}420421int x448_derive_secret(const u8 priv_key[X448_SIZE], const u8 peer_pub_key[X448_SIZE], u8 shared_secret[X448_SIZE])422{423return x25519_448_derive_secret(priv_key, peer_pub_key, shared_secret, X448_SIZE);424}425#endif426427#else /* !(defined(WITH_X25519) || defined(WITH_X448)) */428429/*430* Dummy definition to avoid the empty translation unit ISO C warning431*/432typedef int dummy;433434#endif /* defined(WITH_X25519) || defined(WITH_X448) */435436437