From 12689a050972bc6a07c0b8dfdd379c50087b614b Mon Sep 17 00:00:00 2001
From: zhengxiaoxiao <zhengxiaoxiao2@huawei.com>
Date: Sat, 26 Oct 2024 16:59:41 +0800
Subject: [PATCH] Supports sm2 algorithm signature and uses sm3 hash algorithm.

Co-authored-by: Huaxin Lu <luhuaxin1@huawei.com>
Signed-off-by: zhengxiaoxiao <zhengxiaoxiao2@huawei.com>
---
 agent/pksign.c       |  25 +++++++-
 common/Makefile.am   |   3 +
 common/openpgp-oid.c |  31 +++++++++
 common/openpgpdefs.h |   4 ++
 common/sm2.c         | 149 +++++++++++++++++++++++++++++++++++++++++++
 common/sm2.h         |  24 +++++++
 common/util.h        |   3 +
 configure.ac         |  10 +++
 g10/keygen.c         |   3 +
 g10/misc.c           |  13 +++-
 g10/pkglue.c         |   5 ++
 g10/sig-check.c      |  12 ++++
 g10/sign.c           |  16 ++++-
 sm/gpgsm.c           |   3 +
 14 files changed, 295 insertions(+), 6 deletions(-)
 create mode 100644 common/sm2.c
 create mode 100644 common/sm2.h

diff --git a/agent/pksign.c b/agent/pksign.c
index a7b5c57..249bcd9 100644
--- a/agent/pksign.c
+++ b/agent/pksign.c
@@ -28,7 +28,9 @@
 
 #include "agent.h"
 #include "../common/i18n.h"
