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..63b173d42 --- /dev/null +++ b/apps/sm2_threshold.c @@ -0,0 +1,499 @@ +/* + * 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 MAXSIZE +#define MAXSIZE 1024*1024*8 + +typedef enum OPTION_choice { + OPT_COMMON, + OPT_KEYGEN, OPT_PUBKEY, OPT_CKEYGEN, + OPT_SIGN_INIT, OPT_SIGN_UPDATE, OPT_SIGN_FINAL, OPT_VERIFY, + OPT_KEY_IN, OPT_PUBKEY_IN, OPT_SIGNATURE_IN, + OPT_OUT, OPT_RAND_OUT, +} OPTION_CHOICE; + +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"), + {"keygen", OPT_KEYGEN, '-', "Generate a SM2 threshold keypair, including a private key and a partial public key"}, + {"pubkey", OPT_PUBKEY, '-', "Extract a partial or complete public key from SM2 threshold key pairs"}, + {"ckeygen", OPT_CKEYGEN, '-', "Generate a SM2 complete public key, output a complete keypair"}, + + OPT_SECTION("Sign-Action"), + {"sign_init", OPT_SIGN_INIT, '-', "Compute the 1st part of SM2 threshold signature"}, + {"sign_update", OPT_SIGN_UPDATE, '-', "Compute the 2nd part of SM2 threshold signature"}, + {"sign_final", OPT_SIGN_FINAL, '-', "Compute the 3rd part of SM2 threshold signature"}, + {"verify", OPT_VERIFY, '-', "Verify a SM2 threshold signature using complete public key"}, + + OPT_SECTION("Input"), + {"key_in", OPT_KEY_IN, '<', "Input a SM2 threshold (partial or complete) keypair"}, + {"pubkey_in", OPT_PUBKEY_IN, '<', "Input a SM2 threshold (partial or complete) public key"}, + {"signature_in", OPT_SIGNATURE_IN, '<', "Input a SM2 threshold signature"}, + + OPT_SECTION("Output"), + {"out", OPT_OUT, '>', "Output the SM2 threshold result to specified file"}, + {"rand_out", OPT_RAND_OUT, '>', "Output the SM2 threshold random number to specified file"}, + + OPT_PARAMETERS(), + {"file", 0, 0, "Data file involved in SM2 threshold sign or verify"}, + {NULL} +}; + +static int readbuf(const char *filename, unsigned char **buf); +static int sm2_threshold_keygen(BIO *out); +static int sm2_threshold_pubkey(BIO *key_in, BIO *out); +static int sm2_threshold_ckeygen(BIO *key_in, BIO *pubkey_in, BIO *out); +static int sm2_threshold_sign_init(BIO *key_in, BIO *rand_out, BIO *out, + const unsigned char *msgbuf, int msglen); +static int sm2_threshold_sign_update(BIO *key_in, BIO *out, + const unsigned char *msgbuf, int msglen); +static int sm2_threshold_sign_final(BIO *key_in, BIO *out, + const unsigned char *msgbuf, int msglen, + const unsigned char *psigbuf, int psiglen); +static int sm2_threshold_verify(BIO *pubkey_in, BIO * out, + const unsigned char *msgbuf, int msglen, + const unsigned char *sigbuf, int siglen); + +int sm2_threshold_main(int argc, char **argv) +{ + BIO *out = NULL, *rand_out = NULL, *key_in = NULL, *pubkey_in = NULL; + int ret = 1, msglen = 0, siglen = 0; + int key_action_sum = 0, sign_action_sum = 0, action_sum = 0; + int keygen = 0, pubkey = 0, ckeygen = 0; + int sign_init = 0, sign_update = 0, sign_fianl = 0, verify = 0; + char *key_file = NULL, *pubkey_file = NULL, *sig_file = NULL, *rand_file = NULL; + char *outfile = NULL; + char *prog; + unsigned char *msgbuf = NULL, *sigbuf = 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: +opthelp1: + 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_KEYGEN: + keygen = 1; + break; + case OPT_PUBKEY: + pubkey = 1; + break; + case OPT_CKEYGEN: + ckeygen = 1; + break; + case OPT_SIGN_INIT: + sign_init = 1; + break; + case OPT_SIGN_UPDATE: + sign_update = 1; + break; + case OPT_SIGN_FINAL: + sign_fianl = 1; + break; + case OPT_VERIFY: + verify = 1; + break; + default: + goto opthelp1; + } + } + + key_action_sum = keygen + pubkey + ckeygen; + sign_action_sum = sign_init + sign_update + sign_fianl + verify; + action_sum = key_action_sum + sign_action_sum; + if (action_sum == 0) { + BIO_printf(bio_err, "No action parameter specified.\n"); + goto opthelp1; + } else if (action_sum != 1) { + BIO_printf(bio_err, "Only one action parameter must be specified.\n"); + goto opthelp1; + } + + while ((o = opt_next()) != OPT_EOF) { + switch (o) { + case OPT_EOF: + case OPT_ERR: +opthelp2: + 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_KEY_IN: + key_file = opt_arg(); + break; + case OPT_PUBKEY_IN: + pubkey_file = opt_arg(); + break; + case OPT_SIGNATURE_IN: + sig_file = opt_arg(); + break; + case OPT_OUT: + outfile = opt_arg(); + break; + case OPT_RAND_OUT: + rand_file = opt_arg(); + break; + default: + goto opthelp2; + break; + } + } + + argc = opt_num_rest(); + argv = opt_rest(); + + if (sign_action_sum) { + if (argc > 1) { + BIO_printf(bio_err, "%s: Can only read one file.\n", prog); + goto opthelp2; + } + } + else if (argc > 0) { + BIO_printf(bio_err, "Extra arguments given.\n"); + goto opthelp2; + } + + if (!app_RAND_load()) + goto end; + + if (ckeygen + pubkey + sign_action_sum - verify == 1) { + if (key_file == NULL) { + BIO_printf(bio_err, "No SM2 threshold key file path specified.\n"); + goto end; + } + + key_in = bio_open_default(key_file, 'r', FORMAT_PEM); + if (key_in == NULL) + goto end; + } + + if (ckeygen + verify == 1) { + if (pubkey_file == NULL) { + BIO_printf(bio_err, "No SM2 threshold pubkey file path specified.\n"); + goto end; + } + + pubkey_in = bio_open_default(pubkey_file, 'r', FORMAT_PEM); + if (pubkey_in == NULL) + goto end; + } + + if (key_action_sum) + out = bio_open_owner(outfile, FORMAT_PEM, 1); + else + out = bio_open_default(outfile, 'w', + verify ? FORMAT_TEXT : FORMAT_BINARY); + if (out == NULL) + goto end; + + if (keygen) + ret = sm2_threshold_keygen(out); + else if (pubkey) + ret = sm2_threshold_pubkey(key_in, out); + else if (ckeygen) + ret = sm2_threshold_ckeygen(key_in, pubkey_in, out); + else if (sign_action_sum) { + msglen = readbuf(argv[0], &msgbuf); + if (msglen == -1) + goto end; + + if (sign_init) { + rand_out = bio_open_default(rand_file, 'w', FORMAT_BINARY); + if (rand_out == NULL) + goto end; + + ret = sm2_threshold_sign_init(key_in, rand_out, out, msgbuf, msglen); + } + else if (sign_update) + ret = sm2_threshold_sign_update(key_in, out, msgbuf, msglen); + else { + siglen = readbuf(sig_file, &sigbuf); + /* siglen can not equal to 0 */ + if (siglen <= 0) + goto end; + + if (sign_fianl) + ret = sm2_threshold_sign_final(key_in, out, msgbuf, + msglen, sigbuf, siglen); + else if (verify) + ret = sm2_threshold_verify(pubkey_in, out, msgbuf, + msglen, sigbuf, siglen); + } + } + + ret = ret ? 0 : 1; + end: + OPENSSL_free(msgbuf); + OPENSSL_free(sigbuf); + BIO_free(key_in); + BIO_free(pubkey_in); + BIO_free(out); + BIO_free(rand_out); + + if (ret != 0) { + BIO_printf(bio_err, "Maybe some errors occured, please use -help for usage summary.\n"); + ERR_print_errors(bio_err); + } + return ret; +} + +static int readbuf(const char *filename, unsigned char **buf) +{ + int len = 0; + BIO *in = BIO_new_file(filename, "rb"); + + if (in == NULL) { + BIO_printf(bio_err, "Error opening file %s\n", filename); + return -1; + } + + len = bio_to_mem(buf, MAXSIZE, in); + if (len == -1) + BIO_printf(bio_err, "Error reading file %s\n", filename); + + BIO_free(in); + + return len; +} + +static int sm2_threshold_keygen(BIO *out) +{ + int ret = 0; + EC_KEY *key = NULL; + + if (!(key = SM2_THRESHOLD_keypair_generate())) + goto err; + + if (!PEM_write_bio_ECPrivateKey(out, key, NULL, NULL, 0, NULL, NULL)) + goto err; + + ret = 1; +err: + EC_KEY_free(key); + return ret; +} + +static int sm2_threshold_pubkey(BIO *key_in, BIO *out) +{ + int ret = 0; + EC_KEY *key = NULL; + + if (!(key = PEM_read_bio_ECPrivateKey(key_in ,NULL, NULL, NULL))) + goto err; + + if (!PEM_write_bio_EC_PUBKEY(out, key)) + goto err; + + ret = 1; +err: + EC_KEY_free(key); + return ret; +} + +static int sm2_threshold_ckeygen(BIO *key_in, BIO *pubkey_in, BIO *out) +{ + int ret = 0; + EC_KEY *key = NULL, *pubkey = NULL, *ckey = NULL; + + if (!(key = PEM_read_bio_ECPrivateKey(key_in, NULL, NULL, NULL)) + || !(pubkey = PEM_read_bio_EC_PUBKEY(pubkey_in, NULL, NULL, NULL))) + goto err; + + if (!(ckey = SM2_THRESHOLD_complete_keypair_generate(key, pubkey))) + goto err; + + if (!PEM_write_bio_ECPrivateKey(out, ckey, NULL, NULL, 0, NULL, NULL)) + goto err; + + ret = 1; +err: + EC_KEY_free(key); + EC_KEY_free(pubkey); + EC_KEY_free(ckey); + + return ret; +} + +static int sm2_threshold_sign_init(BIO *key_in, BIO *rand_out, BIO *out, + const unsigned char *msgbuf, int msglen) +{ + int ret = 0; + size_t encode_msg_len = 0, rand_len = 0; + EC_KEY *key = NULL; + BIGNUM *w1 = BN_new(); + SM2_THRESHOLD_MSG *sign_msg = SM2_THRESHOLD_MSG_new(); + unsigned char *encode_msg = NULL, *encode_rand = NULL; + + if (!(key = PEM_read_bio_ECPrivateKey(key_in, NULL, NULL, NULL))) + goto err; + + if (!SM2_THRESHOLD_sign_init(key, EVP_sm3(), NULL, 0, + (const uint8_t *)msgbuf, msglen, w1, sign_msg)) { + BIO_printf(bio_err, "Error signing data\n"); + goto err; + } + + /* Encoding SM2_THRESHOLD_MSG and w1, then writing to specified files */ + encode_msg_len = SM2_THRESHOLD_MSG_encode(key, sign_msg, NULL, 0); + if (!(encode_msg = app_malloc(encode_msg_len, "SM2_THRESHOLD_MSG buffer"))) + goto err; + if (!(SM2_THRESHOLD_MSG_encode(key, sign_msg, encode_msg, encode_msg_len))) + goto err; + + BIO_write(out, encode_msg, encode_msg_len); + + rand_len = (EC_GROUP_order_bits(EC_KEY_get0_group(key)) + 7) / 8; + if (!(encode_rand = app_malloc(rand_len, "Random number buffer")) + || BN_bn2binpad(w1, encode_rand, rand_len) == -1) + goto err; + + BIO_write(rand_out, encode_rand, rand_len); + + ret = 1; + err: + EC_KEY_free(key); + BN_free(w1); + SM2_THRESHOLD_MSG_free(sign_msg); + OPENSSL_clear_free(encode_msg, encode_msg_len); + OPENSSL_clear_free(encode_rand, rand_len); + + return ret; +} + +static int sm2_threshold_sign_update(BIO *key_in, BIO *out, + const unsigned char *msgbuf, int msglen) +{ + int ret = 0, siglen = 0; + EC_KEY *key = NULL; + ECDSA_SIG *partial_sig = ECDSA_SIG_new(); + SM2_THRESHOLD_MSG *sign_msg = SM2_THRESHOLD_MSG_new(); + unsigned char *out_sig = NULL; + + if (!(key = PEM_read_bio_ECPrivateKey(key_in, NULL, NULL, NULL))) + goto err; + + if (!SM2_THRESHOLD_MSG_decode(key, sign_msg, msgbuf, msglen) + || !SM2_THRESHOLD_sign_update(key, sign_msg, partial_sig)) { + BIO_printf(bio_err, "Error signing data\n"); + goto err; + } + + siglen = i2d_ECDSA_SIG(partial_sig, &out_sig); + if (siglen <= 0) { + BIO_printf(bio_err, "Error occur when output signature\n"); + goto err; + } + BIO_write(out, out_sig, siglen); + + ret = 1; + err: + EC_KEY_free(key); + ECDSA_SIG_free(partial_sig); + SM2_THRESHOLD_MSG_free(sign_msg); + OPENSSL_clear_free(out_sig, siglen); + + return ret; +} + +static int sm2_threshold_sign_final(BIO *key_in, BIO *out, + const unsigned char *msgbuf, int msglen, + const unsigned char *psigbuf, int psiglen) +{ + int ret = 0, siglen = 0; + EC_KEY *key; + ECDSA_SIG *sig = ECDSA_SIG_new(), *partial_sig = ECDSA_SIG_new(); + BIGNUM *w1 = BN_new(); + unsigned char *out_sig = NULL; + + if (!(key = PEM_read_bio_ECPrivateKey(key_in, NULL, NULL, NULL))) + goto err; + + /* Read w1 and partial_sig from buffer */ + if (!BN_bin2bn(msgbuf, msglen, w1) + || !d2i_ECDSA_SIG(&partial_sig, &psigbuf, psiglen)) + goto err; + + if (!SM2_THRESHOLD_sign_final(key, w1, partial_sig, sig)) { + BIO_printf(bio_err, "Error signing data\n"); + goto err; + } + + siglen = i2d_ECDSA_SIG(sig, &out_sig); + if (siglen <= 0) { + BIO_printf(bio_err, "Error occur when output signature\n"); + goto err; + } + BIO_write(out, out_sig, siglen); + + ret = 1; + err: + EC_KEY_free(key); + ECDSA_SIG_free(sig); + ECDSA_SIG_free(partial_sig); + BN_free(w1); + OPENSSL_clear_free(out_sig, siglen); + + return ret; +} + +static int sm2_threshold_verify(BIO *pubkey_in, BIO *out, + const unsigned char *msgbuf, int msglen, + const unsigned char *sigbuf, int siglen) +{ + int ret = 0, status = 0; + EC_KEY *pubkey = NULL; + ECDSA_SIG *sig = ECDSA_SIG_new(); + + if (!(pubkey = PEM_read_bio_EC_PUBKEY(pubkey_in, NULL, NULL, NULL))) + goto end; + + if (d2i_ECDSA_SIG(&sig, &sigbuf, siglen) == NULL) + goto end; + + status = SM2_THRESHOLD_verify(pubkey, EVP_sm3(), sig, NULL, 0, + (const uint8_t *)msgbuf, msglen); + + ret = 1; + end: + if (status == 0) + BIO_printf(out, "Verification failure\n"); + else + BIO_printf(out, "Verified OK\n"); + + EC_KEY_free(pubkey); + ECDSA_SIG_free(sig); + + 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..4ba07916c --- /dev/null +++ b/crypto/sm2/sm2_threshold.c @@ -0,0 +1,563 @@ +/* + * 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 + +struct sm2_threshold_message_st { + /* the message digest */ + BIGNUM *e; + /* the EC_POINT represent [w1]*G */ + EC_POINT *Q1; +}; + +SM2_THRESHOLD_MSG *SM2_THRESHOLD_MSG_new(void) +{ + SM2_THRESHOLD_MSG *msg = OPENSSL_zalloc(sizeof(*msg)); + + if (msg == NULL) + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + + return msg; +} + +void SM2_THRESHOLD_MSG_free(SM2_THRESHOLD_MSG *msg) +{ + if (msg == NULL) + return; + BN_clear_free(msg->e); + EC_POINT_free(msg->Q1); + OPENSSL_free(msg); +} + +static int SM2_THRESHOLD_MSG_set0(SM2_THRESHOLD_MSG *msg, + BIGNUM *e , EC_POINT *Q1) +{ + if (msg == NULL || e == NULL || Q1 == NULL) + return 0; + + BN_clear_free(msg->e); + EC_POINT_free(msg->Q1); + msg->e = e; + msg->Q1 = Q1; + + return 1; +} + +size_t SM2_THRESHOLD_MSG_encode(EC_KEY *key, SM2_THRESHOLD_MSG *msg, + unsigned char *out, size_t size) +{ + size_t ret = 0, Q1_len = 0, e_len = 0, len = 0; + unsigned char *p = out; + BN_CTX *ctx = NULL; + + if (key == NULL || msg == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_PASSED_NULL_PARAMETER); + return ret; + } + + ctx = BN_CTX_new(); + if (ctx == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto end; + } + + e_len = DIGEST_LENGTH; + Q1_len = EC_POINT_point2oct(EC_KEY_get0_group(key), msg->Q1, + EC_KEY_get_conv_form(key), NULL, 0, ctx); + len = Q1_len + e_len; + + /* If out is NULL, return number of bytes needed */ + if (out == NULL) { + ret = len; + goto end; + } else { + if (size < len) { + ERR_raise(ERR_LIB_SM2, SM2_R_BUFFER_TOO_SMALL); + goto end; + } + + if (BN_bn2binpad(msg->e, p, e_len) == -1) { + ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR); + goto end; + } + + /* Move pointer to next position */ + p += e_len; + + if (!EC_POINT_point2oct(EC_KEY_get0_group(key), msg->Q1, + EC_KEY_get_conv_form(key), p, Q1_len, ctx)) + goto end; + + ret = len; + } + +end: + BN_CTX_free(ctx); + return ret; +} + +int SM2_THRESHOLD_MSG_decode(EC_KEY *key, SM2_THRESHOLD_MSG *msg, + const unsigned char *in, size_t size) +{ + int ret = 0; + size_t e_len = 0, Q1_len = 0; + const unsigned char *p = in; + EC_POINT *Q1 = NULL; + BIGNUM *e = NULL; + BN_CTX *ctx = NULL; + + if (key == NULL || msg == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_PASSED_NULL_PARAMETER); + return ret; + } + + Q1 = EC_POINT_new(EC_KEY_get0_group(key)); + e = BN_new(); + ctx = BN_CTX_new(); + if (Q1 == NULL || e == NULL || ctx == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto end; + } + + e_len = DIGEST_LENGTH; + + if (size <= e_len) { + ERR_raise(ERR_LIB_SM2, SM2_R_BUFFER_TOO_SMALL); + goto end; + } + + Q1_len = size - e_len; + + if (!BN_bin2bn(p, e_len, e)) { + ERR_raise(ERR_LIB_SM2, ERR_R_BN_LIB); + goto end; + } + + /* Move pointer to next position */ + p += e_len; + + if (!EC_POINT_oct2point(EC_KEY_get0_group(key), Q1, p, Q1_len, ctx)) + goto end; + + if (!SM2_THRESHOLD_MSG_set0(msg, e, Q1)) { + ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR); + goto end; + } + + ret = 1; +end: + if (ret == 0) { + BN_free(e); + EC_POINT_free(Q1); + } + + BN_CTX_free(ctx); + return ret; +} + +int SM2_THRESHOLD_partial_pubkey_generate(EC_KEY *key) +{ + int ret = 0; + const BIGNUM *dA = EC_KEY_get0_private_key(key); + const EC_GROUP *group = EC_KEY_get0_group(key); + EC_POINT *pkey = NULL; + BN_CTX *ctx = NULL; + BIGNUM *dA_inv = NULL; + OSSL_LIB_CTX *libctx = ossl_ec_key_get_libctx(key); + + pkey = EC_POINT_new(group); + ctx = BN_CTX_new_ex(libctx); + if (pkey == NULL || ctx == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto done; + } + + BN_CTX_start(ctx); + dA_inv = BN_CTX_get(ctx); + if (dA_inv == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto done; + } + + /* + * Compute the partial public key: + * P_1 = dA_1^(-1) * G + */ + if (!ossl_ec_group_do_inverse_ord(group, dA_inv, dA, ctx) + || !EC_POINT_mul(group, pkey, dA_inv, NULL, NULL, ctx)) { + ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR); + goto done; + } + + /* Set key->pub_key with threshold partial public key */ + if (!EC_KEY_set_public_key(key, pkey)) { + ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR); + goto done; + } + + ret = 1; + done: + EC_POINT_free(pkey); + BN_CTX_free(ctx); + return ret; +} + +EC_KEY *SM2_THRESHOLD_keypair_generate(void) +{ + EC_KEY *key = NULL; + + if (((key = EC_KEY_new_by_curve_name(NID_sm2)) == NULL) + || !EC_KEY_generate_key(key) + || !SM2_THRESHOLD_partial_pubkey_generate(key)) { + EC_KEY_free(key); + return NULL; + } + + return key; +} + +EC_KEY *SM2_THRESHOLD_complete_keypair_generate(const EC_KEY *key, + const EC_KEY *pkey) +{ + int ret = 0; + const BIGNUM *dA = EC_KEY_get0_private_key(key); + const EC_GROUP *group = EC_KEY_get0_group(key); + EC_KEY *complete_key = NULL; + EC_POINT *complete_pubkey = NULL; + EC_POINT *g = NULL; + BN_CTX *ctx = NULL; + BIGNUM *dA_inv = NULL; + OSSL_LIB_CTX *libctx = ossl_ec_key_get_libctx(key); + + if (key == NULL || pkey == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_PASSED_NULL_PARAMETER); + goto done; + } + + complete_key = EC_KEY_dup(key); + complete_pubkey = EC_POINT_new(group); + g = EC_POINT_new(group); + ctx = BN_CTX_new_ex(libctx); + if (complete_key == NULL || complete_pubkey == NULL + || g == NULL || ctx == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto done; + } + + BN_CTX_start(ctx); + dA_inv = BN_CTX_get(ctx); + if (dA_inv == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto done; + } + + /* + * Compute the complete public key: + * P = dA_2^(-1) * pkey - G + */ + if (!ossl_ec_group_do_inverse_ord(group, dA_inv, dA, ctx) + || !EC_POINT_mul(group, complete_pubkey, NULL, + EC_KEY_get0_public_key(pkey), dA_inv, ctx) + || !EC_POINT_copy(g, EC_GROUP_get0_generator(group)) + || !EC_POINT_invert(group, g, ctx) + || !EC_POINT_add(group, complete_pubkey, complete_pubkey, g, ctx)) { + ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR); + goto done; + } + + /* Set key->pub_key with threshold public key */ + if (!EC_KEY_set_public_key(complete_key, complete_pubkey)) { + ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR); + goto done; + } + + ret = 1; + + done: + if (ret == 0) + EC_KEY_free(complete_key); + + BN_CTX_free(ctx); + EC_POINT_free(g); + EC_POINT_free(complete_pubkey); + + return complete_key; +} + +static int SM2_THRESHOLD_sign_init_internal(const EC_KEY *key, + EC_POINT *Q1, BIGNUM *w1) +{ + int ret = 0; + const EC_GROUP *group = EC_KEY_get0_group(key); + const BIGNUM *order = EC_GROUP_get0_order(group); + BN_CTX *ctx = NULL; + OSSL_LIB_CTX *libctx = ossl_ec_key_get_libctx(key); + + ctx = BN_CTX_new_ex(libctx); + if (ctx == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto done; + } + + BN_CTX_start(ctx); + /* + * SM2 threshold signature part 1: + * 1. Generate a random number w1 in [1,n-1] using random number generators; + * 2. Compute Q1 = [w1]G. + */ + if (!BN_priv_rand_range_ex(w1, order, 0, ctx) + || !EC_POINT_mul(group, Q1, w1, NULL, NULL, ctx)) { + ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR); + goto done; + } + + ret = 1; + done: + BN_CTX_free(ctx); + + return ret; +} + +int SM2_THRESHOLD_sign_init(const EC_KEY *key, + const EVP_MD *digest, + const uint8_t *id, + const size_t id_len, + const uint8_t *msg, size_t msg_len, + BIGNUM *w1, SM2_THRESHOLD_MSG *sign_msg) +{ + int ret = 0; + BIGNUM *e = NULL; + EC_POINT *Q1 = EC_POINT_new(EC_KEY_get0_group(key)); + + if (w1 == NULL || sign_msg == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_PASSED_NULL_PARAMETER); + goto done; + } + + if (Q1 == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto done; + } + + e = ossl_sm2_compute_msg_hash(digest, key, id, id_len, msg, msg_len); + if (e == NULL) { + /* SM2err already called */ + goto done; + } + + if (!SM2_THRESHOLD_sign_init_internal(key, Q1, w1)){ + ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR); + goto done; + } + + if (!SM2_THRESHOLD_MSG_set0(sign_msg, e, Q1)){ + ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR); + goto done; + } + + ret = 1; + + done: + if (ret == 0){ + BN_free(e); + EC_POINT_free(Q1); + } + + return ret; +} + +int SM2_THRESHOLD_sign_update(const EC_KEY *key, const SM2_THRESHOLD_MSG *msg, + ECDSA_SIG *partial_sig) +{ + int ret = 0; + const EC_GROUP *group = EC_KEY_get0_group(key); + const BIGNUM *dA = EC_KEY_get0_private_key(key); + const BIGNUM *order = EC_GROUP_get0_order(group); + EC_POINT *Q1 = NULL; + EC_POINT *Q = NULL; + BN_CTX *ctx = NULL; + BIGNUM *dA_inv = NULL; + BIGNUM *w2 = NULL; + BIGNUM *x1 = NULL; + BIGNUM *r = NULL; + BIGNUM *s1 = NULL; + OSSL_LIB_CTX *libctx = ossl_ec_key_get_libctx(key); + + if (msg == NULL || partial_sig == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_PASSED_NULL_PARAMETER); + goto done; + } + + Q1 = EC_POINT_dup(msg->Q1, group); + Q = EC_POINT_new(group); + ctx = BN_CTX_new_ex(libctx); + if (Q == NULL || ctx == NULL || Q1 == 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) { + 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 + */ + if (!BN_priv_rand_range_ex(w2, order, 0, ctx)) { + ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR); + goto done; + } + + if (!EC_POINT_mul(group, Q, w2, NULL, NULL, ctx) + || !ossl_ec_group_do_inverse_ord(group, dA_inv, dA, ctx) + || !EC_POINT_mul(group, Q1, NULL, Q1, dA_inv, ctx) + || !EC_POINT_add(group, Q, Q, Q1, ctx)) { + ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR); + goto done; + } + + if (!EC_POINT_get_affine_coordinates(group, Q, x1, NULL,ctx) + || !BN_mod_add(r, msg->e, x1, order, ctx)) { + ERR_raise(ERR_LIB_SM2, ERR_R_INTERNAL_ERROR); + goto done; + } + + if (!BN_add(s1, r, w2) + || !BN_mod_mul(s1, s1, dA, order, ctx)) { + ERR_raise(ERR_LIB_SM2, ERR_R_BN_LIB); + goto done; + } + + /* a "partial" signature to stored r and s1 */ + if (!ECDSA_SIG_set0(partial_sig, r, s1)) { + ERR_raise(ERR_LIB_SM2, ERR_R_BN_LIB); + goto done; + } + + ret = 1; + + done: + if (ret == 0) { + BN_free(r); + BN_free(s1); + } + + EC_POINT_free(Q); + EC_POINT_free(Q1); + BN_CTX_free(ctx); + + return ret; +} + +int SM2_THRESHOLD_sign_final(const EC_KEY *key, const BIGNUM *w1, + const ECDSA_SIG *partial_sig, + ECDSA_SIG *final_sig) +{ + int ret = 0; + const EC_GROUP *group = EC_KEY_get0_group(key); + const BIGNUM *dA = EC_KEY_get0_private_key(key); + const BIGNUM *order = EC_GROUP_get0_order(group); + BN_CTX *ctx = NULL; + BIGNUM *r = NULL; + BIGNUM *s = NULL; + OSSL_LIB_CTX *libctx = ossl_ec_key_get_libctx(key); + + if (w1 == NULL || partial_sig == NULL || final_sig == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_PASSED_NULL_PARAMETER); + goto done; + } + + 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(partial_sig)); + s = BN_new(); + + if (r == NULL || s == NULL) { + ERR_raise(ERR_LIB_SM2, ERR_R_MALLOC_FAILURE); + goto done; + } + + /* + * SM2 threshold signature part 3: + * 1. Compute s = (d1(s1 + w1) - r) mod n + * 2. Return sig(r,s) + */ + if (!BN_add(s, ECDSA_SIG_get0_s(partial_sig), 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; + } + + /* takes ownership of r and s */ + if (!ECDSA_SIG_set0(final_sig, r, s)) { + ERR_raise(ERR_LIB_SM2, ERR_R_BN_LIB); + goto done; + } + + ret = 1; + + done: + if (ret == 0) { + BN_free(r); + BN_free(s); + } + + BN_CTX_free(ctx); + + return ret; +} + +int SM2_THRESHOLD_verify(const EC_KEY *key, + const EVP_MD *digest, + const ECDSA_SIG *sig, + const uint8_t *id, + const size_t id_len, + const uint8_t *msg, size_t msg_len) +{ + return ossl_sm2_do_verify(key, digest, sig, id, id_len, msg, msg_len); +} 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..1d5c687af --- /dev/null +++ b/examples/perf/sm2_threshold/threshold.c @@ -0,0 +1,153 @@ +/* + * 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) +{ + struct perf_index *indices = NULL; + struct perf_result result; + int msg_len = strlen(message), i = 0; + long long start = 0, end = 0; + EC_KEY *key1 = NULL, *key2 = NULL; + EC_KEY *complete_key1 = NULL, *complete_key2 = NULL; + ECDSA_SIG *sig = ECDSA_SIG_new(), *partial_sig = ECDSA_SIG_new(); + BIGNUM *w1 = BN_new(); + SM2_THRESHOLD_MSG *sign_msg = SM2_THRESHOLD_MSG_new(); + + 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 = SM2_THRESHOLD_keypair_generate(); + key2 = SM2_THRESHOLD_keypair_generate(); + if (key1 == NULL || key2 == NULL) + goto err; + + complete_key1 = SM2_THRESHOLD_complete_keypair_generate(key1, key2); + complete_key2 = SM2_THRESHOLD_complete_keypair_generate(key2, key1); + 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); + + /* SM2 threshold sign */ + start = get_time(); + if (!SM2_THRESHOLD_sign_init(complete_key1, EVP_sm3(), (const uint8_t *)userid, + strlen(userid), (const uint8_t *)message, msg_len, + w1, sign_msg) + || !SM2_THRESHOLD_sign_update(complete_key2, sign_msg, partial_sig) + || !SM2_THRESHOLD_sign_final(complete_key1, w1, partial_sig, sig)) + goto err; + end = get_time(); + indices[i].sm2_threshold_sign = 1000 * 1000 / (end - start); + + /* SM2 threshold verify */ + start = get_time(); + if (!SM2_THRESHOLD_verify(complete_key1, EVP_sm3(), sig, (const uint8_t *)userid, + strlen(userid), (const uint8_t *)message, msg_len)) + goto err; + end = get_time(); + indices[i].sm2_threshold_verify = 1000 * 1000 / (end - start); + +#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); + + return 0; +err: + fprintf(stderr, "Error: %s\n", ERR_error_string(ERR_get_error(), NULL)); + OPENSSL_free(indices); + EC_KEY_free(key1); + EC_KEY_free(key2); + EC_KEY_free(complete_key1); + EC_KEY_free(complete_key2); + ECDSA_SIG_free(sig); + ECDSA_SIG_free(partial_sig); + BN_free(w1); + SM2_THRESHOLD_MSG_free(sign_msg); + return -1; +} 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..7fdd5b1de --- /dev/null +++ b/include/openssl/sm2_threshold.h @@ -0,0 +1,157 @@ +/* + * 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 + +# define DIGEST_LENGTH 32 +/********************************************************************/ +/* SM2 threshold struct and functions */ +/********************************************************************/ + +typedef struct sm2_threshold_message_st SM2_THRESHOLD_MSG; + +/* Create a SM2_THRESHOLD_MSG object */ +SM2_THRESHOLD_MSG *SM2_THRESHOLD_MSG_new(void); + +/* Free a SM2_THRESHOLD_MSG object. */ +void SM2_THRESHOLD_MSG_free(SM2_THRESHOLD_MSG *msg); + +# ifndef OPENSSL_NO_DEPRECATED_3_0 +/** Encodes SM2_THRESHOLD_MSG to binary + * \param key EC_KEY object + * \param msg SM2_THRESHOLD_MSG object + * \param out the buffer for the result (if NULL the function returns + * number of bytes needed). + * \param size The memory size of the out pointer object + * \return the length of the encoded octet string or 0 if an error occurred + */ +OSSL_DEPRECATEDIN_3_0 size_t SM2_THRESHOLD_MSG_encode(EC_KEY *key, + SM2_THRESHOLD_MSG *msg, + unsigned char *out, + size_t size); + +/** Decodes binary to SM2_THRESHOLD_MSG + * \param key EC_KEY object + * \param msg the resulting SM2_THRESHOLD_MSG object + * \param in Memory buffer with the encoded SM2_THRESHOLD_MSG object + * \param size The memory size of the in pointer object + * \return 1 on success and 0 otherwise + */ +OSSL_DEPRECATEDIN_3_0 int SM2_THRESHOLD_MSG_decode(EC_KEY *key, + SM2_THRESHOLD_MSG *msg, + const unsigned char *in, + size_t size); + +/** Generate threshold partial public key and store in the input key object + * \param key EC_KEY object + * \return 1 on success and 0 if an error occurred + */ +OSSL_DEPRECATEDIN_3_0 int SM2_THRESHOLD_partial_pubkey_generate(EC_KEY *key); + +/** Generate a sm2 threshold key with partial public key + * \param key EC_KEY object + * \return the EC_KEY object including private key and partial public key + */ +OSSL_DEPRECATEDIN_3_0 EC_KEY *SM2_THRESHOLD_keypair_generate(void); + +/** Generate threshold complete public key and return an complete keypair object + * \param key EC_KEY object + * \param pkey partial public key from another participant + * \return the EC_KEY object including private key and complete public key + */ +OSSL_DEPRECATEDIN_3_0 EC_KEY *SM2_THRESHOLD_complete_keypair_generate( + const EC_KEY *key, + const EC_KEY *pkey); + +/** Generate the first part of the SM2 threshold signature. + * \param key EC_KEY object + * \param digest the digest algorithm object (sm3 default) + * \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 w1 an output random number used in the 3rd part of signature + * \param sign_msg a SM2_THRESHOLD_MSG object including an EC_POINT and + * message digest. + * \return 1 on success and 0 if an error occurred. + */ +OSSL_DEPRECATEDIN_3_0 int SM2_THRESHOLD_sign_init(const EC_KEY *key, + const EVP_MD *digest, + const uint8_t *id, + const size_t id_len, + const uint8_t *msg, + size_t msg_len, + BIGNUM *w1, + SM2_THRESHOLD_MSG *sign_msg); + +/** The 2nd step of SM2 threshold signature, generate the partial threshold signature + * \param key EC_KEY object. + * \param msg a SM2_THRESHOLD_MSG object sent from another participant + * \param partial_sig the partial signature sent to another participant + * \return 1 on success and 0 if an error occurred. + */ +OSSL_DEPRECATEDIN_3_0 int SM2_THRESHOLD_sign_update(const EC_KEY *key, + const SM2_THRESHOLD_MSG *msg, + ECDSA_SIG *partial_sig); + +/** The 3rd step of SM2 threshold signature,generate the final threshold signature + * \param key EC_KEY object + * \param a the random number generated in the 1st part of signature + * \param partial_sig the partial signature sent from another participant + * \param final_sig output complete ECDSA_SIG object + * \return 1 on success and 0 if an error occurred. + */ +OSSL_DEPRECATEDIN_3_0 int SM2_THRESHOLD_sign_final(const EC_KEY *key, + const BIGNUM *w1, + const ECDSA_SIG *partial_sig, + ECDSA_SIG *final_sig); + +/** Verify a SM2 threshold signature + * \param key EC_KEY object + * \param digest the digest algorithm object (sm3 default) + * \param sig the ECDSA_SIG threshold signature object + * \param id userid to calculate digest + * \param id_len length of userid + * \param msg message to calculate digest + * \param msg_len length of message + * \return 1 on success and 0 if the signature is invalid. + */ +OSSL_DEPRECATEDIN_3_0 int SM2_THRESHOLD_verify(const EC_KEY *key, + const EVP_MD *digest, + const ECDSA_SIG *sig, + const uint8_t *id, + const size_t id_len, + const uint8_t *msg, + size_t msg_len); +# endif + +# ifdef __cplusplus +} +# endif + +# endif /* OPENSSL_NO_SM2_THRESHOLD */ + +#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_threshold_sm2.t b/test/recipes/15-test_threshold_sm2.t new file mode 100644 index 000000000..e64dd4bc2 --- /dev/null +++ b/test/recipes/15-test_threshold_sm2.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..9130215f4 --- /dev/null +++ b/test/sm2_threshold_test.c @@ -0,0 +1,160 @@ +/* + * 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 + */ + +/* + * Low level APIs are deprecated for public use, but still ok for internal use. + */ +#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 testresult = 0; + const EC_GROUP *group = NULL; + EC_KEY *key1 = NULL, *key2 = NULL; + EC_KEY *complete_key1 = NULL, *complete_key2 = NULL; + + /* Generate SM2 EC_KEY */ + if (!TEST_ptr(key1 = EC_KEY_new_by_curve_name(NID_sm2)) + || !TEST_true(EC_KEY_generate_key(key1)) + || !TEST_ptr(key2 = EC_KEY_new_by_curve_name(NID_sm2)) + || !TEST_true(EC_KEY_generate_key(key2)) + || !TEST_ptr(group = EC_KEY_get0_group(key1))) + goto err; + + /* Generate SM2 threshold partial public key */ + if (!TEST_true(SM2_THRESHOLD_partial_pubkey_generate(key1)) + || !TEST_true(SM2_THRESHOLD_partial_pubkey_generate(key2))) + goto err; + + /* + * Generate a complete threshold public key using partial + * public key from another participant + */ + if (!TEST_ptr(complete_key1 = SM2_THRESHOLD_complete_keypair_generate(key1, key2)) + || !TEST_ptr(complete_key2 = SM2_THRESHOLD_complete_keypair_generate(key2, key1))) + goto err; + + /* Compare if two complete public keys are equal */ + if (!TEST_false(EC_POINT_cmp(group, EC_KEY_get0_public_key(complete_key1), EC_KEY_get0_public_key(complete_key2), NULL))) + goto err; + + testresult = 1; +err: + EC_KEY_free(key1); + EC_KEY_free(key2); + EC_KEY_free(complete_key1); + EC_KEY_free(complete_key2); + + return testresult; +} + +static int sm2_threshold_sign_test(int flag) +{ + int msg_len = strlen(message); + int verify_status1 = 0 , verify_status2 = 0; + EC_KEY *key1 = NULL, *key2 = NULL; + EC_KEY *complete_key1 = NULL, *complete_key2 = NULL; + ECDSA_SIG *sig = ECDSA_SIG_new(), *partial_sig = ECDSA_SIG_new(); + BIGNUM *w1 = BN_new(); + SM2_THRESHOLD_MSG *sign_msg = SM2_THRESHOLD_MSG_new(); + + /* Generate SM2 threshold private key with partial public key */ + if (!TEST_ptr(key1 = SM2_THRESHOLD_keypair_generate()) + || !TEST_ptr(key2 = SM2_THRESHOLD_keypair_generate())) + goto err; + + /* + * Generate a complete threshold public key using partial + * public key from another participant + */ + if (!TEST_ptr(complete_key1 = SM2_THRESHOLD_complete_keypair_generate(key1, key2)) + || !TEST_ptr(complete_key2 = SM2_THRESHOLD_complete_keypair_generate(key2, key1))) + goto err; + + /* Test SM2 threshold sign with id */ + if (flag == 0) { + /* SM2 threshold signature */ + if (!TEST_true(SM2_THRESHOLD_sign_init(complete_key1, EVP_sm3(), (const uint8_t *)userid, + strlen(userid), (const uint8_t *)message, msg_len, + w1, sign_msg)) + || !TEST_true(SM2_THRESHOLD_sign_update(complete_key2, sign_msg, partial_sig)) + || !TEST_true(SM2_THRESHOLD_sign_final(complete_key1, w1, partial_sig, sig))) + goto err; + + /* Verify signature using complete threshold public key */ + verify_status1 = SM2_THRESHOLD_verify(complete_key1, EVP_sm3(), sig, (const uint8_t *)userid, + strlen(userid), (const uint8_t *)message, msg_len); + verify_status2 = SM2_THRESHOLD_verify(complete_key2, EVP_sm3(), sig, (const uint8_t *)userid, + strlen(userid), (const uint8_t *)message, msg_len); + } + /* Test SM2 threshold sign without id */ + else { + /* SM2 threshold signature */ + if (!TEST_true(SM2_THRESHOLD_sign_init(complete_key1, EVP_sm3(), NULL, 0, + (const uint8_t *)message, msg_len, + w1, sign_msg)) + || !TEST_true(SM2_THRESHOLD_sign_update(complete_key2, sign_msg, partial_sig)) + || !TEST_true(SM2_THRESHOLD_sign_final(complete_key1, w1, partial_sig, sig))) + goto err; + + /* Verify signature using complete threshold public key */ + verify_status1 = SM2_THRESHOLD_verify(complete_key1, EVP_sm3(), sig, NULL, 0, + (const uint8_t *)message, msg_len); + verify_status2 = SM2_THRESHOLD_verify(complete_key2, EVP_sm3(), sig, NULL, 0, + (const uint8_t *)message, msg_len); + } + + /* + * Both complete public keys from two parties should be able to verify the + * signature successfully with the same id + */ + TEST_true(verify_status1 && verify_status2); + +err: + EC_KEY_free(key1); + EC_KEY_free(key2); + EC_KEY_free(complete_key1); + EC_KEY_free(complete_key2); + ECDSA_SIG_free(sig); + ECDSA_SIG_free(partial_sig); + BN_free(w1); + SM2_THRESHOLD_MSG_free(sign_msg); + + return verify_status1 && verify_status2; +} + +#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..41c685d0f 100644 --- a/util/libcrypto.num +++ b/util/libcrypto.num @@ -5627,3 +5627,14 @@ 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_MSG_new 5946 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD +SM2_THRESHOLD_MSG_free 5947 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD +SM2_THRESHOLD_MSG_encode 5948 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD +SM2_THRESHOLD_MSG_decode 5949 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD +SM2_THRESHOLD_partial_pubkey_generate 5950 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD +SM2_THRESHOLD_keypair_generate 5951 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD +SM2_THRESHOLD_complete_keypair_generate 5952 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD +SM2_THRESHOLD_sign_init 5953 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD +SM2_THRESHOLD_sign_update 5954 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD +SM2_THRESHOLD_sign_final 5955 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD +SM2_THRESHOLD_verify 5956 3_0_3 EXIST::FUNCTION:SM2_THRESHOLD \ No newline at end of file