summaryrefslogtreecommitdiff
path: root/cipher/ecc-sm2.c
diff options
context:
space:
mode:
authorTianjia Zhang <tianjia.zhang@linux.alibaba.com>2020-01-20 11:42:24 +0800
committerNIIBE Yutaka <gniibe@fsij.org>2020-01-21 13:39:49 +0900
commit6b55246c77089dd372eb1807808111660fd789c7 (patch)
tree3d49f07265f04b0c2682517167ce3f16421c6717 /cipher/ecc-sm2.c
parent8d9958910e54f3fecbab6e133c3971843f6ef310 (diff)
downloadlibgcrypt-6b55246c77089dd372eb1807808111660fd789c7.tar.gz
Add elliptic curve SM2 implementation.
* configure.ac (enabled_pubkey_ciphers): Add ecc-sm2. * cipher/Makefile.am (EXTRA_libcipher_la_SOURCES): Add ecc-sm2.c. * cipher/pubkey-util.c (_gcry_pk_util_parse_flaglist, _gcry_pk_util_preparse_sigval): Add sm2 flags. * cipher/ecc.c: Support ecc-sm2. * cipher/ecc-common.h: Add declarations for ecc-sm2. * cipher/ecc-sm2.c: New. * src/cipher.h: Define PUBKEY_FLAG_SM2. -- Signed-off-by: Tianjia Zhang <tianjia.zhang@linux.alibaba.com>
Diffstat (limited to 'cipher/ecc-sm2.c')
-rw-r--r--cipher/ecc-sm2.c566
1 files changed, 566 insertions, 0 deletions
diff --git a/cipher/ecc-sm2.c b/cipher/ecc-sm2.c
new file mode 100644
index 00000000..a6341132
--- /dev/null
+++ b/cipher/ecc-sm2.c
@@ -0,0 +1,566 @@
+/* ecc-sm2.c - Elliptic Curve SM2 implementation
+ * Copyright (C) 2020 Tianjia Zhang
+ *
+ * This file is part of Libgcrypt.
+ *
+ * Libgcrypt is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * Libgcrypt is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "g10lib.h"
+#include "bithelp.h"
+#include "mpi.h"
+#include "cipher.h"
+#include "context.h"
+#include "ec-context.h"
+#include "pubkey-internal.h"
+#include "ecc-common.h"
+
+#define MPI_NBYTES(m) ((mpi_get_nbits(m) + 7) / 8)
+
+
+/* Key derivation function from X9.63/SECG */
+static gpg_err_code_t
+kdf_x9_63 (int algo, const void *in, size_t inlen, void *out, size_t outlen)
+{
+ gpg_err_code_t rc;
+ gcry_md_hd_t hd;
+ int mdlen;
+ u32 counter = 1;
+ u32 counter_be;
+ unsigned char *dgst;
+ unsigned char *pout = out;
+ size_t rlen = outlen;
+ size_t len;
+
+ rc = _gcry_md_open (&hd, algo, 0);
+ if (rc)
+ return rc;
+
+ mdlen = _gcry_md_get_algo_dlen (algo);
+
+ while (rlen > 0)
+ {
+ counter_be = be_bswap32 (counter); /* cpu_to_be32 */
+ counter++;
+
+ _gcry_md_write (hd, in, inlen);
+ _gcry_md_write (hd, &counter_be, sizeof(counter_be));
+
+ dgst = _gcry_md_read (hd, algo);
+ if (dgst == NULL)
+ {
+ rc = GPG_ERR_DIGEST_ALGO;
+ break;
+ }
+
+ len = mdlen < rlen ? mdlen : rlen; /* min(mdlen, rlen) */
+ memcpy (pout, dgst, len);
+ rlen -= len;
+ pout += len;
+
+ _gcry_md_reset (hd);
+ }
+
+ _gcry_md_close (hd);
+ return rc;
+}
+
+
+/* _gcry_ecc_sm2_encrypt description:
+ * input:
+ * data[0] : octet string
+ * output: A new S-expression with the parameters:
+ * a: c1 : generated ephemeral public key (kG)
+ * b: c3 : Hash(x2 || IN || y2)
+ * c: c2 : cipher
+ *
+ * sm2_decrypt description:
+ * in contrast to encrypt
+ */
+gpg_err_code_t
+_gcry_ecc_sm2_encrypt (gcry_sexp_t *r_ciph, gcry_mpi_t input, mpi_ec_t ec)
+{
+ gpg_err_code_t rc;
+ const int algo = GCRY_MD_SM3;
+ gcry_md_hd_t md = NULL;
+ int mdlen;
+ unsigned char *dgst;
+ gcry_mpi_t k = NULL;
+ mpi_point_struct kG, kP;
+ gcry_mpi_t x1, y1;
+ gcry_mpi_t x2, y2;
+ gcry_mpi_t x2y2 = NULL;
+ unsigned char *in = NULL;
+ unsigned int inlen;
+ unsigned char *raw;
+ unsigned int rawlen;
+ unsigned char *cipher = NULL;
+ int i;
+
+ point_init (&kG);
+ point_init (&kP);
+ x1 = mpi_new (0);
+ y1 = mpi_new (0);
+ x2 = mpi_new (0);
+ y2 = mpi_new (0);
+
+ in = _gcry_mpi_get_buffer (input, 0, &inlen, NULL);
+ if (!in)
+ {
+ rc = gpg_err_code_from_syserror ();
+ goto leave;
+ }
+
+ cipher = xtrymalloc (inlen);
+ if (!cipher)
+ {
+ rc = gpg_err_code_from_syserror ();
+ goto leave;
+ }
+
+ /* rand k in [1, n-1] */
+ k = _gcry_dsa_gen_k (ec->n, GCRY_VERY_STRONG_RANDOM);
+
+ /* [k]G = (x1, y1) */
+ _gcry_mpi_ec_mul_point (&kG, k, ec->G, ec);
+ if (_gcry_mpi_ec_get_affine (x1, y1, &kG, ec))
+ {
+ if (DBG_CIPHER)
+ log_debug ("Bad check: kG can not be a Point at Infinity!\n");
+ rc = GPG_ERR_INV_DATA;
+ goto leave;
+ }
+
+ /* [k]P = (x2, y2) */
+ _gcry_mpi_ec_mul_point (&kP, k, ec->Q, ec);
+ if (_gcry_mpi_ec_get_affine (x2, y2, &kP, ec))
+ {
+ rc = GPG_ERR_INV_DATA;
+ goto leave;
+ }
+
+ /* t = KDF(x2 || y2, klen) */
+ x2y2 = _gcry_mpi_ec_ec2os (&kP, ec);
+ raw = mpi_get_opaque (x2y2, &rawlen);
+ rawlen = (rawlen + 7) / 8;
+
+ /* skip the prefix '0x04' */
+ raw += 1;
+ rawlen -= 1;
+ rc = kdf_x9_63 (algo, raw, rawlen, cipher, inlen);
+ if (rc)
+ goto leave;
+
+ /* cipher = t xor in */
+ for (i = 0; i < inlen; i++)
+ cipher[i] ^= in[i];
+
+ /* hash(x2 || IN || y2) */
+ mdlen = _gcry_md_get_algo_dlen (algo);
+ rc = _gcry_md_open (&md, algo, 0);
+ if (rc)
+ goto leave;
+ _gcry_md_write (md, raw, MPI_NBYTES(x2));
+ _gcry_md_write (md, in, inlen);
+ _gcry_md_write (md, raw + MPI_NBYTES(x2), MPI_NBYTES(y2));
+ dgst = _gcry_md_read (md, algo);
+ if (dgst == NULL)
+ {
+ rc = GPG_ERR_DIGEST_ALGO;
+ goto leave;
+ }
+
+ if (!rc)
+ {
+ gcry_mpi_t c1;
+ gcry_mpi_t c3;
+ gcry_mpi_t c2;
+
+ c3 = mpi_new (0);
+ c2 = mpi_new (0);
+
+ c1 = _gcry_ecc_ec2os (x1, y1, ec->p);
+ _gcry_mpi_set_opaque_copy (c3, dgst, mdlen * 8);
+ _gcry_mpi_set_opaque_copy (c2, cipher, inlen * 8);
+
+ rc = sexp_build (r_ciph, NULL,
+ "(enc-val(flags sm2)(sm2(a%M)(b%M)(c%M)))",
+ c1, c3, c2);
+
+ mpi_free (c1);
+ mpi_free (c3);
+ mpi_free (c2);
+ }
+
+leave:
+ _gcry_md_close (md);
+ mpi_free (x2y2);
+ mpi_free (k);
+
+ point_free (&kG);
+ point_free (&kP);
+ mpi_free (x1);
+ mpi_free (y1);
+ mpi_free (x2);
+ mpi_free (y2);
+
+ xfree (cipher);
+ xfree (in);
+
+ return rc;
+}
+
+
+gpg_err_code_t
+_gcry_ecc_sm2_decrypt (gcry_sexp_t *r_plain, gcry_sexp_t data_list, mpi_ec_t ec)
+{
+ gpg_err_code_t rc;
+ gcry_mpi_t data_c1 = NULL;
+ gcry_mpi_t data_c3 = NULL;
+ gcry_mpi_t data_c2 = NULL;
+
+ /*
+ * Extract the data.
+ */
+ rc = sexp_extract_param (data_list, NULL, "/a/b/c",
+ &data_c1, &data_c3, &data_c2, NULL);
+ if (rc)
+ goto leave;
+ if (DBG_CIPHER)
+ {
+ log_printmpi ("ecc_decrypt d_c1", data_c1);
+ log_printmpi ("ecc_decrypt d_c3", data_c3);
+ log_printmpi ("ecc_decrypt d_c2", data_c2);
+ }
+
+ {
+ const int algo = GCRY_MD_SM3;
+ gcry_md_hd_t md = NULL;
+ int mdlen;
+ unsigned char *dgst;
+ mpi_point_struct c1;
+ mpi_point_struct kP;
+ gcry_mpi_t x2, y2;
+ gcry_mpi_t x2y2 = NULL;
+ unsigned char *in = NULL;
+ unsigned int inlen;
+ unsigned char *plain = NULL;
+ unsigned char *raw;
+ unsigned int rawlen;
+ unsigned char *c3 = NULL;
+ unsigned int c3_len;
+ int i;
+
+ point_init (&c1);
+ point_init (&kP);
+ x2 = mpi_new (0);
+ y2 = mpi_new (0);
+
+ in = mpi_get_opaque (data_c2, &inlen);
+ inlen = (inlen + 7) / 8;
+ plain = xtrymalloc (inlen);
+ if (!plain)
+ {
+ rc = gpg_err_code_from_syserror ();
+ goto leave_main;
+ }
+
+ rc = _gcry_ecc_os2ec (&c1, data_c1);
+ if (rc)
+ goto leave_main;
+
+ if (!_gcry_mpi_ec_curve_point (&c1, ec))
+ {
+ rc = GPG_ERR_INV_DATA;
+ goto leave_main;
+ }
+
+ /* [d]C1 = (x2, y2), C1 = [k]G */
+ _gcry_mpi_ec_mul_point (&kP, ec->d, &c1, ec);
+ if (_gcry_mpi_ec_get_affine (x2, y2, &kP, ec))
+ {
+ rc = GPG_ERR_INV_DATA;
+ goto leave_main;
+ }
+
+ /* t = KDF(x2 || y2, inlen) */
+ x2y2 = _gcry_mpi_ec_ec2os (&kP, ec);
+ raw = mpi_get_opaque (x2y2, &rawlen);
+ rawlen = (rawlen + 7) / 8;
+ /* skip the prefix '0x04' */
+ raw += 1;
+ rawlen -= 1;
+ rc = kdf_x9_63 (algo, raw, rawlen, plain, inlen);
+ if (rc)
+ goto leave_main;
+
+ /* plain = C2 xor t */
+ for (i = 0; i < inlen; i++)
+ plain[i] ^= in[i];
+
+ /* Hash(x2 || IN || y2) == C3 */
+ mdlen = _gcry_md_get_algo_dlen (algo);
+ rc = _gcry_md_open (&md, algo, 0);
+ if (rc)
+ goto leave_main;
+ _gcry_md_write (md, raw, MPI_NBYTES(x2));
+ _gcry_md_write (md, plain, inlen);
+ _gcry_md_write (md, raw + MPI_NBYTES(x2), MPI_NBYTES(y2));
+ dgst = _gcry_md_read (md, algo);
+ if (dgst == NULL)
+ {
+ memset (plain, 0, inlen);
+ rc = GPG_ERR_DIGEST_ALGO;
+ goto leave_main;
+ }
+ c3 = mpi_get_opaque (data_c3, &c3_len);
+ c3_len = (c3_len + 7) / 8;
+ if (c3_len != mdlen || memcmp (dgst, c3, c3_len) != 0)
+ {
+ memset (plain, 0, inlen);
+ rc = GPG_ERR_INV_DATA;
+ goto leave_main;
+ }
+
+ if (!rc)
+ {
+ gcry_mpi_t r;
+
+ r = mpi_new (inlen * 8);
+ _gcry_mpi_set_buffer (r, plain, inlen, 0);
+
+ rc = sexp_build (r_plain, NULL, "(value %m)", r);
+
+ mpi_free (r);
+ }
+
+ leave_main:
+ _gcry_md_close (md);
+ mpi_free (x2y2);
+ xfree (plain);
+
+ point_free (&c1);
+ point_free (&kP);
+ mpi_free (x2);
+ mpi_free (y2);
+ }
+
+ leave:
+ _gcry_mpi_release (data_c1);
+ _gcry_mpi_release (data_c3);
+ _gcry_mpi_release (data_c2);
+
+ return rc;
+}
+
+
+/* Compute an SM2 signature.
+ * Return the signature struct (r,s) from the message hash. The caller
+ * must have allocated R and S.
+ */
+gpg_err_code_t
+_gcry_ecc_sm2_sign (gcry_mpi_t input, mpi_ec_t ec,
+ gcry_mpi_t r, gcry_mpi_t s,
+ int flags, int hashalgo)
+{
+ gpg_err_code_t rc = 0;
+ int extraloops = 0;
+ gcry_mpi_t hash;
+ const void *abuf;
+ unsigned int abits, qbits;
+ gcry_mpi_t tmp = NULL;
+ gcry_mpi_t k = NULL;
+ gcry_mpi_t rk = NULL;
+ mpi_point_struct kG;
+ gcry_mpi_t x1;
+
+ if (DBG_CIPHER)
+ log_mpidump ("sm2 sign hash ", input);
+
+ qbits = mpi_get_nbits (ec->n);
+
+ /* Convert the INPUT into an MPI if needed. */
+ rc = _gcry_dsa_normalize_hash (input, &hash, qbits);
+ if (rc)
+ return rc;
+
+ point_init (&kG);
+ x1 = mpi_new (0);
+ rk = mpi_new (0);
+ tmp = mpi_new (0);
+
+ for (;;)
+ {
+ /* rand k in [1, n-1] */
+ if ((flags & PUBKEY_FLAG_RFC6979) && hashalgo)
+ {
+ /* Use Pornin's method for deterministic DSA. If this
+ flag is set, it is expected that HASH is an opaque
+ MPI with the to be signed hash. That hash is also
+ used as h1 from 3.2.a. */
+ if (!mpi_is_opaque (input))
+ {
+ rc = GPG_ERR_CONFLICT;
+ goto leave;
+ }
+
+ abuf = mpi_get_opaque (input, &abits);
+ rc = _gcry_dsa_gen_rfc6979_k (&k, ec->n, ec->d,
+ abuf, (abits+7)/8,
+ hashalgo, extraloops);
+ if (rc)
+ goto leave;
+ extraloops++;
+ }
+ else
+ k = _gcry_dsa_gen_k (ec->n, GCRY_VERY_STRONG_RANDOM);
+
+ _gcry_dsa_modify_k (k, ec->n, qbits);
+
+ /* [k]G = (x1, y1) */
+ _gcry_mpi_ec_mul_point (&kG, k, ec->G, ec);
+ if (_gcry_mpi_ec_get_affine (x1, NULL, &kG, ec))
+ {
+ rc = GPG_ERR_INV_DATA;
+ goto leave;
+ }
+
+ /* r = (e + x1) % n */
+ mpi_addm (r, hash, x1, ec->n);
+
+ /* r != 0 && r + k != n */
+ if (mpi_cmp_ui (r, 0) == 0)
+ continue;
+ mpi_add (rk, r, k);
+ if (mpi_cmp (rk, ec->n) == 0)
+ continue;
+
+ /* s = ((d + 1)^-1 * (k - rd)) % n */
+ mpi_addm (s, ec->d, GCRYMPI_CONST_ONE, ec->n);
+ mpi_invm (s, s, ec->n);
+ mpi_mulm (tmp, r, ec->d, ec->n);
+ mpi_subm (tmp, k, tmp, ec->n);
+ mpi_mulm (s, s, tmp, ec->n);
+
+ /* s != 0 */
+ if (mpi_cmp_ui (s, 0) == 0)
+ continue;
+
+ break; /* Okay */
+ }
+
+ if (DBG_CIPHER)
+ {
+ log_mpidump ("sm2 sign result r ", r);
+ log_mpidump ("sm2 sign result s ", s);
+ }
+
+leave:
+ point_free (&kG);
+ mpi_free (k);
+ mpi_free (x1);
+ mpi_free (rk);
+ mpi_free (tmp);
+
+ if (hash != input)
+ mpi_free (hash);
+
+ return rc;
+}
+
+
+/* Verify an SM2 signature.
+ * Check if R and S verifies INPUT.
+ */
+gpg_err_code_t
+_gcry_ecc_sm2_verify (gcry_mpi_t input, mpi_ec_t ec,
+ gcry_mpi_t r, gcry_mpi_t s)
+{
+ gpg_err_code_t err = 0;
+ gcry_mpi_t hash = NULL;
+ gcry_mpi_t t = NULL;
+ mpi_point_struct sG, tP;
+ gcry_mpi_t x1, y1;
+ unsigned int nbits;
+
+ /* r, s within [1, n-1] */
+ if (mpi_cmp_ui (r, 1) < 0 || mpi_cmp (r, ec->n) > 0)
+ return GPG_ERR_BAD_SIGNATURE;
+ if (mpi_cmp_ui (s, 1) < 0 || mpi_cmp (s, ec->n) > 0)
+ return GPG_ERR_BAD_SIGNATURE;
+
+ nbits = mpi_get_nbits (ec->n);
+ err = _gcry_dsa_normalize_hash (input, &hash, nbits);
+ if (err)
+ return err;
+
+ point_init (&sG);
+ point_init (&tP);
+ x1 = mpi_new (0);
+ y1 = mpi_new (0);
+ t = mpi_new (0);
+
+ /* t = (r + s) % n, t != 0 */
+ mpi_addm (t, r, s, ec->n);
+ if (mpi_cmp_ui (t, 0) == 0)
+ {
+ err = GPG_ERR_BAD_SIGNATURE;
+ goto leave;
+ }
+
+ /* sG + tP = (x1, y1) */
+ _gcry_mpi_ec_mul_point (&sG, s, ec->G, ec);
+ _gcry_mpi_ec_mul_point (&tP, t, ec->Q, ec);
+ _gcry_mpi_ec_add_points (&sG, &sG, &tP, ec);
+ if (_gcry_mpi_ec_get_affine (x1, y1, &sG, ec))
+ {
+ err = GPG_ERR_INV_DATA;
+ goto leave;
+ }
+
+ /* R = (e + x1) % n */
+ mpi_addm (t, hash, x1, ec->n);
+
+ /* check R == r */
+ if (mpi_cmp (t, r))
+ {
+ if (DBG_CIPHER)
+ {
+ log_mpidump (" R", t);
+ log_mpidump (" r", r);
+ log_mpidump (" s", s);
+ }
+ err = GPG_ERR_BAD_SIGNATURE;
+ goto leave;
+ }
+ if (DBG_CIPHER)
+ log_debug ("sm2 verify: Accepted\n");
+
+ leave:
+ point_free (&sG);
+ point_free (&tP);
+ mpi_free (x1);
+ mpi_free (y1);
+ mpi_free (t);
+ if (hash != input)
+ mpi_free (hash);
+
+ return err;
+}