-
+#ifdef GPG_USE_SM2
+#include "../common/sm2.h"
+#endif
 
 static int
 do_encode_md (const byte * md, size_t mdlen, int algo, gcry_sexp_t * r_hash,
@@ -209,7 +211,14 @@ do_encode_dsa (const byte *md, size_t mdlen, int pkalgo, gcry_sexp_t pkey,
     mdlen = qbits/8;
 
   /* Create the S-expression.  */
-  err = gcry_sexp_build (&hash, NULL,
+#ifdef GPG_USE_SM2
+  if (pkey_is_sm2(pkey))
+    err = gcry_sexp_build (&hash, NULL,
+                         "(data (flags sm2) (hash %s %b))",
+                         "sm3", (int)mdlen, md);
+  else
+#endif
+    err = gcry_sexp_build (&hash, NULL,
                          "(data (flags rfc6979) (hash %s %b))",
                          rfc6979_hash_algo_string (mdlen),
                          (int)mdlen, md);
@@ -494,6 +503,18 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce,
     {
       /* No smartcard, but a private key (in S_SKEY). */
 
+#ifdef GPG_USE_SM2
+      /* calculate sm2 digest */
+      if (pkey_is_sm2(s_skey))
+        {
+          if (!s_pkey)
+	    agent_public_key_from_file (ctrl, ctrl->keygrip, &s_pkey);
+
+          err = update_sm2_hash_value (data, datalen, s_pkey, data);
+          if (err)
+            goto leave;
+        }
+#endif
       /* Put the hash into a sexp */
       if (algo == GCRY_PK_EDDSA)
         err = do_encode_eddsa (gcry_pk_get_nbits (s_skey), data, datalen,
diff --git a/common/Makefile.am b/common/Makefile.am
index d5ab038..5e7ac35 100644
--- a/common/Makefile.am
+++ b/common/Makefile.am
@@ -99,6 +99,9 @@ common_sources = \
 	compliance.c compliance.h \
 	pkscreening.c pkscreening.h
 
+if GPG_USE_SM2
+common_sources += sm2.c sm2.h
+endif
 
 if HAVE_W32_SYSTEM
 common_sources += w32-reg.c w32-cmdline.c
diff --git a/common/openpgp-oid.c b/common/openpgp-oid.c
index 4930549..767a4e5 100644
--- a/common/openpgp-oid.c
+++ b/common/openpgp-oid.c
@@ -62,6 +62,9 @@ static struct {
   { "brainpoolP512r1", "1.3.36.3.3.2.8.1.1.13",  512 },
 
   { "secp256k1",       "1.3.132.0.10",           256 },
+#ifdef GPG_USE_SM2
+  { "sm2p256v1",       "1.2.156.10197.1.301",    256 },
+#endif
 
   { NULL, NULL, 0}
 };
@@ -92,6 +95,10 @@ static const char oid_cv448[] = { 0x03, 0x2b, 0x65, 0x6f };
 /* The OID for Ed448 in OpenPGP format. */
 static const char oid_ed448[] = { 0x03, 0x2b, 0x65, 0x71 };
 
+#ifdef GPG_USE_SM2
+/* The OID for SM2 in OpenPGP format. */
+static const char oid_sm2[] = { 0x08, 0x2a, 0x81, 0x1c, 0xcf, 0x55, 0x01, 0x82, 0x2d };
+#endif
 
 /* A table to store keyalgo strings like "rsa2048 or "ed25519" so that
  * we do not need to allocate them.  This is currently a simple array
@@ -381,6 +388,15 @@ openpgp_oidbuf_is_cv448 (const void *buf, size_t len)
           && !memcmp (buf, oid_cv448, DIM (oid_cv448)));
 }
 
+#ifdef GPG_USE_SM2
+/* Return true if (BUF,LEN) represents the OID for SM2.  */
+static int
+openpgp_oidbuf_is_sm2 (const void *buf, size_t len)
+{
+  return (buf && len == DIM (oid_sm2)
+          && !memcmp (buf, oid_sm2, DIM (oid_sm2)));
+}
+#endif
 
 /* Return true if the MPI A represents the OID for Curve25519.  */
 int
@@ -426,6 +442,21 @@ openpgp_oid_is_cv448 (gcry_mpi_t a)
   return openpgp_oidbuf_is_cv448 (buf, (nbits+7)/8);
 }
 
+#ifdef GPG_USE_SM2
+/* Return true if the MPI A represents the OID for SM2.  */
+int
+openpgp_oid_is_sm2 (gcry_mpi_t a)
+{
+  const unsigned char *buf;
+  unsigned int nbits;
+
+  if (!a || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
+    return 0;
+
+  buf = gcry_mpi_get_opaque (a, &nbits);
+  return openpgp_oidbuf_is_sm2 (buf, (nbits+7)/8);
+}
+#endif
 
 /* Map the Libgcrypt ECC curve NAME to an OID.  If R_NBITS is not NULL
    store the bit size of the curve there.  Returns NULL for unknown
diff --git a/common/openpgpdefs.h b/common/openpgpdefs.h
index 6257479..fd7e668 100644
--- a/common/openpgpdefs.h
+++ b/common/openpgpdefs.h
@@ -184,6 +184,10 @@ typedef enum
     DIGEST_ALGO_SHA384      =  9,
     DIGEST_ALGO_SHA512      = 10,
     DIGEST_ALGO_SHA224      = 11,
+#ifdef GPG_USE_SM2
+    /* 100-110	Private or Experimental Use */
+    DIGEST_ALGO_SM3         = 109,
+#endif
     DIGEST_ALGO_PRIVATE10   = 110
   }
 digest_algo_t;
diff --git a/common/sm2.c b/common/sm2.c
new file mode 100644
index 0000000..2d34dbb
--- /dev/null
+++ b/common/sm2.c
@@ -0,0 +1,149 @@
+/* openpgp-oids.c - OID helper for OpenPGP
+ * Copyright (C) 2024 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of either
+ *
+ *   - the GNU Lesser General Public License as published by the Free
+ *     Software Foundation; either version 3 of the License, or (at
+ *     your option) any later version.
+ *
+ * or
+ *
+ *   - the GNU General Public License as published by the Free
+ *     Software Foundation; either version 2 of the License, or (at
+ *     your option) any later version.
+ *
+ * or both in parallel, as here.
+ *
+ * This file 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "sm2.h"
+
+const unsigned char zin_default[] = {
+  0x00, 0x80, // id length
+  0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x31, 0x32, 0x33, 0x34,
+  0x35, 0x36, 0x37, 0x38, // default id: 1234567812345678
+  0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, // sm2 a
+  0x28, 0xe9, 0xfa, 0x9e, 0x9d, 0x9f, 0x5e, 0x34, 0x4d, 0x5a, 0x9e, 0x4b,
+  0xcf, 0x65, 0x09, 0xa7, 0xf3, 0x97, 0x89, 0xf5, 0x15, 0xab, 0x8f, 0x92,
+  0xdd, 0xbc, 0xbd, 0x41, 0x4d, 0x94, 0x0e, 0x93, // sm2 b
+  0x32, 0xc4, 0xae, 0x2c, 0x1f, 0x19, 0x81, 0x19, 0x5f, 0x99, 0x04, 0x46,
+  0x6a, 0x39, 0xc9, 0x94, 0x8f, 0xe3, 0x0b, 0xbf, 0xf2, 0x66, 0x0b, 0xe1,
+  0x71, 0x5a, 0x45, 0x89, 0x33, 0x4c, 0x74, 0xc7, // sm2 x
+  0xbc, 0x37, 0x36, 0xa2, 0xf4, 0xf6, 0x77, 0x9c, 0x59, 0xbd, 0xce, 0xe3,
+  0x6b, 0x69, 0x21, 0x53, 0xd0, 0xa9, 0x87, 0x7c, 0xc6, 0x2a, 0x47, 0x40,
+  0x02, 0xdf, 0x32, 0xe5, 0x21, 0x39, 0xf0, 0xa0 // sm2 y
+};
+
+static gpg_error_t
+calculate_za (gcry_mpi_t pkey, unsigned char *result)
+{
+  gpg_error_t err;
+  gcry_md_hd_t md;
+  unsigned char q[65];
+  size_t qlen;
+
+  err = gcry_mpi_print (GCRYMPI_FMT_STD, q, sizeof(q), &qlen, pkey);
+  if (err)
+    return err;
+
+  err = gcry_md_open (&md, GCRY_MD_SM3_PGP, 0);
+  if (err)
+    return err;
+
+  gcry_md_write (md, zin_default, sizeof(zin_default));
+  gcry_md_write (md, q + 1, qlen - 1);
+  gcry_md_final (md);
+  memcpy (result, gcry_md_read (md, GCRY_MD_SM3_PGP), 32);
+  gcry_md_close (md);
+
+  return 0;
+}
+
+gpg_error_t
+update_sm2_hash_md (gcry_md_hd_t md, gcry_mpi_t pkey)
+{
+  gpg_error_t err;
+  unsigned char za[32];
+  unsigned char msg[32];
+
+  if (!md || !pkey)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  err = calculate_za(pkey, za);
+  if (err)
+    return err;
+
+  memcpy(msg, gcry_md_read(md, GCRY_MD_SM3_PGP), 32);
+  gcry_md_reset(md);
+  gcry_md_write(md, za, sizeof(za));
+  gcry_md_write(md, msg, sizeof(msg));
+  gcry_md_final(md);
+
+  return 0; 
+}
+
+gpg_error_t 
+update_sm2_hash_value (unsigned char *msg, size_t msglen, gcry_sexp_t s_pkey, unsigned char *result)
+{
+  gpg_error_t err;
+  gcry_sexp_t q_sexp;
+  gcry_mpi_t q_mpi;
+  gcry_md_hd_t md;
+  unsigned char za[32];
+
+  if(!msg || !s_pkey || !result)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  q_sexp = gcry_sexp_find_token(s_pkey, "q", 0);
+  if (!q_sexp)
+    return gpg_error (GPG_ERR_NO_PUBKEY);
+
+  q_mpi = gcry_sexp_nth_mpi (q_sexp, 1, GCRYMPI_FMT_USG);
+  gcry_sexp_release (q_sexp);
+  if (!q_mpi)
+    return gpg_error (GPG_ERR_NO_PUBKEY);
+
+  err = calculate_za (q_mpi, za);
+  gcry_mpi_release (q_mpi);
+  if (err)
+    return err;
+
+  err = gcry_md_open (&md, GCRY_MD_SM3_PGP, 0);
+  if (err)
+    return err;
+
+  gcry_md_write (md, za, sizeof(za));
+  gcry_md_write (md, msg, msglen);
+  gcry_md_final (md);
+  memcpy (result, gcry_md_read (md, GCRY_MD_SM3_PGP), 32);
+  gcry_md_close (md);
+
+  return 0;
+}
+
+int pkey_is_sm2 (gcry_sexp_t pkey)
+{
+  const char *curve;
+
+  if (!pkey)
+    return 0;
+
+  curve = gcry_pk_get_curve (pkey, 0, NULL);
+  if (curve && !strcmp (curve, "sm2p256v1"))
+    return 1;
+
+  return 0;
+}
diff --git a/common/sm2.h b/common/sm2.h
new file mode 100644
index 0000000..7cd38d4
--- /dev/null
+++ b/common/sm2.h
@@ -0,0 +1,24 @@
+/* sm2.h
+ *	Copyright (C) 2024 Free Software Foundation, Inc.
+ *
+ * This file is free software; as a special exception the author gives
+ * unlimited permission to copy and/or distribute it, with or without
+ * modifications, as long as this notice is preserved.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY, to the extent permitted by law; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.
+ */
+
+#ifndef GNUPG_COMMON_SM2_H
+#define GNUPG_COMMON_SM2_H
+
+#include <gcrypt.h>
+#include <errno.h>
+#include <gpg-error.h>
+
+gpg_error_t update_sm2_hash_md (gcry_md_hd_t md, gcry_mpi_t pkey);
+gpg_error_t update_sm2_hash_value (unsigned char *msg, size_t msglen, gcry_sexp_t s_pkey, unsigned char *result);
+
+#endif /*GNUPG_COMMON_SM2_H*/
diff --git a/common/util.h b/common/util.h
index aa24e39..7b5a1d8 100644
--- a/common/util.h
+++ b/common/util.h
@@ -250,6 +250,9 @@ int openpgp_oidbuf_is_cv25519 (const void *buf, size_t len);
 int openpgp_oid_is_cv25519 (gcry_mpi_t a);
 int openpgp_oid_is_cv448 (gcry_mpi_t a);
 int openpgp_oid_is_ed448 (gcry_mpi_t a);
+#ifdef GPG_USE_SM2
+int openpgp_oid_is_sm2 (gcry_mpi_t a);
+#endif
 const char *openpgp_curve_to_oid (const char *name,
                                   unsigned int *r_nbits, int *r_algo);
 const char *openpgp_oid_to_curve (const char *oid, int canon);
diff --git a/configure.ac b/configure.ac
index e68b779..3cffc4d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -341,6 +341,16 @@ GNUPG_GPG_DISABLE_ALGO([sha224],[SHA-224 hash])
 GNUPG_GPG_DISABLE_ALGO([sha384],[SHA-384 hash])
 GNUPG_GPG_DISABLE_ALGO([sha512],[SHA-512 hash])
 
+AC_ARG_ENABLE([sm2],
+    [AS_HELP_STRING([--enable-sm2], [Enable SM2 support])],
+    [enable_sm2=$enableval],
+    [enable_sm2=no])
+
+if test "x$enable_sm2" = "xyes"; then
+    AC_DEFINE([GPG_USE_SM2], [1], [Define if SM2 support is enabled])
+    AM_CONDITIONAL([GPG_USE_SM2], [test "x$enable_sm2" = "xyes"])
+    CFLAGS="$CFLAGS -DUSE_SM3_PGP"
+fi
 
 # Allow disabling of zip support.
 # This is in general not a good idea because according to rfc4880 OpenPGP
diff --git a/g10/keygen.c b/g10/keygen.c
index 6ead599..b6c357e 100644
--- a/g10/keygen.c
+++ b/g10/keygen.c
@@ -2603,6 +2603,9 @@ ask_curve (int *algo, int *subkey_algo, const char *current)
     { "brainpoolP384r1", NULL, "Brainpool P-384",  MY_USE_ECDSADH,  1, 1, 0 },
     { "brainpoolP512r1", NULL, "Brainpool P-512",  MY_USE_ECDSADH,  1, 1, 0 },
     { "secp256k1",       NULL, NULL,               MY_USE_ECDSADH,  0, 1, 0 },
+#ifdef GPG_USE_SM2
+    { "sm2p256v1",       NULL, NULL,               MY_USE_ECDSADH,  0, 1, 0 },
+#endif
   };
 #undef MY_USE_ECDSADH
   int idx;
diff --git a/g10/misc.c b/g10/misc.c
index 2f4b452..7e60131 100644
--- a/g10/misc.c
+++ b/g10/misc.c
@@ -866,6 +866,11 @@ map_md_openpgp_to_gcry (digest_algo_t algo)
     case DIGEST_ALGO_SHA512: return GCRY_MD_SHA512;
 #else
     case DIGEST_ALGO_SHA512: return 0;
+#endif
+#ifdef GPG_USE_SM2
+    case DIGEST_ALGO_SM3: return GCRY_MD_SM3_PGP;
+#else
+    case DIGEST_ALGO_SM3: return 0;
 #endif
     default: return 0;
     }
@@ -902,6 +907,9 @@ openpgp_md_algo_name (int algo)
     case DIGEST_ALGO_SHA384: return "SHA384";
     case DIGEST_ALGO_SHA512: return "SHA512";
     case DIGEST_ALGO_SHA224: return "SHA224";
+#ifdef GPG_USE_SM2
+    case DIGEST_ALGO_SM3:    return "SM3";
+#endif
     }
   return "?";
 }
