diff --git a/common/sexputil.c b/common/sexputil.c index 1fb00776f..b7ddea8fc 100644 --- a/common/sexputil.c +++ b/common/sexputil.c @@ -690,6 +690,300 @@ get_ecc_q_from_canon_sexp (const unsigned char *keydata, size_t keydatalen, } +/* 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) diff --git a/common/t-sexputil.c b/common/t-sexputil.c index 659eadd3a..d75090c5b 100644 --- a/common/t-sexputil.c +++ b/common/t-sexputil.c @@ -28,6 +28,10 @@ __FILE__,__LINE__, (a)); \ exit (1); \ } while(0) +#define fail2(a,e) do { fprintf (stderr, "%s:%d: test %d failed: %s\n", \ + __FILE__,__LINE__, (a), gpg_strerror ((e))); \ + exit (1); \ + } while(0) static void @@ -279,6 +283,206 @@ test_cmp_canon_sexp (void) } +static void +test_ecc_uncompress (void) +{ + struct { + const char *a; /* Uncompressed. */ + const char *b; /* Compressed. */ + } + tests[] = { + { + "(public-key" + " (ecc" + " (curve brainpoolP256r1)" + " (q #042ECD8679930BE2DB4AD42B8600BA3F80" + /* */"2D4D539BFF2F69B83EC9B7BBAA7F3406" + /* */"436DD11A1756AFE56CD93408410FCDA9" + /* */"BA95024EB613BD481A14FCFEC27A448A#)))", + /* The same in compressed form. */ + "(public-key" + " (ecc" + " (curve brainpoolP256r1)" + " (q #022ECD8679930BE2DB4AD42B8600BA3F80" + /* */"2D4D539BFF2F69B83EC9B7BBAA7F3406#)))" + }, + { + "(public-key" + " (ecc" + " (curve brainpoolP256r1)" + " (q #045B784CA008EE64AB3D85017EE0D2BE87" + /* */"558762C7300E0C8E06B1F9AF7C031458" + /* */"9EBBA41915313417BA54218EB0569C59" + /* */"0B156C76DBCAB6E84575E6EF68CE7B87#)))", + /* The same in compressed form. */ + "(public-key" + " (ecc" + " (curve brainpoolP256r1)" + " (q #035B784CA008EE64AB3D85017EE0D2BE87" + /* */"558762C7300E0C8E06B1F9AF7C031458#)))" + }, + { /* A key which does not require a conversion. */ + "(public-key" + " (ecdsa" + " (p #00FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF#)" + " (curve \"NIST P-256\")" + " (b #5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B#)" + " (g #046B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C2964FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5#)" + " (n #00FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551#)" + " (h #000000000000000000000000000000000000000000000000000000000000000001#)" + " (q #04C8A4CEC2E9A9BC8E173531A67B0840DF345C32E261ADD780E6D83D56EFADFD5DE872F8B854819B59543CE0B7F822330464FBC4E6324DADDCD9D059554F63B344#)))" + }, + { /* Nothing to do for an RSA private key. */ + "(private-key" + " (rsa" + " (n #00B6B509596A9ECABC939212F891E656A626BA07DA8521A9CAD4C08E640C04052FBB87F424EF1A0275A48A9299AC9DB69ABE3D0124E6C756B1F7DFB9B842D6251AEA6EE85390495CADA73D671537FCE5850A932F32BAB60AB1AC1F852C1F83C625E7A7D70CDA9EF16D5C8E47739D77DF59261ABE8454807FF441E143FBD37F8545#)" + " (e #010001#)" + " (d #077AD3DE284245F4806A1B82B79E616FBDE821C82D691A65665E57B5FAD3F34E67F401E7BD2E28699E89D9C496CF821945AE83AC7A1231176A196BA6027E77D85789055D50404A7A2A95B1512F91F190BBAEF730ED550D227D512F89C0CDB31AC06FA9A19503DDF6B66D0B42B9691BFD6140EC1720FFC48AE00C34796DC899E5#)" + " (p #00D586C78E5F1B4BF2E7CD7A04CA091911706F19788B93E44EE20AAF462E8363E98A72253ED845CCBF2481BB351E8557C85BCFFF0DABDBFF8E26A79A0938096F27#)" + " (q #00DB0CDF60F26F2A296C88D6BF9F8E5BE45C0DDD713C96CC73EBCB48B061740943F21D2A93D6E42A7211E7F02A95DCED6C390A67AD21ECF739AE8A0CA46FF2EBB3#)" + " (u #33149195F16912DB20A48D020DBC3B9E3881B39D722BF79378F6340F43148A6E9FC5F53E2853B7387BA4443BA53A52FCA8173DE6E85B42F9783D4A7817D0680B#)))" + }, + { /* Nothing to do dor a DSA key. */ + " (public-key" + " (dsa" + " (p #0084E4C626E16005770BD9509ABF7354492E85B8C0060EFAAAEC617F725B592FAA59DF5460575F41022776A9718CE62EDD542AB73C7720869EBDBC834D174ADCD7136827DF51E2613545A25CA573BC502A61B809000B6E35F5EB7FD6F18C35678C23EA1C3638FB9CFDBA2800EE1B62F41A4479DE824F2834666FBF8DC5B53C2617#)" + " (q #00B0E6F710051002A9F425D98A677B18E0E5B038AB#)" + " (g #44370CEE0FE8609994183DBFEBA7EEA97D466838BCF65EFF506E35616DA93FA4E572A2F08886B74977BC00CA8CD3DBEA7AEB7DB8CBB180E6975E0D2CA76E023E6DE9F8CCD8826EBA2F72B8516532F6001DEFFAE76AA5E59E0FA33DBA3999B4E92D1703098CDEDCC416CF008801964084CDE1980132B2B78CB4CE9C15A559528B#)" + " (y #3D5DD14AFA2BF24A791E285B90232213D0E3BA74AB1109E768AED19639A322F84BB7D959E2BA92EF73DE4C7F381AA9F4053CFA3CD4527EF9043E304E5B95ED0A3A5A9D590AA641C13DB2B6E32B9B964A6A2C730DD3EA7C8E13F7A140AFF1A91CE375E9B9B960384779DC4EA180FA1F827C52288F366C0770A220F50D6D8FD6F6#)))" + }, + { /* Nothing to do for an ECDSA key w/o curvename. */ + "(public-key" + " (ecdsa(flags param)" + " (p #00FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF#)" + " (a #00FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC#)" + " (b #5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B#)" + " (g #046B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C2964FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5#)" + " (n #00FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551#)" + " (h #000000000000000000000000000000000000000000000000000000000000000001#)" + " (q #04C8A4CEC2E9A9BC8E173531A67B0840DF345C32E261ADD780E6D83D56EFADFD5DE872F8B854819B59543CE0B7F822330464FBC4E6324DADDCD9D059554F63B344#)))" + }, + { /* Nothing to do for Ed25519 key. */ + "(public-key" + " (ecc" + " (curve Ed25519)" + " (q #04" + " 1CC662926E7EFF4982B7FB8B928E61CD74CCDD85277CC57196C3AD20B611085F" + " 47BD24842905C049257673B3F5249524E0A41FAA17B25B818D0F97E625F1A1D0#)" + " ))" + }, + { /* Nothing to do for Ed25519 with EdDSA key. */ + "(public-key" + " (ecc" + " (curve Ed25519)(flags eddsa)" + " (q #773E72848C1FD5F9652B29E2E7AF79571A04990E96F2016BF4E0EC1890C2B7DB#)" + " ))" + }, + { /* Nothing to do for Ed25519 with EdDSA key with prefix. */ + "(public-key" + " (ecc" + " (curve Ed25519)(flags eddsa)" + " (q #40" + " 773E72848C1FD5F9652B29E2E7AF79571A04990E96F2016BF4E0EC1890C2B7DB#)" + " ))" + }, + { /* Nothing to do for Ed25519 with EdDSA key with uncompress prefix. */ + "(public-key" + " (ecc" + " (curve Ed25519)(flags eddsa)" + " (q #04" + " 629ad237d1ed04dcd4abe1711dd699a1cf51b1584c4de7a4ef8b8a640180b26f" + " 5bb7c29018ece0f46b01f2960e99041a5779afe7e2292b65f9d51f8c84723e77#)" + " ))" + }, + { /* Noting to do for a Cv25519 tweaked key. */ + "(public-key" + " (ecc" + " (curve Curve25519)(flags djb-tweak)" + " (q #40" + " 918C1733127F6BF2646FAE3D081A18AE77111C903B906310B077505EFFF12740#)" + " ))" + }, + { /* Nothing to do for a shadowed key. */ + "(shadowed-private-key" + " (rsa" + " (n #00B493C79928398DA9D99AC0E949FE6EB62F683CB974FFFBFBC01066F5C9A89B" + " D3DC48EAD7C65F36EA943C2B2C865C26C4884FF9EDFDA8C99C855B737D77EEF6" + " B85DBC0CCEC0E900C1F89A6893A2A93E8B31028469B6927CEB2F08687E547C68" + " 6B0A2F7E50A194FF7AB7637E03DE0912EF7F6E5F1EC37625BD1620CCC2E7A564" + " 31E168CDAFBD1D9E61AE47A69A6FA03EF22F844528A710B2392F262B95A3078C" + " F321DC8325F92A5691EF69F34FD0DE0B22C79D29DC87723FCADE463829E8E5F7" + " D196D73D6C9C180F6A6A0DDBF7B9D8F7FA293C36163B12199EF6A1A95CAE4051" + " E3069C522CC6C4A7110F663A5DAD20F66C13A1674D050088208FAE4F33B3AB51" + " 03#)" + " (e #00010001#)" + " (shadowed t1-v1" + " (#D2760001240102000005000123350000# OPENPGP.1)" + ")))" + }, + { + NULL + }}; + gpg_error_t err; + int idx; + gcry_sexp_t sexp; + unsigned char *abuf, *bbuf, *rbuf; + size_t abuflen, bbuflen, rbuflen; + + + for (idx=0; tests[idx].a; idx++) + { + err = gcry_sexp_new (&sexp, tests[idx].a, 0, 1); + if (err) + fail2 (idx,err); + err = make_canon_sexp (sexp, &abuf, &abuflen); + if (err) + fail2 (idx,err); + gcry_sexp_release (sexp); + + if (tests[idx].b) + { + err = gcry_sexp_new (&sexp, tests[idx].b, 0, 1); + if (err) + fail2 (idx,err); + err = make_canon_sexp (sexp, &bbuf, &bbuflen); + if (err) + fail2 (idx,err); + gcry_sexp_release (sexp); + } + else + bbuf = NULL; + + err = uncompress_ecc_q_in_canon_sexp (abuf, abuflen, &rbuf, &rbuflen); + if (err) + fail2 (idx,err); + if (rbuf) + fail (idx); /* Converted without a need. */ + + if (bbuf) + { + err = uncompress_ecc_q_in_canon_sexp (bbuf, bbuflen, &rbuf, &rbuflen); + if (err) + fail2 (idx,err); + if (!rbuf) + fail (idx); /* Not converted despite a need for it. */ + + /* log_printcanon (" orig:", abuf, abuflen); */ + /* log_printcanon (" comp:", bbuf, bbuflen); */ + /* log_printcanon ("uncomp:", rbuf, rbuflen); */ + + if (rbuflen != abuflen || memcmp (rbuf, abuf, abuflen)) + fail (idx); + } + + xfree (abuf); + xfree (bbuf); + xfree (rbuf); + } +} + + + + int main (int argc, char **argv) { @@ -288,6 +492,7 @@ main (int argc, char **argv) test_hash_algo_from_sigval (); test_make_canon_sexp_from_rsa_pk (); test_cmp_canon_sexp (); + test_ecc_uncompress (); return 0; } diff --git a/common/util.h b/common/util.h index 89cde696e..0427bc891 100644 --- a/common/util.h +++ b/common/util.h @@ -207,6 +207,10 @@ 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 uncompress_ecc_q_in_canon_sexp (const unsigned char *keydata, + size_t keydatalen, + unsigned char **r_newkeydata, + size_t *r_newkeydatalen); int get_pk_algo_from_key (gcry_sexp_t key); int get_pk_algo_from_canon_sexp (const unsigned char *keydata,