From a1b7f08852a2fdfc853e127a8d9591aa79764367 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 | 7 +- Configure | 9 + apps/build.info | 4 + apps/sm2_threshold.c | 434 +++++++++++++++++++++ crypto/sm2/build.info | 3 + crypto/sm2/sm2_sign.c | 9 + crypto/sm2/sm2_threshold.c | 497 ++++++++++++++++++++++++ examples/perf/sm2_threshold/Makefile | 12 + examples/perf/sm2_threshold/threshold.c | 202 ++++++++++ include/crypto/sm2.h | 5 + include/openssl/sm2_threshold.h | 121 ++++++ test/build.info | 7 + test/recipes/15-test_sm2_threshold.t | 19 + test/sm2_threshold_test.c | 175 +++++++++ util/libcrypto.num | 8 + 18 files changed, 1533 insertions(+), 2 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.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..13b9d8ab2 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -27,7 +27,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/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..740f232c7 --- /dev/null +++ b/apps/sm2_threshold.c @@ -0,0 +1,434 @@ +/* + * Copyright 2023 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 OPTION_choice { + OPT_COMMON, + OPT_DERIVE, OPT_KEY, OPT_KEY1, OPT_PUBKEY2, OPT_PUBOUT, OPT_SIGN1, + OPT_SIGN2, OPT_SIGN3, OPT_TEMP_PUBKEY, OPT_DIGEST, OPT_TEMP_KEY, OPT_OUT, + OPT_PARTIAL_SIGNATURE, OPT_PASSIN, +} OPTION_CHOICE; + +/* +Examples: +sm2_threshold -derive -key key1 -pubout pubkey1 +sm2_threshold -derive -key1 key1 -pubkey2 pubkey2 -pubout pubkey +sm2_threshold -sign1 pubkey1 file +sm2_threshold -sign2 key2 -digest dgst -temp_pubkey pub -out out +sm2_threshold -sign3 key1 -partial_signature partial_sig -temp_key key -out out +*/ +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"}, + + OPT_SECTION("Input"), + {"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"}, + {"key", OPT_KEY, '<', "Input local SM2 threshold partial keypair"}, + {"key1", OPT_KEY1, '<', "Input local SM2 threshold partial keypair"}, + {"passin", OPT_PASSIN, 's', "Key input pass phrase source"}, + {"pubkey2", OPT_PUBKEY2, '<', "Input peer SM2 threshold partial public key"}, + {"partial_signature", OPT_PARTIAL_SIGNATURE, '<', "Input a SM2 threshold partial signature"}, + {"digest", OPT_DIGEST, 's', "Input the message digest in hex format, calculated by 1st part SM2 threshold signature"}, + {"temp_pubkey", OPT_TEMP_PUBKEY, '<', "Input the temp public key in SM2 threshold signature"}, + {"temp_key", OPT_TEMP_KEY, '<', "Input the temp key in SM2 threshold signature"}, + + OPT_SECTION("Output"), + {"pubout", OPT_PUBOUT, '>', "Output public key"}, + {"out", OPT_OUT, '>', "Output file"}, + + OPT_PARAMETERS(), + {"file", 0, 0, "Data file involved in SM2 threshold sign"}, + {NULL} +}; + +static int sign1(EVP_PKEY *pubkey1, BIO *in_bio, BIO *out_bio); +static int sign2(const EVP_PKEY *key1, const EVP_PKEY *temp_pubkey, + const char *digest, BIO *out); +static int sign3(EVP_PKEY *key1, EVP_PKEY *temp_key, + const char *partial_sigfile, BIO *out); + +int sm2_threshold_main(int argc, char **argv) +{ + BIO *in_bio = NULL, *out_bio = NULL; + EVP_PKEY *key1 = NULL, *pubkey1 = NULL, *pubkey2 = NULL; + EVP_PKEY *temp_key = NULL, *temp_pubkey = NULL; + int ret = 1; + int derive = 0; + int sign_step = 0; + char *hex_digest = NULL; + const char *key_file = NULL, *pubkey1_file = NULL, *pubkey2_file = NULL; + const char *partial_sigfile = NULL, *outfile = NULL; + const char *temp_key_file = NULL, *temp_pubkey_file = NULL; + char *passin = NULL, *passinarg = NULL; + char *prog; + unsigned char *msgbuf = NULL; + OPTION_CHOICE o; + + prog = opt_init(argc, argv, sm2_threshold_options); + if ((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_SIGN1: + sign_step = 1; + pubkey1_file = opt_arg(); + break; + case OPT_SIGN2: + sign_step = 2; + key_file = opt_arg(); + break; + case OPT_SIGN3: + sign_step = 3; + key_file = opt_arg(); + break; + case OPT_DIGEST: + hex_digest = opt_arg(); + break; + case OPT_TEMP_PUBKEY: + temp_pubkey_file = opt_arg(); + break; + case OPT_KEY: + key_file = opt_arg(); + break; + case OPT_KEY1: + key_file = opt_arg(); + break; + case OPT_PUBKEY2: + pubkey2_file = opt_arg(); + break; + case OPT_PARTIAL_SIGNATURE: + 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_TEMP_KEY: + temp_key_file = opt_arg(); + break; + default: + goto opthelp; + } + } + + argc = opt_num_rest(); + argv = opt_rest(); + + if (sign_step == 1 && argc != 1) { + BIO_printf(bio_err, "%s: Can only sign one file.\n", prog); + goto end; + } + + 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 (key_file != NULL) { + if (!app_passwd(passinarg, NULL, &passin, NULL)) { + BIO_printf(bio_err, "Error getting passwords\n"); + goto end; + } + + key1 = load_key(key_file, FORMAT_PEM, 1, passin, NULL, "key1"); + if (key1 == NULL) + goto end; + } + + if (pubkey2_file) { + pubkey2 = load_pubkey(pubkey2_file, FORMAT_PEM, 1, NULL, NULL, + "pubkey2"); + if (pubkey2 == 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 (temp_pubkey_file) { + temp_pubkey = load_pubkey(temp_pubkey_file, FORMAT_PEM, 1, NULL, NULL, + "temp_pubkey_file"); + if (temp_pubkey == NULL) + goto end; + } + + if (partial_sigfile != NULL) { + if (key1 == NULL) { + BIO_printf(bio_err, "No key specified\n"); + goto opthelp; + } + } + + if (derive) { + EVP_PKEY *pubkey = NULL; + + if (key1 && pubkey2) { + pubkey = SM2_THRESHOLD_derive_complete_pubkey(key1, pubkey2); + } else if (key1) { + pubkey = SM2_THRESHOLD_derive_partial_pubkey(key1); + } 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 pubkey1\n"); + goto end; + } + } else if (sign_step == 1) { + if (pubkey1 == NULL) { + BIO_printf(bio_err, "No SM2 threshold partial public key1 specified\n"); + goto opthelp; + } + + pubkey1 = load_pubkey(pubkey1_file, FORMAT_PEM, 1, NULL, NULL, + "pubkey1"); + if (pubkey1 == NULL) + goto end; + + in_bio = bio_open_default(argv[0], 'r', FORMAT_BINARY); + + if (!sign1(pubkey1, in_bio, bio_out)) + goto end; + } else if (sign_step == 2) { + if (key1 == NULL) { + BIO_printf(bio_err, "No key specified\n"); + goto opthelp; + } + + if (!sign2(key1, temp_pubkey, hex_digest, out_bio)) + goto end; + } else if (sign_step == 3) { + if (key1 == NULL || temp_key == NULL) { + BIO_printf(bio_err, "No key specified\n"); + goto opthelp; + } + + if (!sign3(key1, temp_key, partial_sigfile, 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(key1); + EVP_PKEY_free(pubkey1); + EVP_PKEY_free(pubkey2); + OPENSSL_free(msgbuf); + BIO_free(in_bio); + BIO_free(out_bio); + + return ret; +} + +static int sign3(EVP_PKEY *key1, EVP_PKEY *temp_key, + const char *partial_sigfile, BIO *out) +{ + int ret = 0, tmplen; + BIO *sigbio = NULL; + unsigned char *sigbuf = NULL, *final_sig = NULL; + size_t siglen, final_siglen; + + 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; + } + + siglen = EVP_PKEY_size(key1); + sigbuf = app_malloc(siglen, "signature buffer"); + if (sigbuf == NULL) + goto end; + + siglen = BIO_read(sigbio, sigbuf, siglen); + if (siglen <= 0) { + BIO_printf(bio_err, "Error reading signature file %s\n", + partial_sigfile); + goto end; + } + + 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; + } + + tmplen = BIO_write(out, final_sig, final_siglen); + if (tmplen != (int)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, 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 (!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 *temp_pubkey, + const char *digest, BIO *out) +{ + int ret = 0; + unsigned char *buf, *sigbuf = NULL; + size_t siglen, tmplen; + 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, temp_pubkey, buf, buflen, &sigbuf, &siglen); + + if (ret != 1) + goto end; + + tmplen = BIO_write(out, sigbuf, siglen); + if (tmplen != 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_sign.c b/crypto/sm2/sm2_sign.c index 104096766..1833c5f13 100644 --- a/crypto/sm2/sm2_sign.c +++ b/crypto/sm2/sm2_sign.c @@ -199,6 +199,15 @@ static BIGNUM *sm2_compute_msg_hash(const EVP_MD *digest, return e; } +BIGNUM *ossl_sm2_compute_msg_hash(const EVP_MD *digest, + const EC_KEY *key, + const uint8_t *id, + const size_t id_len, + const uint8_t *msg, size_t msg_len) +{ + return sm2_compute_msg_hash(digest, key, id, id_len, msg, msg_len); +} + static ECDSA_SIG *sm2_sig_gen(const EC_KEY *key, const BIGNUM *e) { const BIGNUM *dA = EC_KEY_get0_private_key(key); diff --git a/crypto/sm2/sm2_threshold.c b/crypto/sm2/sm2_threshold.c new file mode 100644 index 000000000..13f42bc10 --- /dev/null +++ b/crypto/sm2/sm2_threshold.c @@ -0,0 +1,497 @@ +/* + * Copyright 2023 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) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto err; + } + + BN_CTX_start(ctx); + dA_inv = BN_CTX_get(ctx); + if (dA_inv == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto err; + } + + P1 = EC_POINT_new(group); + if (P1 == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + 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)) { + ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR); + goto err; + } + + ret = EVP_PKEY_new(); + tmpkey = EC_KEY_new_by_curve_name(NID_sm2); + if (ret == NULL || tmpkey == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto err; + } + + if (!EC_KEY_set_public_key(tmpkey, P1) + || !EVP_PKEY_assign_EC_KEY(ret, tmpkey)) { + ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR); + goto err; + } + + EC_POINT_free(P1); + BN_CTX_free(ctx); + return ret; +err: + EC_POINT_free(P1); + BN_CTX_free(ctx); + EC_KEY_free(tmpkey); + EVP_PKEY_free(ret); + return NULL; +} + +EVP_PKEY *SM2_THRESHOLD_derive_complete_pubkey(const EVP_PKEY *key1, + const EVP_PKEY *pubkey2) +{ + 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 (key1 == NULL || pubkey2 == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_PASSED_NULL_PARAMETER); + return NULL; + } + + eckey = EVP_PKEY_get0_EC_KEY(pubkey2); + if (eckey == NULL) + return NULL; + + P2 = EC_KEY_get0_public_key(eckey); + + eckey = EVP_PKEY_get0_EC_KEY(key1); + 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); + G_inv = EC_POINT_dup(EC_GROUP_get0_generator(group), group); + ctx = BN_CTX_new_ex(libctx); + if (P == NULL || G_inv == NULL || ctx == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto err; + } + + BN_CTX_start(ctx); + d1_inv = BN_CTX_get(ctx); + if (d1_inv == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + 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_INTERNAL_ERROR); + goto err; + } + + ret = EVP_PKEY_new(); + tmpkey = EC_KEY_new_by_curve_name(NID_sm2); + if (ret == NULL || tmpkey == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto err; + } + + if (!EC_KEY_set_public_key(tmpkey, P) + || !EVP_PKEY_assign_EC_KEY(ret, tmpkey)) { + ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR); + goto err; + } + + EC_POINT_free(G_inv); + EC_POINT_free(P); + BN_CTX_free(ctx); + return ret; + + err: + 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) +{ + if (ctx == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + return EVP_DigestFinal(ctx, digest, (unsigned int *)dlen); +} + +int SM2_THRESHOLD_sign1(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 *temp_pubkey, + 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 || temp_pubkey == 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(temp_pubkey); + 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); + ctx = BN_CTX_new_ex(libctx); + if (Q == NULL || ctx == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto done; + } + + BN_CTX_start(ctx); + dA_inv = BN_CTX_get(ctx); + w2 = BN_CTX_get(ctx); + x1 = BN_CTX_get(ctx); + if (dA_inv == NULL || w2 == NULL || x1 == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto done; + } + + /* + * These values are returned and so should not be allocated out of the + * context + */ + r = BN_new(); + s1 = BN_new(); + + if (r == NULL || s1 == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + 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)) { + ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR); + 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) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto done; + } + + /* a "partial" signature to stored r and s1 */ + if (!ECDSA_SIG_set0(tmpsig, r, s1)) { + ERR_raise(ERR_LIB_SM2, ERR_R_BN_LIB); + 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_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) { + ERR_raise(ERR_LIB_SM2, ERR_R_EC_LIB); + return 0; + } + + if (!EVP_PKEY_get_bn_param(temp_key, OSSL_PKEY_PARAM_PRIV_KEY, &w1)) { + ERR_raise(ERR_LIB_SM2, ERR_R_EC_LIB); + 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) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto done; + } + BN_CTX_start(ctx); + + /* + * These values are returned and so should not be allocated out of the + * context + */ + r = BN_dup(ECDSA_SIG_get0_r(tmpsig)); + s = BN_new(); + + if (r == NULL || s == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + 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)) { + ERR_raise(ERR_LIB_SM2, ERR_R_BN_LIB); + 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..bd9ff5c32 --- /dev/null +++ b/examples/perf/sm2_threshold/threshold.c @@ -0,0 +1,202 @@ +/* + * Copyright 2023 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(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..a59d84e72 100644 --- a/include/crypto/sm2.h +++ b/include/crypto/sm2.h @@ -31,6 +31,11 @@ int ossl_sm2_compute_z_digest(uint8_t *out, size_t id_len, const EC_KEY *key); +BIGNUM *ossl_sm2_compute_msg_hash(const EVP_MD *digest, + const EC_KEY *key, + const uint8_t *id, + const size_t id_len, + const uint8_t *msg, size_t msg_len); /* * 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..0975832ea --- /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 EVP_PKEY object + * \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 key1 EVP_PKEY object + * \param pubkey2 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 *key1, + const EVP_PKEY *pubkey2); + +/** 1st step of SM2 threshold signature, generates the message digest + * \param pubkey EVP_PKEY object including 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(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 temp_pubkey the 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 *temp_pubkey, + 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.t b/test/recipes/15-test_sm2_threshold.t new file mode 100644 index 000000000..e64dd4bc2 --- /dev/null +++ b/test/recipes/15-test_sm2_threshold.t @@ -0,0 +1,19 @@ +#! /usr/bin/env perl +# Copyright 2023 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; # get 'plan' +use OpenSSL::Test::Simple; +use OpenSSL::Test::Utils; + +setup("test_threshold_sm2"); + +plan skip_all => "This test is unsupported in a no-sm2_threshold build" + if disabled("sm2_threshold"); + +simple_test("test_threshold_sm2", "sm2_threshold_test", "sm2"); diff --git a/test/sm2_threshold_test.c b/test/sm2_threshold_test.c new file mode 100644 index 000000000..4ab72061a --- /dev/null +++ b/test/sm2_threshold_test.c @@ -0,0 +1,175 @@ +/* + * 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; + + key1 = EVP_PKEY_Q_keygen(NULL, NULL, "SM2"); + key2 = EVP_PKEY_Q_keygen(NULL, NULL, "SM2"); + if (!TEST_ptr(key1) || !TEST_ptr(key2)) + goto err; + + pubkey1 = SM2_THRESHOLD_derive_partial_pubkey(key1); + pubkey2 = SM2_THRESHOLD_derive_partial_pubkey(key2); + if (!TEST_ptr(pubkey1) || !TEST_ptr(pubkey2)) + goto err; + + complete_key1 = SM2_THRESHOLD_derive_complete_pubkey(key1, pubkey2); + complete_key2 = SM2_THRESHOLD_derive_complete_pubkey(key2, pubkey1); + if (!TEST_ptr(complete_key1) || !TEST_ptr(complete_key2)) + 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]; + + key1 = EVP_PKEY_Q_keygen(NULL, NULL, "SM2"); + key2 = EVP_PKEY_Q_keygen(NULL, NULL, "SM2"); + if (!TEST_ptr(key1) || !TEST_ptr(key2)) + goto err; + + pubkey1 = SM2_THRESHOLD_derive_partial_pubkey(key1); + pubkey2 = SM2_THRESHOLD_derive_partial_pubkey(key2); + if (!TEST_ptr(pubkey1) || !TEST_ptr(pubkey2)) + goto err; + + complete_key1 = SM2_THRESHOLD_derive_complete_pubkey(key1, pubkey2); + complete_key2 = SM2_THRESHOLD_derive_complete_pubkey(key2, pubkey1); + if (!TEST_ptr(complete_key1) || !TEST_ptr(complete_key2)) + goto err; + + if (!TEST_true(EVP_PKEY_eq(complete_key1, complete_key2))) + goto err; + + temp_key = EVP_PKEY_Q_keygen(NULL, NULL, "SM2"); + + /* Test SM2 threshold sign with id */ + if (id == 0) { + if (!TEST_true(SM2_THRESHOLD_sign1(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(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..29a50b287 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 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 \ No newline at end of file