@@ -1291,7 +1299,10 @@ string_to_digest_algo (const char *string)
       if (!*string || *endptr || openpgp_md_test_algo (val))
         val = 0;
     }
-
+#ifdef GPG_USE_SM2
+  if (val == GCRY_MD_SM3)
+    val = GCRY_MD_SM3_PGP;
+#endif
   return val;
 }
 
diff --git a/g10/pkglue.c b/g10/pkglue.c
index f183139..497b555 100644
--- a/g10/pkglue.c
+++ b/g10/pkglue.c
@@ -297,6 +297,11 @@ pk_verify (pubkey_algo_t pkalgo, gcry_mpi_t hash,
     {
       if (!data[0] || !data[1])
         rc = gpg_error (GPG_ERR_BAD_MPI);
+#ifdef GPG_USE_SM2
+      else if (openpgp_oid_is_sm2(pkey[0]))
+        rc = gcry_sexp_build (&s_sig, NULL,
+                              "(sig-val(sm2(r%m)(s%m)))", data[0], data[1]);
+#endif
       else
         rc = gcry_sexp_build (&s_sig, NULL,
                               "(sig-val(ecdsa(r%m)(s%m)))", data[0], data[1]);
diff --git a/g10/sig-check.c b/g10/sig-check.c
index 06329f6..d59de92 100644
--- a/g10/sig-check.c
+++ b/g10/sig-check.c
@@ -34,6 +34,9 @@
 #include "options.h"
 #include "pkglue.h"
 #include "../common/compliance.h"
+#ifdef GPG_USE_SM2
+#include "../common/sm2.h"
+#endif
 
 static int check_signature_end (PKT_public_key *pk, PKT_signature *sig,
 				gcry_md_hd_t digest,
@@ -622,6 +625,15 @@ check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig,
     }
     gcry_md_final( digest );
 
+#ifdef GPG_USE_SM2
+    if (openpgp_oid_is_sm2 (pk->pkey[0]))
+      {
+        rc = update_sm2_hash_md (digest, pk->pkey[1]);
+        if (!rc)
+          return rc;
+      }
+#endif
+
     /* Convert the digest to an MPI.  */
     result = encode_md_value (pk, digest, sig->digest_algo );
     if (!result)
diff --git a/g10/sign.c b/g10/sign.c
index 05ab5b7..913edb4 100644
--- a/g10/sign.c
+++ b/g10/sign.c
@@ -671,6 +671,10 @@ hash_for (PKT_public_key *pk)
     {
       unsigned int qbytes = gcry_mpi_get_nbits (pk->pkey[1]);
 
+#ifdef GPG_USE_SM2
+      if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA && openpgp_oid_is_sm2(pk->pkey[0]))
+        return DIGEST_ALGO_SM3;
+#endif
       if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
         qbytes = ecdsa_qbits_from_Q (qbytes);
       qbytes = qbytes/8;
@@ -703,7 +707,6 @@ hash_for (PKT_public_key *pk)
 		  return prefs->value;
 	    }
 	}
