diff mbox series

[v4,01/12] crypto: ecdsa - Convert byte arrays with key coordinates to digits

Message ID 20240301022007.344948-2-stefanb@linux.ibm.com (mailing list archive)
State New
Headers show
Series Add support for NIST P521 to ecdsa | expand

Commit Message

Stefan Berger March 1, 2024, 2:19 a.m. UTC
For NIST P192/256/384 the public key's x and y parameters could be copied
directly from a given array since both parameters filled 'ndigits' of
digits (a 'digit' is a u64). For support of NIST P521 the key parameters
need to have leading zeros prepended to the most significant digit since
only 2 bytes of the most significant digit are provided.

Therefore, implement ecc_digits_from_bytes to convert a byte array into an
array of digits and use this function in ecdsa_set_pub_key where an input
byte array needs to be converted into digits.

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
---
 crypto/ecdsa.c                | 14 +++++++++-----
 include/crypto/internal/ecc.h | 25 +++++++++++++++++++++++++
 2 files changed, 34 insertions(+), 5 deletions(-)

Comments

Jarkko Sakkinen March 1, 2024, 8:36 p.m. UTC | #1
On Fri Mar 1, 2024 at 4:19 AM EET, Stefan Berger wrote:
> For NIST P192/256/384 the public key's x and y parameters could be copied
> directly from a given array since both parameters filled 'ndigits' of
> digits (a 'digit' is a u64). For support of NIST P521 the key parameters
> need to have leading zeros prepended to the most significant digit since
> only 2 bytes of the most significant digit are provided.
>
> Therefore, implement ecc_digits_from_bytes to convert a byte array into an
> array of digits and use this function in ecdsa_set_pub_key where an input
> byte array needs to be converted into digits.
>
> Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
> ---
>  crypto/ecdsa.c                | 14 +++++++++-----
>  include/crypto/internal/ecc.h | 25 +++++++++++++++++++++++++
>  2 files changed, 34 insertions(+), 5 deletions(-)
>
> diff --git a/crypto/ecdsa.c b/crypto/ecdsa.c
> index fbd76498aba8..6653dec17327 100644
> --- a/crypto/ecdsa.c
> +++ b/crypto/ecdsa.c
> @@ -222,9 +222,8 @@ static int ecdsa_ecc_ctx_reset(struct ecc_ctx *ctx)
>  static int ecdsa_set_pub_key(struct crypto_akcipher *tfm, const void *key, unsigned int keylen)
>  {
>  	struct ecc_ctx *ctx = akcipher_tfm_ctx(tfm);
> +	unsigned int digitlen, ndigits;
>  	const unsigned char *d = key;
> -	const u64 *digits = (const u64 *)&d[1];
> -	unsigned int ndigits;
>  	int ret;
>  
>  	ret = ecdsa_ecc_ctx_reset(ctx);
> @@ -238,12 +237,17 @@ static int ecdsa_set_pub_key(struct crypto_akcipher *tfm, const void *key, unsig
>  		return -EINVAL;
>  
>  	keylen--;
> -	ndigits = (keylen >> 1) / sizeof(u64);
> +	digitlen = keylen >> 1;
> +
> +	ndigits = DIV_ROUND_UP(digitlen, sizeof(u64));
>  	if (ndigits != ctx->curve->g.ndigits)
>  		return -EINVAL;
>  
> -	ecc_swap_digits(digits, ctx->pub_key.x, ndigits);
> -	ecc_swap_digits(&digits[ndigits], ctx->pub_key.y, ndigits);
> +	d++;
> +
> +	ecc_digits_from_bytes(d, digitlen, ctx->pub_key.x, ndigits);
> +	ecc_digits_from_bytes(&d[digitlen], digitlen, ctx->pub_key.y, ndigits);
> +
>  	ret = ecc_is_pubkey_valid_full(ctx->curve, &ctx->pub_key);
>  
>  	ctx->pub_key_set = ret == 0;
> diff --git a/include/crypto/internal/ecc.h b/include/crypto/internal/ecc.h
> index 4f6c1a68882f..48a04605da7f 100644
> --- a/include/crypto/internal/ecc.h
> +++ b/include/crypto/internal/ecc.h
> @@ -56,6 +56,31 @@ static inline void ecc_swap_digits(const void *in, u64 *out, unsigned int ndigit
>  		out[i] = get_unaligned_be64(&src[ndigits - 1 - i]);
>  }
>  
> +/**
> + * ecc_digits_from_bytes() - Create ndigits-sized digits array from byte array
> + * @in:       Input byte array
> + * @nbytes    Size of input byte array
> + * @out       Output digits array
> + * @ndigits:  Number of digits to create from byte array
> + */
> +static inline void ecc_digits_from_bytes(const u8 *in, unsigned int nbytes,
> +					 u64 *out, unsigned int ndigits)
> +{
> +	unsigned int o = nbytes & 7;
> +	u64 msd = 0;
> +	size_t i;
> +
> +	if (o == 0) {
> +		ecc_swap_digits(in, out, ndigits);
> +	} else {
> +		/* if key length is not a multiple of 64 bits (NIST P521) */
> +		for (i = 0; i < o; i++)
> +			msd = (msd << 8) | in[i];
> +		out[ndigits - 1] = msd;
> +		ecc_swap_digits(&in[o], out, (nbytes - o) >> 3);
> +	}

https://lore.kernel.org/keyrings/CZIOY02QS2QC.LV0A0HNT7VKM@suppilovahvero/

BR, Jarkko
Lukas Wunner March 2, 2024, 9:34 p.m. UTC | #2
On Thu, Feb 29, 2024 at 09:19:56PM -0500, Stefan Berger wrote:
> --- a/crypto/ecdsa.c
> +++ b/crypto/ecdsa.c
> @@ -222,9 +222,8 @@ static int ecdsa_ecc_ctx_reset(struct ecc_ctx *ctx)
>  static int ecdsa_set_pub_key(struct crypto_akcipher *tfm, const void *key, unsigned int keylen)
>  {
>  	struct ecc_ctx *ctx = akcipher_tfm_ctx(tfm);
> +	unsigned int digitlen, ndigits;
>  	const unsigned char *d = key;
> -	const u64 *digits = (const u64 *)&d[1];
> -	unsigned int ndigits;
>  	int ret;
>  
>  	ret = ecdsa_ecc_ctx_reset(ctx);

Hm, the removal of digits isn't strictly necessary.  If you would keep it,
the patch would become simpler (fewer lines changes).


> @@ -238,12 +237,17 @@ static int ecdsa_set_pub_key(struct crypto_akcipher *tfm, const void *key, unsig
>  		return -EINVAL;
>  
>  	keylen--;
> -	ndigits = (keylen >> 1) / sizeof(u64);
> +	digitlen = keylen >> 1;
> +
> +	ndigits = DIV_ROUND_UP(digitlen, sizeof(u64));

Instead of introducing an additional digitlen variable, you could just
use keylen.  It seems it's not used in the remainder of the function,
so modifying it is harmless:

 	keylen--;
+ 	keylen >>= 1;
-	ndigits = (keylen >> 1) / sizeof(u64);
+	ndigits = DIV_ROUND_UP(digitlen, sizeof(u64));

Just a suggestion.

Thanks,

Lukas
James Bottomley March 3, 2024, 6:37 a.m. UTC | #3
On Sat, 2024-03-02 at 22:34 +0100, Lukas Wunner wrote:
> On Thu, Feb 29, 2024 at 09:19:56PM -0500, Stefan Berger wrote:
[...]
> > @@ -238,12 +237,17 @@ static int ecdsa_set_pub_key(struct
> > crypto_akcipher *tfm, const void *key, unsig
> >                 return -EINVAL;
> >  
> >         keylen--;
> > -       ndigits = (keylen >> 1) / sizeof(u64);
> > +       digitlen = keylen >> 1;
> > +
> > +       ndigits = DIV_ROUND_UP(digitlen, sizeof(u64));
> 
> Instead of introducing an additional digitlen variable, you could
> just use keylen.  It seems it's not used in the remainder of the
> function, so modifying it is harmless:
> 
>         keylen--;
> +       keylen >>= 1;
> -       ndigits = (keylen >> 1) / sizeof(u64);
> +       ndigits = DIV_ROUND_UP(digitlen, sizeof(u64));
> 
> Just a suggestion.

The compiler will optimize the variables like this anyway (reuse
registers or frames after a current consumer becomes unused) so there's
no requirement to do this for efficiency, the only real question is
whether using digitlen is clearer than reusing keylen, which I'll leave
to the author.

James
Stefan Berger March 3, 2024, 4:34 p.m. UTC | #4
On 3/2/24 16:34, Lukas Wunner wrote:
> On Thu, Feb 29, 2024 at 09:19:56PM -0500, Stefan Berger wrote:
>> --- a/crypto/ecdsa.c
>> +++ b/crypto/ecdsa.c
>> @@ -222,9 +222,8 @@ static int ecdsa_ecc_ctx_reset(struct ecc_ctx *ctx)
>>   static int ecdsa_set_pub_key(struct crypto_akcipher *tfm, const void *key, unsigned int keylen)
>>   {
>>   	struct ecc_ctx *ctx = akcipher_tfm_ctx(tfm);
>> +	unsigned int digitlen, ndigits;
>>   	const unsigned char *d = key;
>> -	const u64 *digits = (const u64 *)&d[1];
>> -	unsigned int ndigits;
>>   	int ret;
>>   
>>   	ret = ecdsa_ecc_ctx_reset(ctx);
> 
> Hm, the removal of digits isn't strictly necessary.  If you would keep it,
> the patch would become simpler (fewer lines changes).
> 
> 
>> @@ -238,12 +237,17 @@ static int ecdsa_set_pub_key(struct crypto_akcipher *tfm, const void *key, unsig
>>   		return -EINVAL;
>>   
>>   	keylen--;
>> -	ndigits = (keylen >> 1) / sizeof(u64);
>> +	digitlen = keylen >> 1;
>> +
>> +	ndigits = DIV_ROUND_UP(digitlen, sizeof(u64));
> 
> Instead of introducing an additional digitlen variable, you could just
> use keylen.  It seems it's not used in the remainder of the function,
> so modifying it is harmless:
> 
>   	keylen--;
> + 	keylen >>= 1;
> -	ndigits = (keylen >> 1) / sizeof(u64);
> +	ndigits = DIV_ROUND_UP(digitlen, sizeof(u64));
> 
> Just a suggestion.

I would prefer 'digitlen' rather than repurposing keylen and giving it a 
different meaning...

    Stefan
> 
> Thanks,
> 
> Lukas
diff mbox series

Patch

diff --git a/crypto/ecdsa.c b/crypto/ecdsa.c
index fbd76498aba8..6653dec17327 100644
--- a/crypto/ecdsa.c
+++ b/crypto/ecdsa.c
@@ -222,9 +222,8 @@  static int ecdsa_ecc_ctx_reset(struct ecc_ctx *ctx)
 static int ecdsa_set_pub_key(struct crypto_akcipher *tfm, const void *key, unsigned int keylen)
 {
 	struct ecc_ctx *ctx = akcipher_tfm_ctx(tfm);
+	unsigned int digitlen, ndigits;
 	const unsigned char *d = key;
-	const u64 *digits = (const u64 *)&d[1];
-	unsigned int ndigits;
 	int ret;
 
 	ret = ecdsa_ecc_ctx_reset(ctx);
@@ -238,12 +237,17 @@  static int ecdsa_set_pub_key(struct crypto_akcipher *tfm, const void *key, unsig
 		return -EINVAL;
 
 	keylen--;
-	ndigits = (keylen >> 1) / sizeof(u64);
+	digitlen = keylen >> 1;
+
+	ndigits = DIV_ROUND_UP(digitlen, sizeof(u64));
 	if (ndigits != ctx->curve->g.ndigits)
 		return -EINVAL;
 
-	ecc_swap_digits(digits, ctx->pub_key.x, ndigits);
-	ecc_swap_digits(&digits[ndigits], ctx->pub_key.y, ndigits);
+	d++;
+
+	ecc_digits_from_bytes(d, digitlen, ctx->pub_key.x, ndigits);
+	ecc_digits_from_bytes(&d[digitlen], digitlen, ctx->pub_key.y, ndigits);
+
 	ret = ecc_is_pubkey_valid_full(ctx->curve, &ctx->pub_key);
 
 	ctx->pub_key_set = ret == 0;
diff --git a/include/crypto/internal/ecc.h b/include/crypto/internal/ecc.h
index 4f6c1a68882f..48a04605da7f 100644
--- a/include/crypto/internal/ecc.h
+++ b/include/crypto/internal/ecc.h
@@ -56,6 +56,31 @@  static inline void ecc_swap_digits(const void *in, u64 *out, unsigned int ndigit
 		out[i] = get_unaligned_be64(&src[ndigits - 1 - i]);
 }
 
+/**
+ * ecc_digits_from_bytes() - Create ndigits-sized digits array from byte array
+ * @in:       Input byte array
+ * @nbytes    Size of input byte array
+ * @out       Output digits array
+ * @ndigits:  Number of digits to create from byte array
+ */
+static inline void ecc_digits_from_bytes(const u8 *in, unsigned int nbytes,
+					 u64 *out, unsigned int ndigits)
+{
+	unsigned int o = nbytes & 7;
+	u64 msd = 0;
+	size_t i;
+
+	if (o == 0) {
+		ecc_swap_digits(in, out, ndigits);
+	} else {
+		/* if key length is not a multiple of 64 bits (NIST P521) */
+		for (i = 0; i < o; i++)
+			msd = (msd << 8) | in[i];
+		out[ndigits - 1] = msd;
+		ecc_swap_digits(&in[o], out, (nbytes - o) >> 3);
+	}
+}
+
 /**
  * ecc_is_key_valid() - Validate a given ECDH private key
  *