1
0
Fork 0
mirror of git://git.gnupg.org/gnupg.git synced 2025-07-03 22:56:33 +02:00

common: New function to uncompress an ECC public key.

* common/sexputil.c (ec2os): New.
(uncompress_ecc_q_in_canon_sexp): New.

* common/t-sexputil.c (fail2): new.
(test_ecc_uncompress): New.
(main): Run new test.
--

Signed-off-by: Werner Koch <wk@gnupg.org>
(cherry picked from commit 935765b451)
This commit is contained in:
Werner Koch 2021-04-29 12:31:14 +02:00
parent 473e649ea1
commit c825117c5f
No known key found for this signature in database
GPG key ID: E3FDFF218E45B72B
3 changed files with 593 additions and 1 deletions

View file

@ -602,6 +602,388 @@ get_rsa_pk_from_canon_sexp (const unsigned char *keydata, size_t keydatalen,
}
/* Return the public key parameter Q of a public RSA or ECC key
* expressed as an canonical encoded S-expression. */
gpg_error_t
get_ecc_q_from_canon_sexp (const unsigned char *keydata, size_t keydatalen,
unsigned char const **r_q, size_t *r_qlen)
{
gpg_error_t err;
const unsigned char *buf, *tok;
size_t buflen, toklen;
int depth, last_depth1, last_depth2;
const unsigned char *ecc_q = NULL;
size_t ecc_q_len;
*r_q = NULL;
*r_qlen = 0;
buf = keydata;
buflen = keydatalen;
depth = 0;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (!tok || toklen != 10 || memcmp ("public-key", tok, toklen))
return gpg_error (GPG_ERR_BAD_PUBKEY);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 3 && !memcmp ("ecc", tok, toklen))
;
else if (tok && toklen == 5 && (!memcmp ("ecdsa", tok, toklen)
|| !memcmp ("eddsa", tok, toklen)))
;
else
return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 1)
{
const unsigned char **mpi;
size_t *mpi_len;
switch (*tok)
{
case 'q': mpi = &ecc_q; mpi_len = &ecc_q_len; break;
default: mpi = NULL; mpi_len = NULL; break;
}
if (mpi && *mpi)
return gpg_error (GPG_ERR_DUP_VALUE);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && mpi)
{
*mpi = tok;
*mpi_len = toklen;
}
}
/* Skip to the end of the list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
return err;
}
if (err)
return err;
if (!ecc_q || !ecc_q_len)
return gpg_error (GPG_ERR_BAD_PUBKEY);
*r_q = ecc_q;
*r_qlen = ecc_q_len;
return 0;
}
/* Return an uncompressed point (X,Y) in P at R_BUF as a malloced
* buffer with its byte length stored at R_BUFLEN. May not be used
* for sensitive data. */
static gpg_error_t
ec2os (gcry_mpi_t x, gcry_mpi_t y, gcry_mpi_t p,
unsigned char **r_buf, unsigned int *r_buflen)
{
gpg_error_t err;
int pbytes = (mpi_get_nbits (p)+7)/8;
size_t n;
unsigned char *buf, *ptr;
*r_buf = NULL;
*r_buflen = 0;
buf = xtrymalloc (1 + 2*pbytes);
if (!buf)
return gpg_error_from_syserror ();
*buf = 04; /* Uncompressed point. */
ptr = buf+1;
err = gcry_mpi_print (GCRYMPI_FMT_USG, ptr, pbytes, &n, x);
if (err)
{
xfree (buf);
return err;
}
if (n < pbytes)
{
memmove (ptr+(pbytes-n), ptr, n);
memset (ptr, 0, (pbytes-n));
}
ptr += pbytes;
err = gcry_mpi_print (GCRYMPI_FMT_USG, ptr, pbytes, &n, y);
if (err)
{
xfree (buf);
return err;
}
if (n < pbytes)
{
memmove (ptr+(pbytes-n), ptr, n);
memset (ptr, 0, (pbytes-n));
}
*r_buf = buf;
*r_buflen = 1 + 2*pbytes;
return 0;
}
/* Convert the ECC parameter Q in the canonical s-expression
* (KEYDATA,KEYDATALEN) to uncompressed form. On success and if a
* conversion was done, the new canonical encoded s-expression is
* returned at (R_NEWKEYDAT,R_NEWKEYDATALEN); if a conversion was not
* required (NULL,0) is stored there. On error an error code is
* returned. The function may take any kind of key but will only do
* the conversion for ECC curves where compression is supported. */
gpg_error_t
uncompress_ecc_q_in_canon_sexp (const unsigned char *keydata,
size_t keydatalen,
unsigned char **r_newkeydata,
size_t *r_newkeydatalen)
{
gpg_error_t err;
const unsigned char *buf, *tok;
size_t buflen, toklen, n;
int depth, last_depth1, last_depth2;
const unsigned char *q_ptr; /* Points to the value of "q". */
size_t q_ptrlen; /* Remaining length in KEYDATA. */
size_t q_toklen; /* Q's length including prefix. */
const unsigned char *curve_ptr; /* Points to the value of "curve". */
size_t curve_ptrlen; /* Remaining length in KEYDATA. */
gcry_mpi_t x, y; /* Point Q */
gcry_mpi_t p, a, b; /* Curve parameters. */
gcry_mpi_t x3, t, p1_4; /* Helper */
int y_bit;
unsigned char *qvalue; /* Q in uncompressed form. */
unsigned int qvaluelen;
unsigned char *dst; /* Helper */
char lenstr[35]; /* Helper for a length prefix. */
*r_newkeydata = NULL;
*r_newkeydatalen = 0;
buf = keydata;
buflen = keydatalen;
depth = 0;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (!tok)
return gpg_error (GPG_ERR_BAD_PUBKEY);
else if (toklen == 10 || !memcmp ("public-key", tok, toklen))
;
else if (toklen == 11 || !memcmp ("private-key", tok, toklen))
;
else if (toklen == 20 || !memcmp ("shadowed-private-key", tok, toklen))
;
else
return gpg_error (GPG_ERR_BAD_PUBKEY);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 3 && !memcmp ("ecc", tok, toklen))
;
else if (tok && toklen == 5 && !memcmp ("ecdsa", tok, toklen))
;
else
return 0; /* Other algo - no need for conversion. */
last_depth1 = depth;
q_ptr = curve_ptr = NULL;
q_ptrlen = 0; /*(silence cc warning)*/
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 1 && *tok == 'q' && !q_ptr)
{
q_ptr = buf;
q_ptrlen = buflen;
}
else if (tok && toklen == 5 && !memcmp (tok, "curve", 5) && !curve_ptr)
{
curve_ptr = buf;
curve_ptrlen = buflen;
}
if (q_ptr && curve_ptr)
break; /* We got all what we need. */
/* Skip to the end of the list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
return err;
}
if (err)
return err;
if (!q_ptr)
return 0; /* No Q - nothing to do. */
/* Get Q's value and check whether uncompressing is at all required. */
buf = q_ptr;
buflen = q_ptrlen;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (toklen < 2 || !(*tok == 0x02 || *tok == 0x03))
return 0; /* Invalid length or not compressed. */
q_toklen = buf - q_ptr; /* We want the length with the prefix. */
/* Put the x-coordinate of q into X and remember the y bit */
y_bit = (*tok == 0x03);
err = gcry_mpi_scan (&x, GCRYMPI_FMT_USG, tok+1, toklen-1, NULL);
if (err)
return err;
/* For uncompressing we need to know the curve. */
if (!curve_ptr)
{
gcry_mpi_release (x);
return gpg_error (GPG_ERR_INV_CURVE);
}
buf = curve_ptr;
buflen = curve_ptrlen;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
{
gcry_mpi_release (x);
return err;
}
{
char name[50];
gcry_sexp_t curveparam;
if (toklen + 1 > sizeof name)
{
gcry_mpi_release (x);
return gpg_error (GPG_ERR_TOO_LARGE);
}
mem2str (name, tok, toklen+1);
curveparam = gcry_pk_get_param (GCRY_PK_ECC, name);
if (!curveparam)
{
gcry_mpi_release (x);
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
}
err = gcry_sexp_extract_param (curveparam, NULL, "pab", &p, &a, &b, NULL);
gcry_sexp_release (curveparam);
if (err)
{
gcry_mpi_release (x);
return gpg_error (GPG_ERR_INTERNAL);
}
}
if (!mpi_test_bit (p, 1))
{
/* No support for point compression for this curve. */
gcry_mpi_release (x);
gcry_mpi_release (p);
gcry_mpi_release (a);
gcry_mpi_release (b);
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
/*
* Recover Y. The Weierstrass curve: y^2 = x^3 + a*x + b
*/
x3 = mpi_new (0);
t = mpi_new (0);
p1_4 = mpi_new (0);
y = mpi_new (0);
/* Compute right hand side. */
mpi_powm (x3, x, GCRYMPI_CONST_THREE, p);
mpi_mul (t, a, x);
mpi_mod (t, t, p);
mpi_add (t, t, b);
mpi_mod (t, t, p);
mpi_add (t, t, x3);
mpi_mod (t, t, p);
/*
* When p mod 4 = 3, modular square root of A can be computed by
* A^((p+1)/4) mod p
*/
/* Compute (p+1)/4 into p1_4 */
mpi_rshift (p1_4, p, 2);
mpi_add_ui (p1_4, p1_4, 1);
mpi_powm (y, t, p1_4, p);
if (y_bit != mpi_test_bit (y, 0))
mpi_sub (y, p, y);
gcry_mpi_release (p1_4);
gcry_mpi_release (t);
gcry_mpi_release (x3);
gcry_mpi_release (a);
gcry_mpi_release (b);
err = ec2os (x, y, p, &qvalue, &qvaluelen);
gcry_mpi_release (x);
gcry_mpi_release (y);
gcry_mpi_release (p);
if (err)
return err;
snprintf (lenstr, sizeof lenstr, "%u:", (unsigned int)qvaluelen);
/* Note that for simplicity we do not subtract the old length of Q
* for the new buffer. */
*r_newkeydata = xtrymalloc (qvaluelen + strlen(lenstr) + qvaluelen);
if (!*r_newkeydata)
return gpg_error_from_syserror ();
dst = *r_newkeydata;
n = q_ptr - keydata;
memcpy (dst, keydata, n); /* Copy first part of original data. */
dst += n;
n = strlen (lenstr);
memcpy (dst, lenstr, n); /* Copy new prefix of Q's value. */
dst += n;
memcpy (dst, qvalue, qvaluelen); /* Copy new value of Q. */
dst += qvaluelen;
log_assert (q_toklen < q_ptrlen);
n = q_ptrlen - q_toklen;
memcpy (dst, q_ptr + q_toklen, n);/* Copy rest of original data. */
dst += n;
*r_newkeydatalen = dst - *r_newkeydata;
xfree (qvalue);
return 0;
}
/* Return the algo of a public KEY of SEXP. */
int
get_pk_algo_from_key (gcry_sexp_t key)