-
       return match_dsa_hash(qbytes);
     }
   else if (openpgp_card_v1_p (pk))
@@ -1869,8 +1872,15 @@ make_keysig_packet (ctrl_t ctrl,
   else if (pksk->pubkey_algo == PUBKEY_ALGO_DSA) /* Meet DSA requirements.  */
     digest_algo = match_dsa_hash (gcry_mpi_get_nbits (pksk->pkey[1])/8);
   else if (pksk->pubkey_algo == PUBKEY_ALGO_ECDSA) /* Meet ECDSA requirements. */
-    digest_algo = match_dsa_hash
-      (ecdsa_qbits_from_Q (gcry_mpi_get_nbits (pksk->pkey[1]))/8);
+    {
+#ifdef GPG_USE_SM2
+      if (openpgp_oid_is_sm2(pk->pkey[0]))
+        digest_algo = DIGEST_ALGO_SM3;
+      else
+#endif
+        digest_algo = match_dsa_hash
+          (ecdsa_qbits_from_Q (gcry_mpi_get_nbits (pksk->pkey[1]))/8);
+    }
   else if (pksk->pubkey_algo == PUBKEY_ALGO_EDDSA)
     {
       if (gcry_mpi_get_nbits (pksk->pkey[1]) > 256)
diff --git a/sm/gpgsm.c b/sm/gpgsm.c
index 636d14a..d4aa70f 100644
--- a/sm/gpgsm.c
+++ b/sm/gpgsm.c
@@ -590,6 +590,9 @@ our_md_test_algo (int algo)
     case GCRY_MD_SHA384:
     case GCRY_MD_SHA512:
     case GCRY_MD_WHIRLPOOL:
+#ifdef GPG_USE_SM2
+    case GCRY_MD_SM3:
+#endif
       return gcry_md_test_algo (algo);
     default:
       return 1;
-- 
2.43.0
