From f91c17bc2f2d8ca76e8a2ddbdff7d7751888494f Mon Sep 17 00:00:00 2001 From: K1 Date: Mon, 8 Jan 2024 11:46:54 +0800 Subject: [PATCH] Support SM2 two-party threshold signature Refer to Liu ZY, Lin JQ. Framework of Two-party Threshold Schemes for SM2 Digital Signatures. Ruan Jian Xue Bao/Journal of Software (in Chinese). --- .github/workflows/ci.yml | 14 + .github/workflows/coveralls.yml | 8 +- .github/workflows/run-checker-daily.yml | 1 + .github/workflows/static-analysis.yml | 16 +- CHANGES | 2 + Configure | 9 + apps/build.info | 4 + apps/sm2_threshold.c | 580 +++++++++++++++++++++++ crypto/sm2/build.info | 3 + crypto/sm2/sm2_threshold.c | 485 +++++++++++++++++++ examples/perf/sm2_threshold/Makefile | 12 + examples/perf/sm2_threshold/threshold.c | 203 ++++++++ include/crypto/sm2.h | 1 - include/openssl/sm2_threshold.h | 121 +++++ test/build.info | 7 + test/recipes/15-test_sm2_threshold_api.t | 19 + test/recipes/20-test_sm2_threshold_app.t | 115 +++++ test/sm2_threshold_test.c | 172 +++++++ util/libcrypto.num | 8 + 19 files changed, 1776 insertions(+), 4 deletions(-) create mode 100644 apps/sm2_threshold.c create mode 100644 crypto/sm2/sm2_threshold.c create mode 100644 examples/perf/sm2_threshold/Makefile create mode 100644 examples/perf/sm2_threshold/threshold.c create mode 100644 include/openssl/sm2_threshold.h create mode 100644 test/recipes/15-test_sm2_threshold_api.t create mode 100644 test/recipes/20-test_sm2_threshold_app.t create mode 100644 test/sm2_threshold_test.c diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9b6af681..374aef26b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -391,3 +391,17 @@ jobs: - name: check dirty run: test $(git status --porcelain | wc -l) -eq "0" + sm2-threshold-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: config + run: CC=clang ./config --strict-warnings --debug -O1 -fsanitize=memory -DOSSL_SANITIZE_MEMORY enable-sm2_threshold && perl configdata.pm --dump + - name: make + run: make -s -j4 + - name: make test + run: make test + - name: make clean + run: make clean + - name: check dirty + run: test $(git status --porcelain | wc -l) -eq "0" diff --git a/.github/workflows/coveralls.yml b/.github/workflows/coveralls.yml index d1b40ead3..0bc5160f0 100644 --- a/.github/workflows/coveralls.yml +++ b/.github/workflows/coveralls.yml @@ -22,7 +22,13 @@ jobs: run: | sudo apt-get -yq install lcov - name: config - run: CC=gcc ./config --banner=Configured --debug --coverage no-asm enable-rc5 enable-ssl3 enable-nextprotoneg enable-ssl3-method enable-weak-ssl-ciphers enable-zlib enable-ec_nistp_64_gcc_128 enable-ec_sm2p_64_gcc_128 no-shared enable-buildtest-c++ enable-external-tests -DPEDANTIC -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION enable-ntls enable-cert-compression enable-delegated-credential enable-status enable-ec_elgamal enable-paillier + run: | + CC=gcc ./config --banner=Configured --debug --coverage no-asm enable-rc5 enable-ssl3 enable-nextprotoneg \ + enable-ssl3-method enable-weak-ssl-ciphers enable-zlib enable-ec_nistp_64_gcc_128 enable-ec_sm2p_64_gcc_128 \ + no-shared enable-buildtest-c++ enable-external-tests -DPEDANTIC -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION \ + enable-ntls enable-cert-compression enable-delegated-credential enable-status enable-ec_elgamal \ + enable-paillier enable-sm2_threshold + - name: config dump run: ./configdata.pm --dump - name: make diff --git a/.github/workflows/run-checker-daily.yml b/.github/workflows/run-checker-daily.yml index c2cafe3ee..1a927a904 100644 --- a/.github/workflows/run-checker-daily.yml +++ b/.github/workflows/run-checker-daily.yml @@ -119,6 +119,7 @@ jobs: enable-ec_elgamal enable-twisted_ec_elgamal, enable-bulletproofs, enable-bulletproofs enable-nizk enable-zkp-gadget enable-ec_elgamal enable-twisted_ec_elgamal, + enable-sm2_threshold, -DOPENSSL_NO_BUILTIN_OVERFLOW_CHECKING ] runs-on: ubuntu-latest diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 63a82b85b..11ef55c21 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -1,4 +1,11 @@ -# Copyright 2023 The Tongsuo Project Authors. All Rights Reserved. +# Copyright 2021-2023 The OpenSSL Project Authors. All Rights Reserved. +# +# Licensed under the Apache License 2.0 (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://www.openssl.org/source/license.html +# +# Copyright 2023-2024 The Tongsuo Project Authors. All Rights Reserved. # # Licensed under the Apache License 2.0 (the "License"). You may not use # this file except in compliance with the License. You can obtain a copy @@ -27,7 +34,12 @@ jobs: --post-data "token=${{ secrets.COVERITY_TOKEN }}&project=Tongsuo-Project%2FTongsuo" \ --progress=dot:giga -O coverity_tool.tgz - name: config - run: CC=gcc ./config --banner=Configured --debug enable-ntls enable-smtc enable-smtc-debug enable-rc5 enable-ssl3 enable-nextprotoneg enable-ssl3-method enable-weak-ssl-ciphers enable-zlib enable-ec_nistp_64_gcc_128 no-shared enable-buildtest-c++ enable-external-tests enable-ec_elgamal enable-twisted_ec_elgamal enable-paillier enable-cert-compression enable-delegated-credential enable-bn-method enable-bulletproofs enable-nizk enable-zkp-gadget -DPEDANTIC + run: | + CC=gcc ./config --banner=Configured --debug enable-ntls enable-smtc enable-smtc-debug enable-rc5 enable-ssl3 \ + enable-nextprotoneg enable-ssl3-method enable-weak-ssl-ciphers enable-zlib enable-ec_nistp_64_gcc_128 no-shared \ + enable-buildtest-c++ enable-external-tests enable-ec_elgamal enable-twisted_ec_elgamal enable-paillier \ + enable-cert-compression enable-delegated-credential enable-bn-method enable-bulletproofs enable-nizk \ + enable-zkp-gadget enable-sm2_threshold -DPEDANTIC - name: config dump run: ./configdata.pm --dump - name: tool install diff --git a/CHANGES b/CHANGES index 22af5606e..eb1933c76 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,8 @@ Changes between 8.4.0 and 8.5.0 [xx XXX xxxx] + *) 增加SM2两方门限签名算法 + *) 修复CVE-2023-4807 *) 修复CVE-2023-5363 diff --git a/Configure b/Configure index 40eebb90b..ce92fd517 100755 --- a/Configure +++ b/Configure @@ -479,6 +479,7 @@ my @disablables = ( "siphash", "siv", "sm2", + "sm2_threshold", "sm3", "sm4", "zuc", @@ -566,6 +567,7 @@ our %disabled = ( # "what" => "comment" "ntls" => "default", "rc5" => "default", "sctp" => "default", + "sm2_threshold" => "default", "ssl3" => "default", "ssl3-method" => "default", "trace" => "default", @@ -615,6 +617,7 @@ my @disable_cascades = ( "ssl3-method" => [ "ssl3" ], "zlib" => [ "zlib-dynamic" ], "ec" => [ "ec2m", "ecdsa", "ecdh", "sm2" ], + "sm2" => [ "sm2_threshold" ], "ec_elgamal" => [ "twisted_ec_elgamal" ], "dgram" => [ "dtls", "sctp" ], "sock" => [ "dgram" ], @@ -1166,6 +1169,12 @@ if (!defined($disabled{'bn-method'})) { $config{api}=$apitable->{"1.1.1"}; } +if (!defined($disabled{'sm2_threshold'})) { + die "sm2_threshold only supports api with 1.1.1\n" + if ($config{api} && $config{api} != $apitable->{"1.1.1"}); + $config{api}=$apitable->{"1.1.1"}; +} + if (keys %deprecated_options) { warn "***** Deprecated options: ", diff --git a/apps/build.info b/apps/build.info index 365c611a4..56ac5a3a3 100644 --- a/apps/build.info +++ b/apps/build.info @@ -71,6 +71,10 @@ IF[{- !$disabled{'smtc'} -}] $OPENSSLSRC=$OPENSSLSRC mod.c ENDIF +IF[{- !$disabled{'sm2_threshold'} -}] + $OPENSSLSRC=$OPENSSLSRC sm2_threshold.c +ENDIF + IF[{- !$disabled{apps} -}] PROGRAMS=openssl SOURCE[openssl]=$INITSRC $OPENSSLSRC diff --git a/apps/sm2_threshold.c b/apps/sm2_threshold.c new file mode 100644 index 000000000..04e7ac901 --- /dev/null +++ b/apps/sm2_threshold.c @@ -0,0 +1,580 @@ +/* + * Copyright 2024 The Tongsuo Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://github.com/Tongsuo-Project/Tongsuo/blob/master/LICENSE.txt + */ + +#include +#include +#include +#include "apps.h" +#include "progs.h" +#include +#include +#include +#include +#include +#include +#include + +#undef BUFSIZE +#define BUFSIZE 1024*8 + +typedef enum { + OUT_FORMAT_DEFAULT = 0, + OUT_FORMAT_HEX, + OUT_FORMAT_BINARY, +} OUT_FORMAT; + +typedef enum OPTION_choice { + OPT_COMMON, + OPT_DERIVE, OPT_IN, OPT_INKEY, OPT_PEERKEY, OPT_PUBOUT, OPT_PUBIN, + OPT_SIGN1, OPT_SIGN2, OPT_SIGN3, OPT_DIGEST, OPT_TEMP_KEY, OPT_OUT, + OPT_TEMP_PEER_KEY, OPT_SIGFILE, OPT_PASSIN, OPT_SIGFORM, OPT_NEWKEY, + OPT_PASSOUT, OPT_CIPHER, OPT_HEX, OPT_BINARY, OPT_R_ENUM, OPT_PROV_ENUM, +} OPTION_CHOICE; + +/* +Examples: +Assume that Alice and Bob perform SM2 threshold signature: + +[Key generation] +Alice: +# Generate SM2 private key, for example +genpkey -algorithm "ec" -pkeyopt "ec_paramgen_curve:sm2" -out A.key +# Derive SM2 threshold partial public key +sm2_threshold -derive -inkey A.key -pubout A.pub + +Bob: +# Generate SM2 private key, for example +genpkey -algorithm "ec" -pkeyopt "ec_paramgen_curve:sm2" -out B.key +# Derive SM2 threshold partial public key +sm2_threshold -derive -inkey B.key -pubout B.pub + +Alice -> Bob: A.pub +Bob -> Alice: B.pub + +Alice: +sm2_threshold -inkey A.key -peerkey B.pub -pubout pubkey.pem + +Bob: +sm2_threshold -inkey B.key -peerkey A.pub -pubout pubkey.pem + +[Threshold signature] +Alice: +# Sign1, calculate the digest of the message and generate a new SM2 keypair +sm2_threshold -sign1 -newkey tempA.key -pubout tempA.pub -pubin -inkey pubkey.pem -in file + +Alice -> Bob: the message digest and tempA.pub + +Bob: +# Sign2, calculate the partial signature +sm2_threshold -sign2 -inkey B.key -temppubin -temppeerkey tempA.pub -digest dgst -sigform hex -out partial_sig.txt + +Bob -> Alice: partial_sig.txt + +Alice: +# Sign3, calculate the final signature +sm2_threshold -sign3 -inkey A.key -sigform hex -sigfile partial_sig.txt -tempkey tempA.key -out final_sig.txt +*/ +const OPTIONS sm2_threshold_options[] = { + {OPT_HELP_STR, 1, '-', "Usage: %s [action options] [input/output options] [file]\n"}, + + OPT_SECTION("General"), + {"help", OPT_HELP, '-', "Display this summary"}, + + OPT_SECTION("Key-Action"), + {"derive", OPT_DERIVE, '-', "Derive SM2 two-party threshold partial or complete public key"}, + {"sign1", OPT_SIGN1, '-', "1st step of SM2 threshold signature using partial public key1"}, + {"sign2", OPT_SIGN2, '-', "2nd step of SM2 threshold signature using key2"}, + {"sign3", OPT_SIGN3, '-', "3rd step of SM2 threshold signature using key1"}, + + OPT_SECTION("Input"), + {"in", OPT_IN, '<', "Input file"}, + {"inkey", OPT_INKEY, '<', "Input local SM2 threshold partial keypair"}, + {"pubin", OPT_PUBIN, '-', "Input key is a public key"}, + {"passin", OPT_PASSIN, 's', "Key input pass phrase source"}, + {"peerkey", OPT_PEERKEY, '<', "Input peer SM2 threshold partial public key"}, + {"sigfile", OPT_SIGFILE, '<', "Input a SM2 threshold partial signature"}, + {"digest", OPT_DIGEST, 's', "Input the message digest in hex format, calculated by 1st part SM2 threshold signature"}, + {"tempkey", OPT_TEMP_KEY, '<', "Input the temp key in SM2 threshold signature"}, + {"temppeerkey", OPT_TEMP_PEER_KEY, '<', "Input the temp peer key (Q1) in SM2 threshold signature"}, + {"sigform", OPT_SIGFORM, 's', "The format of input sigfile, binary or hex, default is binary"}, + + OPT_SECTION("Output"), + {"newkey", OPT_NEWKEY, '>', "Generate a new SM2 keypair"}, + {"passout", OPT_PASSOUT, 's', "Output key file pass phrase source"}, + {"", OPT_CIPHER, '-', "Any supported cipher to be used for encryption"}, + {"pubout", OPT_PUBOUT, '>', "Output public key"}, + {"out", OPT_OUT, '>', "Output file"}, + {"hex", OPT_HEX, '-', "Output digest or signature in hex format"}, + {"binary", OPT_BINARY, '-', "Output digest or signature in binary format"}, + + OPT_R_OPTIONS, + OPT_PROV_OPTIONS, + {NULL} +}; + +static int sign1(EVP_PKEY *pubkey1, BIO *in_bio, int out_format, BIO *out_bio); +static int sign2(const EVP_PKEY *key1, const EVP_PKEY *peer_pubkey, + const char *digest, int out_format, BIO *out); +static int sign3(EVP_PKEY *key1, EVP_PKEY *temp_key, + const char *sigformat, const char *partial_sigfile, + int out_format, BIO *out); + +int sm2_threshold_main(int argc, char **argv) +{ + BIO *in_bio = NULL, *out_bio = NULL, *key_bio = NULL; + EVP_PKEY *inkey = NULL, *peerkey = NULL; + EVP_PKEY *temp_key = NULL, *sm2key = NULL; + EVP_CIPHER *cipher = NULL; + int ret = 1, derive = 0, sign_step = 0, pubin = 0; + int out_format = OUT_FORMAT_DEFAULT; + const char *sigformat = "binary"; + char *hex_digest = NULL, *newkey_file = NULL; + const char *inkey_file = NULL, *peerkey_file = NULL; + const char *partial_sigfile = NULL, *infile = NULL, *outfile = NULL; + const char *temp_key_file = NULL, *temp_peer_key_file = NULL; + char *passin = NULL, *passinarg = NULL; + char *passout = NULL, *passoutarg = NULL; + char *prog, *ciphername = NULL; + unsigned char *msgbuf = NULL; + OPTION_CHOICE o; + + prog = opt_init(argc, argv, sm2_threshold_options); + while ((o = opt_next()) != OPT_EOF) { + switch (o) { + case OPT_EOF: + case OPT_ERR: +opthelp: + BIO_printf(bio_err, "%s: Use -help for summary.\n", prog); + goto end; + case OPT_HELP: + ret = 0; + opt_help(sm2_threshold_options); + goto end; + case OPT_DERIVE: + derive = 1; + break; + case OPT_INKEY: + inkey_file = opt_arg(); + break; + case OPT_PUBIN: + pubin = 1; + break; + case OPT_SIGN1: + sign_step = 1; + break; + case OPT_SIGN2: + sign_step = 2; + break; + case OPT_SIGN3: + sign_step = 3; + break; + case OPT_IN: + infile = opt_arg(); + break; + case OPT_DIGEST: + hex_digest = opt_arg(); + break; + case OPT_PEERKEY: + peerkey_file = opt_arg(); + break; + case OPT_SIGFILE: + partial_sigfile = opt_arg(); + break; + case OPT_PUBOUT: + outfile = opt_arg(); + break; + case OPT_OUT: + outfile = opt_arg(); + break; + case OPT_PASSIN: + passinarg = opt_arg(); + break; + case OPT_PASSOUT: + passoutarg = opt_arg(); + break; + case OPT_TEMP_KEY: + temp_key_file = opt_arg(); + break; + case OPT_TEMP_PEER_KEY: + temp_peer_key_file = opt_arg(); + break; + case OPT_SIGFORM: + sigformat = opt_arg(); + break; + case OPT_NEWKEY: + newkey_file = opt_arg(); + break; + case OPT_CIPHER: + ciphername = opt_unknown(); + break; + case OPT_HEX: + out_format = OUT_FORMAT_HEX; + break; + case OPT_BINARY: + out_format = OUT_FORMAT_BINARY; + break; + default: + goto opthelp; + } + } + + /* No extra arguments. */ + argc = opt_num_rest(); + if (argc != 0) + goto opthelp; + + if (!app_RAND_load()) + goto end; + + if (outfile) { + if (derive) + out_bio = bio_open_owner(outfile, FORMAT_PEM, 1); + else + out_bio = bio_open_default(outfile, 'w', FORMAT_BINARY); + + if (out_bio == NULL) + goto end; + } + + if (inkey_file != NULL) { + if (pubin) { + inkey = load_pubkey(inkey_file, FORMAT_PEM, 1, NULL, NULL, "inkey"); + } else { + if (!app_passwd(passinarg, NULL, &passin, NULL)) { + BIO_printf(bio_err, "Error getting passwords\n"); + goto end; + } + + inkey = load_key(inkey_file, FORMAT_PEM, 1, passin, NULL, "inkey"); + } + + if (inkey == NULL) + goto end; + } + + if (peerkey_file) { + peerkey = load_pubkey(peerkey_file, FORMAT_PEM, 1, NULL, NULL, + "peerkey"); + if (peerkey == NULL) + goto end; + } + + if (temp_peer_key_file) { + temp_key = load_pubkey(temp_peer_key_file, FORMAT_PEM, 1, NULL, NULL, + "temp_peer_key"); + if (temp_key == NULL) + goto end; + } + + if (temp_key_file != NULL) { + if (!app_passwd(passinarg, NULL, &passin, NULL)) { + BIO_printf(bio_err, "Error getting passwords\n"); + goto end; + } + + temp_key = load_key(temp_key_file, FORMAT_PEM, 1, passin, NULL, + "temp_key"); + + if (temp_key == NULL) + goto end; + } + + if (partial_sigfile != NULL) { + if (inkey == NULL) { + BIO_printf(bio_err, "No key specified\n"); + goto opthelp; + } + } + + if (ciphername != NULL) { + if (!opt_cipher(ciphername, &cipher)) + goto opthelp; + } + + if (sign_step == 1 && newkey_file) { + if (!app_passwd(passoutarg, NULL, &passout, NULL)) { + BIO_printf(bio_err, "Error getting passwords\n"); + goto end; + } + + sm2key = EVP_PKEY_Q_keygen(NULL, NULL, "SM2"); + if (sm2key == NULL) { + BIO_printf(bio_err, "Failed to generate SM2 key\n"); + goto end; + } + + key_bio = bio_open_default(newkey_file, 'w', FORMAT_PEM); + + if (!PEM_write_bio_PrivateKey(key_bio, sm2key, cipher, NULL, 0, NULL, + passout)) { + BIO_printf(bio_err, "Error writing private key\n"); + goto end; + } + + if (out_bio) { + if (!PEM_write_bio_PUBKEY(out_bio, sm2key)) { + BIO_printf(bio_err, "Error writing pubkey\n"); + goto end; + } + } + } + + if (derive) { + EVP_PKEY *pubkey = NULL; + + if (inkey && peerkey) { + pubkey = SM2_THRESHOLD_derive_complete_pubkey(inkey, peerkey); + } else if (inkey) { + pubkey = SM2_THRESHOLD_derive_partial_pubkey(inkey); + } else { + BIO_printf(bio_err, "No key specified\n"); + goto opthelp; + } + + if (pubkey == NULL) { + BIO_printf(bio_err, "Failed to derive public key\n"); + goto end; + } + + if (!PEM_write_bio_PUBKEY(out_bio, pubkey)) { + EVP_PKEY_free(pubkey); + BIO_printf(bio_err, "Error writing pubkey\n"); + goto end; + } + } else if (sign_step == 1) { + in_bio = bio_open_default(infile, 'r', FORMAT_BINARY); + + if (!sign1(inkey, in_bio, out_format, bio_out)) + goto end; + } else if (sign_step == 2) { + if (inkey == NULL || temp_key == NULL) { + BIO_printf(bio_err, "No key specified\n"); + goto opthelp; + } + + if (!sign2(inkey, temp_key, hex_digest, out_format, out_bio)) + goto end; + } else if (sign_step == 3) { + if (inkey == NULL || temp_key == NULL) { + BIO_printf(bio_err, "No key specified\n"); + goto opthelp; + } + + if (!sign3(inkey, temp_key, sigformat, partial_sigfile, out_format, + out_bio)) + goto end; + } else { + BIO_printf(bio_err, "No action specified.\n"); + goto opthelp; + } + + ret = 0; +end: + if (ret != 0) { + BIO_printf(bio_err, "Maybe some errors occured, please use -help for usage summary.\n"); + ERR_print_errors(bio_err); + } + + EVP_PKEY_free(inkey); + EVP_PKEY_free(temp_key); + EVP_PKEY_free(peerkey); + EVP_PKEY_free(sm2key); + EVP_CIPHER_free(cipher); + OPENSSL_free(msgbuf); + BIO_free(in_bio); + BIO_free(out_bio); + BIO_free(key_bio); + + return ret; +} + +static int sign3(EVP_PKEY *key1, EVP_PKEY *temp_key, + const char *sigformat, const char *partial_sigfile, + int out_format, BIO *out) +{ + int ret = 0, is_hex; + BIO *sigbio = NULL; + unsigned char *final_sig = NULL, *sigbuf = NULL; + long buflen; + size_t siglen, final_siglen, tmplen; + + sigbio = bio_open_default(partial_sigfile, 'r', FORMAT_BINARY); + if (sigbio == NULL) { + BIO_printf(bio_err, "Error opening signature file %s\n", + partial_sigfile); + goto end; + } + + buflen = EVP_PKEY_size(key1) * 3; + sigbuf = app_malloc(buflen, "signature buffer"); + + siglen = BIO_read(sigbio, sigbuf, buflen - 1); + if (siglen <= 0) { + BIO_printf(bio_err, "Error reading signature file %s\n", + partial_sigfile); + goto end; + } + + if (strcmp(sigformat, "hex") == 0) { + is_hex = 1; + } else if (strcmp(sigformat, "binary") == 0) { + is_hex = 0; + } else { + BIO_printf(bio_err, "Unknown signature format %s\n", sigformat); + goto end; + } + + if (is_hex) { + sigbuf[siglen] = '\0'; + + unsigned char *buf = OPENSSL_hexstr2buf((char *)sigbuf, &buflen); + if (buf == NULL) { + BIO_printf(bio_err, "Error decoding signature\n"); + goto end; + } + + OPENSSL_free(sigbuf); + sigbuf = buf; + siglen = buflen; + } + + if (!SM2_THRESHOLD_sign3(key1, temp_key, sigbuf, siglen, &final_sig, + &final_siglen)) { + BIO_printf(bio_err, "Failed to do SM2 threshold sign3\n"); + goto end; + } + + if (out_format == OUT_FORMAT_HEX) { + if (BIO_hex_string(out, 0, final_siglen, final_sig, final_siglen) + != 1) { + BIO_printf(bio_err, "Error occur when output signature\n"); + goto end; + } + } else { + tmplen = BIO_write(out, final_sig, final_siglen); + if (tmplen != final_siglen) { + BIO_printf(bio_err, "Error occur when output signature\n"); + goto end; + } + } + + ret = 1; +end: + OPENSSL_free(sigbuf); + OPENSSL_free(final_sig); + BIO_free(sigbio); + + return ret; +} + +static int sign1(EVP_PKEY *pubkey1, BIO *in_bio, int out_format, BIO *out_bio) +{ + int ret = 0; + unsigned char *buf = NULL; + size_t buflen, dlen; + EVP_MD_CTX *ctx = NULL; + unsigned char digest[EVP_MAX_MD_SIZE]; + + ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + ERR_raise(ERR_LIB_ASN1, ERR_R_MALLOC_FAILURE); + goto err; + } + + if (!SM2_THRESHOLD_sign1_init(ctx, EVP_sm3(), pubkey1, NULL, 0)) { + BIO_printf(bio_err, "sign1 init failed\n"); + goto err; + } + + buf = app_malloc(BUFSIZE, "I/O buffer"); + if (buf == NULL) + goto err; + + while (BIO_pending(in_bio) || !BIO_eof(in_bio)) { + ret = BIO_read(in_bio, (char *)buf, BUFSIZE); + if (ret < 0) { + BIO_printf(bio_err, "Read error when do sign1\n"); + goto err; + } + if (ret == 0) + break; + + if (!SM2_THRESHOLD_sign1_update(ctx, buf, ret)) { + BIO_printf(bio_err, "sign1 update failed\n"); + goto err; + } + } + + if (!SM2_THRESHOLD_sign1_final(ctx, digest, &dlen)) { + BIO_printf(bio_err, "sign1 final failed\n"); + goto err; + } + + if (out_format == OUT_FORMAT_BINARY) { + if (BIO_write(out_bio, digest, dlen) != (int)dlen) { + BIO_printf(bio_err, "Error occur when output digest\n"); + goto err; + } + } else { + if (!OPENSSL_buf2hexstr_ex((char *)buf, BUFSIZE, &buflen, digest, dlen, + '\0')) { + BIO_printf(bio_err, "Error encoding digest\n"); + goto err; + } + + BIO_printf(out_bio, "SM2_threshold_sign1, digest=%s\n", buf); + } + + ret = 1; + + err: + OPENSSL_free(buf); + EVP_MD_CTX_free(ctx); + return ret; +} + +static int sign2(const EVP_PKEY *key1, const EVP_PKEY *peer_pubkey, + const char *digest, int out_format, BIO *out) +{ + int ret = 0; + unsigned char *buf, *sigbuf = NULL; + size_t siglen; + long buflen; + + buf = OPENSSL_hexstr2buf(digest, &buflen); + if (buf == NULL) { + BIO_printf(bio_err, "failed to decode digest\n"); + goto end; + } + + ret = SM2_THRESHOLD_sign2(key1, peer_pubkey, buf, buflen, &sigbuf, &siglen); + + if (ret != 1) + goto end; + + /* Note: default format for signature is binary */ + if (out_format == OUT_FORMAT_HEX) { + if (BIO_hex_string(out, 0, siglen, sigbuf, siglen) != 1) { + BIO_printf(bio_err, "Error occur when output signature\n"); + goto end; + } + } else { + ret = BIO_write(out, sigbuf, siglen); + if (ret != (int)siglen) { + BIO_printf(bio_err, "Error occur when output signature\n"); + goto end; + } + } + + ret = 1; +end: + OPENSSL_free(buf); + OPENSSL_free(sigbuf); + return ret; +} diff --git a/crypto/sm2/build.info b/crypto/sm2/build.info index 6f71ae0ae..fbd2780aa 100644 --- a/crypto/sm2/build.info +++ b/crypto/sm2/build.info @@ -2,4 +2,7 @@ LIBS=../../libcrypto SOURCE[../../libcrypto]=\ sm2_sign.c sm2_crypt.c sm2_err.c sm2_key.c sm2_kmeth.c +IF[{- !$disabled{sm2_threshold} -}] + SOURCE[../../libcrypto]=sm2_threshold.c +ENDIF diff --git a/crypto/sm2/sm2_threshold.c b/crypto/sm2/sm2_threshold.c new file mode 100644 index 000000000..feae932e5 --- /dev/null +++ b/crypto/sm2/sm2_threshold.c @@ -0,0 +1,485 @@ +/* + * Copyright 2024 The Tongsuo Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://github.com/Tongsuo-Project/Tongsuo/blob/master/LICENSE.txt + */ + +#include "internal/deprecated.h" + +#include "crypto/sm2.h" +#include "crypto/sm2err.h" +#include "crypto/ec.h" +#include "internal/numbers.h" +#include +#include +#include +#include + +EVP_PKEY *SM2_THRESHOLD_derive_partial_pubkey(const EVP_PKEY *key) +{ + EVP_PKEY *ret = NULL; + const EC_KEY *eckey = NULL; + EC_KEY *tmpkey = NULL; + const BIGNUM *dA; + const EC_GROUP *group; + EC_POINT *P1 = NULL; + BN_CTX *ctx = NULL; + BIGNUM *dA_inv = NULL; + OSSL_LIB_CTX *libctx; + + if (key == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + eckey = EVP_PKEY_get0_EC_KEY(key); + if (eckey == NULL) + return 0; + + dA = EC_KEY_get0_private_key(eckey); + group = EC_KEY_get0_group(eckey); + libctx = ossl_ec_key_get_libctx(eckey); + + ctx = BN_CTX_new_ex(libctx); + if (ctx == NULL) + return 0; + + BN_CTX_start(ctx); + dA_inv = BN_CTX_get(ctx); + if (dA_inv == NULL) + goto err; + + P1 = EC_POINT_new(group); + if (P1 == NULL) + goto err; + + /* + * Compute the partial public key: + * P_1 = d_1^(-1) * G + */ + if (!ossl_ec_group_do_inverse_ord(group, dA_inv, dA, ctx) + || !EC_POINT_mul(group, P1, dA_inv, NULL, NULL, ctx)) + goto err; + + ret = EVP_PKEY_new(); + if (ret == NULL) + goto err; + + tmpkey = EC_KEY_new_by_curve_name(NID_sm2); + if (tmpkey == NULL) + goto err; + + if (!EC_KEY_set_public_key(tmpkey, P1) + || !EVP_PKEY_assign_EC_KEY(ret, tmpkey)) + goto err; + + EC_POINT_free(P1); + BN_CTX_end(ctx); + BN_CTX_free(ctx); + return ret; +err: + EC_POINT_free(P1); + BN_CTX_end(ctx); + BN_CTX_free(ctx); + EC_KEY_free(tmpkey); + EVP_PKEY_free(ret); + return NULL; +} + +EVP_PKEY *SM2_THRESHOLD_derive_complete_pubkey(const EVP_PKEY *self_key, + const EVP_PKEY *peer_pubkey) +{ + EVP_PKEY *ret = NULL; + const EC_KEY *eckey; + const BIGNUM *d1; + const EC_GROUP *group; + EC_KEY *tmpkey = NULL; + EC_POINT *P = NULL, *G_inv = NULL; + const EC_POINT *P2; + BN_CTX *ctx = NULL; + BIGNUM *d1_inv = NULL; + OSSL_LIB_CTX *libctx; + + if (self_key == NULL || peer_pubkey == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_PASSED_NULL_PARAMETER); + return NULL; + } + + eckey = EVP_PKEY_get0_EC_KEY(peer_pubkey); + if (eckey == NULL) + return NULL; + + P2 = EC_KEY_get0_public_key(eckey); + + eckey = EVP_PKEY_get0_EC_KEY(self_key); + if (eckey == NULL) + return NULL; + + d1 = EC_KEY_get0_private_key(eckey); + group = EC_KEY_get0_group(eckey); + libctx = ossl_ec_key_get_libctx(eckey); + + P = EC_POINT_new(group); + if (P == NULL) + goto err; + + G_inv = EC_POINT_dup(EC_GROUP_get0_generator(group), group); + if (G_inv == NULL) + goto err; + + ctx = BN_CTX_new_ex(libctx); + if (ctx == NULL) + goto err; + + BN_CTX_start(ctx); + d1_inv = BN_CTX_get(ctx); + if (d1_inv == NULL) + goto err; + + /* + * Compute the complete public key: + * P = d_1^(-1) * P_2 - G + */ + if (!ossl_ec_group_do_inverse_ord(group, d1_inv, d1, ctx) + || !EC_POINT_mul(group, P, NULL, P2, d1_inv, ctx) + || !EC_POINT_invert(group, G_inv, ctx) + || !EC_POINT_add(group, P, P, G_inv, ctx)) { + ERR_raise(ERR_LIB_SM2, ERR_R_EC_LIB); + goto err; + } + + ret = EVP_PKEY_new(); + if (ret == NULL) + goto err; + + tmpkey = EC_KEY_new_by_curve_name(NID_sm2); + if (tmpkey == NULL) + goto err; + + if (!EC_KEY_set_public_key(tmpkey, P) + || !EVP_PKEY_assign_EC_KEY(ret, tmpkey)) + goto err; + + EC_POINT_free(G_inv); + EC_POINT_free(P); + BN_CTX_end(ctx); + BN_CTX_free(ctx); + return ret; + + err: + BN_CTX_end(ctx); + BN_CTX_free(ctx); + EC_POINT_free(G_inv); + EC_POINT_free(P); + EVP_PKEY_free(ret); + + return NULL; +} + +int SM2_THRESHOLD_sign1_init(EVP_MD_CTX *ctx, const EVP_MD *type, + const EVP_PKEY *pubkey, const uint8_t *id, + const size_t id_len) +{ + int ret = 0; + uint8_t *z = NULL; + const EC_KEY *eckey; + int md_size; + + if (ctx == NULL || type == NULL || pubkey == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + md_size = EVP_MD_get_size(type); + z = OPENSSL_zalloc(md_size); + if (z == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + return 0; + } + + eckey = EVP_PKEY_get0_EC_KEY(pubkey); + if (eckey == NULL) + goto end; + + /* get hashed prefix 'z' of tbs message */ + if (!ossl_sm2_compute_z_digest(z, type, id, id_len, eckey)) + goto end; + + if (!EVP_DigestInit(ctx, type) + || !EVP_DigestUpdate(ctx, z, md_size)) + goto end; + + ret = 1; +end: + OPENSSL_free(z); + return ret; +} + +int SM2_THRESHOLD_sign1_update(EVP_MD_CTX *ctx, const uint8_t *msg, + size_t msg_len) +{ + if (ctx == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + return EVP_DigestUpdate(ctx, msg, msg_len); +} + +int SM2_THRESHOLD_sign1_final(EVP_MD_CTX *ctx, uint8_t *digest, size_t *dlen) +{ + int ret; + unsigned int len = 0; + + if (ctx == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + ret = EVP_DigestFinal(ctx, digest, &len); + if (dlen != NULL) + *dlen = len; + + return ret; +} + +int SM2_THRESHOLD_sign1_oneshot(const EVP_PKEY *pubkey, + const EVP_MD *type, + const uint8_t *id, + const size_t id_len, + const uint8_t *msg, size_t msg_len, + uint8_t *digest, size_t *dlen) +{ + EVP_MD_CTX *ctx = NULL; + + if (pubkey == NULL || type == NULL || msg == NULL || digest == NULL + || dlen == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + return 0; + } + + if (!SM2_THRESHOLD_sign1_init(ctx, type, pubkey, id, id_len) + || !SM2_THRESHOLD_sign1_update(ctx, msg, msg_len) + || !SM2_THRESHOLD_sign1_final(ctx, digest, dlen)) { + EVP_MD_CTX_free(ctx); + return 0; + } + + EVP_MD_CTX_free(ctx); + return 1; +} + +int SM2_THRESHOLD_sign2(const EVP_PKEY *key, const EVP_PKEY *peer_Q1, + uint8_t *digest, size_t dlen, + unsigned char **sig, size_t *siglen) +{ + int ret = 0; + const EC_KEY *eckey; + const EC_GROUP *group; + const BIGNUM *dA, *order; + EC_POINT *Q = NULL; + const EC_POINT *Q1 = NULL; + BN_CTX *ctx = NULL; + BIGNUM *dA_inv = NULL, *w2 = NULL, *x1 = NULL, *r = NULL, *s1 = NULL, *e; + OSSL_LIB_CTX *libctx; + ECDSA_SIG *tmpsig = NULL; + + if (key == NULL || peer_Q1 == NULL || digest == NULL || sig == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + e = BN_bin2bn(digest, dlen, NULL); + if (e == NULL) + return 0; + + eckey = EVP_PKEY_get0_EC_KEY(peer_Q1); + if (eckey == NULL) + return 0; + + Q1 = EC_KEY_get0_public_key(eckey); + if (Q1 == NULL) + return 0; + + eckey = EVP_PKEY_get0_EC_KEY(key); + if (eckey == NULL) + return 0; + + group = EC_KEY_get0_group(eckey); + order = EC_GROUP_get0_order(group); + dA = EC_KEY_get0_private_key(eckey); + libctx = ossl_ec_key_get_libctx(eckey); + + Q = EC_POINT_new(group); + if (Q == NULL) + goto done; + + ctx = BN_CTX_new_ex(libctx); + if (ctx == NULL) + goto done; + + BN_CTX_start(ctx); + dA_inv = BN_CTX_get(ctx); + w2 = BN_CTX_get(ctx); + x1 = BN_CTX_get(ctx); + if (x1 == NULL) + goto done; + + /* + * These values are returned and so should not be allocated out of the + * context + */ + r = BN_new(); + if (r == NULL) + goto done; + + s1 = BN_new(); + if (s1 == NULL) + goto done; + + /* + * SM2 threshold signature part 2: + * 1. Generate a random number w2 in [1,n-1] using random number generators; + * 2. Compute Q = [w2]G + dA^(-1) * Q1 + * 3. Compute r = (e + x1) mod n + * 4. Compute s1 = dA(r + w2) mod n + */ + + do { + if (!BN_priv_rand_range_ex(w2, order, 0, ctx)) + goto done; + } while (BN_is_zero(w2)); + + if (!ossl_ec_group_do_inverse_ord(group, dA_inv, dA, ctx) + || !EC_POINT_mul(group, Q, w2, Q1, dA_inv, ctx) + || !EC_POINT_get_affine_coordinates(group, Q, x1, NULL, ctx)) + goto done; + + if (!BN_mod_add(r, e, x1, order, ctx) + || !BN_add(s1, r, w2) + || !BN_mod_mul(s1, s1, dA, order, ctx)) { + ERR_raise(ERR_LIB_SM2, ERR_R_BN_LIB); + goto done; + } + + tmpsig = ECDSA_SIG_new(); + if (tmpsig == NULL) + goto done; + + /* a "partial" signature to stored r and s1 */ + if (!ECDSA_SIG_set0(tmpsig, r, s1)) + goto done; + + r = NULL; + s1 = NULL; + + ret = i2d_ECDSA_SIG(tmpsig, sig); + if (ret <= 0) + goto done; + + if (siglen != NULL) + *siglen = ret; + + ret = 1; + + done: + BN_free(r); + BN_free(s1); + BN_free(e); + ECDSA_SIG_free(tmpsig); + EC_POINT_free(Q); + BN_CTX_end(ctx); + BN_CTX_free(ctx); + + return ret; +} + +int SM2_THRESHOLD_sign3(const EVP_PKEY *key, const EVP_PKEY *temp_key, + const unsigned char *sig2, size_t sig2_len, + unsigned char **sig, size_t *siglen) +{ + int ret = 0; + const EC_KEY *eckey; + BIGNUM *w1 = NULL, *r = NULL, *s = NULL; + const EC_GROUP *group; + const BIGNUM *dA, *order; + BN_CTX *ctx = NULL; + OSSL_LIB_CTX *libctx; + ECDSA_SIG *tmpsig = NULL; + + if (key == NULL || temp_key == NULL || sig2 == NULL || sig == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + tmpsig = d2i_ECDSA_SIG(NULL, &sig2, sig2_len); + if (tmpsig == NULL) + return 0; + + if (!EVP_PKEY_get_bn_param(temp_key, OSSL_PKEY_PARAM_PRIV_KEY, &w1)) + goto done; + + eckey = EVP_PKEY_get0_EC_KEY(key); + if (eckey == NULL) + return 0; + + group = EC_KEY_get0_group(eckey); + dA = EC_KEY_get0_private_key(eckey); + order = EC_GROUP_get0_order(group); + libctx = ossl_ec_key_get_libctx(eckey); + + ctx = BN_CTX_new_ex(libctx); + if (ctx == NULL) + goto done; + + /* + * These values are returned and so should not be allocated out of the + * context + */ + r = BN_dup(ECDSA_SIG_get0_r(tmpsig)); + if (r == NULL) + goto done; + + s = BN_new(); + if (s == NULL) + goto done; + + /* + * SM2 threshold signature part 3: + * s = (d1 * (s1 + w1) - r) mod n + */ + if (!BN_add(s, ECDSA_SIG_get0_s(tmpsig), w1) + || !BN_mod_mul(s, s, dA, order, ctx) + || !BN_mod_sub(s, s, r, order, ctx)) { + ERR_raise(ERR_LIB_SM2, ERR_R_BN_LIB); + goto done; + } + + if (!ECDSA_SIG_set0(tmpsig, r, s)) + goto done; + + ret = i2d_ECDSA_SIG(tmpsig, sig); + if (siglen != NULL) + *siglen = ret; + + ret = 1; + + done: + if (ret == 0) { + BN_free(r); + BN_free(s); + } + + BN_CTX_free(ctx); + BN_free(w1); + return ret; +} diff --git a/examples/perf/sm2_threshold/Makefile b/examples/perf/sm2_threshold/Makefile new file mode 100644 index 000000000..a417b177e --- /dev/null +++ b/examples/perf/sm2_threshold/Makefile @@ -0,0 +1,12 @@ +CC=gcc +CFLAGS=-I/opt/tongsuo/include +LDFLAGS=-L/opt/tongsuo/lib + +%.o: %.c + $(CC) -c -o $@ $< $(CFLAGS) -O2 + +threshold: threshold.o + $(CC) -o threshold threshold.o -lcrypto $(LDFLAGS) + +clean: + rm -rf *.o threshold diff --git a/examples/perf/sm2_threshold/threshold.c b/examples/perf/sm2_threshold/threshold.c new file mode 100644 index 000000000..c4cbcee75 --- /dev/null +++ b/examples/perf/sm2_threshold/threshold.c @@ -0,0 +1,203 @@ +/* + * Copyright 2024 The Tongsuo Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://github.com/Tongsuo-Project/Tongsuo/blob/master/LICENSE.txt + */ + +/* Performance test for SM2-threshold sign(TPS), verify(TPS), keygen(TPS) */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static long long get_time(); + +/* Iteration number, could be adjusted as required */ +#define ITR_NUM 1000 + +/* Time difference on each index */ +struct perf_index { + int sm2_threshold_sign; + int sm2_threshold_verify; + int sm2_threshold_keygen; +}; + +/* Final result TPS */ +struct perf_result { + int sm2_threshold_sign_avg; + int sm2_threshold_verify_avg; + int sm2_threshold_keygen_avg; +}; + +static long long get_time() +{ + /* Use gettimeofday() to adequate for our case */ + struct timeval tp; + + if (gettimeofday(&tp, NULL) != 0) + return 0; + else + return (long long)(tp.tv_sec * 1000 * 1000 + tp.tv_usec); +} + +/* These values are from GM/T 0003.2-2012 standard */ +static const char *userid = "ALICE123@YAHOO.COM"; +static const char *message = "message digest"; + +int main(void) +{ + int ret = -1; + struct perf_index *indices = NULL; + struct perf_result result; + int msg_len = strlen(message), i = 0; + long long start = 0, end = 0; + EVP_PKEY *key1 = NULL, *key2 = NULL, *pubkey1 = NULL, *pubkey2 = NULL; + EVP_PKEY *complete_key1 = NULL, *complete_key2 = NULL; + EVP_MD_CTX *mctx = NULL; + EVP_PKEY_CTX *pctx = NULL; + unsigned char *sigbuf = NULL, *final_sig = NULL; + EVP_PKEY *temp_key = NULL; + size_t siglen, final_siglen; + unsigned char digest[EVP_MAX_MD_SIZE]; + size_t dlen = 0; + + memset(&result, 0, sizeof(result)); + indices = malloc(sizeof(struct perf_index) * ITR_NUM); + if (indices == NULL) { + fprintf(stderr, "malloc error - indices\n"); + return -1; + } + memset(indices, 0, sizeof(struct perf_index) * ITR_NUM); + + for (; i < ITR_NUM; i++) { + fprintf(stdout, "Iteration %d: ", i); + + /* SM2 threshold keygen */ + start = get_time(); + + key1 = EVP_PKEY_Q_keygen(NULL, NULL, "SM2"); + key2 = EVP_PKEY_Q_keygen(NULL, NULL, "SM2"); + if (key1 == NULL || key2 == NULL) + goto err; + + pubkey1 = SM2_THRESHOLD_derive_partial_pubkey(key1); + pubkey2 = SM2_THRESHOLD_derive_partial_pubkey(key2); + if (pubkey1 == NULL || pubkey2 == NULL) + goto err; + + complete_key1 = SM2_THRESHOLD_derive_complete_pubkey(key1, pubkey2); + complete_key2 = SM2_THRESHOLD_derive_complete_pubkey(key2, pubkey1); + if (complete_key1 == NULL || complete_key2 == NULL) + goto err; + + end = get_time(); + /* Generate 2 keypair per iteration, so the result need to multiple 2 */ + indices[i].sm2_threshold_keygen = 1000 * 1000 * 2/ (end - start); + + temp_key = EVP_PKEY_Q_keygen(NULL, NULL, "SM2"); + if (temp_key == NULL) + goto err; + + /* SM2 threshold sign */ + start = get_time(); + if (!SM2_THRESHOLD_sign1_oneshot(complete_key1, EVP_sm3(), + (const uint8_t *)userid, + strlen(userid), + (const uint8_t *)message, + msg_len, digest, &dlen) + || !SM2_THRESHOLD_sign2(key2, temp_key, digest, dlen, &sigbuf, + &siglen) + || !SM2_THRESHOLD_sign3(key1, temp_key, sigbuf, siglen, + &final_sig, &final_siglen)) + goto err; + end = get_time(); + indices[i].sm2_threshold_sign = 1000 * 1000 / (end - start); + + start = get_time(); + + if ((mctx = EVP_MD_CTX_new()) == NULL + || (pctx = EVP_PKEY_CTX_new(complete_key1, NULL)) == NULL) + goto err; + + EVP_MD_CTX_set_pkey_ctx(mctx, pctx); + + if (!EVP_PKEY_CTX_set1_id(pctx, userid, strlen(userid))) + goto err; + + if (!EVP_DigestVerifyInit(mctx, NULL, EVP_sm3(), NULL, complete_key1) + || !EVP_DigestVerify(mctx, final_sig, final_siglen, + (const unsigned char *)message, msg_len)) + goto err; + + end = get_time(); + indices[i].sm2_threshold_verify = 1000 * 1000 / (end - start); + + EVP_PKEY_free(key1); + key1 = NULL; + EVP_PKEY_free(key2); + key2 = NULL; + EVP_PKEY_free(pubkey1); + pubkey1 = NULL; + EVP_PKEY_free(pubkey2); + pubkey2 = NULL; + EVP_PKEY_free(complete_key1); + complete_key1 = NULL; + EVP_PKEY_free(complete_key2); + complete_key2 = NULL; + EVP_PKEY_free(temp_key); + temp_key = NULL; + OPENSSL_free(sigbuf); + sigbuf = NULL; + OPENSSL_free(final_sig); + final_sig = NULL; + EVP_MD_CTX_free(mctx); +#if 1 + fprintf(stdout, "sm2-threshold-sign: %d, " + "sm2-threshold-verify: %d, " + "sm2-threshold-keygen: %d\n", + indices[i].sm2_threshold_sign, + indices[i].sm2_threshold_verify, + indices[i].sm2_threshold_keygen); +#endif + } + + /* calculate the final average result */ + for (i = 0; i < ITR_NUM; i++) { + result.sm2_threshold_sign_avg += indices[i].sm2_threshold_sign; + result.sm2_threshold_verify_avg += indices[i].sm2_threshold_verify; + result.sm2_threshold_keygen_avg += indices[i].sm2_threshold_keygen; + } + + result.sm2_threshold_sign_avg /= ITR_NUM; + result.sm2_threshold_verify_avg /= ITR_NUM; + result.sm2_threshold_keygen_avg /= ITR_NUM; + + fprintf(stdout, "sm2-threshold-sign: %d/s\n" + "sm2-threshold-verify: %d/s\n" + "sm2-threshold-keygen: %d/s\n", + result.sm2_threshold_sign_avg, result.sm2_threshold_verify_avg, + result.sm2_threshold_keygen_avg); + + ret = 0; +err: + if (ret != 0) + fprintf(stderr, "Error: %s\n", ERR_error_string(ERR_get_error(), NULL)); + OPENSSL_free(indices); + EVP_PKEY_free(key1); + EVP_PKEY_free(key2); + EVP_PKEY_free(complete_key1); + EVP_PKEY_free(complete_key2); + EVP_PKEY_free(temp_key); + + return ret; +} diff --git a/include/crypto/sm2.h b/include/crypto/sm2.h index d032aa13b..d5d85f2f0 100644 --- a/include/crypto/sm2.h +++ b/include/crypto/sm2.h @@ -30,7 +30,6 @@ int ossl_sm2_compute_z_digest(uint8_t *out, const uint8_t *id, size_t id_len, const EC_KEY *key); - /* * SM2 signature operation. Computes Z and then signs H(Z || msg) using SM2 */ diff --git a/include/openssl/sm2_threshold.h b/include/openssl/sm2_threshold.h new file mode 100644 index 000000000..191469b3f --- /dev/null +++ b/include/openssl/sm2_threshold.h @@ -0,0 +1,121 @@ +/* + * Copyright 2024 The Tongsuo Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://github.com/Tongsuo-Project/Tongsuo/blob/master/LICENSE.txt + */ + +#ifndef OPENSSL_SM2_THRESHOLD_H +# define OPENSSL_SM2_THRESHOLD_H +# pragma once + +# include +# ifndef OPENSSL_NO_DEPRECATED_3_0 +# define HEADER_SM2_THRESHOLD_H +# endif + +# include + +# if !defined(OPENSSL_NO_SM2_THRESHOLD) && !defined(FIPS_MODULE) + +# include + +# ifdef __cplusplus +extern "C" { +# endif + +/********************************************************************/ +/* SM2 threshold struct and functions */ +/********************************************************************/ + +/** Derives SM2 threshold partial public key from the private key + * \param key self private key + * \return EVP_PKEY object including partial public key on success or NULL on + * failure + */ +EVP_PKEY *SM2_THRESHOLD_derive_partial_pubkey(const EVP_PKEY *key); + +/** Derives SM2 threshold complete public key from the private key key1 and + * public key pubkey2 from another participant + * \param self_key self private key + * \param peer_pubkey partial public key from another participant + * \return EVP_PKEY object including complete public key or NULL on failure + */ +EVP_PKEY *SM2_THRESHOLD_derive_complete_pubkey(const EVP_PKEY *self_key, + const EVP_PKEY *peer_pubkey); + +/** 1st step of SM2 threshold signature, generates the message digest + * \param pubkey complete public key + * \param type the digest algorithm + * \param id userid to calculate digest + * \param id_len length of userid + * \param msg message to calculate digest + * \param msg_len length of message + * \param digest the output buffer of message digest + * \param dlen length of digest + * \return 1 on success and 0 if an error occurred. + */ +int SM2_THRESHOLD_sign1_oneshot(const EVP_PKEY *pubkey, + const EVP_MD *type, + const uint8_t *id, + const size_t id_len, + const uint8_t *msg, size_t msg_len, + uint8_t *digest, size_t *dlen); +/** The 1st step of SM2 threshold signature, initialize the EVP_MD_CTX + * \param ctx EVP_MD_CTX object. + * \param digest the digest algorithm + * \param pubkey EVP_PKEY object including complete public key + * \param id userid to calculate digest + * \param id_len length of userid + * \return 1 on success and 0 if an error occurred. + */ +int SM2_THRESHOLD_sign1_init(EVP_MD_CTX *ctx, const EVP_MD *digest, + const EVP_PKEY *pubkey, const uint8_t *id, + const size_t id_len); +/** The 1st step of SM2 threshold signature, update the EVP_MD_CTX + * \param ctx EVP_MD_CTX object. + * \param msg message to calculate digest + * \param msg_len length of message + * \return 1 on success and 0 if an error occurred. + */ +int SM2_THRESHOLD_sign1_update(EVP_MD_CTX *ctx, const uint8_t *msg, + size_t msg_len); +/** The 1st step of SM2 threshold signature, finalize the EVP_MD_CTX + * \param ctx EVP_MD_CTX object. + * \param digest the output buffer of message digest + * \param dlen length of digest + * \return 1 on success and 0 if an error occurred. + */ +int SM2_THRESHOLD_sign1_final(EVP_MD_CTX *ctx, uint8_t *digest, size_t *dlen); +/** The 2nd step of SM2 threshold signature, generate the partial threshold signature + * \param key EVP_PKEY object + * \param peer_Q1 the peer temporary public key + * \param digest the message digest generated in the 1st part of signature + * \param dlen length of digest + * \param sig output partial signature + * \param siglen length of sig + * \return 1 on success and 0 if an error occurred. + */ +int SM2_THRESHOLD_sign2(const EVP_PKEY *key, const EVP_PKEY *peer_Q1, + uint8_t *digest, size_t dlen, unsigned char **sig, size_t *siglen); +/** The 3rd step of SM2 threshold signature,generate the final threshold signature + * \param key EVP_PKEY object + * \param temp_key the temporary private key + * \param sig2 the partial signature generated in the 2nd part of signature + * \param sig2_len length of sig2 + * \param sig output final signature + * \param siglen length of sig + * \return 1 on success and 0 if an error occurred. + */ +int SM2_THRESHOLD_sign3(const EVP_PKEY *key, const EVP_PKEY *temp_key, + const unsigned char *sig2, size_t sig2_len, + unsigned char **sig, size_t *siglen); + +# ifdef __cplusplus +} +# endif + +# endif +#endif diff --git a/test/build.info b/test/build.info index 0c52184b8..b0b915caf 100644 --- a/test/build.info +++ b/test/build.info @@ -603,6 +603,9 @@ IF[{- !$disabled{tests} -}] IF[{- !$disabled{sm2} -}] PROGRAMS{noinst}=sm2_internal_test sm2_mod_test ENDIF + IF[{- !$disabled{sm2_threshold} -}] + PROGRAMS{noinst}=sm2_threshold_test + ENDIF IF[{- !$disabled{sm3} -}] PROGRAMS{noinst}=sm3_internal_test ENDIF @@ -737,6 +740,10 @@ IF[{- !$disabled{tests} -}] INCLUDE[siphash_internal_test]=.. ../include ../apps/include DEPEND[siphash_internal_test]=../libcrypto.a libtestutil.a + SOURCE[sm2_threshold_test]=sm2_threshold_test.c + INCLUDE[sm2_threshold_test]=../include ../apps/include + DEPEND[sm2_threshold_test]=../libcrypto.a libtestutil.a + SOURCE[sm2_mod_test]=sm2_mod_test.c INCLUDE[sm2_mod_test]=../include ../apps/include DEPEND[sm2_mod_test]=../libcrypto.a libtestutil.a diff --git a/test/recipes/15-test_sm2_threshold_api.t b/test/recipes/15-test_sm2_threshold_api.t new file mode 100644 index 000000000..f8b168141 --- /dev/null +++ b/test/recipes/15-test_sm2_threshold_api.t @@ -0,0 +1,19 @@ +#! /usr/bin/env perl +# Copyright 2024 The Tongsuo Project Authors. All Rights Reserved. +# +# Licensed under the Apache License 2.0 (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://github.com/Tongsuo-Project/Tongsuo/blob/master/LICENSE.txt + +use strict; +use OpenSSL::Test; +use OpenSSL::Test::Simple; +use OpenSSL::Test::Utils; + +setup("test_threshold_sm2_api"); + +plan skip_all => "This test is unsupported in a no-sm2_threshold build" + if disabled("sm2_threshold"); + +simple_test("test_threshold_sm2_api", "sm2_threshold_test", "sm2"); diff --git a/test/recipes/20-test_sm2_threshold_app.t b/test/recipes/20-test_sm2_threshold_app.t new file mode 100644 index 000000000..ac8423a4d --- /dev/null +++ b/test/recipes/20-test_sm2_threshold_app.t @@ -0,0 +1,115 @@ +#! /usr/bin/env perl +# Copyright 2024 The Tongsuo Project Authors. All Rights Reserved. +# +# Licensed under the Apache License 2.0 (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://github.com/Tongsuo-Project/Tongsuo/blob/master/LICENSE.txt + +use strict; +use OpenSSL::Test; +use OpenSSL::Test::Utils; +use File::Compare; +use OpenSSL::Test qw/:DEFAULT srctop_file/; + +setup("test_threshold_sm2_app"); + +plan skip_all => "This test is unsupported in a no-sm2_threshold build" + if disabled("sm2_threshold"); + +plan tests => 3; + +sub hex_to_binary { + my ($infile, $outfile) = @_; + + open(my $in, '<', $infile) or return 0; + my $hex = <$in>; + close($in); + + $hex =~ s/://g; + + my $bytes = pack "H*", $hex; + + open(my $fh, '>', $outfile) or return 0; + print $fh $bytes; + close($fh); + + return 1; +} + +subtest "Derive SM2 threshold keys" => sub { + plan tests => 7; + + ok(run(app(['openssl', 'genpkey', '-algorithm', 'ec', '-pkeyopt', + 'ec_paramgen_curve:sm2', '-out', 'A-sm2.key']))); + ok(run(app(['openssl', 'genpkey', '-algorithm', 'ec', '-pkeyopt', + 'ec_paramgen_curve:sm2', '-out', 'B-sm2.key']))); + + ok(run(app(['openssl', 'sm2_threshold', '-derive', '-inkey', 'A-sm2.key', + '-pubout', 'A-pub.key']))); + ok(run(app(['openssl', 'sm2_threshold', '-derive', '-inkey', 'B-sm2.key', + '-pubout', 'B-pub.key']))); + + ok(run(app(['openssl', 'sm2_threshold', '-derive', '-inkey', 'A-sm2.key', + '-peerkey', 'B-pub.key', '-pubout', 'ABpub.key']))); + ok(run(app(['openssl', 'sm2_threshold', '-derive', '-inkey', 'B-sm2.key', + '-peerkey', 'A-pub.key', '-pubout', 'BApub.key']))); + + is(compare("ABpub.key", "BApub.key"), 0); +}; + +subtest "SM2 two-party threshold signature" => sub { + plan tests => 5; + + ok(run(app(['openssl', 'genpkey', '-algorithm', 'ec', '-pkeyopt', + 'ec_paramgen_curve:sm2', '-out', 'temp-sm2.key']))); + ok(run(app(['openssl', 'pkey', '-in', 'temp-sm2.key', '-pubout', + '-out', 'temp-sm2pub.key']))); + + my @dgst = run(app(['openssl', 'sm2_threshold', '-sign1', '-pubin', + '-inkey', 'ABpub.key', '-in', + srctop_file('test', 'data.bin')]), capture => 1); + chomp(@dgst); + + @dgst[0] =~ m|^SM2_threshold_sign1, digest=([0-9a-fA-F]+)$|; + my $dgst = $1; + + ok(run(app(['openssl', 'sm2_threshold', '-sign2', '-inkey', 'B-sm2.key', + '-digest', $dgst, '-temppeerkey', 'temp-sm2pub.key', + '-out', 'partial_sig.bin']))); + + ok(run(app(['openssl', 'sm2_threshold', '-sign3', '-inkey', 'A-sm2.key', + '-sigfile', 'partial_sig.bin', '-tempkey', 'temp-sm2.key', + '-out', 'signature.bin']))); + + ok(run(app(['openssl', 'dgst', '-sm3', '-verify', 'ABpub.key', + '-signature', 'signature.bin', + srctop_file('test', 'data.bin')]))); +}; + +subtest "SM2 two-party threshold signature, newkey + hex format sigfile" => sub { + plan tests => 4; + + my @dgst = run(app(['openssl', 'sm2_threshold', '-sign1', '-newkey', + 'temp-sm2.key', '-pubout', 'temp-sm2pub.key', '-pubin', + '-inkey', 'ABpub.key', '-in', + srctop_file('test', 'data.bin')]), capture => 1); + chomp(@dgst); + + @dgst[0] =~ m|^SM2_threshold_sign1, digest=([0-9a-fA-F]+)$|; + my $dgst = $1; + + ok(run(app(['openssl', 'sm2_threshold', '-sign2', '-inkey', 'B-sm2.key', + '-digest', $dgst, '-temppeerkey', 'temp-sm2pub.key', + '-hex', '-out', 'partial_sig.txt']))); + + ok(run(app(['openssl', 'sm2_threshold', '-sign3', '-inkey', 'A-sm2.key', + '-sigform', 'hex', '-sigfile', 'partial_sig.txt', '-tempkey', + 'temp-sm2.key', '-hex', '-out', 'signature.txt']))); + + ok(hex_to_binary("signature.txt", "signature.bin")); + + ok(run(app(['openssl', 'dgst', '-sm3', '-verify', 'ABpub.key', + '-signature', 'signature.bin', + srctop_file('test', 'data.bin')]))); +}; diff --git a/test/sm2_threshold_test.c b/test/sm2_threshold_test.c new file mode 100644 index 000000000..2a367bec9 --- /dev/null +++ b/test/sm2_threshold_test.c @@ -0,0 +1,172 @@ +/* + * Copyright 2024 The Tongsuo Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://github.com/Tongsuo-Project/Tongsuo/blob/master/LICENSE.txt + */ + +#include "internal/deprecated.h" + +#include +#include +#include + +#include +#include +#include +#include "testutil.h" + +#ifndef OPENSSL_NO_SM2_THRESHOLD + +# include + +/* These values are from GM/T 0003.2-2012 standard */ +static const char *userid = "ALICE123@YAHOO.COM"; +static const char *message = "message digest"; + +static int sm2_threshold_keygen_test(void) +{ + int ret = 0; + EVP_PKEY *key1 = NULL, *key2 = NULL; + EVP_PKEY *pubkey1 = NULL, *pubkey2 = NULL; + EVP_PKEY *complete_key1 = NULL, *complete_key2 = NULL; + + if (!TEST_ptr(key1 = EVP_PKEY_Q_keygen(NULL, NULL, "SM2")) + || !TEST_ptr(key2 = EVP_PKEY_Q_keygen(NULL, NULL, "SM2"))) + goto err; + + if (!TEST_ptr(pubkey1 = SM2_THRESHOLD_derive_partial_pubkey(key1)) + || !TEST_ptr(pubkey2 = SM2_THRESHOLD_derive_partial_pubkey(key2))) + goto err; + + if (!TEST_ptr(complete_key1 = + SM2_THRESHOLD_derive_complete_pubkey(key1, pubkey2)) + || !TEST_ptr(complete_key2 = + SM2_THRESHOLD_derive_complete_pubkey(key2, pubkey1))) + goto err; + + if (!TEST_true(EVP_PKEY_eq(complete_key1, complete_key2))) + goto err; + + ret = 1; +err: + EVP_PKEY_free(key1); + EVP_PKEY_free(key2); + EVP_PKEY_free(pubkey1); + EVP_PKEY_free(pubkey2); + EVP_PKEY_free(complete_key1); + EVP_PKEY_free(complete_key2); + + return ret; +} + +static int sm2_threshold_sign_test(int id) +{ + int ret = 0; + int msg_len = strlen(message); + EVP_PKEY *key1 = NULL, *key2 = NULL, *pubkey1 = NULL, *pubkey2 = NULL; + EVP_PKEY *complete_key1 = NULL, *complete_key2 = NULL, *temp_key = NULL; + EVP_MD_CTX *mctx = NULL; + EVP_PKEY_CTX *pctx = NULL; + unsigned char *sigbuf = NULL, *final_sig = NULL; + size_t siglen, final_siglen, dlen; + unsigned char digest[EVP_MAX_MD_SIZE]; + + if (!TEST_ptr(key1 = EVP_PKEY_Q_keygen(NULL, NULL, "SM2")) + || !TEST_ptr(key2 = EVP_PKEY_Q_keygen(NULL, NULL, "SM2"))) + goto err; + + if (!TEST_ptr(pubkey1 = SM2_THRESHOLD_derive_partial_pubkey(key1)) + || !TEST_ptr(pubkey2 = SM2_THRESHOLD_derive_partial_pubkey(key2))) + goto err; + + if (!TEST_ptr(complete_key1 = + SM2_THRESHOLD_derive_complete_pubkey(key1, pubkey2)) + || !TEST_ptr(complete_key2 = + SM2_THRESHOLD_derive_complete_pubkey(key2, pubkey1))) + goto err; + + if (!TEST_true(EVP_PKEY_eq(complete_key1, complete_key2))) + goto err; + + if (!TEST_ptr(temp_key = EVP_PKEY_Q_keygen(NULL, NULL, "SM2"))) + goto err; + + /* Test SM2 threshold sign with id */ + if (id == 0) { + if (!TEST_true(SM2_THRESHOLD_sign1_oneshot(complete_key1, EVP_sm3(), + (const uint8_t *)userid, + strlen(userid), + (const uint8_t *)message, + msg_len, + digest, &dlen)) + || !TEST_true(SM2_THRESHOLD_sign2(key2, temp_key, digest, dlen, + &sigbuf, &siglen)) + || !TEST_true(SM2_THRESHOLD_sign3(key1, temp_key, sigbuf, siglen, + &final_sig, &final_siglen))) + goto err; + + if (!TEST_ptr(mctx = EVP_MD_CTX_new()) + || !TEST_ptr(pctx = EVP_PKEY_CTX_new(complete_key1, NULL))) + goto err; + + EVP_MD_CTX_set_pkey_ctx(mctx, pctx); + + if (!TEST_true(EVP_PKEY_CTX_set1_id(pctx, userid, strlen(userid)))) + goto err; + + if (!TEST_true(EVP_DigestVerifyInit(mctx, NULL, EVP_sm3(), NULL, + complete_key1)) + || !TEST_true(EVP_DigestVerify(mctx, final_sig, final_siglen, + (const unsigned char *)message, + msg_len))) + goto err; + } else { + if (!TEST_true(SM2_THRESHOLD_sign1_oneshot(complete_key1, EVP_sm3(), + NULL, 0, + (const uint8_t *)message, + msg_len, digest, &dlen)) + || !TEST_true(SM2_THRESHOLD_sign2(key2, temp_key, digest, dlen, + &sigbuf, &siglen)) + || !TEST_true(SM2_THRESHOLD_sign3(key1, temp_key, sigbuf, siglen, + &final_sig, &final_siglen))) + goto err; + + if (!TEST_ptr(mctx = EVP_MD_CTX_new()) + || !TEST_true(EVP_DigestVerifyInit(mctx, NULL, EVP_sm3(), NULL, + complete_key1)) + || !TEST_true(EVP_DigestVerify(mctx, final_sig, final_siglen, + (const unsigned char *)message, + msg_len))) + goto err; + } + + ret = 1; +err: + EVP_PKEY_free(key1); + EVP_PKEY_free(key2); + EVP_PKEY_free(pubkey1); + EVP_PKEY_free(pubkey2); + EVP_PKEY_free(complete_key1); + EVP_PKEY_free(complete_key2); + EVP_PKEY_free(temp_key); + EVP_MD_CTX_free(mctx); + OPENSSL_free(sigbuf); + + return ret; +} + +#endif + +int setup_tests(void) +{ +#ifdef OPENSSL_NO_SM2_THRESHOLD + TEST_note("SM2 threshold is disabled."); +#else + ADD_TEST(sm2_threshold_keygen_test); + ADD_ALL_TESTS(sm2_threshold_sign_test, 2); +#endif + return 1; +} diff --git a/util/libcrypto.num b/util/libcrypto.num index 27a7e2693..08ac79238 100644 --- a/util/libcrypto.num +++ b/util/libcrypto.num @@ -5627,3 +5627,11 @@ ZKP_RANGE_PROOF_new 5942 3_0_3 EXIST::FUNCTION:ZKP_GADGET ZKP_RANGE_PROOF_free 5943 3_0_3 EXIST::FUNCTION:ZKP_GADGET ZKP_RANGE_PROOF_prove 5944 3_0_3 EXIST::FUNCTION:ZKP_GADGET ZKP_RANGE_PROOF_verify 5945 3_0_3 EXIST::FUNCTION:ZKP_GADGET +SM2_THRESHOLD_derive_partial_pubkey 5946 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD +SM2_THRESHOLD_derive_complete_pubkey 5947 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD +SM2_THRESHOLD_sign1_init 5948 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD +SM2_THRESHOLD_sign1_update 5949 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD +SM2_THRESHOLD_sign1_final 5950 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD +SM2_THRESHOLD_sign1_oneshot 5951 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD +SM2_THRESHOLD_sign2 5952 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD +SM2_THRESHOLD_sign3 5953 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD