diff --git a/.gitignore b/.gitignore index 7bf158b58..724bb7931 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,7 @@ providers/common/der/der_rsa_gen.c providers/common/der/der_wrap_gen.c providers/common/der/der_sm2_gen.c providers/common/der/der_ml_dsa_gen.c +providers/common/der/der_sm2_mldsa65_hybrid_gen.c providers/common/include/prov/der_dsa.h providers/common/include/prov/der_ec.h providers/common/include/prov/der_ecx.h @@ -68,6 +69,7 @@ providers/common/include/prov/der_digests.h providers/common/include/prov/der_wrap.h providers/common/include/prov/der_sm2.h providers/common/include/prov/der_ml_dsa.h +providers/common/include/prov/der_sm2_mldsa65_hybrid.h providers/smtc/smtckey.h diff --git a/Configure b/Configure index 7c209d057..0f65d3a80 100755 --- a/Configure +++ b/Configure @@ -488,6 +488,7 @@ my @disablables = ( "siv", "sm2", "sm2_threshold", + "sm2-mldsa65-hybrid", "sm3", "sm4", "wbsm4-xiaolai", @@ -627,6 +628,7 @@ our %disabled = ( # "what" => "comment" "wbsm4-wsise" => "default", #"kyber" => "default", #"sm2dh-mlkem768-hybrid" => "default", + "sm2-mldsa65-hybrid" => "default", ); # Note: => pair form used for aesthetics, not to truly make a hash table @@ -654,7 +656,7 @@ my @disable_cascades = ( "ssl3-method" => [ "ssl3" ], "zlib" => [ "zlib-dynamic" ], "ec" => [ "ec2m", "ecdsa", "ecdh", "sm2", "sm2dh-mlkem768-hybrid"], - "sm2" => [ "sm2_threshold", "sm2dh-mlkem768-hybrid"], + "sm2" => [ "sm2_threshold", "sm2dh-mlkem768-hybrid","sm2-mldsa65-hybrid"], "ec_elgamal" => [ "twisted_ec_elgamal" ], "dgram" => [ "dtls", "sctp" ], "sock" => [ "dgram" ], @@ -718,6 +720,7 @@ my @disable_cascades = ( sub { !$disabled{"smtc"}} => [ "ct", "module" ], "sdf-lib" => [ "sdf-lib-dynamic" ], "kyber" => ["sm2dh-mlkem768-hybrid"], + "ml_dsa" => ["sm2-mldsa65-hybrid"], ); # Avoid protocol support holes. Also disable all versions below N, if version diff --git a/crypto/build.info b/crypto/build.info index 8feb00570..64c7e82f2 100644 --- a/crypto/build.info +++ b/crypto/build.info @@ -6,7 +6,7 @@ SUBDIRS=objects buffer bio stack lhash rand evp asn1 pem x509 conf \ siphash sm3 des aes rc4 rc5 zuc \ sm4 chacha modes bn ec rsa dsa dh sm2 dso engine \ err comp http ocsp cms ts srp cmac ct async ess crmf cmp encode_decode \ - ffc paillier zkp sdf tsapi kyber sm2dh-mlkem768-hybrid ml_dsa + ffc paillier zkp sdf tsapi kyber sm2dh-mlkem768-hybrid ml_dsa sm2_mldsa65_hybrid LIBS=../libcrypto diff --git a/crypto/evp/evp_lib.c b/crypto/evp/evp_lib.c index 0b1c6dd26..9d757eca4 100644 --- a/crypto/evp/evp_lib.c +++ b/crypto/evp/evp_lib.c @@ -1185,7 +1185,8 @@ EVP_PKEY *EVP_PKEY_Q_keygen(OSSL_LIB_CTX *libctx, const char *propq, && OPENSSL_strcasecmp(type, "ED448") != 0 && OPENSSL_strcasecmp(type, "X448") != 0 && OPENSSL_strcasecmp(type, "SM2") != 0 - && OPENSSL_strcasecmp(type, "ML-DSA-65") != 0) { + && OPENSSL_strcasecmp(type, "ML-DSA-65") != 0 + && OPENSSL_strcasecmp(type, "SM2-MLDSA65-HYBRID") != 0) { ERR_raise(ERR_LIB_EVP, ERR_R_PASSED_INVALID_ARGUMENT); goto end; } diff --git a/crypto/evp/p_lib.c b/crypto/evp/p_lib.c index e57f2bc6c..24cfdb9fd 100644 --- a/crypto/evp/p_lib.c +++ b/crypto/evp/p_lib.c @@ -1052,6 +1052,8 @@ static const OSSL_ITEM standard_name2type[] = { /* SM2DH-MLKEM768-HYBRID, only for experimental purpose */ { EVP_PKEY_SM2DH_MLKEM768_HYBRID, "SM2DH-MLKEM768-HYBRID"}, { EVP_PKEY_ML_DSA_65, "ML-DSA-65" }, + /* SM2-MLDSA65-HYBRID, only for experimental purpose */ + { EVP_PKEY_SM2_MLDSA65_HYBRID, "SM2-MLDSA65-HYBRID" }, }; int evp_pkey_name2type(const char *name) diff --git a/crypto/objects/obj_dat.h b/crypto/objects/obj_dat.h index d4de741c6..9ae3faa10 100644 --- a/crypto/objects/obj_dat.h +++ b/crypto/objects/obj_dat.h @@ -10,7 +10,7 @@ */ /* Serialized OID's */ -static const unsigned char so[6648] = { +static const unsigned char so[6669] = { 0x2A,0x86,0x48,0x86,0xF7,0x0D, /* [ 0] OBJ_rsadsi */ 0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, /* [ 6] OBJ_pkcs */ 0x2A,0x86,0x48,0x86,0xF7,0x0D,0x02,0x05, /* [ 13] OBJ_md5 */ @@ -934,9 +934,11 @@ static const unsigned char so[6648] = { 0x2A,0x81,0x1C,0xCF,0x55,0x01,0x87,0x69, /* [ 6627] OBJ_sm2dh_mlkem768_hybrid */ 0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x03,0x12, /* [ 6635] OBJ_ML_DSA_65 */ 0x55,0x1D,0x4B, /* [ 6644] OBJ_associated_information */ + 0x60,0x86,0x48,0x01,0x86,0xFA,0x6B,0x50,0x09,0x01, /* [ 6647] OBJ_compsig */ + 0x60,0x86,0x48,0x01,0x86,0xFA,0x6B,0x50,0x09,0x01,0x15, /* [ 6657] OBJ_sm2_mldsa65_hybrid */ }; -#define NUM_NID 1320 +#define NUM_NID 1322 static const ASN1_OBJECT nid_objs[NUM_NID] = { {"UNDEF", "undefined", NID_undef}, {"rsadsi", "RSA Data Security, Inc.", NID_rsadsi, 6, &so[0]}, @@ -2258,9 +2260,11 @@ static const ASN1_OBJECT nid_objs[NUM_NID] = { { NULL, NULL, NID_undef }, { NULL, NULL, NID_undef }, {"associatedInformation", "X509v3 Associated Information", NID_associated_information, 3, &so[6644]}, + {"Compsig", "composite ML-DSA signature", NID_compsig, 10, &so[6647]}, + {"SM2-MLDSA65-HYBRID", "sm2-mldsa65-hybrid", NID_sm2_mldsa65_hybrid, 11, &so[6657]}, }; -#define NUM_SN 1034 +#define NUM_SN 1036 static const unsigned int sn_objs[NUM_SN] = { 364, /* "AD_DVCS" */ 419, /* "AES-128-CBC" */ @@ -2313,6 +2317,7 @@ static const unsigned int sn_objs[NUM_SN] = { 417, /* "CSPName" */ 1019, /* "ChaCha20" */ 1018, /* "ChaCha20-Poly1305" */ + 1320, /* "Compsig" */ 367, /* "CrlID" */ 391, /* "DC" */ 31, /* "DES-CBC" */ @@ -2434,6 +2439,7 @@ static const unsigned int sn_objs[NUM_SN] = { 1100, /* "SHAKE128" */ 1101, /* "SHAKE256" */ 1172, /* "SM2" */ + 1321, /* "SM2-MLDSA65-HYBRID" */ 1204, /* "SM2-SM3" */ 1281, /* "SM2DH-MLKEM768-HYBRID" */ 1143, /* "SM3" */ @@ -3298,7 +3304,7 @@ static const unsigned int sn_objs[NUM_SN] = { 1093, /* "x509ExtAdmission" */ }; -#define NUM_LN 1034 +#define NUM_LN 1036 static const unsigned int ln_objs[NUM_LN] = { 363, /* "AD Time Stamping" */ 405, /* "ANSI X9.62" */ @@ -3590,6 +3596,7 @@ static const unsigned int ln_objs[NUM_LN] = { 633, /* "cleartext track 2" */ 894, /* "cmac" */ 13, /* "commonName" */ + 1320, /* "composite ML-DSA signature" */ 513, /* "content types" */ 50, /* "contentType" */ 53, /* "countersignature" */ @@ -4249,6 +4256,7 @@ static const unsigned int ln_objs[NUM_LN] = { 1062, /* "siphash" */ 1142, /* "sm-scheme" */ 1172, /* "sm2" */ + 1321, /* "sm2-mldsa65-hybrid" */ 1281, /* "sm2dh-mlkem768-hybrid" */ 1143, /* "sm3" */ 1144, /* "sm3WithRSAEncryption" */ @@ -4336,7 +4344,7 @@ static const unsigned int ln_objs[NUM_LN] = { 1255, /* "zuc-128-eia3" */ }; -#define NUM_OBJ 929 +#define NUM_OBJ 931 static const unsigned int obj_objs[NUM_OBJ] = { 0, /* OBJ_undef 0 */ 181, /* OBJ_iso 1 */ @@ -5173,6 +5181,7 @@ static const unsigned int obj_objs[NUM_OBJ] = { 952, /* OBJ_ct_precert_poison 1 3 6 1 4 1 11129 2 4 3 */ 953, /* OBJ_ct_precert_signer 1 3 6 1 4 1 11129 2 4 4 */ 954, /* OBJ_ct_cert_scts 1 3 6 1 4 1 11129 2 4 5 */ + 1320, /* OBJ_compsig 2 16 840 1 114027 80 9 1 */ 1158, /* OBJ_dstu4145le 1 2 804 2 1 1 1 1 3 1 1 */ 196, /* OBJ_id_smime_mod_cms 1 2 840 113549 1 9 16 0 1 */ 197, /* OBJ_id_smime_mod_ess 1 2 840 113549 1 9 16 0 2 */ @@ -5255,6 +5264,7 @@ static const unsigned int obj_objs[NUM_OBJ] = { 955, /* OBJ_jurisdictionLocalityName 1 3 6 1 4 1 311 60 2 1 1 */ 956, /* OBJ_jurisdictionStateOrProvinceName 1 3 6 1 4 1 311 60 2 1 2 */ 957, /* OBJ_jurisdictionCountryName 1 3 6 1 4 1 311 60 2 1 3 */ + 1321, /* OBJ_sm2_mldsa65_hybrid 2 16 840 1 114027 80 9 1 21 */ 1259, /* OBJ_oracle_jdk_trustedkeyusage 2 16 840 1 113894 746875 1 1 */ 1159, /* OBJ_dstu4145be 1 2 804 2 1 1 1 1 3 1 1 1 1 */ 1160, /* OBJ_uacurve0 1 2 804 2 1 1 1 1 3 1 1 2 0 */ diff --git a/crypto/objects/obj_mac.num b/crypto/objects/obj_mac.num index f80b5d8e3..e3d4c3cc8 100644 --- a/crypto/objects/obj_mac.num +++ b/crypto/objects/obj_mac.num @@ -1052,3 +1052,5 @@ sm2dh_mlkem768_hybrid 1281 ML_DSA_65 1282 delegated_name_constraints 1298 associated_information 1319 +compsig 1320 +sm2_mldsa65_hybrid 1321 diff --git a/crypto/objects/obj_xref.h b/crypto/objects/obj_xref.h index a3a0ceed4..e628ce544 100644 --- a/crypto/objects/obj_xref.h +++ b/crypto/objects/obj_xref.h @@ -65,6 +65,7 @@ static const nid_triple sigoid_srt[] = { {NID_RSA_SHA3_512, NID_sha3_512, NID_rsaEncryption}, {NID_SM2_with_SM3, NID_sm3, NID_sm2}, {NID_ML_DSA_65, NID_undef, NID_ML_DSA_65}, + {NID_sm2_mldsa65_hybrid, NID_sm3, NID_sm2_mldsa65_hybrid}, }; static const nid_triple *const sigoid_srt_xref[] = { @@ -102,4 +103,5 @@ static const nid_triple *const sigoid_srt_xref[] = { &sigoid_srt[36], &sigoid_srt[37], &sigoid_srt[38], + &sigoid_srt[40], }; diff --git a/crypto/objects/obj_xref.txt b/crypto/objects/obj_xref.txt index d28737ee8..77bba9262 100644 --- a/crypto/objects/obj_xref.txt +++ b/crypto/objects/obj_xref.txt @@ -59,3 +59,5 @@ dhSinglePass_cofactorDH_sha384kdf_scheme sha384 dh_cofactor_kdf dhSinglePass_cofactorDH_sha512kdf_scheme sha512 dh_cofactor_kdf SM2_with_SM3 sm3 sm2 + +sm2_mldsa65_hybrid sm3 sm2_mldsa65_hybrid diff --git a/crypto/objects/objects.txt b/crypto/objects/objects.txt index f66186e84..d9309ff8f 100644 --- a/crypto/objects/objects.txt +++ b/crypto/objects/objects.txt @@ -1453,3 +1453,8 @@ oracle 746875 1 1 : oracle-jdk-trustedkeyusage : Trusted key usage (Oracle) : WBSM4-WSISE-CTR : wbsm4-wsise-ctr : WBSM4-WSISE-GCM : wbsm4-wsise-gcm : WBSM4-WSISE-CCM : wbsm4-wsise-ccm + +# sm2 & ml-dsa-65 hybrid signature (testing) +!Cname compsig +joint-iso-itu-t 16 840 1 114027 80 9 1 : Compsig : composite ML-DSA signature +compsig 21 : SM2-MLDSA65-HYBRID : sm2-mldsa65-hybrid diff --git a/crypto/sm2_mldsa65_hybrid/build.info b/crypto/sm2_mldsa65_hybrid/build.info new file mode 100644 index 000000000..9eab8cae0 --- /dev/null +++ b/crypto/sm2_mldsa65_hybrid/build.info @@ -0,0 +1,7 @@ +LIBS = ../../libcrypto + +IF[{- !$disabled{"sm2-mldsa65-hybrid"} -}] + SOURCE[../../libcrypto] = \ + sm2_mldsa65_hybrid_key.c \ + sm2_mldsa65_hybrid_codecs.c +ENDIF \ No newline at end of file diff --git a/crypto/sm2_mldsa65_hybrid/sm2_mldsa65_hybrid_codecs.c b/crypto/sm2_mldsa65_hybrid/sm2_mldsa65_hybrid_codecs.c new file mode 100644 index 000000000..86e77fca3 --- /dev/null +++ b/crypto/sm2_mldsa65_hybrid/sm2_mldsa65_hybrid_codecs.c @@ -0,0 +1,67 @@ +/* + * Copyright 2025 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 "crypto/x509.h" +#include "crypto/sm2_mldsa65_hybrid.h" + +static const uint8_t sm2_mldsa65_hybrid_spkifmt[SM2_MLDSA_HYBRID_SPKI_OVERHEAD] = { + 0x30, 0x82, 0x07, 0xd5, 0x30, 0x0d, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, + 0x86, 0xfa, 0x6b, 0x50, 0x09, 0x01, 0x15, 0x03, 0x82, 0x07, 0xc2, 0x00 +}; + +SM2_MLDSA65_HYBRID_KEY *ossl_sm2_mldsa65_hybrid_key_from_pkcs8(const PKCS8_PRIV_KEY_INFO *p8inf, + OSSL_LIB_CTX *libctx, const char *propq) +{ + SM2_MLDSA65_HYBRID_KEY *key = NULL, *ret = NULL; + const uint8_t *buf; + int len; + const X509_ALGOR *palg; + + if (!PKCS8_pkey_get0(NULL, &buf, &len, &palg, p8inf)) + goto err; + + if ((key = sm2_mldsa65_hybrid_key_new(libctx, propq)) == NULL) + goto err; + + if (!sm2_mldsa65_hybrid_priv_key_deserialize(key, buf, len)) + goto err; + + ret = key; +err: + if (ret == NULL) + sm2_mldsa65_hybrid_key_free(key); + return ret; +} + +SM2_MLDSA65_HYBRID_KEY * +ossl_sm2_mldsa65_hybrid_d2i_PUBKEY(const uint8_t *pk, int pk_len, OSSL_LIB_CTX *libctx) +{ + SM2_MLDSA65_HYBRID_KEY *ret; + + if (pk_len != SM2_MLDSA_HYBRID_SPKI_OVERHEAD + (ossl_ssize_t) SM2_MLDSA65_HYBRID_PK_SIZE + || memcmp(pk, sm2_mldsa65_hybrid_spkifmt, SM2_MLDSA_HYBRID_SPKI_OVERHEAD) != 0) + return NULL; + pk_len -= SM2_MLDSA_HYBRID_SPKI_OVERHEAD; + pk += SM2_MLDSA_HYBRID_SPKI_OVERHEAD; + + if ((ret = sm2_mldsa65_hybrid_key_new(libctx, NULL)) == NULL) + return NULL; + + if (!sm2_mldsa65_hybrid_pub_key_deserialize(ret, pk, (size_t)pk_len)) { + ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING, + "error parsing sm2-mldsa65-hybrid public key from input SPKI"); + sm2_mldsa65_hybrid_key_free(ret); + return NULL; + } + + return ret; +} diff --git a/crypto/sm2_mldsa65_hybrid/sm2_mldsa65_hybrid_key.c b/crypto/sm2_mldsa65_hybrid/sm2_mldsa65_hybrid_key.c new file mode 100644 index 000000000..b65782147 --- /dev/null +++ b/crypto/sm2_mldsa65_hybrid/sm2_mldsa65_hybrid_key.c @@ -0,0 +1,226 @@ +/* + * Copyright 2025 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 "prov/providercommon.h" + +#include "crypto/sm2_mldsa65_hybrid.h" + +void *sm2_mldsa65_hybrid_key_new(OSSL_LIB_CTX *libctx, const char *propq) +{ + SM2_MLDSA65_HYBRID_KEY *key = NULL; + + if (!ossl_prov_is_running()) + goto err; + key = OPENSSL_zalloc(sizeof(SM2_MLDSA65_HYBRID_KEY)); + if (key == NULL) + goto err; + + key->libctx = libctx; + key->status = SM2_MLDSA65_HYBRID_HAVE_NOKEYS; + + if (propq != NULL) { + key->propq = OPENSSL_strdup(propq); + if (key->propq == NULL) + goto err; + } + + return key; +err: + OPENSSL_free(key); + return NULL; +} + +static void sm2_mldsa65_hybrid_key_cleanup(SM2_MLDSA65_HYBRID_KEY *key) +{ + if (key == NULL) + return; + + EVP_PKEY_free(key->sm2_key); + EVP_PKEY_free(key->mldsa_key); + key->sm2_key = key->mldsa_key = NULL; + key->status = SM2_MLDSA65_HYBRID_HAVE_NOKEYS; +} + +void sm2_mldsa65_hybrid_key_free(SM2_MLDSA65_HYBRID_KEY *key) +{ + if (key == NULL) + return; + + sm2_mldsa65_hybrid_key_cleanup(key); + OPENSSL_free(key->propq); + OPENSSL_free(key); +} + +int sm2_mldsa65_hybrid_key_serialize(SM2_MLDSA65_HYBRID_KEY *pkey, + uint8_t *pk, size_t pksize, size_t *pklen, + uint8_t *sk, size_t sksize, size_t *sklen) +{ + int ret = 0; + BIGNUM *bn_sm2_sk = NULL; + size_t len_tmp = 0; + + if (pk != NULL) { + if (pksize < SM2_MLDSA65_HYBRID_PK_SIZE || pklen == NULL) + goto err; + if (!EVP_PKEY_get_octet_string_param(pkey->mldsa_key, OSSL_PKEY_PARAM_PUB_KEY, pk, MLDSA_PK_SIZE, &len_tmp) || + len_tmp != MLDSA_PK_SIZE) + goto err; + *pklen = len_tmp; + if (!EVP_PKEY_get_octet_string_param(pkey->sm2_key, OSSL_PKEY_PARAM_PUB_KEY, pk + MLDSA_PK_SIZE, SM2_PK_SIZE, &len_tmp)) + goto err; + *pklen += len_tmp; + } + + if (sk != NULL) { + if (sksize < SM2_MLDSA65_HYBRID_SK_SIZE || sklen == NULL) + goto err; + if (!EVP_PKEY_get_octet_string_param(pkey->mldsa_key, OSSL_PKEY_PARAM_ML_DSA_SEED, sk, MLDSA_SK_SIZE, &len_tmp) || + len_tmp != MLDSA_SK_SIZE) + goto err; + *sklen = len_tmp; + if (!EVP_PKEY_get_bn_param(pkey->sm2_key, OSSL_PKEY_PARAM_PRIV_KEY, &bn_sm2_sk)) + goto err; + if (!(len_tmp = BN_bn2binpad(bn_sm2_sk, sk + MLDSA_SK_SIZE, SM2_SK_SIZE))) + goto err; + *sklen += len_tmp; + } + ret = 1; +err: + BN_free(bn_sm2_sk); + return ret; +} + +static int do_fromdata(OSSL_LIB_CTX *libctx, const char *propq, OSSL_PARAM *params, + EVP_PKEY **ppkey, const char *alg_name, int selection) +{ + int ret = 0; + EVP_PKEY_CTX *ctx; + + if ((ctx = EVP_PKEY_CTX_new_from_name(libctx, alg_name, propq)) == NULL + || EVP_PKEY_fromdata_init(ctx) <= 0 + || EVP_PKEY_fromdata(ctx, ppkey, selection, params) <= 0) + goto err; + ret = 1; +err: + EVP_PKEY_CTX_free(ctx); + return ret; +} + +int sm2_mldsa65_hybrid_pub_key_deserialize(SM2_MLDSA65_HYBRID_KEY *key, + const uint8_t *pk, size_t pklen) +{ + int ret = 0; + OSSL_PARAM params[3], *p = params; + char *group_name = SM2_MLDSA65_HYBRID_TNAME; + + if (pk == NULL) + return SM2_MLDSA65_HYBRID_PK_SIZE; + + if (pklen < SM2_MLDSA65_HYBRID_PK_SIZE) + goto err; + + /* Set ML-DSA public key */ + *p++ = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY, + (void*)pk, MLDSA_PK_SIZE); + *p = OSSL_PARAM_construct_end(); + if (!do_fromdata(key->libctx, key->propq, params, &key->mldsa_key, + SM2_MLDSA65_HYBRID_QNAME, OSSL_KEYMGMT_SELECT_PUBLIC_KEY)) + goto err; + + /* Set SM2 public key */ + p = params; + *p++ = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY, + (void*)(pk + MLDSA_PK_SIZE), SM2_PK_SIZE); + *p++ = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, group_name, 0); + *p = OSSL_PARAM_construct_end(); + if (!do_fromdata(key->libctx, key->propq, params, &key->sm2_key, + SM2_MLDSA65_HYBRID_TNAME, OSSL_KEYMGMT_SELECT_PUBLIC_KEY | OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS)) + goto err; + + key->status |= SM2_MLDSA65_HYBRID_HAVE_PUBKEY; + + ret = 1; +err: + if (!ret) { + sm2_mldsa65_hybrid_key_cleanup(key); + } + return ret; +} + +int sm2_mldsa65_hybrid_priv_key_deserialize(SM2_MLDSA65_HYBRID_KEY *key, + const uint8_t *sk, size_t sklen) +{ + int ret = 0; + OSSL_PARAM *p_mldsa = NULL, *p_sm2 = NULL; + char *group_name = SM2_MLDSA65_HYBRID_TNAME; + BIGNUM *bn_sm2_sk = NULL; + OSSL_PARAM_BLD *param_bld = NULL; + uint8_t sm2_pub[SM2_PK_SIZE]; + size_t sm2_pub_len = 0; + EC_GROUP *group = NULL; + EC_POINT *pub_point = NULL; + + if (sk == NULL) + return SM2_MLDSA65_HYBRID_SK_SIZE; + + if (sklen < SM2_MLDSA65_HYBRID_SK_SIZE) + goto err; + + /* Set ML-DSA private key (seed) */ + if ((param_bld = OSSL_PARAM_BLD_new()) == NULL + || !OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_ML_DSA_SEED, sk, MLDSA_SK_SIZE) + || (p_mldsa = OSSL_PARAM_BLD_to_param(param_bld)) == NULL) + goto err; + + if (!do_fromdata(key->libctx, key->propq, p_mldsa, &key->mldsa_key, + SM2_MLDSA65_HYBRID_QNAME, OSSL_KEYMGMT_SELECT_PRIVATE_KEY)) + goto err; + + /* Set SM2 private key */ + if ((bn_sm2_sk = BN_bin2bn(sk + MLDSA_SK_SIZE, SM2_SK_SIZE, NULL)) == NULL) + goto err; + + /* calc SM2 public key */ + if ((group = EC_GROUP_new_by_curve_name(SM2_MLDSA65_HYBRID_TID)) == NULL + || (pub_point = EC_POINT_new(group)) == NULL + || !EC_POINT_mul(group, pub_point, bn_sm2_sk, NULL, NULL, NULL) + || (sm2_pub_len = EC_POINT_point2oct(group, pub_point, POINT_CONVERSION_COMPRESSED, + sm2_pub, sizeof(sm2_pub), NULL)) != SM2_PK_SIZE) + goto err; + + OSSL_PARAM_BLD_free(param_bld); + if ((param_bld = OSSL_PARAM_BLD_new()) == NULL + || !OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME, group_name, 0) + || !OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY, sm2_pub, sm2_pub_len) + || !OSSL_PARAM_BLD_push_BN_pad(param_bld, OSSL_PKEY_PARAM_PRIV_KEY, bn_sm2_sk, SM2_SK_SIZE) + || (p_sm2 = OSSL_PARAM_BLD_to_param(param_bld)) == NULL) + goto err; + + if (!do_fromdata(key->libctx, key->propq, p_sm2, &key->sm2_key, + SM2_MLDSA65_HYBRID_TNAME, OSSL_KEYMGMT_SELECT_KEYPAIR | OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS)) + goto err; + + key->status |= SM2_MLDSA65_HYBRID_HAVE_PRVKEY; + + ret = 1; +err: + BN_clear_free(bn_sm2_sk); + OSSL_PARAM_BLD_free(param_bld); + OSSL_PARAM_free(p_mldsa); + OSSL_PARAM_free(p_sm2); + EC_POINT_free(pub_point); + EC_GROUP_free(group); + if (!ret) { + sm2_mldsa65_hybrid_key_cleanup(key); + } + return ret; +} diff --git a/fuzz/oids.txt b/fuzz/oids.txt index d28e6488e..4a2f546d9 100644 --- a/fuzz/oids.txt +++ b/fuzz/oids.txt @@ -930,3 +930,5 @@ OBJ_oracle_jdk_trustedkeyusage="\x60\x86\x48\x01\x86\xF9\x66\xAD\xCA\x7B\x01\x01 OBJ_sm2dh_mlkem768_hybrid="\x2A\x81\x1C\xCF\x55\x01\x87\x69" OBJ_ML_DSA_65="\x60\x86\x48\x01\x65\x03\x04\x03\x12" OBJ_associated_information="\x55\x1D\x4B" +OBJ_compsig="\x60\x86\x48\x01\x86\xFA\x6B\x50\x09\x01" +OBJ_sm2_mldsa65_hybrid="\x60\x86\x48\x01\x86\xFA\x6B\x50\x09\x01\x15" diff --git a/include/crypto/sm2_mldsa65_hybrid.h b/include/crypto/sm2_mldsa65_hybrid.h new file mode 100644 index 000000000..e78a43b25 --- /dev/null +++ b/include/crypto/sm2_mldsa65_hybrid.h @@ -0,0 +1,93 @@ +/* + * Copyright 2025 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 OSSL_CRYPTO_SM2sig_MLDSA65_HYBRID_H +# define OSSL_CRYPTO_SM2sig_MLDSA65_HYBRID_H + +#include +#include +#include +#include +#include "crypto/ml_dsa.h" + +#define SM2_MLDSA65_HYBRID_QNAME "ML-DSA-65" +#define SM2_MLDSA65_HYBRID_TNAME "SM2" +#define SM2_MLDSA65_HYBRID_QID EVP_PKEY_ML_DSA_65 +#define SM2_MLDSA65_HYBRID_TID EVP_PKEY_SM2 + +#define SM2_PK_SIZE 33 +#define SM2_SK_SIZE 32 +#define SM2_SIG_SIZE 72 + +#define MLDSA_PK_SIZE ML_DSA_PUBLICKEYBYTES +#define MLDSA_SK_SIZE ML_DSA_SEEDBYTES +#define MLDSA_SIG_SIZE ML_DSA_SIGBYTES + +#define SM2_MLDSA65_HYBRID_RANDOM_BYTES 32 +#define SM2_MLDSA65_HYBRID_PREFIX_SIZE 32 +#define SM2_MLDSA65_HYBRID_DOMAIN_SIZE 13 +#define SM2_MLDSA65_HYBRID_MAX_CONTEXT_STRING_BYTES 255 + +#define SM2_MLDSA65_HYBRID_PK_SIZE (SM2_PK_SIZE + MLDSA_PK_SIZE) +#define SM2_MLDSA65_HYBRID_SK_SIZE (SM2_SK_SIZE + MLDSA_SK_SIZE) +#define SM2_MLDSA65_HYBRID_SIG_SIZE (SM2_MLDSA65_HYBRID_RANDOM_BYTES + \ + SM2_SIG_SIZE + MLDSA_SIG_SIZE) + +typedef struct { + OSSL_LIB_CTX * libctx; + char *propq; + + EVP_PKEY *sm2_key; + EVP_PKEY *mldsa_key; + int status; +} SM2_MLDSA65_HYBRID_KEY; + +int sm2_mldsa65_hybrid_key_serialize(SM2_MLDSA65_HYBRID_KEY *pkey, + uint8_t *pk, size_t pksize, size_t *pklen, + uint8_t *sk, size_t sksize, size_t *sklen); + +int sm2_mldsa65_hybrid_pub_key_deserialize(SM2_MLDSA65_HYBRID_KEY *key, + const uint8_t *pk, size_t pklen); + +int sm2_mldsa65_hybrid_priv_key_deserialize(SM2_MLDSA65_HYBRID_KEY *key, + const uint8_t *sk, size_t sklen); + +void *sm2_mldsa65_hybrid_key_new(OSSL_LIB_CTX *libctx, const char *propq); + +void sm2_mldsa65_hybrid_key_free(SM2_MLDSA65_HYBRID_KEY *key); + +#define SM2_MLDSA65_HYBRID_HAVE_NOKEYS 0 +#define SM2_MLDSA65_HYBRID_HAVE_PUBKEY 1 +#define SM2_MLDSA65_HYBRID_HAVE_PRVKEY 2 + +#define sm2_mldsa65_hybrid_have_pubkey(key) ((key)->status > 0) +#define sm2_mldsa65_hybrid_have_prvkey(key) ((key)->status > 1) + +#ifndef OPENSSL_NO_SM2_MLDSA65_HYBRID + /*- + * The DER ASN.1 encoding of sm2-mldsa65-hybrid public keys prepends 24 bytes + * to the encoded public key: + * + * - 4 byte outer sequence tag and length + * - 2 byte algorithm sequence tag and length + * - 2 byte algorithm OID tag and length + * - 11 byte algorithm OID (testing) + * - 4 byte bit string tag and length + * - 1 bitstring lead byte + */ +#define SM2_MLDSA_HYBRID_SPKI_OVERHEAD 24 + +__owur +SM2_MLDSA65_HYBRID_KEY *ossl_sm2_mldsa65_hybrid_d2i_PUBKEY(const uint8_t *pk, int pk_len, OSSL_LIB_CTX *libctx); +__owur +SM2_MLDSA65_HYBRID_KEY *ossl_sm2_mldsa65_hybrid_key_from_pkcs8(const PKCS8_PRIV_KEY_INFO *p8inf, + OSSL_LIB_CTX *libctx, const char *propq); +#endif /* OPENSSL_NO_SM2_MLDSA65_HYBRID */ + +#endif diff --git a/include/openssl/evp.h b/include/openssl/evp.h index b5e31eead..fd14217f2 100644 --- a/include/openssl/evp.h +++ b/include/openssl/evp.h @@ -84,6 +84,8 @@ # define EVP_PKEY_SM2DH_MLKEM768_HYBRID NID_sm2dh_mlkem768_hybrid /* ML_DSA */ # define EVP_PKEY_ML_DSA_65 NID_ML_DSA_65 +/* SM2-MLDSA65-HYBRID */ +# define EVP_PKEY_SM2_MLDSA65_HYBRID NID_sm2_mldsa65_hybrid /* Special indicator that the object is uniquely provider side */ # define EVP_PKEY_KEYMGMT -1 diff --git a/include/openssl/obj_mac.h b/include/openssl/obj_mac.h index 496df6730..6cf73f207 100644 --- a/include/openssl/obj_mac.h +++ b/include/openssl/obj_mac.h @@ -4510,4 +4510,14 @@ #define LN_wbsm4_wsise_ccm "wbsm4-wsise-ccm" #define NID_wbsm4_wsise_ccm 1276 +#define SN_compsig "Compsig" +#define LN_compsig "composite ML-DSA signature" +#define NID_compsig 1320 +#define OBJ_compsig OBJ_joint_iso_itu_t,16L,840L,1L,114027L,80L,9L,1L + +#define SN_sm2_mldsa65_hybrid "SM2-MLDSA65-HYBRID" +#define LN_sm2_mldsa65_hybrid "sm2-mldsa65-hybrid" +#define NID_sm2_mldsa65_hybrid 1321 +#define OBJ_sm2_mldsa65_hybrid OBJ_compsig,21L + #endif /* OPENSSL_OBJ_MAC_H */ diff --git a/providers/common/der/SM2_MLDSA65_HYBRID.asn1 b/providers/common/der/SM2_MLDSA65_HYBRID.asn1 new file mode 100644 index 000000000..453f4d0c3 --- /dev/null +++ b/providers/common/der/SM2_MLDSA65_HYBRID.asn1 @@ -0,0 +1,79 @@ +-- Copyright 2025 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 + +-- ASN.1 Module for PQC Composite Signatures +-- Based on draft-ietf-lamps-pq-composite-sigs-07 + + + +Composite-MLDSA-2025 + { iso(1) identified-organization(3) dod(6) internet(1) + security(5) mechanisms(5) pkix(7) id-mod(0) + id-mod-composite-mldsa-2025(TBDMOD) } + + +DEFINITIONS IMPLICIT TAGS ::= BEGIN + +EXPORTS ALL; + +IMPORTS + PUBLIC-KEY, SIGNATURE-ALGORITHM, SMIME-CAPS, AlgorithmIdentifier{} + FROM AlgorithmInformation-2009 -- RFC 5912 [X509ASN1] + { iso(1) identified-organization(3) dod(6) internet(1) + security(5) mechanisms(5) pkix(7) id-mod(0) + id-mod-algorithmInformation-02(58) } +; + +-- +-- Object Identifiers +-- + +-- +-- Information Object Classes +-- + +pk-CompositeSignature {OBJECT IDENTIFIER:id} + PUBLIC-KEY ::= { + IDENTIFIER id + -- KEY without ASN.1 wrapping -- + PARAMS ARE absent + CERT-KEY-USAGE { digitalSignature, nonRepudiation, keyCertSign, + cRLSign} + } + +sa-CompositeSignature{OBJECT IDENTIFIER:id, + PUBLIC-KEY:publicKeyType } + SIGNATURE-ALGORITHM ::= { + IDENTIFIER id + -- VALUE without ASN.1 wrapping -- + PARAMS ARE absent + PUBLIC-KEYS {publicKeyType} + } + + +-- Composite ML-DSA which uses a PreHash Message + +-- TODO: OID to be replaced by IANA +id-MLDSA65-SM2-SM3 OBJECT IDENTIFIER ::= { + joint-iso-itu-t(2) country(16) us(840) organization(1) + entrust(114027) algorithm(80) composite-mldsa(9) signature(1) 21 } + +pk-MLDSA65-SM2-SM3 PUBLIC-KEY ::= + pk-CompositeSignature{ id-MLDSA65-SM2-SM3} + +sa-MLDSA65-SM2-SM3 SIGNATURE-ALGORITHM ::= + sa-CompositeSignature{ + id-MLDSA65-SM2-SM3, + pk-MLDSA65-SM2-SM3 } + +SignatureAlgorithmSet SIGNATURE-ALGORITHM ::= { + sa-MLDSA65-SM2-SM3, + ... } + +END + + diff --git a/providers/common/der/build.info b/providers/common/der/build.info index 82df4addc..8b51ca627 100644 --- a/providers/common/der/build.info +++ b/providers/common/der/build.info @@ -111,6 +111,21 @@ IF[{- !$disabled{sm2} -}] DEPEND[$DER_SM2_H]=oids_to_c.pm SM2.asn1 ENDIF +#----- SM2_MLDSA65_HYBRID +IF[{- !$disabled{'sm2-mldsa65-hybrid'} -}] + $DER_SM2_MLDSA65_HYBRID_H=$INCDIR/der_sm2_mldsa65_hybrid.h + $DER_SM2_MLDSA65_HYBRID_GEN=der_sm2_mldsa65_hybrid_gen.c + $DER_SM2_MLDSA65_HYBRID_AUX=der_sm2_mldsa65_hybrid_key.c der_sm2_mldsa65_hybrid_sig.c + + GENERATE[$DER_SM2_MLDSA65_HYBRID_GEN]=der_sm2_mldsa65_hybrid_gen.c.in + DEPEND[$DER_SM2_MLDSA65_HYBRID_GEN]=oids_to_c.pm SM2_MLDSA65_HYBRID.asn1 + + DEPEND[${DER_SM2_MLDSA65_HYBRID_AUX/.c/.o}]=$DER_SM2_MLDSA65_HYBRID_H + DEPEND[${DER_SM2_MLDSA65_HYBRID_GEN/.c/.o}]=$DER_SM2_MLDSA65_HYBRID_H + GENERATE[$DER_SM2_MLDSA65_HYBRID_H]=$INCDIR/der_sm2_mldsa65_hybrid.h.in + DEPEND[$DER_SM2_MLDSA65_HYBRID_H]=oids_to_c.pm SM2_MLDSA65_HYBRID.asn1 +ENDIF + #----- Conclusion $COMMON= $DER_RSA_COMMON $DER_DIGESTS_GEN $DER_WRAP_GEN @@ -132,6 +147,10 @@ IF[{- !$disabled{sm2} -}] $NONFIPS = $NONFIPS $DER_SM2_GEN $DER_SM2_AUX ENDIF +IF[{- !$disabled{'sm2-mldsa65-hybrid'} -}] + $NONFIPS = $NONFIPS $DER_SM2_MLDSA65_HYBRID_GEN $DER_SM2_MLDSA65_HYBRID_AUX +ENDIF + SOURCE[../../libcommon.a]= $COMMON SOURCE[../../libfips.a]= $DER_RSA_FIPSABLE SOURCE[../../libdefault.a]= $DER_RSA_FIPSABLE $NONFIPS diff --git a/providers/common/der/der_sm2_mldsa65_hybrid_gen.c.in b/providers/common/der/der_sm2_mldsa65_hybrid_gen.c.in new file mode 100644 index 000000000..2d0e54023 --- /dev/null +++ b/providers/common/der/der_sm2_mldsa65_hybrid_gen.c.in @@ -0,0 +1,19 @@ +/* + * {- join("\n * ", @autowarntext) -} + * + * Copyright 2025 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 "prov/der_sm2_mldsa65_hybrid.h" + +/* Well known OIDs precompiled */ +{- + $OUT = oids_to_c::process_leaves('providers/common/der/SM2_MLDSA65_HYBRID.asn1', + { dir => $config{sourcedir}, + filter => \&oids_to_c::filter_to_C }); +-} diff --git a/providers/common/der/der_sm2_mldsa65_hybrid_key.c b/providers/common/der/der_sm2_mldsa65_hybrid_key.c new file mode 100644 index 000000000..c184b5047 --- /dev/null +++ b/providers/common/der/der_sm2_mldsa65_hybrid_key.c @@ -0,0 +1,22 @@ +/* + * Copyright 2025 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 "internal/packet.h" +#include "prov/der_sm2_mldsa65_hybrid.h" + +int ossl_DER_w_algorithmIdentifier_SM2_MLDSA65_HYBRID(WPACKET *pkt, int cont, SM2_MLDSA65_HYBRID_KEY *key) +{ + return ossl_DER_w_begin_sequence(pkt, cont) + /* No parameters (yet?) */ + /* It seems SM2 identifier is the same as id_ecPublidKey */ + && ossl_DER_w_precompiled(pkt, -1, ossl_der_oid_id_MLDSA65_SM2_SM3, + sizeof(ossl_der_oid_id_MLDSA65_SM2_SM3)) + && ossl_DER_w_end_sequence(pkt, cont); +} diff --git a/providers/common/der/der_sm2_mldsa65_hybrid_sig.c b/providers/common/der/der_sm2_mldsa65_hybrid_sig.c new file mode 100644 index 000000000..bdedb2cea --- /dev/null +++ b/providers/common/der/der_sm2_mldsa65_hybrid_sig.c @@ -0,0 +1,39 @@ +/* + * Copyright 2025 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 "internal/packet.h" +#include "prov/der_sm2_mldsa65_hybrid.h" + +/* Aliases so we can have a uniform MD_CASE */ +#define ossl_der_oid_id_mldsa65_sm2_with_sm3 ossl_der_oid_id_MLDSA65_SM2_SM3 + +#define MD_CASE(name) \ + case NID_##name: \ + precompiled = ossl_der_oid_id_mldsa65_sm2_with_##name; \ + precompiled_sz = sizeof(ossl_der_oid_id_mldsa65_sm2_with_##name); \ + break; + +int ossl_DER_w_algorithmIdentifier_SM2_MLDSA65_HYBRID_with_MD(WPACKET *pkt, int cont, + SM2_MLDSA65_HYBRID_KEY *key, int mdnid) +{ + const unsigned char *precompiled = NULL; + size_t precompiled_sz = 0; + + switch (mdnid) { + MD_CASE(sm3); + default: + return 0; + } + + return ossl_DER_w_begin_sequence(pkt, cont) + /* No parameters (yet?) */ + && ossl_DER_w_precompiled(pkt, -1, precompiled, precompiled_sz) + && ossl_DER_w_end_sequence(pkt, cont); +} diff --git a/providers/common/include/prov/der_sm2_mldsa65_hybrid.h.in b/providers/common/include/prov/der_sm2_mldsa65_hybrid.h.in new file mode 100644 index 000000000..e8b09b919 --- /dev/null +++ b/providers/common/include/prov/der_sm2_mldsa65_hybrid.h.in @@ -0,0 +1,26 @@ +/* + * {- join("\n * ", @autowarntext) -} + * + * Copyright 2025 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 "crypto/sm2_mldsa65_hybrid.h" +#include "internal/der.h" + +/* Well known OIDs precompiled */ +{- + $OUT = oids_to_c::process_leaves('providers/common/der/SM2_MLDSA65_HYBRID.asn1', + { dir => $config{sourcedir}, + filter => \&oids_to_c::filter_to_H }); +-} + +/* Subject Public Key Info */ +int ossl_DER_w_algorithmIdentifier_SM2_MLDSA65_HYBRID(WPACKET *pkt, int cont, SM2_MLDSA65_HYBRID_KEY *key); +/* Signature */ +int ossl_DER_w_algorithmIdentifier_SM2_MLDSA65_HYBRID_with_MD(WPACKET *pkt, int cont, + SM2_MLDSA65_HYBRID_KEY *key, int mdnid); diff --git a/providers/decoders.inc b/providers/decoders.inc index f2b7a3076..fe8ca20cb 100644 --- a/providers/decoders.inc +++ b/providers/decoders.inc @@ -85,6 +85,10 @@ DECODER("RSA", pvk, rsa, yes), DECODER_w_structure("ML-DSA-65", der, PrivateKeyInfo, ml_dsa_65, no), DECODER_w_structure("ML-DSA-65", der, SubjectPublicKeyInfo, ml_dsa_65, no), #endif +#ifndef OPENSSL_NO_SM2_MLDSA65_HYBRID +DECODER_w_structure("SM2-MLDSA65-HYBRID", der, PrivateKeyInfo, sm2_mldsa65_hybrid, no), +DECODER_w_structure("SM2-MLDSA65-HYBRID", der, SubjectPublicKeyInfo, sm2_mldsa65_hybrid, no), +#endif /* * A decoder that takes a SubjectPublicKeyInfo and figures out the types of key diff --git a/providers/defltprov.c b/providers/defltprov.c index 81093a78d..db9fbf759 100644 --- a/providers/defltprov.c +++ b/providers/defltprov.c @@ -369,6 +369,9 @@ static const OSSL_ALGORITHM deflt_signature[] = { #endif #ifndef OPENSSL_NO_ML_DSA { PROV_NAMES_ML_DSA_65, "provider=default", ossl_ml_dsa_65_signature_functions }, +#endif +#ifndef OPENSSL_NO_SM2_MLDSA65_HYBRID + { PROV_NAMES_SM2_MLDSA65_HYBRID, "provider=default", ossl_sm2_mldsa65_hybrid_signature_functions }, #endif { NULL, NULL, NULL } }; @@ -446,6 +449,10 @@ static const OSSL_ALGORITHM deflt_keymgmt[] = { #ifndef OPENSSL_NO_ML_DSA { PROV_NAMES_ML_DSA_65, "provider=default", ossl_ml_dsa_65_keymgmt_functions, PROV_DESCS_ML_DSA_65 }, +#endif +#ifndef OPENSSL_NO_SM2_MLDSA65_HYBRID + { PROV_NAMES_SM2_MLDSA65_HYBRID, "provider=default", ossl_sm2_mldsa65_hybrid_keymgmt_functions, + PROV_DESCS_SM2_MLDSA65_HYBRID }, #endif { NULL, NULL, NULL } }; diff --git a/providers/encoders.inc b/providers/encoders.inc index 538e03dfb..19f7ef8a4 100644 --- a/providers/encoders.inc +++ b/providers/encoders.inc @@ -71,6 +71,10 @@ ENCODER_TEXT("SM2", sm2, no), ENCODER_TEXT("ML-DSA-65", ml_dsa_65, no), #endif +#ifndef OPENSSL_NO_SM2_MLDSA65_HYBRID +ENCODER_TEXT("SM2-MLDSA65-HYBRID", sm2_mldsa65_hybrid, no), +#endif + /* * Entries for key type specific output formats. The structure name on these * is the same as the key type name. This allows us to say something like: @@ -233,6 +237,15 @@ ENCODER_w_structure("ML-DSA-65", ml_dsa_65, no, der, SubjectPublicKeyInfo), ENCODER_w_structure("ML-DSA-65", ml_dsa_65, no, pem, SubjectPublicKeyInfo), #endif +#ifndef OPENSSL_NO_SM2_MLDSA65_HYBRID +ENCODER_w_structure("SM2-MLDSA65-HYBRID", sm2_mldsa65_hybrid, no, der, EncryptedPrivateKeyInfo), +ENCODER_w_structure("SM2-MLDSA65-HYBRID", sm2_mldsa65_hybrid, no, pem, EncryptedPrivateKeyInfo), +ENCODER_w_structure("SM2-MLDSA65-HYBRID", sm2_mldsa65_hybrid, no, der, PrivateKeyInfo), +ENCODER_w_structure("SM2-MLDSA65-HYBRID", sm2_mldsa65_hybrid, no, pem, PrivateKeyInfo), +ENCODER_w_structure("SM2-MLDSA65-HYBRID", sm2_mldsa65_hybrid, no, der, SubjectPublicKeyInfo), +ENCODER_w_structure("SM2-MLDSA65-HYBRID", sm2_mldsa65_hybrid, no, pem, SubjectPublicKeyInfo), +#endif + /* * Entries for key type specific output formats. These are exactly the * same as the type specific above, except that they use the key type diff --git a/providers/implementations/encode_decode/decode_der2key.c b/providers/implementations/encode_decode/decode_der2key.c index ea5ba10c2..90843855b 100644 --- a/providers/implementations/encode_decode/decode_der2key.c +++ b/providers/implementations/encode_decode/decode_der2key.c @@ -32,6 +32,7 @@ #include "crypto/ecx.h" #include "crypto/rsa.h" #include "crypto/ml_dsa.h" +#include "crypto/sm2_mldsa65_hybrid.h" #include "crypto/x509.h" #include "prov/bio.h" #include "prov/implementations.h" @@ -593,6 +594,38 @@ static ossl_inline void * ml_dsa_d2i_PUBKEY(void **a, const uint8_t **der, long /* ---------------------------------------------------------------------- */ +#ifndef OPENSSL_NO_SM2_MLDSA65_HYBRID +static void * +sm2_mldsa65_hybrid_d2i_PKCS8(void **a, const uint8_t **der, long der_len, struct der2key_ctx_st *ctx) +{ + return der2key_decode_p8(der, der_len, ctx, + (key_from_pkcs8_t *)ossl_sm2_mldsa65_hybrid_key_from_pkcs8); +} + +static void * sm2_mldsa65_hybrid_d2i_PUBKEY(void **a, const uint8_t **der, long der_len) +{ + SM2_MLDSA65_HYBRID_KEY *key = NULL; + + key = ossl_sm2_mldsa65_hybrid_d2i_PUBKEY(*der, der_len, NULL); + if (key != NULL) + *der += der_len; + return key; +} + +# define sm2_mldsa65_hybrid_evp_type EVP_PKEY_SM2_MLDSA65_HYBRID +# define sm2_mldsa65_hybrid_d2i_private_key NULL +# define sm2_mldsa65_hybrid_d2i_public_key NULL +# define sm2_mldsa65_hybrid_d2i_key_params NULL +# define sm2_mldsa65_hybrid_d2i_PUBKEY sm2_mldsa65_hybrid_d2i_PUBKEY +# define sm2_mldsa65_hybrid_d2i_PKCS8 sm2_mldsa65_hybrid_d2i_PKCS8 +# define sm2_mldsa65_hybrid_free (free_key_fn *)sm2_mldsa65_hybrid_key_free +# define sm2_mldsa65_hybrid_check NULL +# define sm2_mldsa65_hybrid_adjust NULL + +#endif + +/* ---------------------------------------------------------------------- */ + /* * The DO_ macros help define the selection mask and the method functions * for each kind of object we want to decode. @@ -850,3 +883,8 @@ MAKE_DECODER("RSA-PSS", rsapss, rsapss, SubjectPublicKeyInfo); MAKE_DECODER("ML-DSA-65", ml_dsa_65, ml_dsa_65, PrivateKeyInfo); MAKE_DECODER("ML-DSA-65", ml_dsa_65, ml_dsa_65, SubjectPublicKeyInfo); #endif + +#ifndef OPENSSL_NO_SM2_MLDSA65_HYBRID +MAKE_DECODER("SM2-MLDSA65-HYBRID", sm2_mldsa65_hybrid, sm2_mldsa65_hybrid, PrivateKeyInfo); +MAKE_DECODER("SM2-MLDSA65-HYBRID", sm2_mldsa65_hybrid, sm2_mldsa65_hybrid, SubjectPublicKeyInfo); +#endif diff --git a/providers/implementations/encode_decode/encode_key2any.c b/providers/implementations/encode_decode/encode_key2any.c index c2aa97943..068ee6591 100644 --- a/providers/implementations/encode_decode/encode_key2any.c +++ b/providers/implementations/encode_decode/encode_key2any.c @@ -31,6 +31,7 @@ #include "crypto/ecx.h" #include "crypto/rsa.h" #include "crypto/ml_dsa.h" +#include "crypto/sm2_mldsa65_hybrid.h" #include "prov/implementations.h" #include "prov/bio.h" #include "prov/provider_ctx.h" @@ -835,6 +836,76 @@ static int ml_dsa_pki_priv_to_der(const void *vkey, unsigned char **pder) /* ---------------------------------------------------------------------- */ +#ifndef OPENSSL_NO_SM2_MLDSA65_HYBRID +static int sm2_mldsa65_hybrid_spki_pub_to_der(const void *vkey, unsigned char **pder) +{ + SM2_MLDSA65_HYBRID_KEY *key = (SM2_MLDSA65_HYBRID_KEY *)vkey; + uint8_t *buf = NULL; + size_t pklen = 0; + + if (key == NULL || !sm2_mldsa65_hybrid_have_pubkey(key)) { + ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_A_PUBLIC_KEY, + "no sm2_mldsa65_hybrid public key data available"); + return 0; + } + + if (pder == NULL) + return SM2_MLDSA65_HYBRID_PK_SIZE; + + if ((buf = OPENSSL_malloc(SM2_MLDSA65_HYBRID_PK_SIZE)) == NULL) { + ERR_raise(ERR_LIB_CRYPTO, ERR_R_MALLOC_FAILURE); + return 0; + } + + if (!sm2_mldsa65_hybrid_key_serialize(key, + buf, SM2_MLDSA65_HYBRID_PK_SIZE, &pklen, NULL, 0, NULL)) { + OPENSSL_free(buf); + return 0; + } + + *pder = buf; + + return pklen; +} + +static int sm2_mldsa65_hybrid_pki_priv_to_der(const void *vkey, unsigned char **pder) +{ + SM2_MLDSA65_HYBRID_KEY *key = (SM2_MLDSA65_HYBRID_KEY *)vkey; + uint8_t *buf = NULL; + size_t sklen = 0; + + if (key == NULL || !sm2_mldsa65_hybrid_have_prvkey(key)) { + ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_A_PRIVATE_KEY, + "no sm2_mldsa65_hybrid private key data available"); + return 0; + } + + if ((buf = OPENSSL_malloc(SM2_MLDSA65_HYBRID_SK_SIZE)) == NULL) { + ERR_raise(ERR_LIB_CRYPTO, ERR_R_MALLOC_FAILURE); + return 0; + } + + if (!sm2_mldsa65_hybrid_key_serialize(key, + NULL, 0, NULL, buf, SM2_MLDSA65_HYBRID_SK_SIZE, &sklen)) { + OPENSSL_free(buf); + return 0; + } + + *pder = buf; + + return sklen; +} + +# define sm2_mldsa65_hybrid_epki_priv_to_der sm2_mldsa65_hybrid_pki_priv_to_der +# define prepare_sm2_mldsa65_hybrid_params NULL +# define sm2_mldsa65_hybrid_check_key_type NULL + +# define sm2_mldsa65_hybrid_evp_type EVP_PKEY_SM2_MLDSA65_HYBRID +# define sm2_mldsa65_hybrid_pem_type "SM2-MLDSA65-HYBRID" +#endif + +/* ---------------------------------------------------------------------- */ + /* * Helper functions to prepare RSA-PSS params for encoding. We would * have simply written the whole AlgorithmIdentifier, but existing libcrypto @@ -1485,3 +1556,12 @@ MAKE_ENCODER(ml_dsa_65, ml_dsa, EVP_PKEY_ML_DSA_65, PrivateKeyInfo, pem); MAKE_ENCODER(ml_dsa_65, ml_dsa, EVP_PKEY_ML_DSA_65, SubjectPublicKeyInfo, der); MAKE_ENCODER(ml_dsa_65, ml_dsa, EVP_PKEY_ML_DSA_65, SubjectPublicKeyInfo, pem); #endif + +#ifndef OPENSSL_NO_SM2_MLDSA65_HYBRID +MAKE_ENCODER(sm2_mldsa65_hybrid, sm2_mldsa65_hybrid, EVP_PKEY_SM2_MLDSA65_HYBRID ,EncryptedPrivateKeyInfo, der); +MAKE_ENCODER(sm2_mldsa65_hybrid, sm2_mldsa65_hybrid, EVP_PKEY_SM2_MLDSA65_HYBRID, EncryptedPrivateKeyInfo, pem); +MAKE_ENCODER(sm2_mldsa65_hybrid, sm2_mldsa65_hybrid, EVP_PKEY_SM2_MLDSA65_HYBRID, PrivateKeyInfo, der); +MAKE_ENCODER(sm2_mldsa65_hybrid, sm2_mldsa65_hybrid, EVP_PKEY_SM2_MLDSA65_HYBRID, PrivateKeyInfo, pem); +MAKE_ENCODER(sm2_mldsa65_hybrid, sm2_mldsa65_hybrid, EVP_PKEY_SM2_MLDSA65_HYBRID, SubjectPublicKeyInfo, der); +MAKE_ENCODER(sm2_mldsa65_hybrid, sm2_mldsa65_hybrid, EVP_PKEY_SM2_MLDSA65_HYBRID, SubjectPublicKeyInfo, pem); +#endif diff --git a/providers/implementations/encode_decode/encode_key2text.c b/providers/implementations/encode_decode/encode_key2text.c index 27162ff7e..6028ee656 100644 --- a/providers/implementations/encode_decode/encode_key2text.c +++ b/providers/implementations/encode_decode/encode_key2text.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include "internal/ffc.h" @@ -29,6 +30,7 @@ #include "crypto/ecx.h" /* ECX_KEY, etc... */ #include "crypto/rsa.h" /* RSA_PSS_PARAMS_30, etc... */ #include "crypto/ml_dsa.h" +#include "crypto/sm2_mldsa65_hybrid.h" #include "prov/bio.h" #include "prov/implementations.h" #include "endecoder_local.h" @@ -842,6 +844,26 @@ static int ml_dsa_to_text(BIO *out, const void *vkey, int selection) /* ---------------------------------------------------------------------- */ +#ifndef OPENSSL_NO_SM2_MLDSA65_HYBRID +static int sm2_mldsa65_hybrid_to_text(BIO *out, const void *vkey, int selection) +{ + SM2_MLDSA65_HYBRID_KEY *key = (SM2_MLDSA65_HYBRID_KEY *)vkey; + + if (out == NULL || key == NULL || key->mldsa_key == NULL || key->sm2_key == NULL) { + ERR_raise(ERR_LIB_PROV, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + if (sm2_mldsa65_hybrid_have_prvkey(key)) + return EVP_PKEY_print_private(out, key->mldsa_key, 0, NULL) + && EVP_PKEY_print_private(out, key->sm2_key, 0, NULL); + return EVP_PKEY_print_public(out, key->mldsa_key, 0, NULL) + && EVP_PKEY_print_public(out, key->sm2_key, 0, NULL); +} +#endif + +/* ---------------------------------------------------------------------- */ + static void *key2text_newctx(void *provctx) { return provctx; @@ -938,3 +960,7 @@ MAKE_TEXT_ENCODER(rsapss, rsa); #ifndef OPENSSL_NO_ML_DSA MAKE_TEXT_ENCODER(ml_dsa_65, ml_dsa); #endif + +#ifndef OPENSSL_NO_SM2_MLDSA65_HYBRID +MAKE_TEXT_ENCODER(sm2_mldsa65_hybrid, sm2_mldsa65_hybrid); +#endif diff --git a/providers/implementations/include/prov/implementations.h b/providers/implementations/include/prov/implementations.h index 167ab38b2..815e5d0ad 100644 --- a/providers/implementations/include/prov/implementations.h +++ b/providers/implementations/include/prov/implementations.h @@ -244,6 +244,9 @@ extern const OSSL_DISPATCH ossl_sm2dh_mlkem768_hybrid_keymgmt_functions[]; #ifndef OPENSSL_NO_ML_DSA extern const OSSL_DISPATCH ossl_ml_dsa_65_keymgmt_functions[]; #endif +#ifndef OPENSSL_NO_SM2_MLDSA65_HYBRID +extern const OSSL_DISPATCH ossl_sm2_mldsa65_hybrid_keymgmt_functions[]; +#endif /* Key Exchange */ extern const OSSL_DISPATCH ossl_dh_keyexch_functions[]; @@ -273,6 +276,7 @@ extern const OSSL_DISPATCH ossl_mac_legacy_cmac_signature_functions[]; extern const OSSL_DISPATCH ossl_mac_legacy_eia3_signature_functions[]; extern const OSSL_DISPATCH ossl_sm2_signature_functions[]; extern const OSSL_DISPATCH ossl_ml_dsa_65_signature_functions[]; +extern const OSSL_DISPATCH ossl_sm2_mldsa65_hybrid_signature_functions[]; /* Asym Cipher */ extern const OSSL_DISPATCH ossl_rsa_asym_cipher_functions[]; @@ -427,6 +431,15 @@ extern const OSSL_DISPATCH ossl_ml_dsa_65_to_SubjectPublicKeyInfo_pem_encoder_fu extern const OSSL_DISPATCH ossl_ml_dsa_65_to_OSSL_current_der_encoder_functions[]; extern const OSSL_DISPATCH ossl_ml_dsa_65_to_text_encoder_functions[]; +extern const OSSL_DISPATCH ossl_sm2_mldsa65_hybrid_to_EncryptedPrivateKeyInfo_der_encoder_functions[]; +extern const OSSL_DISPATCH ossl_sm2_mldsa65_hybrid_to_EncryptedPrivateKeyInfo_pem_encoder_functions[]; +extern const OSSL_DISPATCH ossl_sm2_mldsa65_hybrid_to_PrivateKeyInfo_der_encoder_functions[]; +extern const OSSL_DISPATCH ossl_sm2_mldsa65_hybrid_to_PrivateKeyInfo_pem_encoder_functions[]; +extern const OSSL_DISPATCH ossl_sm2_mldsa65_hybrid_to_SubjectPublicKeyInfo_der_encoder_functions[]; +extern const OSSL_DISPATCH ossl_sm2_mldsa65_hybrid_to_SubjectPublicKeyInfo_pem_encoder_functions[]; +extern const OSSL_DISPATCH ossl_sm2_mldsa65_hybrid_to_OSSL_current_der_encoder_functions[]; +extern const OSSL_DISPATCH ossl_sm2_mldsa65_hybrid_to_text_encoder_functions[]; + /* Decoders */ extern const OSSL_DISPATCH ossl_PrivateKeyInfo_der_to_dh_decoder_functions[]; extern const OSSL_DISPATCH ossl_SubjectPublicKeyInfo_der_to_dh_decoder_functions[]; @@ -486,3 +499,6 @@ extern const OSSL_DISPATCH ossl_file_store_functions[]; extern const OSSL_DISPATCH ossl_PrivateKeyInfo_der_to_ml_dsa_65_decoder_functions[]; extern const OSSL_DISPATCH ossl_SubjectPublicKeyInfo_der_to_ml_dsa_65_decoder_functions[]; + +extern const OSSL_DISPATCH ossl_PrivateKeyInfo_der_to_sm2_mldsa65_hybrid_decoder_functions[]; +extern const OSSL_DISPATCH ossl_SubjectPublicKeyInfo_der_to_sm2_mldsa65_hybrid_decoder_functions[]; diff --git a/providers/implementations/include/prov/names.h b/providers/implementations/include/prov/names.h index e22c13d36..c671b4275 100644 --- a/providers/implementations/include/prov/names.h +++ b/providers/implementations/include/prov/names.h @@ -269,3 +269,5 @@ #define PROV_DESCS_SM2DH_MLKEM768_HYBRID "Hybrid KEM combining curveSM2-DH and MLKEM-768" #define PROV_NAMES_ML_DSA_65 "ML-DSA-65:MLDSA65:2.16.840.1.101.3.4.3.18:id-ml-dsa-65" #define PROV_DESCS_ML_DSA_65 "PQCRYSTALS ML-DSA-65 implementation" +#define PROV_NAMES_SM2_MLDSA65_HYBRID "SM2-MLDSA65-HYBRID" +#define PROV_DESCS_SM2_MLDSA65_HYBRID "Hybrid DSA combining curveSM2 and ML-DSA-65" diff --git a/providers/implementations/keymgmt/build.info b/providers/implementations/keymgmt/build.info index 81758f79b..7d5f5d991 100644 --- a/providers/implementations/keymgmt/build.info +++ b/providers/implementations/keymgmt/build.info @@ -9,7 +9,8 @@ $KDF_GOAL=../../libdefault.a ../../libfips.a $MAC_GOAL=../../libdefault.a ../../libfips.a $RSA_GOAL=../../libdefault.a ../../libfips.a $SM2DH_MLKEM768_GOAL=../../libdefault.a -$ML_DSA_GOAL=../../libdefault.a +$ML_DSA_GOAL=../../libdefault.a +$SM2_MLDSA65_GOAL=../../libdefault.a IF[{- !$disabled{dh} -}] SOURCE[$DH_GOAL]=dh_kmgmt.c @@ -46,6 +47,10 @@ IF[{- !$disabled{sm2} -}] ENDIF ENDIF +IF[{- !$disabled{'sm2-mldsa65-hybrid'} -}] + SOURCE[$SM2_MLDSA65_GOAL]=sm2_mldsa65_hybrid_kmgmt.c +ENDIF + SOURCE[$RSA_GOAL]=rsa_kmgmt.c SOURCE[$KDF_GOAL]=kdf_legacy_kmgmt.c diff --git a/providers/implementations/keymgmt/sm2_mldsa65_hybrid_kmgmt.c b/providers/implementations/keymgmt/sm2_mldsa65_hybrid_kmgmt.c new file mode 100644 index 000000000..4c34e261b --- /dev/null +++ b/providers/implementations/keymgmt/sm2_mldsa65_hybrid_kmgmt.c @@ -0,0 +1,484 @@ +/* + * Copyright 2025 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 +#include +#include +#include +#include +#include "prov/implementations.h" +#include "prov/providercommon.h" +#include "prov/provider_ctx.h" +#include "internal/param_build_set.h" +#include "crypto/sm2_mldsa65_hybrid.h" + +static OSSL_FUNC_keymgmt_new_fn sm2_mldsa65_hybrid_new; +static OSSL_FUNC_keymgmt_free_fn sm2_mldsa65_hybrid_free; +static OSSL_FUNC_keymgmt_load_fn sm2_mldsa65_hybrid_load; +static OSSL_FUNC_keymgmt_gen_init_fn sm2_mldsa65_hybrid_gen_init; +static OSSL_FUNC_keymgmt_gen_fn sm2_mldsa65_hybrid_gen; +static OSSL_FUNC_keymgmt_gen_set_params_fn sm2_mldsa65_hybrid_gen_set_params; +static OSSL_FUNC_keymgmt_gen_settable_params_fn sm2_mldsa65_hybrid_gen_settable_params; +static OSSL_FUNC_keymgmt_gen_cleanup_fn sm2_mldsa65_hybrid_gen_cleanup; +static OSSL_FUNC_keymgmt_settable_params_fn sm2_mldsa65_hybrid_settable_params; +static OSSL_FUNC_keymgmt_set_params_fn sm2_mldsa65_hybrid_set_params; +static OSSL_FUNC_keymgmt_gettable_params_fn sm2_mldsa65_hybrid_gettable_params; +static OSSL_FUNC_keymgmt_get_params_fn sm2_mldsa65_hybrid_get_params; +static OSSL_FUNC_keymgmt_has_fn sm2_mldsa65_hybrid_has; +static OSSL_FUNC_keymgmt_match_fn sm2_mldsa65_hybrid_match; +static OSSL_FUNC_keymgmt_import_fn sm2_mldsa65_hybrid_import; +static OSSL_FUNC_keymgmt_export_fn sm2_mldsa65_hybrid_export; +static OSSL_FUNC_keymgmt_import_types_fn sm2_mldsa65_hybrid_import_types; +static OSSL_FUNC_keymgmt_export_types_fn sm2_mldsa65_hybrid_export_types; + +static const int minimal_selection = OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS + | OSSL_KEYMGMT_SELECT_PRIVATE_KEY; + +typedef struct { + OSSL_LIB_CTX *libctx; + char *propq; + int selection; +} SM2_MLDSA65_HYBRID_GEN_CTX; + +static void *sm2_mldsa65_hybrid_new(void *provctx) +{ + OSSL_LIB_CTX *libctx; + libctx = provctx == NULL ? NULL : PROV_LIBCTX_OF(provctx); + return sm2_mldsa65_hybrid_key_new(libctx, NULL); +} + +static void sm2_mldsa65_hybrid_free(void *vkey) +{ + SM2_MLDSA65_HYBRID_KEY *key = vkey; + sm2_mldsa65_hybrid_key_free(key); +} + +static void *sm2_mldsa65_hybrid_load(const void *reference, size_t reference_sz) +{ + SM2_MLDSA65_HYBRID_KEY *key = NULL; + + if (ossl_prov_is_running() && reference_sz == sizeof(key)) { + /* The contents of the reference is the address to our object */ + key = *(SM2_MLDSA65_HYBRID_KEY **)reference; + + /* We grabbed, so we detach it */ + *(SM2_MLDSA65_HYBRID_KEY **)reference = NULL; + return key; + } + return NULL; +} + +static void *sm2_mldsa65_hybrid_gen_init(void *provctx, int selection, + const OSSL_PARAM params[]) +{ + SM2_MLDSA65_HYBRID_GEN_CTX *gctx = NULL; + + if (!ossl_prov_is_running() + || (selection & minimal_selection) == 0) + return NULL; + + if ((gctx = OPENSSL_zalloc(sizeof(*gctx))) != NULL) { + gctx->libctx = PROV_LIBCTX_OF(provctx); + gctx->selection = selection; + if (!sm2_mldsa65_hybrid_gen_set_params(gctx, params)) { + OPENSSL_free(gctx); + gctx = NULL; + } + } + return gctx; +} + +static void sm2_mldsa65_hybrid_gen_cleanup(void *genctx) +{ + SM2_MLDSA65_HYBRID_GEN_CTX *gctx = genctx; + if (gctx == NULL) + return; + + OPENSSL_free(gctx->propq); + gctx->propq = NULL; + OPENSSL_free(gctx); +} + +static void *sm2_mldsa65_hybrid_gen(void * genctx, OSSL_CALLBACK * osslcb, void * cbarg) +{ + SM2_MLDSA65_HYBRID_GEN_CTX *gctx = genctx; + SM2_MLDSA65_HYBRID_KEY *key = NULL; + char *propq = NULL; + + if (gctx == NULL || (gctx->selection & OSSL_KEYMGMT_SELECT_KEYPAIR) == + OSSL_KEYMGMT_SELECT_PUBLIC_KEY) + return NULL; + + propq = gctx->propq; + gctx->propq = NULL; + + if ((key = sm2_mldsa65_hybrid_key_new(gctx->libctx, propq)) == NULL) + return NULL; + + if ((gctx->selection & OSSL_KEYMGMT_SELECT_KEYPAIR) == 0) + return key; + + key->mldsa_key = EVP_PKEY_Q_keygen(key->libctx, key->propq, SM2_MLDSA65_HYBRID_QNAME); + key->sm2_key = EVP_PKEY_Q_keygen(key->libctx, key->propq, SM2_MLDSA65_HYBRID_TNAME); + if (key->mldsa_key == NULL || key->sm2_key == NULL) { + sm2_mldsa65_hybrid_free(key); + return NULL; + } + + key->status = SM2_MLDSA65_HYBRID_HAVE_PRVKEY; + return key; +} + +static const OSSL_PARAM sm2_mldsa65_hybrid_gen_set_params_list[] = { + OSSL_PARAM_utf8_string(OSSL_PKEY_PARAM_PROPERTIES, NULL, 0), + OSSL_PARAM_END +}; + +static const OSSL_PARAM *sm2_mldsa65_hybrid_gen_settable_params(ossl_unused void *genctx, + ossl_unused void *provctx) +{ + return sm2_mldsa65_hybrid_gen_set_params_list; +} + +static int sm2_mldsa65_hybrid_gen_set_params(void *genctx, const OSSL_PARAM params[]) +{ + SM2_MLDSA65_HYBRID_GEN_CTX *gctx = genctx; + const OSSL_PARAM *p; + + if (gctx == NULL) + return 0; + if (params == NULL) + return 1; + + if ((p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PROPERTIES)) != NULL) { + if (p->data_type != OSSL_PARAM_UTF8_STRING) + return 0; + OPENSSL_free(gctx->propq); + gctx->propq = OPENSSL_strdup(p->data); + if (gctx->propq == NULL) + return 0; + } + + return 1; +} + +static int sm2_mldsa65_hybrid_has(const void *keydata, int selection) +{ + const SM2_MLDSA65_HYBRID_KEY *key = keydata; + + if (!ossl_prov_is_running() || key == NULL) + return 0; + + if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0 + && !sm2_mldsa65_hybrid_have_prvkey(key)) + return 0; /* No private key */ + if ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0 + && !sm2_mldsa65_hybrid_have_pubkey(key)) + return 0; /* No public key */ + + return 1; +} + +static int sm2_mldsa65_hybrid_match(const void *keydata1, const void *keydata2, int selection) +{ + const SM2_MLDSA65_HYBRID_KEY *key1 = keydata1; + const SM2_MLDSA65_HYBRID_KEY *key2 = keydata2; + int have_pub1 = sm2_mldsa65_hybrid_have_pubkey(key1); + int have_pub2 = sm2_mldsa65_hybrid_have_pubkey(key2); + + if (!ossl_prov_is_running()) + return 0; + + if ((selection & OSSL_KEYMGMT_SELECT_KEYPAIR) == 0) + return 1; /* Nothing to match */ + + if (have_pub1 ^ have_pub2) + return 0; /* One has pubkey, the other does not */ + + /* As in other providers, equal when both have no key material. */ + if (!have_pub1) + return 1; + + return EVP_PKEY_eq(key1->mldsa_key, key2->mldsa_key) + && EVP_PKEY_eq(key1->sm2_key, key2->sm2_key); +} + +static int sm2_mldsa65_hybrid_import(void *keydata, int selection, const OSSL_PARAM params[]) +{ + int ret = 0; + SM2_MLDSA65_HYBRID_KEY *key = keydata; + const OSSL_PARAM *p; + uint8_t *pk = NULL, *sk = NULL; + size_t pk_len = 0, sk_len = 0; + + if (!ossl_prov_is_running() || key == NULL) + return 0; + + if ((selection & OSSL_KEYMGMT_SELECT_KEYPAIR) == 0) + return 0; + + if (sm2_mldsa65_hybrid_have_pubkey(key)) + return 0; /* Already have public key */ + + if ((p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PUB_KEY)) != NULL + && !OSSL_PARAM_get_octet_string(p, (void **)&pk, 0, &pk_len)) + goto err; + + if (selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY + && (p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PRIV_KEY)) != NULL + && !OSSL_PARAM_get_octet_string(p, (void **)&sk, 0, &sk_len)) + goto err; + + if (pk == NULL && sk == NULL) { + ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY); + goto err; /* No keys provided */ + } + + if (pk != NULL && pk_len != SM2_MLDSA65_HYBRID_PK_SIZE) + goto err; + if (sk != NULL && sk_len != SM2_MLDSA65_HYBRID_SK_SIZE) + goto err; + + if (sk != NULL) + /* Ignore public keys when private provided */ + ret = sm2_mldsa65_hybrid_priv_key_deserialize(key, sk, sk_len); + else if (pk != NULL) + /* Absent private key data, import public keys */ + ret = sm2_mldsa65_hybrid_pub_key_deserialize(key, pk, pk_len); + +err: + OPENSSL_free(pk); + OPENSSL_clear_free(sk, sk_len); + return ret; +} +static int sm2_mldsa65_hybrid_export(void *keydata, int selection, + OSSL_CALLBACK *param_cb, void *cbarg) +{ + int ret = 0; + SM2_MLDSA65_HYBRID_KEY *key = keydata; + OSSL_PARAM_BLD *tmpl; + OSSL_PARAM *params = NULL; + uint8_t *pkbuf = NULL, *skbuf = NULL; + size_t pklen = 0, sklen = 0; + + if (!ossl_prov_is_running() || key == NULL) + return 0; + + if ((selection & OSSL_KEYMGMT_SELECT_KEYPAIR) == 0) + return 0; + + if (sm2_mldsa65_hybrid_have_pubkey(key)) + return 0; + + tmpl = OSSL_PARAM_BLD_new(); + if (tmpl == NULL) + return 0; + + if ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0) { + pkbuf = OPENSSL_zalloc(SM2_MLDSA65_HYBRID_PK_SIZE); + if (pkbuf == NULL) + goto err; + } + + if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0 && + sm2_mldsa65_hybrid_have_prvkey(key)) { + skbuf = OPENSSL_zalloc(SM2_MLDSA65_HYBRID_SK_SIZE); + if (skbuf == NULL) + goto err; + } + + if (!sm2_mldsa65_hybrid_key_serialize(key, \ + pkbuf, SM2_MLDSA65_HYBRID_PK_SIZE, &pklen, skbuf, SM2_MLDSA65_HYBRID_SK_SIZE, &sklen)) + goto err; + + if (pkbuf != NULL && !ossl_param_build_set_octet_string( + tmpl, NULL, OSSL_PKEY_PARAM_PUB_KEY, pkbuf, pklen)) + goto err; + + if (skbuf != NULL && !ossl_param_build_set_octet_string( + tmpl, NULL, OSSL_PKEY_PARAM_PRIV_KEY, skbuf, sklen)) + goto err; + + params = OSSL_PARAM_BLD_to_param(tmpl); + if (params == NULL) + goto err; + + ret = param_cb(params, cbarg); + OSSL_PARAM_free(params); +err: + OPENSSL_free(pkbuf); + OPENSSL_clear_free(skbuf, SM2_MLDSA65_HYBRID_SK_SIZE); + OSSL_PARAM_BLD_free(tmpl); + return ret; +} + +static const OSSL_PARAM sm2_mldsa65_hybrid_import_type_params_list[] = { + OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, NULL, 0), + OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, NULL, 0), + OSSL_PARAM_END +}; + +static const OSSL_PARAM *sm2_mldsa65_hybrid_import_types(int selection) +{ + if ((selection & OSSL_KEYMGMT_SELECT_KEYPAIR) == 0) + return NULL; + return sm2_mldsa65_hybrid_import_type_params_list; +} + +static const OSSL_PARAM *sm2_mldsa65_hybrid_export_types(int selection) +{ + return sm2_mldsa65_hybrid_import_types(selection); +} + +static const OSSL_PARAM sm2_mldsa65_hybrid_set_params_list[] = { + OSSL_PARAM_utf8_string(OSSL_PKEY_PARAM_PROPERTIES, NULL, 0), + OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY, NULL, 0), + OSSL_PARAM_END +}; + +static const OSSL_PARAM *sm2_mldsa65_hybrid_settable_params(void *provctx) +{ + return sm2_mldsa65_hybrid_set_params_list; +} + +static int sm2_mldsa65_hybrid_set_params(void *keydata, const OSSL_PARAM params[]) +{ + SM2_MLDSA65_HYBRID_KEY *key = keydata; + const OSSL_PARAM *p; + const uint8_t *pkbuf = NULL; + size_t pklen = 0; + + if (key == NULL) + return 0; + + if (params == NULL) + return 1; + + if ((p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY)) != NULL) { + if (sm2_mldsa65_hybrid_have_pubkey(key)) + return 0; + if (!OSSL_PARAM_get_octet_string_ptr(p, (const void**)&pkbuf, &pklen)) + return 0; + if (pklen != SM2_MLDSA65_HYBRID_PK_SIZE || + !sm2_mldsa65_hybrid_pub_key_deserialize(key, pkbuf, pklen)) + return 0; + } + + if ((p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PROPERTIES)) != NULL) { + OPENSSL_free(key->propq); + key->propq = NULL; + if (!OSSL_PARAM_get_utf8_string(p, &key->propq, 0)) + return 0; + } + + return 1; +} + +static const OSSL_PARAM sm2_mldsa65_hybrid_get_params_list[] = { + OSSL_PARAM_int(OSSL_PKEY_PARAM_BITS, NULL), + OSSL_PARAM_int(OSSL_PKEY_PARAM_SECURITY_BITS, NULL), + OSSL_PARAM_int(OSSL_PKEY_PARAM_MAX_SIZE, NULL), + OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, NULL, 0), + OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, NULL, 0), + OSSL_PARAM_END +}; + +static const OSSL_PARAM *sm2_mldsa65_hybrid_gettable_params(void *provctx) +{ + return sm2_mldsa65_hybrid_get_params_list; +} + +static int sm2_mldsa65_hybrid_get_params(void *keydata, OSSL_PARAM params[]) +{ + int ret = 0; + SM2_MLDSA65_HYBRID_KEY *key = keydata; + OSSL_PARAM *p; + uint8_t *pkbuf = NULL, *skbuf = NULL; + size_t pklen = 0, sklen = 0; + int sz_sm2 = 0, sz_mldsa = 0; + + if (params == NULL) + return 1; + + if ((p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_BITS)) != NULL) { + if (!OSSL_PARAM_set_int(p, ML_DSA_PUBLICKEYBYTES * 8)) + return 0; + } + + if ((p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_SECURITY_BITS)) != NULL) { + if (!OSSL_PARAM_set_int(p, 192)) + return 0; + } + + if ((p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_MAX_SIZE)) != NULL) { + if (!EVP_PKEY_get_int_param(key->sm2_key, OSSL_PKEY_PARAM_MAX_SIZE, &sz_sm2) + || !EVP_PKEY_get_int_param(key->mldsa_key, OSSL_PKEY_PARAM_MAX_SIZE, &sz_mldsa) + || !OSSL_PARAM_set_int(p, sz_mldsa + sz_sm2)) + return 0; + } + + if ((p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PUB_KEY)) != NULL) { + pkbuf = OPENSSL_zalloc(SM2_MLDSA65_HYBRID_PK_SIZE); + if (pkbuf == NULL) + goto err; + } + + if ((p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PRIV_KEY)) != NULL) { + skbuf = OPENSSL_zalloc(SM2_MLDSA65_HYBRID_SK_SIZE); + if (skbuf == NULL) + goto err; + } + + if (pkbuf == NULL && skbuf == NULL) + return 1; /* Nothing to get */ + + if (!sm2_mldsa65_hybrid_key_serialize(key, + pkbuf, SM2_MLDSA65_HYBRID_PK_SIZE, &pklen, skbuf, SM2_MLDSA65_HYBRID_SK_SIZE, &sklen)) + goto err; + if (pkbuf != NULL && + (p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PUB_KEY)) != NULL && + !OSSL_PARAM_set_octet_string(p, pkbuf, pklen)) + goto err; + if (skbuf != NULL && + (p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PRIV_KEY)) != NULL && + !OSSL_PARAM_set_octet_string(p, skbuf, sklen)) + goto err; + + ret = 1; +err: + OPENSSL_free(pkbuf); + OPENSSL_clear_free(skbuf, SM2_MLDSA65_HYBRID_SK_SIZE); + return ret; +} + +const OSSL_DISPATCH ossl_sm2_mldsa65_hybrid_keymgmt_functions[] = { + { OSSL_FUNC_KEYMGMT_NEW, (void (*)(void))sm2_mldsa65_hybrid_new }, + { OSSL_FUNC_KEYMGMT_FREE, (void (*)(void))sm2_mldsa65_hybrid_free }, + { OSSL_FUNC_KEYMGMT_LOAD, (void (*)(void))sm2_mldsa65_hybrid_load }, + { OSSL_FUNC_KEYMGMT_SETTABLE_PARAMS, (void (*)(void))sm2_mldsa65_hybrid_settable_params }, + { OSSL_FUNC_KEYMGMT_SET_PARAMS, (void (*)(void))sm2_mldsa65_hybrid_set_params }, + { OSSL_FUNC_KEYMGMT_GETTABLE_PARAMS, (void (*)(void))sm2_mldsa65_hybrid_gettable_params }, + { OSSL_FUNC_KEYMGMT_GET_PARAMS, (void (*)(void))sm2_mldsa65_hybrid_get_params }, + { OSSL_FUNC_KEYMGMT_HAS, (void (*)(void))sm2_mldsa65_hybrid_has }, + { OSSL_FUNC_KEYMGMT_MATCH, (void (*)(void))sm2_mldsa65_hybrid_match }, + { OSSL_FUNC_KEYMGMT_IMPORT, (void (*)(void))sm2_mldsa65_hybrid_import }, + { OSSL_FUNC_KEYMGMT_EXPORT, (void (*)(void))sm2_mldsa65_hybrid_export }, + { OSSL_FUNC_KEYMGMT_IMPORT_TYPES, (void (*)(void))sm2_mldsa65_hybrid_import_types }, + { OSSL_FUNC_KEYMGMT_EXPORT_TYPES, (void (*)(void))sm2_mldsa65_hybrid_export_types }, + { OSSL_FUNC_KEYMGMT_GEN_INIT, (void (*)(void))sm2_mldsa65_hybrid_gen_init }, + { OSSL_FUNC_KEYMGMT_GEN, (void (*)(void))sm2_mldsa65_hybrid_gen }, + { OSSL_FUNC_KEYMGMT_GEN_SET_PARAMS, (void (*)(void))sm2_mldsa65_hybrid_gen_set_params }, + { OSSL_FUNC_KEYMGMT_GEN_SETTABLE_PARAMS, (void (*)(void))sm2_mldsa65_hybrid_gen_settable_params }, + { OSSL_FUNC_KEYMGMT_GEN_CLEANUP, (void (*)(void))sm2_mldsa65_hybrid_gen_cleanup }, + { 0, NULL } +}; diff --git a/providers/implementations/signature/build.info b/providers/implementations/signature/build.info index dccc112bb..ae37d07e4 100644 --- a/providers/implementations/signature/build.info +++ b/providers/implementations/signature/build.info @@ -7,6 +7,7 @@ $MAC_GOAL=../../libdefault.a ../../libfips.a $RSA_GOAL=../../libdefault.a ../../libfips.a $SM2_GOAL=../../libdefault.a $ML_DSA_GOAL=../../libdefault.a +$SM2_MLDSA65_GOAL=../../libdefault.a IF[{- !$disabled{dsa} -}] SOURCE[$DSA_GOAL]=dsa_sig.c @@ -24,6 +25,10 @@ IF[{- !$disabled{ml_dsa} -}] SOURCE[$ML_DSA_GOAL]=ml_dsa_sig.c ENDIF +IF[{- !$disabled{'sm2-mldsa65-hybrid'} -}] + SOURCE[$SM2_MLDSA65_GOAL]=sm2_mldsa65_hybrid_sig.c +ENDIF + SOURCE[$RSA_GOAL]=rsa_sig.c DEPEND[rsa_sig.o]=../../common/include/prov/der_rsa.h diff --git a/providers/implementations/signature/sm2_mldsa65_hybrid_sig.c b/providers/implementations/signature/sm2_mldsa65_hybrid_sig.c new file mode 100644 index 000000000..0c7380038 --- /dev/null +++ b/providers/implementations/signature/sm2_mldsa65_hybrid_sig.c @@ -0,0 +1,612 @@ +/* + * Copyright 2025 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 +#include +#include +#include "internal/sizes.h" +#include "prov/implementations.h" +#include "prov/providercommon.h" +#include "prov/provider_ctx.h" + +#include "crypto/sm2_mldsa65_hybrid.h" +#include "prov/der_sm2_mldsa65_hybrid.h" + +static OSSL_FUNC_signature_newctx_fn sm2_mldsa65_hybrid_newctx; +static OSSL_FUNC_signature_sign_init_fn sm2_mldsa65_hybrid_signverify_init; +static OSSL_FUNC_signature_verify_init_fn sm2_mldsa65_hybrid_signverify_init; +static OSSL_FUNC_signature_sign_fn sm2_mldsa65_hybrid_sign; +static OSSL_FUNC_signature_verify_fn sm2_mldsa65_hybrid_verify; +static OSSL_FUNC_signature_digest_sign_init_fn sm2_mldsa65_hybrid_digest_signverify_init; +static OSSL_FUNC_signature_digest_sign_update_fn sm2_mldsa65_hybrid_digest_signverify_update; +static OSSL_FUNC_signature_digest_sign_final_fn sm2_mldsa65_hybrid_digest_sign_final; +static OSSL_FUNC_signature_digest_verify_init_fn sm2_mldsa65_hybrid_digest_signverify_init; +static OSSL_FUNC_signature_digest_verify_update_fn sm2_mldsa65_hybrid_digest_signverify_update; +static OSSL_FUNC_signature_digest_verify_final_fn sm2_mldsa65_hybrid_digest_verify_final; +static OSSL_FUNC_signature_freectx_fn sm2_mldsa65_hybrid_freectx; +static OSSL_FUNC_signature_dupctx_fn sm2_mldsa65_hybrid_dupctx; +static OSSL_FUNC_signature_get_ctx_params_fn sm2_mldsa65_hybrid_get_ctx_params; +static OSSL_FUNC_signature_gettable_ctx_params_fn sm2_mldsa65_hybrid_gettable_ctx_params; +static OSSL_FUNC_signature_set_ctx_params_fn sm2_mldsa65_hybrid_set_ctx_params; +static OSSL_FUNC_signature_settable_ctx_params_fn sm2_mldsa65_hybrid_settable_ctx_params; +static OSSL_FUNC_signature_get_ctx_md_params_fn sm2_mldsa65_hybrid_get_ctx_md_params; +static OSSL_FUNC_signature_gettable_ctx_md_params_fn sm2_mldsa65_hybrid_gettable_ctx_md_params; +static OSSL_FUNC_signature_set_ctx_md_params_fn sm2_mldsa65_hybrid_set_ctx_md_params; +static OSSL_FUNC_signature_settable_ctx_md_params_fn sm2_mldsa65_hybrid_settable_ctx_md_params; + +static const uint8_t SM2_MLDSA65_HYBRID_PREFIX[SM2_MLDSA65_HYBRID_PREFIX_SIZE + 1] = "CompositeAlgorithmSignatures2025"; +static const uint8_t SM2_MLDSA65_HYBRID_DOMAIN[SM2_MLDSA65_HYBRID_DOMAIN_SIZE] = { + 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x15 +}; + +typedef struct { + OSSL_LIB_CTX *libctx; + char *propq; + SM2_MLDSA65_HYBRID_KEY *key; + + uint8_t context_string[SM2_MLDSA65_HYBRID_MAX_CONTEXT_STRING_BYTES]; + size_t context_string_len; + + char mdname[OSSL_MAX_NAME_SIZE]; + + /* The Algorithm Identifier of the combined signature algorithm */ + unsigned char aid_buf[OSSL_MAX_ALGORITHM_ID_SIZE]; + unsigned char *aid; + size_t aid_len; + + /* main digest */ + EVP_MD *md; + EVP_MD_CTX *mdctx; + size_t mdsize; + + /* SM2 ID used for calculating the Z value */ + unsigned char *id; + size_t id_len; +} PROV_SM2_MLDSA65_HYBRID_CTX; + +static void *sm2_mldsa65_hybrid_newctx(void *provctx, const char *propq) +{ + PROV_SM2_MLDSA65_HYBRID_CTX *ctx; + + if (!ossl_prov_is_running()) + return NULL; + + ctx = OPENSSL_zalloc(sizeof(PROV_SM2_MLDSA65_HYBRID_CTX)); + if (ctx == NULL) + return NULL; + + ctx->libctx = PROV_LIBCTX_OF(provctx); + if (propq != NULL && (ctx->propq = OPENSSL_strdup(propq)) == NULL) { + OPENSSL_free(ctx); + ERR_raise(ERR_LIB_PROV, ERR_R_MALLOC_FAILURE); + return NULL; + } + + ctx->mdsize = SM3_DIGEST_LENGTH; + strcpy(ctx->mdname, OSSL_DIGEST_NAME_SM3); + return ctx; +} + +static void sm2_mldsa65_hybrid_freectx(void *vctx) +{ + PROV_SM2_MLDSA65_HYBRID_CTX *ctx = (PROV_SM2_MLDSA65_HYBRID_CTX *)vctx; + + EVP_MD_CTX_free(ctx->mdctx); + EVP_MD_free(ctx->md); + ctx->mdctx = NULL; + ctx->md = NULL; + OPENSSL_cleanse(ctx->context_string, sizeof(ctx->context_string)); + OPENSSL_free(ctx); +} + +static void *sm2_mldsa65_hybrid_dupctx(void *vctx) +{ + PROV_SM2_MLDSA65_HYBRID_CTX *srcctx = (PROV_SM2_MLDSA65_HYBRID_CTX *)vctx; + PROV_SM2_MLDSA65_HYBRID_CTX *dstctx; + + dstctx = OPENSSL_memdup(srcctx, sizeof(*srcctx)); + if (dstctx == NULL) + return NULL; + + if (srcctx->md != NULL && !EVP_MD_up_ref(srcctx->md)) + goto err; + dstctx->md = srcctx->md; + + if (srcctx->mdctx != NULL) { + dstctx->mdctx = EVP_MD_CTX_new(); + if (dstctx->mdctx == NULL + || !EVP_MD_CTX_copy_ex(dstctx->mdctx, srcctx->mdctx)) + goto err; + } + + if (srcctx->id != NULL) { + dstctx->id = OPENSSL_malloc(srcctx->id_len); + if (dstctx->id == NULL) + goto err; + dstctx->id_len = srcctx->id_len; + memcpy(dstctx->id, srcctx->id, srcctx->id_len); + } + + return dstctx; + err: + sm2_mldsa65_hybrid_freectx(dstctx); + return NULL; +} + +static int sm2_mldsa65_hybrid_signverify_init(void *vctx, void *vkey, + const OSSL_PARAM params[]) +{ + PROV_SM2_MLDSA65_HYBRID_CTX *ctx = (PROV_SM2_MLDSA65_HYBRID_CTX *)vctx; + + if (!ossl_prov_is_running() + || ctx == NULL) + return 0; + + if (vkey == NULL && ctx->key == NULL) { + ERR_raise(ERR_LIB_PROV, PROV_R_NO_KEY_SET); + return 0; + } + + if (vkey != NULL) { + ctx->key = vkey; + } + + return sm2_mldsa65_hybrid_set_ctx_params(ctx, params); +} + +static int evp_signverify_common(int operation, EVP_PKEY *key, const EVP_MD *md, + uint8_t *sig, size_t *siglen, size_t sigsize, + const uint8_t *context, size_t contextlen, + const uint8_t *id, size_t idlen, + const uint8_t *rnd, size_t rndlen, + const uint8_t *tbs, size_t tbslen) +{ + int ret = 0; + EVP_MD_CTX *mctx = NULL; + EVP_PKEY_CTX *pctx = NULL; + OSSL_PARAM params[2], *p = params; + + if ((mctx = EVP_MD_CTX_new()) == NULL) + goto err; + + if (operation == EVP_PKEY_OP_SIGN) { + if (!EVP_DigestSignInit(mctx, &pctx, md, NULL, key)) + goto err; + } else { + if (!EVP_DigestVerifyInit(mctx, &pctx, md, NULL, key)) + goto err; + } + + if (EVP_PKEY_get_id(key) == SM2_MLDSA65_HYBRID_QID) { + *p++ = OSSL_PARAM_construct_octet_string(OSSL_SIGNATURE_PARAM_CONTEXT_STRING, \ + (void*)SM2_MLDSA65_HYBRID_DOMAIN, SM2_MLDSA65_HYBRID_DOMAIN_SIZE); + *p = OSSL_PARAM_construct_end(); + if (!EVP_PKEY_CTX_set_params(pctx, params)) + goto err; + } else if (EVP_PKEY_get_id(key) == EVP_PKEY_SM2) { + if (id != NULL && EVP_PKEY_CTX_set1_id(pctx, id, idlen) <= 0) + goto err; + } + + /* m' = prefix | domain(OID) | len(ctx) | ctx | rnd | H(m) */ + if (!EVP_DigestUpdate(mctx, SM2_MLDSA65_HYBRID_PREFIX, SM2_MLDSA65_HYBRID_PREFIX_SIZE) + || !EVP_DigestUpdate(mctx, SM2_MLDSA65_HYBRID_DOMAIN, SM2_MLDSA65_HYBRID_DOMAIN_SIZE) + || !EVP_DigestUpdate(mctx, &contextlen, 1) + || !EVP_DigestUpdate(mctx, context, contextlen) + || !EVP_DigestUpdate(mctx, rnd, rndlen) + || !EVP_DigestUpdate(mctx, tbs, tbslen)) + goto err; + + if (operation == EVP_PKEY_OP_SIGN) { + if (!EVP_DigestSignFinal(mctx, sig, siglen)) + goto err; + } else { + if (EVP_DigestVerifyFinal(mctx, sig, sigsize) <= 0) + goto err; + } + + ret = 1; +err: + EVP_MD_CTX_free(mctx); + return ret; +} + +static int sm2_mldsa65_hybrid_sign(void *vctx, unsigned char *sig, size_t *siglen, + size_t sigsize, const unsigned char *tbs, size_t tbslen) +{ + int ret_mldsa = 0, ret_sm2 = 0; + size_t sl_mldsa = MLDSA_SIG_SIZE, sl_sm2 = SM2_SIG_SIZE; + PROV_SM2_MLDSA65_HYBRID_CTX *ctx = (PROV_SM2_MLDSA65_HYBRID_CTX *)vctx; + size_t hybrid_size = SM2_MLDSA65_HYBRID_SIG_SIZE; + uint8_t *rnd = NULL, *mldsa_sig = NULL, *sm2_sig = NULL; + + if (sig == NULL) { + *siglen = hybrid_size; + return 1; + } + + if (sigsize < hybrid_size) { + ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_SIGNATURE_SIZE, + "is %zu, should be at least %zu", sigsize, *siglen); + return 0; + } + + if (ctx->mdsize != 0 && tbslen != ctx->mdsize) + return 0; + + /* Composite Signature: rnd | mldsa_sig | sm2_sig */ + rnd = sig; + mldsa_sig = rnd + SM2_MLDSA65_HYBRID_RANDOM_BYTES; + sm2_sig = mldsa_sig + MLDSA_SIG_SIZE; + + if (!RAND_bytes(rnd, SM2_MLDSA65_HYBRID_RANDOM_BYTES)) + return 0; + + /* mldsa sign */ + ret_mldsa = evp_signverify_common(EVP_PKEY_OP_SIGN, ctx->key->mldsa_key, NULL, + mldsa_sig, &sl_mldsa, MLDSA_SIG_SIZE, + ctx->context_string, ctx->context_string_len, + ctx->id, ctx->id_len, + rnd, SM2_MLDSA65_HYBRID_RANDOM_BYTES, tbs, tbslen); + + /* sm2 sign */ + ret_sm2 = evp_signverify_common(EVP_PKEY_OP_SIGN, ctx->key->sm2_key, ctx->md, + sm2_sig, &sl_sm2, SM2_SIG_SIZE, + ctx->context_string, ctx->context_string_len, + ctx->id, ctx->id_len, + rnd, SM2_MLDSA65_HYBRID_RANDOM_BYTES, tbs, tbslen); + + if (!ret_mldsa || !ret_sm2) + return 0; + + *siglen = SM2_MLDSA65_HYBRID_RANDOM_BYTES + sl_mldsa + sl_sm2; + + return 1; +} + +static int sm2_mldsa65_hybrid_verify(void *vctx, const unsigned char *sig, size_t siglen, + const unsigned char *tbs, size_t tbslen) +{ + PROV_SM2_MLDSA65_HYBRID_CTX *ctx = (PROV_SM2_MLDSA65_HYBRID_CTX *)vctx; + uint8_t *rnd = NULL, *mldsa_sig = NULL, *sm2_sig = NULL; + + if (ctx->mdsize != 0 && tbslen != ctx->mdsize) + return 0; + + /* Composite Signature: rnd | mldsa_sig | sm2_sig */ + rnd = (uint8_t*)sig; + mldsa_sig = rnd + SM2_MLDSA65_HYBRID_RANDOM_BYTES; + sm2_sig = mldsa_sig + MLDSA_SIG_SIZE; + + /* mldsa verify */ + if (!evp_signverify_common(EVP_PKEY_OP_VERIFY, ctx->key->mldsa_key, NULL, + mldsa_sig, NULL, MLDSA_SIG_SIZE, + ctx->context_string, ctx->context_string_len, + ctx->id, ctx->id_len, + rnd, SM2_MLDSA65_HYBRID_RANDOM_BYTES, tbs, tbslen)) + return 0; + + /* sm2 verify */ + return evp_signverify_common(EVP_PKEY_OP_VERIFY, ctx->key->sm2_key, ctx->md, + sm2_sig, NULL, siglen - SM2_MLDSA65_HYBRID_RANDOM_BYTES - MLDSA_SIG_SIZE, + ctx->context_string, ctx->context_string_len, + ctx->id, ctx->id_len, + rnd, SM2_MLDSA65_HYBRID_RANDOM_BYTES, tbs, tbslen); +} + +static int sm2_mldsa65_hybrid_set_mdname(PROV_SM2_MLDSA65_HYBRID_CTX *ctx, const char *mdname) +{ + if (ctx->md == NULL) /* We need an SM3 md to compare with */ + ctx->md = EVP_MD_fetch(ctx->libctx, ctx->mdname, + ctx->propq); + if (ctx->md == NULL) + return 0; + + if (mdname == NULL) + return 1; + + if (strlen(mdname) >= sizeof(ctx->mdname) + || !EVP_MD_is_a(ctx->md, mdname)) { + ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_DIGEST, "digest=%s", + mdname); + return 0; + } + + OPENSSL_strlcpy(ctx->mdname, mdname, sizeof(ctx->mdname)); + return 1; +} + +static int sm2_mldsa65_hybrid_digest_signverify_init(void *vctx, const char *mdname, + void *vkey, const OSSL_PARAM params[]) +{ + PROV_SM2_MLDSA65_HYBRID_CTX *ctx = (PROV_SM2_MLDSA65_HYBRID_CTX *)vctx; + int md_nid; + WPACKET pkt; + int ret = 0; + + if (!sm2_mldsa65_hybrid_signverify_init(vctx, vkey, params) + || !sm2_mldsa65_hybrid_set_mdname(ctx, mdname)) + return ret; + + if (ctx->mdctx == NULL) { + ctx->mdctx = EVP_MD_CTX_new(); + if (ctx->mdctx == NULL) + goto error; + } + + md_nid = EVP_MD_get_type(ctx->md); + + /* + * We do not care about DER writing errors. + * All it really means is that for some reason, there's no + * AlgorithmIdentifier to be had, but the operation itself is + * still valid, just as long as it's not used to construct + * anything that needs an AlgorithmIdentifier. + */ + ctx->aid_len = 0; + if (WPACKET_init_der(&pkt, ctx->aid_buf, sizeof(ctx->aid_buf)) + && ossl_DER_w_algorithmIdentifier_SM2_MLDSA65_HYBRID_with_MD(&pkt, -1, ctx->key, md_nid) + && WPACKET_finish(&pkt)) { + WPACKET_get_total_written(&pkt, &ctx->aid_len); + ctx->aid = WPACKET_get_curr(&pkt); + } + WPACKET_cleanup(&pkt); + + if (!EVP_DigestInit_ex2(ctx->mdctx, ctx->md, params)) + goto error; + + ret = 1; + + error: + return ret; +} + +int sm2_mldsa65_hybrid_digest_signverify_update(void *vctx, const unsigned char *data, + size_t datalen) +{ + PROV_SM2_MLDSA65_HYBRID_CTX *ctx = (PROV_SM2_MLDSA65_HYBRID_CTX *)vctx; + + if (ctx == NULL || ctx->mdctx == NULL) + return 0; + + return EVP_DigestUpdate(ctx->mdctx, data, datalen); +} + +int sm2_mldsa65_hybrid_digest_sign_final(void *vctx, unsigned char *sig, size_t *siglen, + size_t sigsize) +{ + PROV_SM2_MLDSA65_HYBRID_CTX *ctx = (PROV_SM2_MLDSA65_HYBRID_CTX *)vctx; + unsigned char digest[EVP_MAX_MD_SIZE]; + unsigned int dlen = 0; + + if (ctx == NULL || ctx->mdctx == NULL) + return 0; + + /* + * If sig is NULL then we're just finding out the sig size. Other fields + * are ignored. Defer to sm2_mldsa65_hybrid_sign. + */ + if (sig != NULL) { + if (!EVP_DigestFinal_ex(ctx->mdctx, digest, &dlen)) + return 0; + } + + return sm2_mldsa65_hybrid_sign(vctx, sig, siglen, sigsize, digest, (size_t)dlen); +} + +int sm2_mldsa65_hybrid_digest_verify_final(void *vctx, const unsigned char *sig, + size_t siglen) +{ + PROV_SM2_MLDSA65_HYBRID_CTX *ctx = (PROV_SM2_MLDSA65_HYBRID_CTX *)vctx; + unsigned char digest[EVP_MAX_MD_SIZE]; + unsigned int dlen = 0; + + if (ctx == NULL || ctx->mdctx == NULL + || EVP_MD_get_size(ctx->md) > (int)sizeof(digest)) + return 0; + + if (!EVP_DigestFinal_ex(ctx->mdctx, digest, &dlen)) + return 0; + + return sm2_mldsa65_hybrid_verify(vctx, sig, siglen, digest, (size_t)dlen); +} + +static int sm2_mldsa65_hybrid_get_ctx_params(void *vctx, OSSL_PARAM *params) +{ + PROV_SM2_MLDSA65_HYBRID_CTX *ctx = (PROV_SM2_MLDSA65_HYBRID_CTX *)vctx; + OSSL_PARAM *p; + + if (ctx == NULL) + return 0; + + p = OSSL_PARAM_locate(params, OSSL_SIGNATURE_PARAM_ALGORITHM_ID); + if (p != NULL + && !OSSL_PARAM_set_octet_string(p, ctx->aid, ctx->aid_len)) + return 0; + + p = OSSL_PARAM_locate(params, OSSL_SIGNATURE_PARAM_DIGEST_SIZE); + if (p != NULL && !OSSL_PARAM_set_size_t(p, ctx->mdsize)) + return 0; + + p = OSSL_PARAM_locate(params, OSSL_SIGNATURE_PARAM_DIGEST); + if (p != NULL && !OSSL_PARAM_set_utf8_string(p, ctx->md == NULL + ? ctx->mdname + : EVP_MD_get0_name(ctx->md))) + return 0; + + return 1; +} + +static const OSSL_PARAM known_gettable_ctx_params[] = { + OSSL_PARAM_octet_string(OSSL_SIGNATURE_PARAM_ALGORITHM_ID, NULL, 0), + OSSL_PARAM_size_t(OSSL_SIGNATURE_PARAM_DIGEST_SIZE, NULL), + OSSL_PARAM_utf8_string(OSSL_SIGNATURE_PARAM_DIGEST, NULL, 0), + OSSL_PARAM_END +}; + +static const OSSL_PARAM *sm2_mldsa65_hybrid_gettable_ctx_params(ossl_unused void *vctx, + ossl_unused void *provctx) +{ + return known_gettable_ctx_params; +} + +static int sm2_mldsa65_hybrid_set_ctx_params(void *vctx, const OSSL_PARAM params[]) +{ + PROV_SM2_MLDSA65_HYBRID_CTX *ctx = (PROV_SM2_MLDSA65_HYBRID_CTX *)vctx; + const OSSL_PARAM *p; + size_t mdsize; + + if (ctx == NULL) + return 0; + if (params == NULL) + return 1; + + p = OSSL_PARAM_locate_const(params, OSSL_SIGNATURE_PARAM_CONTEXT_STRING); + if (p != NULL) { + void *vp = ctx->context_string; + if (!OSSL_PARAM_get_octet_string(p, &vp, sizeof(ctx->context_string), + &(ctx->context_string_len))) { + ctx->context_string_len = 0; + return 0; + } + } + + /* + * The following code checks that the size is the same as the SM3 digest + * size returning an error otherwise. + * If there is ever any different digest algorithm allowed with SM2 + * this needs to be adjusted accordingly. + */ + p = OSSL_PARAM_locate_const(params, OSSL_SIGNATURE_PARAM_DIGEST_SIZE); + if (p != NULL && (!OSSL_PARAM_get_size_t(p, &mdsize) + || mdsize != ctx->mdsize)) + return 0; + + p = OSSL_PARAM_locate_const(params, OSSL_SIGNATURE_PARAM_DIGEST); + if (p != NULL) { + char *mdname = NULL; + + if (!OSSL_PARAM_get_utf8_string(p, &mdname, 0)) + return 0; + if (!sm2_mldsa65_hybrid_set_mdname(ctx, mdname)) { + OPENSSL_free(mdname); + return 0; + } + OPENSSL_free(mdname); + } + + p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_DIST_ID); + if (p != NULL) { + void *tmp_id = NULL; + size_t tmp_idlen = 0; + + if (p->data_size != 0 + && !OSSL_PARAM_get_octet_string(p, &tmp_id, 0, &tmp_idlen)) + return 0; + OPENSSL_free(ctx->id); + ctx->id = tmp_id; + ctx->id_len = tmp_idlen; + } + + return 1; +} + +static const OSSL_PARAM known_settable_ctx_params[] = { + OSSL_PARAM_octet_string(OSSL_SIGNATURE_PARAM_CONTEXT_STRING, NULL, 0), + OSSL_PARAM_size_t(OSSL_SIGNATURE_PARAM_DIGEST_SIZE, NULL), + OSSL_PARAM_utf8_string(OSSL_SIGNATURE_PARAM_DIGEST, NULL, 0), + OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_DIST_ID, NULL, 0), + OSSL_PARAM_END +}; + +static const OSSL_PARAM *sm2_mldsa65_hybrid_settable_ctx_params(ossl_unused void *vctx, + ossl_unused void *provctx) +{ + return known_settable_ctx_params; +} + +static int sm2_mldsa65_hybrid_get_ctx_md_params(void *vctx, OSSL_PARAM *params) +{ + PROV_SM2_MLDSA65_HYBRID_CTX *ctx = (PROV_SM2_MLDSA65_HYBRID_CTX *)vctx; + + if (ctx->mdctx == NULL) + return 0; + + return EVP_MD_CTX_get_params(ctx->mdctx, params); +} + +static const OSSL_PARAM *sm2_mldsa65_hybrid_gettable_ctx_md_params(void *vctx) +{ + PROV_SM2_MLDSA65_HYBRID_CTX *ctx = (PROV_SM2_MLDSA65_HYBRID_CTX *)vctx; + + if (ctx->md == NULL) + return 0; + + return EVP_MD_gettable_ctx_params(ctx->md); +} + +static int sm2_mldsa65_hybrid_set_ctx_md_params(void *vctx, const OSSL_PARAM params[]) +{ + PROV_SM2_MLDSA65_HYBRID_CTX *ctx = (PROV_SM2_MLDSA65_HYBRID_CTX *)vctx; + + if (ctx->mdctx == NULL) + return 0; + + return EVP_MD_CTX_set_params(ctx->mdctx, params); +} + +static const OSSL_PARAM *sm2_mldsa65_hybrid_settable_ctx_md_params(void *vctx) +{ + PROV_SM2_MLDSA65_HYBRID_CTX *ctx = (PROV_SM2_MLDSA65_HYBRID_CTX *)vctx; + + if (ctx->md == NULL) + return 0; + + return EVP_MD_settable_ctx_params(ctx->md); +} + +const OSSL_DISPATCH ossl_sm2_mldsa65_hybrid_signature_functions[] = { + { OSSL_FUNC_SIGNATURE_NEWCTX, (void (*)(void))sm2_mldsa65_hybrid_newctx }, + { OSSL_FUNC_SIGNATURE_SIGN_INIT, (void (*)(void))sm2_mldsa65_hybrid_signverify_init }, + { OSSL_FUNC_SIGNATURE_SIGN, (void (*)(void))sm2_mldsa65_hybrid_sign }, + { OSSL_FUNC_SIGNATURE_VERIFY_INIT, (void (*)(void))sm2_mldsa65_hybrid_signverify_init }, + { OSSL_FUNC_SIGNATURE_VERIFY, (void (*)(void))sm2_mldsa65_hybrid_verify }, + { OSSL_FUNC_SIGNATURE_DIGEST_SIGN_INIT, + (void (*)(void))sm2_mldsa65_hybrid_digest_signverify_init }, + { OSSL_FUNC_SIGNATURE_DIGEST_SIGN_UPDATE, + (void (*)(void))sm2_mldsa65_hybrid_digest_signverify_update }, + { OSSL_FUNC_SIGNATURE_DIGEST_SIGN_FINAL, + (void (*)(void))sm2_mldsa65_hybrid_digest_sign_final }, + { OSSL_FUNC_SIGNATURE_DIGEST_VERIFY_INIT, + (void (*)(void))sm2_mldsa65_hybrid_digest_signverify_init }, + { OSSL_FUNC_SIGNATURE_DIGEST_VERIFY_UPDATE, + (void (*)(void))sm2_mldsa65_hybrid_digest_signverify_update }, + { OSSL_FUNC_SIGNATURE_DIGEST_VERIFY_FINAL, + (void (*)(void))sm2_mldsa65_hybrid_digest_verify_final }, + { OSSL_FUNC_SIGNATURE_FREECTX, (void (*)(void))sm2_mldsa65_hybrid_freectx }, + { OSSL_FUNC_SIGNATURE_DUPCTX, (void (*)(void))sm2_mldsa65_hybrid_dupctx }, + { OSSL_FUNC_SIGNATURE_GET_CTX_PARAMS, (void (*)(void))sm2_mldsa65_hybrid_get_ctx_params }, + { OSSL_FUNC_SIGNATURE_GETTABLE_CTX_PARAMS, + (void (*)(void))sm2_mldsa65_hybrid_gettable_ctx_params }, + { OSSL_FUNC_SIGNATURE_SET_CTX_PARAMS, (void (*)(void))sm2_mldsa65_hybrid_set_ctx_params }, + { OSSL_FUNC_SIGNATURE_SETTABLE_CTX_PARAMS, + (void (*)(void))sm2_mldsa65_hybrid_settable_ctx_params }, + { OSSL_FUNC_SIGNATURE_GET_CTX_MD_PARAMS, + (void (*)(void))sm2_mldsa65_hybrid_get_ctx_md_params }, + { OSSL_FUNC_SIGNATURE_GETTABLE_CTX_MD_PARAMS, + (void (*)(void))sm2_mldsa65_hybrid_gettable_ctx_md_params }, + { OSSL_FUNC_SIGNATURE_SET_CTX_MD_PARAMS, + (void (*)(void))sm2_mldsa65_hybrid_set_ctx_md_params }, + { OSSL_FUNC_SIGNATURE_SETTABLE_CTX_MD_PARAMS, + (void (*)(void))sm2_mldsa65_hybrid_settable_ctx_md_params }, + { 0, NULL } +}; diff --git a/test/build.info b/test/build.info index 419e29b74..63f98178f 100644 --- a/test/build.info +++ b/test/build.info @@ -658,6 +658,10 @@ IF[{- !$disabled{tests} -}] PROGRAMS{noinst}=ml_dsa_internal_test ENDIF + IF[{- !$disabled{'sm2-mldsa65-hybrid'} -}] + PROGRAMS{noinst}=sm2_mldsa65_hybrid_test + ENDIF + SOURCE[poly1305_internal_test]=poly1305_internal_test.c INCLUDE[poly1305_internal_test]=.. ../include ../apps/include DEPEND[poly1305_internal_test]=../libcrypto.a libtestutil.a @@ -844,6 +848,10 @@ IF[{- !$disabled{tests} -}] SOURCE[ml_dsa_internal_test]=ml_dsa_internal_test.c INCLUDE[ml_dsa_internal_test]=../include ../apps/include DEPEND[ml_dsa_internal_test]=../libcrypto.a libtestutil.a + + SOURCE[sm2_mldsa65_hybrid_test]=sm2_mldsa65_hybrid_test.c + INCLUDE[sm2_mldsa65_hybrid_test]=../include ../apps/include + DEPEND[sm2_mldsa65_hybrid_test]=../libcrypto.a libtestutil.a ENDIF PROGRAMS{noinst}=asn1_time_test diff --git a/test/recipes/15-test_sm2_mldsa65_hybrid.t b/test/recipes/15-test_sm2_mldsa65_hybrid.t new file mode 100644 index 000000000..72050a965 --- /dev/null +++ b/test/recipes/15-test_sm2_mldsa65_hybrid.t @@ -0,0 +1,16 @@ +#! /usr/bin/env perl +# Copyright 2025 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_sm2_mldsa65_hybrid"); + +simple_test("test_sm2_mldsa65_hybrid", "sm2_mldsa65_hybrid_test", "sm2-mldsa65-hybrid"); diff --git a/test/sm2_mldsa65_hybrid_test.c b/test/sm2_mldsa65_hybrid_test.c new file mode 100644 index 000000000..b5534ded9 --- /dev/null +++ b/test/sm2_mldsa65_hybrid_test.c @@ -0,0 +1,286 @@ +/* + * Copyright 2025 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 +#include +#include +#include +#include +#include +#include +#include "testutil.h" +#include "internal/nelem.h" +#include "crypto/sm2_mldsa65_hybrid.h" + +#ifndef OPENSSL_NO_SM2_MLDSA65_HYBRID + +#define MLEN 590 +#define CTXLEN 100 +#define NTESTS 1000 +#define ALG_NAME "SM2-MLDSA65-HYBRID" +static EVP_PKEY *do_gen_key(void) { + static int ret = 0; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *key = NULL; + + ctx = EVP_PKEY_CTX_new_from_name(NULL, ALG_NAME, NULL); + if (!TEST_ptr(ctx)) { + goto end; + } + + ret = EVP_PKEY_keygen_init(ctx); + if (!TEST_int_eq(ret, 1)) { + goto end; + } + + ret = EVP_PKEY_generate(ctx, &key); + if (!TEST_int_eq(ret, 1)) { + goto end; + } + EVP_PKEY_CTX_free(ctx); + return key; + +end: + EVP_PKEY_CTX_free(ctx); + return NULL; +} + +static int test_sm2_mldsa65_hybrid_genkey(void) { + int ret = 0; + static uint8_t pk[SM2_MLDSA65_HYBRID_PK_SIZE] = {0}; + static uint8_t sk[SM2_MLDSA65_HYBRID_SK_SIZE] = {0}; + static uint8_t pk2[SM2_MLDSA65_HYBRID_PK_SIZE] = {0}; + static uint8_t sk2[SM2_MLDSA65_HYBRID_SK_SIZE] = {0}; + size_t sk_len = 0, pk_len = 0, sk2_len = 0, pk2_len = 0; + EVP_PKEY *k1 = NULL, *k2 = NULL; + int bits = 0, sig_len = 0; + + k1 = do_gen_key(); + if (!TEST_ptr(k1)) { + goto end; + } + + if (!TEST_true(EVP_PKEY_get_octet_string_param(k1, OSSL_PKEY_PARAM_PRIV_KEY, + sk, sizeof(sk), &sk_len)) + || !TEST_true(EVP_PKEY_get_octet_string_param(k1, OSSL_PKEY_PARAM_PUB_KEY, + pk, sizeof(pk), &pk_len)) + || !TEST_true(EVP_PKEY_get_int_param(k1, OSSL_PKEY_PARAM_BITS, &bits)) + || !TEST_int_eq(bits, 1952 * 8) + || !TEST_true(EVP_PKEY_get_int_param(k1, OSSL_PKEY_PARAM_MAX_SIZE, &sig_len)) + || !TEST_int_ge(sig_len, 3309 + 72)) { + goto end; + } + + k2 = EVP_PKEY_Q_keygen(NULL, NULL, ALG_NAME); + if (!TEST_ptr(k2)) { + goto end; + } + + if (!TEST_true(EVP_PKEY_get_octet_string_param(k2, OSSL_PKEY_PARAM_PRIV_KEY, + sk2, sizeof(sk2), &sk2_len)) + || !TEST_true(EVP_PKEY_get_octet_string_param(k2, OSSL_PKEY_PARAM_PUB_KEY, + pk2, sizeof(pk2), &pk2_len)) + || !TEST_int_eq(sk2_len, SM2_MLDSA65_HYBRID_SK_SIZE) + || !TEST_int_eq(pk2_len, SM2_MLDSA65_HYBRID_PK_SIZE)) + goto end; + + if (!TEST_mem_ne(pk, pk_len, pk2, pk2_len)) { + goto end; + } + if (!TEST_mem_ne(sk, sk_len, sk2, sk2_len)) { + goto end; + } + + ret = 1; +end: + EVP_PKEY_free(k1); + EVP_PKEY_free(k2); + return ret; +} + +static int test_sm2_mldsa65_hybrid_signverify(void) { + int ret = 0; + static uint8_t m[MLEN] = {0}; + static uint8_t sig[SM2_MLDSA65_HYBRID_SIG_SIZE] = {0}; + size_t sig_len = 0; + EVP_PKEY *pkey = NULL; + EVP_MD_CTX *mctx = NULL; + EVP_PKEY_CTX *pctx = NULL; + static uint8_t ctx[CTXLEN]; + OSSL_PARAM params[3], *p = params; + int i; + + for (i = 0; i < NTESTS; i++) { + pkey = NULL; + pctx = NULL; + mctx = NULL; + p = params; + + RAND_bytes(ctx, CTXLEN); + RAND_bytes(m, MLEN); + + if (!TEST_ptr(pctx = EVP_PKEY_CTX_new_from_name(NULL, ALG_NAME, NULL)) + || !TEST_int_eq(EVP_PKEY_keygen_init(pctx), 1) + || !TEST_int_eq(EVP_PKEY_generate(pctx, &pkey), 1)) { + EVP_PKEY_CTX_free(pctx); + return 0; + } + EVP_PKEY_CTX_free(pctx); + pctx = NULL; + + // sign + mctx = EVP_MD_CTX_new(); + if (!TEST_ptr(mctx)) + goto err; + + if (!TEST_int_eq(EVP_DigestSignInit(mctx, &pctx, EVP_sm3(), NULL, pkey), 1)) + goto err; + + *p++ = OSSL_PARAM_construct_octet_string(OSSL_SIGNATURE_PARAM_CONTEXT_STRING, ctx, sizeof(ctx)); + *p = OSSL_PARAM_construct_end(); + if (!TEST_int_eq(EVP_PKEY_CTX_set_params(pctx, params), 1)) + goto err; + + if (!TEST_int_eq(EVP_DigestSign(mctx, NULL, &sig_len, m, sizeof(m)), 1) + || !TEST_int_ge(sig_len, SM2_MLDSA65_HYBRID_SIG_SIZE)) + goto err; + + if (!TEST_int_eq(EVP_DigestSign(mctx, sig, &sig_len, m, sizeof(m)), 1)) + goto err; + + EVP_MD_CTX_free(mctx); + mctx = NULL; + // verify + mctx = EVP_MD_CTX_new(); + if (!TEST_ptr(mctx)) + goto err; + + if (!TEST_int_eq(EVP_DigestVerifyInit(mctx, &pctx, EVP_sm3(), NULL, pkey), 1)) + goto err; + + if (!TEST_int_eq(EVP_PKEY_CTX_set_params(pctx, params), 1)) + goto err; + + if (!TEST_int_eq(EVP_DigestVerify(mctx, sig, sig_len, m, sizeof(m)), 1)) + goto err; + + // negative tests + sig[0] = sig[0] ^ 0xff; + if (!TEST_int_eq(EVP_DigestVerify(mctx, sig, sig_len, m, sizeof(m)), 0)) + goto err; + + sig[0] = sig[0] ^ 0xff; + m[1] = m[1] ^ 0xff; + if (!TEST_int_eq(EVP_DigestVerify(mctx, sig, sig_len, m, sizeof(m)), 0)) + goto err; + + EVP_MD_CTX_free(mctx); + EVP_PKEY_free(pkey); + } + + return 1; +err: + EVP_MD_CTX_free(mctx); + EVP_PKEY_free(pkey); + + return ret; +} + +static int test_sm2_mldsa65_hybrid_export(void) +{ + int ret = 0; + EVP_PKEY *pkey = NULL, *pkey2_from_priv = NULL, *pkey3_from_pub = NULL; + BIO *bio_pub = NULL, *bio_priv = NULL; + + size_t pub_len1 = 0, pub_len2 = 0; + uint8_t pub1[SM2_MLDSA65_HYBRID_PK_SIZE] = {0}; + uint8_t pub2[SM2_MLDSA65_HYBRID_PK_SIZE] = {0}; + + size_t priv_len1 = 0, priv_len2 = 0; + uint8_t priv1[SM2_MLDSA65_HYBRID_SK_SIZE] = {0}; + uint8_t priv2[SM2_MLDSA65_HYBRID_SK_SIZE] = {0}; + + /* --- 1. 生成原始密钥对 --- */ + pkey = EVP_PKEY_Q_keygen(NULL, NULL, ALG_NAME); + if (!TEST_ptr(pkey)) + goto err; + + /* --- 2. 测试私钥的导出和导入 --- */ + TEST_info("Testing Private Key Export/Import..."); + + bio_priv = BIO_new(BIO_s_mem()); + if (!TEST_ptr(bio_priv)) + goto err; + + if (!TEST_int_eq(PEM_write_bio_PKCS8PrivateKey(bio_priv, pkey, NULL, NULL, 0, NULL, NULL), 1)) + goto err; + + pkey2_from_priv = PEM_read_bio_PrivateKey(bio_priv, NULL, NULL, NULL); + if (!TEST_ptr(pkey2_from_priv)) { + TEST_error("Failed to import private key from PEM."); + goto err; + } + + if (!TEST_true(EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, priv1, sizeof(priv1), &priv_len1)) || + !TEST_true(EVP_PKEY_get_octet_string_param(pkey2_from_priv, OSSL_PKEY_PARAM_PRIV_KEY, priv2, sizeof(priv2), &priv_len2)) || + !TEST_mem_eq(priv1, priv_len1, priv2, priv_len2)) { + TEST_error("Private keys do not match after PEM import."); + goto err; + } + TEST_info("Private key test PASSED."); + + + /* --- 3. 继续测试公钥的导出和导入 --- */ + TEST_info("Testing Public Key Export/Import..."); + + bio_pub = BIO_new(BIO_s_mem()); + if (!TEST_ptr(bio_pub)) + goto err; + + if (!TEST_int_eq(PEM_write_bio_PUBKEY(bio_pub, pkey), 1)) + goto err; + + pkey3_from_pub = PEM_read_bio_PUBKEY(bio_pub, NULL, NULL, NULL); + if (!TEST_ptr(pkey3_from_pub)) { + TEST_error("Failed to import public key from PEM."); + goto err; + } + + if (!TEST_true(EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, pub1, sizeof(pub1), &pub_len1)) || + !TEST_true(EVP_PKEY_get_octet_string_param(pkey3_from_pub, OSSL_PKEY_PARAM_PUB_KEY, pub2, sizeof(pub2), &pub_len2)) || + !TEST_mem_eq(pub1, pub_len1, pub2, pub_len2)) { + TEST_error("Public keys do not match after PEM import."); + goto err; + } + TEST_info("Public key test PASSED."); + + ret = 1; + +err: + EVP_PKEY_free(pkey); + EVP_PKEY_free(pkey2_from_priv); + EVP_PKEY_free(pkey3_from_pub); + BIO_free_all(bio_priv); + BIO_free_all(bio_pub); + return ret; +} +#endif + +int setup_tests(void) +{ +#ifndef OPENSSL_NO_SM2_MLDSA65_HYBRID + ADD_TEST(test_sm2_mldsa65_hybrid_genkey); + ADD_TEST(test_sm2_mldsa65_hybrid_signverify); + ADD_TEST(test_sm2_mldsa65_hybrid_export); +#endif + return 1; +}