diff options
author | Jack Lloyd <[email protected]> | 2017-06-21 14:43:52 -0400 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2017-06-29 12:38:37 -0400 |
commit | 6ac870e6d81d98420a102661a27ad9b521da86f5 (patch) | |
tree | 0341a30bbf1003614e81e54f98b0a08105b6d9d1 | |
parent | 8b0986310ae9fdf7fa93e28e2820d818cc954cdd (diff) |
Add SM2 signature scheme
From https://tools.ietf.org/html/draft-shen-sm2-ecdsa-02
This is a contribution from Ribose Inc (@riboseinc).
-rw-r--r-- | src/build-data/oids.txt | 6 | ||||
-rw-r--r-- | src/build-data/policy/bsi.txt | 2 | ||||
-rw-r--r-- | src/cli/speed.cpp | 32 | ||||
-rw-r--r-- | src/lib/asn1/oids.cpp | 10 | ||||
-rw-r--r-- | src/lib/ffi/ffi.cpp | 56 | ||||
-rw-r--r-- | src/lib/ffi/ffi.h | 9 | ||||
-rw-r--r-- | src/lib/pubkey/ec_group/ec_group.cpp | 4 | ||||
-rw-r--r-- | src/lib/pubkey/ec_group/ec_named.cpp | 10 | ||||
-rw-r--r-- | src/lib/pubkey/pk_algs.cpp | 20 | ||||
-rw-r--r-- | src/lib/pubkey/sm2/info.txt | 13 | ||||
-rw-r--r-- | src/lib/pubkey/sm2/sm2.cpp | 234 | ||||
-rw-r--r-- | src/lib/pubkey/sm2/sm2.h | 98 | ||||
-rw-r--r-- | src/tests/data/hash/sm3.vec | 5 | ||||
-rw-r--r-- | src/tests/data/pubkey/sm2_sig.vec | 14 | ||||
-rw-r--r-- | src/tests/test_ffi.cpp | 92 | ||||
-rw-r--r-- | src/tests/test_sm2.cpp | 68 |
16 files changed, 671 insertions, 2 deletions
diff --git a/src/build-data/oids.txt b/src/build-data/oids.txt index 41e599e29..1ee09583b 100644 --- a/src/build-data/oids.txt +++ b/src/build-data/oids.txt @@ -16,6 +16,10 @@ 1.2.840.10045.2.1 = ECDSA 1.3.132.1.12 = ECDH +1.2.156.10197.1.301.1 = SM2_Sig +1.2.156.10197.1.301.2 = SM2_Kex +1.2.156.10197.1.301.3 = SM2_Enc + # ecgPublicKey (see https://www.teletrust.de/projekte/oid/) 1.3.36.3.3.2.5.2.1 = ECGDSA @@ -236,6 +240,8 @@ 1.2.840.10045.3.1.6 = x962_p239v3 1.2.840.10045.3.1.7 = secp256r1 +1.2.156.10197.1.301 = sm2p256v1 + 1.3.36.3.3.2.8.1.1.1 = brainpool160r1 1.3.36.3.3.2.8.1.1.3 = brainpool192r1 1.3.36.3.3.2.8.1.1.5 = brainpool224r1 diff --git a/src/build-data/policy/bsi.txt b/src/build-data/policy/bsi.txt index ae722d4c1..011168463 100644 --- a/src/build-data/policy/bsi.txt +++ b/src/build-data/policy/bsi.txt @@ -92,6 +92,7 @@ noekeon_simd seed serpent serpent_simd +sm4 threefish threefish_avx2 twofish @@ -128,6 +129,7 @@ mce mceies rfc6979 newhope +sm2 # pk_pad #eme_pkcs1 // needed for tls diff --git a/src/cli/speed.cpp b/src/cli/speed.cpp index 296b4b07a..cae11e52f 100644 --- a/src/cli/speed.cpp +++ b/src/cli/speed.cpp @@ -112,6 +112,10 @@ #include <botan/xmss.h> #endif +#if defined(BOTAN_HAS_SM2) + #include <botan/sm2.h> +#endif + #if defined(BOTAN_HAS_NEWHOPE) && defined(BOTAN_HAS_CHACHA) #include <botan/newhope.h> #include <botan/chacha.h> @@ -499,6 +503,12 @@ class Speed final : public Command bench_ecdsa(ecc_groups, provider, msec); } #endif +#if defined(BOTAN_HAS_SM2) + else if(algo == "SM2") + { + bench_sm2(ecc_groups, provider, msec); + } +#endif #if defined(BOTAN_HAS_ECKCDSA) else if(algo == "ECKCDSA") { @@ -1387,6 +1397,28 @@ class Speed final : public Command } #endif +#if defined(BOTAN_HAS_SM2) + void bench_sm2(const std::vector<std::string>& groups, + const std::string& provider, + std::chrono::milliseconds msec) + { + for(std::string grp : groups) + { + const std::string nm = "SM2-" + grp; + + Timer keygen_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::Private_Key> key(keygen_timer.run([&] + { + return new Botan::SM2_Signature_PrivateKey(rng(), Botan::EC_Group(grp)); + })); + + output() << Timer::result_string_ops(keygen_timer); + bench_pk_sig(*key, nm, provider, "SM3", msec); + } + } +#endif + #if defined(BOTAN_HAS_ECGDSA) void bench_ecgdsa(const std::vector<std::string>& groups, const std::string& provider, diff --git a/src/lib/asn1/oids.cpp b/src/lib/asn1/oids.cpp index 794f0f6f0..1fcbe456d 100644 --- a/src/lib/asn1/oids.cpp +++ b/src/lib/asn1/oids.cpp @@ -1,7 +1,7 @@ /* * OID maps * -* This file was automatically generated by ./src/scripts/oids.py on 2017-06-12 +* This file was automatically generated by ./src/scripts/oids.py on 2017-06-22 * * All manual edits to this file will be lost. Edit the script * then regenerate this source file. @@ -19,6 +19,10 @@ std::string lookup(const OID& oid) { const std::string oid_str = oid.as_string(); if(oid_str == "1.0.14888.3.0.5") return "ECKCDSA"; + if(oid_str == "1.2.156.10197.1.301") return "sm2p256v1"; + if(oid_str == "1.2.156.10197.1.301.1") return "SM2_Sig"; + if(oid_str == "1.2.156.10197.1.301.2") return "SM2_Kex"; + if(oid_str == "1.2.156.10197.1.301.3") return "SM2_Enc"; if(oid_str == "1.2.156.10197.1.401") return "SM3"; if(oid_str == "1.2.156.10197.1.504") return "RSA/EMSA3(SM3)"; if(oid_str == "1.2.250.1.223.101.256.1") return "frp256v1"; @@ -327,6 +331,9 @@ OID lookup(const std::string& name) if(name == "SHA-512-256") return OID("2.16.840.1.101.3.4.2.6"); if(name == "SHAKE-128") return OID("2.16.840.1.101.3.4.2.11"); if(name == "SHAKE-256") return OID("2.16.840.1.101.3.4.2.12"); + if(name == "SM2_Enc") return OID("1.2.156.10197.1.301.3"); + if(name == "SM2_Kex") return OID("1.2.156.10197.1.301.2"); + if(name == "SM2_Sig") return OID("1.2.156.10197.1.301.1"); if(name == "SM3") return OID("1.2.156.10197.1.401"); if(name == "Serpent/CBC") return OID("1.3.6.1.4.1.25258.3.1"); if(name == "Serpent/GCM") return OID("1.3.6.1.4.1.25258.3.101"); @@ -388,6 +395,7 @@ OID lookup(const std::string& name) if(name == "secp256r1") return OID("1.2.840.10045.3.1.7"); if(name == "secp384r1") return OID("1.3.132.0.34"); if(name == "secp521r1") return OID("1.3.132.0.35"); + if(name == "sm2p256v1") return OID("1.2.156.10197.1.301"); if(name == "x962_p192v2") return OID("1.2.840.10045.3.1.2"); if(name == "x962_p192v3") return OID("1.2.840.10045.3.1.3"); if(name == "x962_p239v1") return OID("1.2.840.10045.3.1.4"); diff --git a/src/lib/ffi/ffi.cpp b/src/lib/ffi/ffi.cpp index 38e3ba425..d99569778 100644 --- a/src/lib/ffi/ffi.cpp +++ b/src/lib/ffi/ffi.cpp @@ -45,6 +45,10 @@ #include <botan/ecdsa.h> #endif +#if defined(BOTAN_HAS_SM2) + #include <botan/sm2.h> +#endif + #if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO) #include <botan/ecc_key.h> #endif @@ -1797,6 +1801,32 @@ int botan_pubkey_load_ecdsa(botan_pubkey_t* key, #endif } +int botan_pubkey_load_sm2(botan_pubkey_t* key, + const botan_mp_t public_x, + const botan_mp_t public_y, + const char* curve_name) + { +#if defined(BOTAN_HAS_SM2) + std::unique_ptr<Botan::SM2_Signature_PublicKey> p_key; + try + { + if(!pubkey_load_ec(p_key, safe_get(public_x), safe_get(public_y), curve_name)) + { + *key = new botan_pubkey_struct(p_key.release()); + return 0; + } + } + catch(std::exception& exn) + { + log_exception(BOTAN_CURRENT_FUNCTION, exn.what()); + } + return -1; +#else + BOTAN_UNUSED(key, public_x, public_y, curve_name); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif + } + int botan_pubkey_load_ecdh(botan_pubkey_t* key, const botan_mp_t public_x, const botan_mp_t public_y, @@ -1847,6 +1877,32 @@ int botan_privkey_load_ecdsa(botan_privkey_t* key, return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } + +int botan_privkey_load_sm2(botan_privkey_t* key, + const botan_mp_t scalar, + const char* curve_name) + { +#if defined(BOTAN_HAS_SM2) + std::unique_ptr<Botan::SM2_Signature_PrivateKey> p_key; + try + { + if(!privkey_load_ec(p_key, safe_get(scalar), curve_name)) + { + *key = new botan_privkey_struct(p_key.release()); + return 0; + } + } + catch(std::exception& exn) + { + log_exception(BOTAN_CURRENT_FUNCTION, exn.what()); + } + return -1; +#else + BOTAN_UNUSED(key, scalar, curve_name); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif + } + int botan_privkey_load_ecdh(botan_privkey_t* key, const botan_mp_t scalar, const char* curve_name) diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index a179e5fa4..4a7723974 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -882,6 +882,15 @@ BOTAN_DLL int botan_privkey_load_ecdh(botan_privkey_t* key, const botan_mp_t scalar, const char* curve_name); +BOTAN_DLL int botan_pubkey_load_sm2(botan_pubkey_t* key, + const botan_mp_t public_x, + const botan_mp_t public_y, + const char* curve_name); + +BOTAN_DLL int botan_privkey_load_sm2(botan_privkey_t* key, + const botan_mp_t scalar, + const char* curve_name); + /* * Public Key Encryption */ diff --git a/src/lib/pubkey/ec_group/ec_group.cpp b/src/lib/pubkey/ec_group/ec_group.cpp index c8d464a22..831c41e37 100644 --- a/src/lib/pubkey/ec_group/ec_group.cpp +++ b/src/lib/pubkey/ec_group/ec_group.cpp @@ -21,7 +21,9 @@ EC_Group::EC_Group(const OID& domain_oid) const std::string pem = PEM_for_named_group(OIDS::lookup(domain_oid)); if(pem == "") - throw Lookup_Error("No ECC domain data for " + domain_oid.as_string()); + { + throw Lookup_Error("No ECC domain data for '" + domain_oid.as_string() + "'"); + } *this = EC_Group(pem); m_oid = domain_oid.as_string(); diff --git a/src/lib/pubkey/ec_group/ec_named.cpp b/src/lib/pubkey/ec_group/ec_named.cpp index 2aef0ad31..fc4a67fc6 100644 --- a/src/lib/pubkey/ec_group/ec_named.cpp +++ b/src/lib/pubkey/ec_group/ec_named.cpp @@ -266,6 +266,16 @@ std::string EC_Group::PEM_for_named_group(const std::string& name) "8f0XjAs61Y8QEm3ozkJDW1PcZ+FA0r+UH/3UWcbWVeECAQE=" "-----END EC PARAMETERS-----"; + if(name == "sm2p256v1") + return + "-----BEGIN EC PARAMETERS-----" + "MIHgAgEBMCwGByqGSM49AQECIQD////+/////////////////////wAAAAD/////" + "/////zBEBCD////+/////////////////////wAAAAD//////////AQgKOn6np2f" + "XjRNWp5Lz2UJp/OXifUVq4+S3by9QU2UDpMEQQQyxK4sHxmBGV+ZBEZqOcmUj+ML" + "v/JmC+FxWkWJM0x0x7w3NqL09necWb3O42tpIVPQqYd8xipHQALfMuUhOfCgAiEA" + "/////v///////////////3ID32shxgUrU7v0CTnVQSMCAQE=" + "-----END EC PARAMETERS-----"; + #if defined(BOTAN_HOUSE_ECC_CURVE_NAME) if(name == BOTAN_HOUSE_ECC_CURVE_NAME) return BOTAN_HOUSE_ECC_CURVE_PEM; diff --git a/src/lib/pubkey/pk_algs.cpp b/src/lib/pubkey/pk_algs.cpp index 45e88f2a3..34d659c8f 100644 --- a/src/lib/pubkey/pk_algs.cpp +++ b/src/lib/pubkey/pk_algs.cpp @@ -60,6 +60,10 @@ #include <botan/xmss.h> #endif +#if defined(BOTAN_HAS_SM2) + #include <botan/sm2.h> +#endif + #if defined(BOTAN_HAS_OPENSSL) #include <botan/internal/openssl.h> #endif @@ -134,6 +138,11 @@ load_public_key(const AlgorithmIdentifier& alg_id, return std::unique_ptr<Public_Key>(new GOST_3410_PublicKey(alg_id, key_bits)); #endif +#if defined(BOTAN_HAS_SM2) + if(alg_name == "SM2_Sig") + return std::unique_ptr<Public_Key>(new SM2_Signature_PublicKey(alg_id, key_bits)); +#endif + #if defined(BOTAN_HAS_XMSS) if(alg_name == "XMSS") return std::unique_ptr<Public_Key>(new XMSS_PublicKey(key_bits)); @@ -205,6 +214,11 @@ load_private_key(const AlgorithmIdentifier& alg_id, return std::unique_ptr<Private_Key>(new GOST_3410_PrivateKey(alg_id, key_bits)); #endif +#if defined(BOTAN_HAS_SM2) + if(alg_name == "SM2_Sig") + return std::unique_ptr<Private_Key>(new SM2_Signature_PrivateKey(alg_id, key_bits)); +#endif + #if defined(BOTAN_HAS_ELGAMAL) if(alg_name == "ElGamal") return std::unique_ptr<Private_Key>(new ElGamal_PrivateKey(alg_id, key_bits)); @@ -290,6 +304,7 @@ create_private_key(const std::string& alg_name, alg_name == "ECDH" || alg_name == "ECKCDSA" || alg_name == "ECGDSA" || + alg_name == "SM2_Sig" || alg_name == "GOST-34.10") { const EC_Group ec_group(params.empty() ? "secp256r1" : params); @@ -314,6 +329,11 @@ create_private_key(const std::string& alg_name, return std::unique_ptr<Private_Key>(new GOST_3410_PrivateKey(rng, ec_group)); #endif +#if defined(BOTAN_HAS_SM2) + if(alg_name == "SM2_Sig") + return std::unique_ptr<Private_Key>(new SM2_Signature_PrivateKey(rng, ec_group)); +#endif + #if defined(BOTAN_HAS_ECGDSA) if(alg_name == "ECGDSA") return std::unique_ptr<Private_Key>(new ECGDSA_PrivateKey(rng, ec_group)); diff --git a/src/lib/pubkey/sm2/info.txt b/src/lib/pubkey/sm2/info.txt new file mode 100644 index 000000000..e94500277 --- /dev/null +++ b/src/lib/pubkey/sm2/info.txt @@ -0,0 +1,13 @@ +<defines> +SM2 -> 20170621 +</defines> + +<requires> +asn1 +ec_group +ecc_key +keypair +numbertheory +rng +sm3 +</requires> diff --git a/src/lib/pubkey/sm2/sm2.cpp b/src/lib/pubkey/sm2/sm2.cpp new file mode 100644 index 000000000..45a51b4cc --- /dev/null +++ b/src/lib/pubkey/sm2/sm2.cpp @@ -0,0 +1,234 @@ +/* +* SM2 +* (C) Ribose Inc +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/sm2.h> +#include <botan/internal/pk_ops_impl.h> +#include <botan/keypair.h> +#include <botan/reducer.h> +#include <botan/hash.h> + +#include <iostream> +#include <botan/hex.h> + +namespace Botan { + +bool SM2_Signature_PrivateKey::check_key(RandomNumberGenerator& rng, + bool strong) const + { + if(!public_point().on_the_curve()) + return false; + + if(!strong) + return true; + + return KeyPair::signature_consistency_check(rng, *this, "SM3"); + } + +SM2_Signature_PrivateKey::SM2_Signature_PrivateKey(const AlgorithmIdentifier& alg_id, + const secure_vector<uint8_t>& key_bits) : + EC_PrivateKey(alg_id, key_bits) + { + m_da_inv = inverse_mod(m_private_key + 1, domain().get_order()); + } + +SM2_Signature_PrivateKey::SM2_Signature_PrivateKey(RandomNumberGenerator& rng, + const EC_Group& domain, + const BigInt& x) : + EC_PrivateKey(rng, domain, x) + { + m_da_inv = inverse_mod(m_private_key + 1, domain.get_order()); + } + +namespace { + +std::vector<uint8_t> compute_za(HashFunction& hash, + const std::string& user_id, + const EC_Group& domain, + const PointGFp& pubkey) + { + if(user_id.size() >= 8192) + throw Invalid_Argument("SM2 user id too long to represent"); + + const uint16_t uid_len = static_cast<uint16_t>(8 * user_id.size()); + + hash.update(get_byte(0, uid_len)); + hash.update(get_byte(1, uid_len)); + hash.update(user_id); + + const size_t p_bytes = domain.get_curve().get_p().bytes(); + + hash.update(BigInt::encode_1363(domain.get_curve().get_a(), p_bytes)); + hash.update(BigInt::encode_1363(domain.get_curve().get_b(), p_bytes)); + hash.update(BigInt::encode_1363(domain.get_base_point().get_affine_x(), p_bytes)); + hash.update(BigInt::encode_1363(domain.get_base_point().get_affine_y(), p_bytes)); + hash.update(BigInt::encode_1363(pubkey.get_affine_x(), p_bytes)); + hash.update(BigInt::encode_1363(pubkey.get_affine_y(), p_bytes)); + + std::vector<uint8_t> za(hash.output_length()); + hash.final(za.data()); + + #if 0 + std::cout << "Ent0 " << (int)get_byte(0, uid_len) << "\n"; + std::cout << "Ent1 " << (int)get_byte(1, uid_len) << "\n"; + std::cout << "ID " << user_id << "\n"; + std::cout << "A = " << Botan::hex_encode(BigInt::encode_1363(domain.get_curve().get_a(), p_bytes)) << "\n"; + std::cout << "B = " << Botan::hex_encode(BigInt::encode_1363(domain.get_curve().get_b(), p_bytes)) << "\n"; + std::cout << "xG = " << Botan::hex_encode(BigInt::encode_1363(domain.get_base_point().get_affine_x(), p_bytes)) << "\n"; + std::cout << "yG = " << Botan::hex_encode(BigInt::encode_1363(domain.get_base_point().get_affine_y(), p_bytes)) << "\n"; + std::cout << "xP = " << Botan::hex_encode(BigInt::encode_1363(pubkey.get_affine_x(), p_bytes)) << "\n"; + std::cout << "yP = " << Botan::hex_encode(BigInt::encode_1363(pubkey.get_affine_y(), p_bytes)) << "\n"; + std::cout << "ZA = " << hex_encode(za) << "\n"; + #endif + + return za; + } + +/** +* SM2 signature operation +*/ +class SM2_Signature_Operation : public PK_Ops::Signature + { + public: + + SM2_Signature_Operation(const SM2_Signature_PrivateKey& sm2, + const std::string& ident) : + m_order(sm2.domain().get_order()), + m_base_point(sm2.domain().get_base_point(), m_order), + m_x(sm2.private_value()), + m_da_inv(sm2.get_da_inv()), + m_mod_order(m_order), + m_hash(HashFunction::create_or_throw("SM3")) + { + // ZA=H256(ENTLA || IDA || a || b || xG || yG || xA || yA) + m_za = compute_za(*m_hash, ident, sm2.domain(), sm2.public_point()); + m_hash->update(m_za); + } + + void update(const uint8_t msg[], size_t msg_len) override + { + m_hash->update(msg, msg_len); + } + + secure_vector<uint8_t> sign(RandomNumberGenerator& rng) override; + + private: + const BigInt& m_order; + Blinded_Point_Multiply m_base_point; + const BigInt& m_x; + const BigInt& m_da_inv; + Modular_Reducer m_mod_order; + + std::vector<uint8_t> m_za; + std::unique_ptr<HashFunction> m_hash; + }; + +secure_vector<uint8_t> +SM2_Signature_Operation::sign(RandomNumberGenerator& rng) + { + const BigInt k = BigInt::random_integer(rng, 1, m_order); + + const PointGFp k_times_P = m_base_point.blinded_multiply(k, rng); + + const BigInt e = BigInt::decode(m_hash->final()); + const BigInt r = m_mod_order.reduce(k_times_P.get_affine_x() + e); + const BigInt s = m_mod_order.multiply(m_da_inv, (k - r*m_x)); + + // prepend ZA for next signature if any + m_hash->update(m_za); + + return BigInt::encode_fixed_length_int_pair(r, s, m_order.bytes()); + } + +/** +* SM2 verification operation +*/ +class SM2_Verification_Operation : public PK_Ops::Verification + { + public: + SM2_Verification_Operation(const SM2_Signature_PublicKey& sm2, + const std::string& ident) : + m_base_point(sm2.domain().get_base_point()), + m_public_point(sm2.public_point()), + m_order(sm2.domain().get_order()), + m_mod_order(m_order), + m_hash(HashFunction::create_or_throw("SM3")) + { + // ZA=H256(ENTLA || IDA || a || b || xG || yG || xA || yA) + m_za = compute_za(*m_hash, ident, sm2.domain(), sm2.public_point()); + m_hash->update(m_za); + } + + void update(const uint8_t msg[], size_t msg_len) override + { + m_hash->update(msg, msg_len); + } + + bool is_valid_signature(const uint8_t sig[], size_t sig_len) override; + private: + const PointGFp& m_base_point; + const PointGFp& m_public_point; + const BigInt& m_order; + // FIXME: should be offered by curve + Modular_Reducer m_mod_order; + std::vector<uint8_t> m_za; + std::unique_ptr<HashFunction> m_hash; + }; + +bool SM2_Verification_Operation::is_valid_signature(const uint8_t sig[], size_t sig_len) + { + const BigInt e = BigInt::decode(m_hash->final()); + + // Update for next verification + m_hash->update(m_za); + + if(sig_len != m_order.bytes()*2) + return false; + + const BigInt r(sig, sig_len / 2); + const BigInt s(sig + sig_len / 2, sig_len / 2); + + if(r <= 0 || r >= m_order || s <= 0 || s >= m_order) + return false; + + const BigInt t = m_mod_order.reduce(r + s); + + if(t == 0) + return false; + + const PointGFp R = multi_exponentiate(m_base_point, s, m_public_point, t); + + // ??? + if(R.is_zero()) + return false; + + return (m_mod_order.reduce(R.get_affine_x() + e) == r); + } + +} + +std::unique_ptr<PK_Ops::Verification> +SM2_Signature_PublicKey::create_verification_op(const std::string& params, + const std::string& provider) const + { + if(provider == "base" || provider.empty()) + return std::unique_ptr<PK_Ops::Verification>(new SM2_Verification_Operation(*this, params)); + + throw Provider_Not_Found(algo_name(), provider); + } + +std::unique_ptr<PK_Ops::Signature> +SM2_Signature_PrivateKey::create_signature_op(RandomNumberGenerator& /*rng*/, + const std::string& params, + const std::string& provider) const + { + if(provider == "base" || provider.empty()) + return std::unique_ptr<PK_Ops::Signature>(new SM2_Signature_Operation(*this, params)); + + throw Provider_Not_Found(algo_name(), provider); + } + +} diff --git a/src/lib/pubkey/sm2/sm2.h b/src/lib/pubkey/sm2/sm2.h new file mode 100644 index 000000000..a66654dbd --- /dev/null +++ b/src/lib/pubkey/sm2/sm2.h @@ -0,0 +1,98 @@ +/* +* SM2 +* (C) 2017 Ribose Inc +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_SM2_KEY_H__ +#define BOTAN_SM2_KEY_H__ + +#include <botan/ecc_key.h> + +namespace Botan { + +/** +* This class represents SM2 Signature Keys +*/ +class BOTAN_DLL SM2_Signature_PublicKey : public virtual EC_PublicKey + { + public: + + /** + * Create a public key from a given public point. + * @param dom_par the domain parameters associated with this key + * @param public_point the public point defining this key + */ + SM2_Signature_PublicKey(const EC_Group& dom_par, + const PointGFp& public_point) : + EC_PublicKey(dom_par, public_point) {} + + /** + * Load a public key. + * @param alg_id the X.509 algorithm identifier + * @param key_bits DER encoded public key bits + */ + SM2_Signature_PublicKey(const AlgorithmIdentifier& alg_id, + const std::vector<uint8_t>& key_bits) : + EC_PublicKey(alg_id, key_bits) {} + + /** + * Get this keys algorithm name. + * @result this keys algorithm name + */ + std::string algo_name() const override { return "SM2_Sig"; } + + size_t message_parts() const override { return 2; } + + size_t message_part_size() const override + { return domain().get_order().bytes(); } + + std::unique_ptr<PK_Ops::Verification> + create_verification_op(const std::string& params, + const std::string& provider) const override; + protected: + SM2_Signature_PublicKey() = default; + }; + +/** +* This class represents SM2 Private Keys +*/ +class BOTAN_DLL SM2_Signature_PrivateKey : public SM2_Signature_PublicKey, + public EC_PrivateKey + { + public: + + /** + * Load a private key + * @param alg_id the X.509 algorithm identifier + * @param key_bits ECPrivateKey bits + */ + SM2_Signature_PrivateKey(const AlgorithmIdentifier& alg_id, + const secure_vector<uint8_t>& key_bits); + + /** + * Create a private key. + * @param rng a random number generator + * @param domain parameters to used for this key + * @param x the private key (if zero, generate a new random key) + */ + SM2_Signature_PrivateKey(RandomNumberGenerator& rng, + const EC_Group& domain, + const BigInt& x = 0); + + bool check_key(RandomNumberGenerator& rng, bool) const override; + + std::unique_ptr<PK_Ops::Signature> + create_signature_op(RandomNumberGenerator& rng, + const std::string& params, + const std::string& provider) const override; + + const BigInt& get_da_inv() const { return m_da_inv; } + private: + BigInt m_da_inv; + }; + +} + +#endif diff --git a/src/tests/data/hash/sm3.vec b/src/tests/data/hash/sm3.vec index 2dd4710bc..f5dee3ccb 100644 --- a/src/tests/data/hash/sm3.vec +++ b/src/tests/data/hash/sm3.vec @@ -1,5 +1,10 @@ [SM3] +# From ZA computation in https://tools.ietf.org/html/draft-shen-sm2-ecdsa-02 + +In = 0090414C494345313233405941484F4F2E434F4D787968B4FA32C3FD2417842E73BBFEFF2F3C848B6831D7E0EC65228B3937E49863E4C6D3B23B0C849CF84241484BFE48F61D59A5B16BA06E6E12D1DA27C5249A421DEBD61B62EAB6746434EBC3CC315E32220B3BADD50BDC4C4E6C147FEDD43D0680512BCBB42C07D47349D2153B70C4E5D7FDFCBFA36EA1A85841B9E46E09A20AE4C7798AA0F119471BEE11825BE46202BB79E2A5844495E97C04FF4DF2548A7C0240F88F1CD4E16352A73C17B7F16F07353E53A176D684A9FE0C6BB798E857 +Out = F4A38489E32B45B6F876E3AC2168CA392362DC8F23459C1D1146FC3DBFB7BC9A + # https://tools.ietf.org/html/draft-shen-sm3-hash-01 In = 616263 Out = 66C7F0F462EEEDD9D1F2D46BDC10E4E24167C4875CF2F7A2297DA02B8F4BA8E0 diff --git a/src/tests/data/pubkey/sm2_sig.vec b/src/tests/data/pubkey/sm2_sig.vec new file mode 100644 index 000000000..d3356cceb --- /dev/null +++ b/src/tests/data/pubkey/sm2_sig.vec @@ -0,0 +1,14 @@ + +P = 0x8542D69E4C044F18E8B92435BF6FF7DE457283915C45517D722EDB8B08F1DFC3 +A = 0x787968B4FA32C3FD2417842E73BBFEFF2F3C848B6831D7E0EC65228B3937E498 +B = 0x63E4C6D3B23B0C849CF84241484BFE48F61D59A5B16BA06E6E12D1DA27C5249A +xG = 0x421DEBD61B62EAB6746434EBC3CC315E32220B3BADD50BDC4C4E6C147FEDD43D +yG = 0x0680512BCBB42C07D47349D2153B70C4E5D7FDFCBFA36EA1A85841B9E46E09A2 +Order = 0x8542D69E4C044F18E8B92435BF6FF7DD297720630485628D5AE74EE7C32E79B7 +Cofactor = 1 + +Ident = [email protected] +Msg = 6D65737361676520646967657374 +x = 0x128B2FA8BD433C6C068C8D803DFF79792A519A55171B1B650C23661D15897263 +Nonce = 6CB28D99385C175C94F94E934817663FC176D925DD72B727260DBAAE1FB2F96F +Signature = 40F1EC59F793D9F49E09DCEF49130D4194F79FB1EED2CAA55BACDB49C4E755D16FC6DAC32C5D5CF10C77DFB20F7C2EB667A457872FB09EC56327A67EC7DEEBE7 diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index e9d9b6e20..bf5fc0e9c 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -383,6 +383,10 @@ class FFI_Unit_Tests : public Test results.push_back(ffi_test_ecdh(rng)); #endif +#if defined(BOTAN_HAS_SM2) + results.push_back(ffi_test_sm2(rng)); +#endif + #if defined(BOTAN_HAS_MCELIECE) results.push_back(ffi_test_mceliece(rng)); #endif @@ -1280,6 +1284,94 @@ class FFI_Unit_Tests : public Test return result; } + Test::Result ffi_test_sm2(botan_rng_t rng) + { + Test::Result result("FFI SM2"); + static const char* kCurve = "sm2p256v1"; + const std::string sm2_ident = "SM2 Ident Field"; + botan_privkey_t priv; + botan_pubkey_t pub; + botan_privkey_t loaded_privkey; + botan_pubkey_t loaded_pubkey; + + REQUIRE_FFI_OK(botan_privkey_create, (&priv, "SM2_Sig", kCurve, rng)); + TEST_FFI_OK(botan_privkey_export_pubkey, (&pub, priv)); + ffi_test_pubkey_export(result, pub, priv, rng); + + // Check key load functions + botan_mp_t private_scalar, public_x, public_y; + botan_mp_init(&private_scalar); + botan_mp_init(&public_x); + botan_mp_init(&public_y); + + TEST_FFI_OK(botan_privkey_get_field, (private_scalar, priv, "x")); + TEST_FFI_OK(botan_pubkey_get_field, (public_x, pub, "public_x")); + TEST_FFI_OK(botan_pubkey_get_field, (public_y, pub, "public_y")); + TEST_FFI_OK(botan_privkey_load_sm2, (&loaded_privkey, private_scalar, kCurve)); + TEST_FFI_OK(botan_pubkey_load_sm2, (&loaded_pubkey, public_x, public_y, kCurve)); + TEST_FFI_OK(botan_privkey_check_key, (loaded_privkey, rng, 0)); + TEST_FFI_OK(botan_pubkey_check_key, (loaded_pubkey, rng, 0)); + + char namebuf[32] = { 0 }; + size_t name_len = sizeof(namebuf); + + TEST_FFI_OK(botan_pubkey_algo_name, (pub, &namebuf[0], &name_len)); + result.test_eq(namebuf, namebuf, "SM2_Sig"); + + std::vector<uint8_t> message(1280), signature; + TEST_FFI_OK(botan_rng_get, (rng, message.data(), message.size())); + botan_pk_op_sign_t signer; + if(TEST_FFI_OK(botan_pk_op_sign_create, (&signer, loaded_privkey, sm2_ident.c_str(), 0))) + { + // TODO: break input into multiple calls to update + TEST_FFI_OK(botan_pk_op_sign_update, (signer, message.data(), message.size())); + + signature.resize(96); // TODO: no way to derive this from API + size_t sig_len = signature.size(); + TEST_FFI_OK(botan_pk_op_sign_finish, (signer, rng, signature.data(), &sig_len)); + signature.resize(sig_len); + + TEST_FFI_OK(botan_pk_op_sign_destroy, (signer)); + } + + botan_pk_op_verify_t verifier; + + if(signature.size() > 0 && TEST_FFI_OK(botan_pk_op_verify_create, (&verifier, pub, sm2_ident.c_str(), 0))) + { + TEST_FFI_OK(botan_pk_op_verify_update, (verifier, message.data(), message.size())); + TEST_FFI_OK(botan_pk_op_verify_finish, (verifier, signature.data(), signature.size())); + + // TODO: randomize this + signature[0] ^= 1; + TEST_FFI_OK(botan_pk_op_verify_update, (verifier, message.data(), message.size())); + TEST_FFI_FAIL("bad signature", botan_pk_op_verify_finish, (verifier, signature.data(), signature.size())); + + message[0] ^= 1; + TEST_FFI_OK(botan_pk_op_verify_update, (verifier, message.data(), message.size())); + TEST_FFI_FAIL("bad signature", botan_pk_op_verify_finish, (verifier, signature.data(), signature.size())); + + signature[0] ^= 1; + TEST_FFI_OK(botan_pk_op_verify_update, (verifier, message.data(), message.size())); + TEST_FFI_FAIL("bad signature", botan_pk_op_verify_finish, (verifier, signature.data(), signature.size())); + + message[0] ^= 1; + TEST_FFI_OK(botan_pk_op_verify_update, (verifier, message.data(), message.size())); + TEST_FFI_OK(botan_pk_op_verify_finish, (verifier, signature.data(), signature.size())); + + TEST_FFI_OK(botan_pk_op_verify_destroy, (verifier)); + } + + TEST_FFI_OK(botan_mp_destroy, (private_scalar)); + TEST_FFI_OK(botan_mp_destroy, (public_x)); + TEST_FFI_OK(botan_mp_destroy, (public_y)); + TEST_FFI_OK(botan_pubkey_destroy, (pub)); + TEST_FFI_OK(botan_privkey_destroy, (priv)); + TEST_FFI_OK(botan_privkey_destroy, (loaded_privkey)); + TEST_FFI_OK(botan_pubkey_destroy, (loaded_pubkey)); + + return result; + } + Test::Result ffi_test_ecdh(botan_rng_t rng) { Test::Result result("FFI ECDH"); diff --git a/src/tests/test_sm2.cpp b/src/tests/test_sm2.cpp new file mode 100644 index 000000000..0361ea3a3 --- /dev/null +++ b/src/tests/test_sm2.cpp @@ -0,0 +1,68 @@ +/* +* (C) 2017 Ribose Inc +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "tests.h" +#include "test_rng.h" + +#if defined(BOTAN_HAS_SM2) + #include <botan/sm2.h> + #include "test_pubkey.h" +#endif + +namespace Botan_Tests { + +#if defined(BOTAN_HAS_SM2) + +namespace { + +class SM2_Signature_KAT_Tests : public PK_Signature_Generation_Test + { + public: + SM2_Signature_KAT_Tests() + : PK_Signature_Generation_Test( + "SM2", + "pubkey/sm2_sig.vec", + "P,A,B,xG,yG,Order,Cofactor,Ident,Msg,x,Nonce,Signature", + "") {} + + virtual std::string default_padding(const VarMap& vars) const + { + return get_req_str(vars, "Ident"); + } + + Botan::RandomNumberGenerator* test_rng(const std::vector<uint8_t>& nonce) const override + { + return new Fixed_Output_Position_RNG(nonce, 1); + } + + std::unique_ptr<Botan::Private_Key> load_private_key(const VarMap& vars) override + { + // group params + const BigInt p = get_req_bn(vars, "P"); + const BigInt a = get_req_bn(vars, "A"); + const BigInt b = get_req_bn(vars, "B"); + const BigInt xG = get_req_bn(vars, "xG"); + const BigInt yG = get_req_bn(vars, "yG"); + const BigInt order = get_req_bn(vars, "Order"); + const BigInt cofactor = get_req_bn(vars, "Cofactor"); + const BigInt x = get_req_bn(vars, "x"); + + Botan::CurveGFp curve(p, a, b); + Botan::PointGFp base_point(curve, xG, yG); + Botan::EC_Group domain(curve, base_point, order, cofactor); + + Botan::Null_RNG null_rng; + std::unique_ptr<Botan::Private_Key> key(new Botan::SM2_Signature_PrivateKey(null_rng, domain, x)); + return key; + } + }; + +} + +BOTAN_REGISTER_TEST("sm2_sig", SM2_Signature_KAT_Tests); +#endif + +} |