From 87cae3d74cc034df00e94d48d4c64ae7f4060f81 Mon Sep 17 00:00:00 2001 From: Johannes Roth Date: Fri, 28 Jul 2023 16:02:16 +0200 Subject: [PATCH] add SPHINCS+ --- include/repgp/repgp_def.h | 4 + include/rnp/rnp.h | 23 ++- src/lib/CMakeLists.txt | 3 +- src/lib/crypto.cpp | 19 ++ src/lib/crypto/common.h | 1 + src/lib/crypto/signatures.cpp | 30 +++ src/lib/crypto/sphincsplus.cpp | 365 +++++++++++++++++++++++++++++++++ src/lib/crypto/sphincsplus.h | 202 ++++++++++++++++++ src/lib/generate-key.cpp | 42 ++++ src/lib/pgp-key.cpp | 8 + src/lib/rnp.cpp | 53 +++++ src/lib/types.h | 13 +- src/librekey/rnp_key_store.cpp | 5 + src/librepgp/stream-dump.cpp | 25 +++ src/librepgp/stream-key.cpp | 51 +++++ src/librepgp/stream-sig.cpp | 23 +++ src/rnp/fficli.cpp | 16 ++ src/rnp/rnpcfg.h | 4 + src/rnpkeys/tui.cpp | 43 +++- src/tests/cipher.cpp | 43 ++++ src/tests/ffi.cpp | 2 +- src/tests/pqc.cpp | 28 +++ 22 files changed, 994 insertions(+), 9 deletions(-) create mode 100644 src/lib/crypto/sphincsplus.cpp create mode 100644 src/lib/crypto/sphincsplus.h diff --git a/include/repgp/repgp_def.h b/include/repgp/repgp_def.h index 52ded8dc..16dbae5e 100644 --- a/include/repgp/repgp_def.h +++ b/include/repgp/repgp_def.h @@ -250,6 +250,10 @@ typedef enum : uint8_t { 39, /* Dilithium 3 + ECDSA-brainpoolP256r1 from draft-wussler-openpgp-pqc-02 */ PGP_PKA_DILITHIUM5_BP384 = 40, /* Dilithium 5 + ECDSA-brainpoolP384r1 from draft-wussler-openpgp-pqc-02 */ + + PGP_PKA_SPHINCSPLUS_SHA2 = 41, /* SPHINCS+-simple-SHA2 from draft-wussler-openpgp-pqc-02 */ + PGP_PKA_SPHINCSPLUS_SHAKE = + 42, /* SPHINCS+-simple-SHAKE from draft-wussler-openpgp-pqc-02 */ #endif PGP_PKA_SM2 = 99, /* SM2 encryption/signature schemes */ diff --git a/include/rnp/rnp.h b/include/rnp/rnp.h index 66351635..51c6790d 100644 --- a/include/rnp/rnp.h +++ b/include/rnp/rnp.h @@ -1188,6 +1188,24 @@ RNP_API rnp_result_t rnp_op_generate_set_pref_keyserver(rnp_op_generate_t op, RNP_API rnp_result_t rnp_op_generate_set_v6_key(rnp_op_generate_t op); #endif +#if defined(ENABLE_PQC) +/** Set the SPHINCS+ parameter set + * NOTE: This is an experimantal feature and this function can be replaced (or removed) at any + * time. + * + * @param op pointer to opaque key generation context. + * @param param string, representing the SHPINCS+ parameter set. + * Possible Values: + * 128s, 128f, 192s, 192f, 256s, 256f + * All parameter sets refer to the simple variant and the hash function is given + * by the algorithm id. + * + * @return RNP_SUCCESS or error code if failed. + */ +RNP_API rnp_result_t rnp_op_generate_set_sphincsplus_param(rnp_op_generate_t op, + const char * param); +#endif + /** Execute the prepared key or subkey generation operation. * Note: if you set protection algorithm, then you need to specify ffi password provider to * be able to request password for key encryption. @@ -2996,7 +3014,8 @@ RNP_API rnp_result_t rnp_op_encrypt_add_recipient(rnp_op_encrypt_t op, rnp_key_h /** * @brief Enables the creation of PKESK v6 (instead of v3) which results in the use of SEIPDv2. * The actually created version depends on the capabilities of the list of recipients. - * NOTE: This is an experimental feature and this function can be replaced (or removed) at any time. + * NOTE: This is an experimental feature and this function can be replaced (or removed) at any + * time. * * @param op opaque encrypting context. Must be allocated and initialized. * @return RNP_SUCCESS or errorcode if failed. @@ -3417,6 +3436,8 @@ RNP_API const char *rnp_backend_version(); #define RNP_ALGNAME_DILITHIUM5_P384 "DILITHIUM5_P384" #define RNP_ALGNAME_DILITHIUM3_BP256 "DILITHIUM3_BP256" #define RNP_ALGNAME_DILITHIUM5_BP384 "DILITHIUM5_BP384" +#define RNP_ALGNAME_SPHINCSPLUS_SHA2 "SPHINCSPLUS_SHA2" +#define RNP_ALGNAME_SPHINCSPLUS_SHAKE "SPHINCSPLUS_SHAKE" #endif #define RNP_ALGNAME_IDEA "IDEA" #define RNP_ALGNAME_TRIPLEDES "TRIPLEDES" diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index f84c62a1..046434be 100755 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -302,8 +302,9 @@ elseif(CRYPTO_BACKEND_BOTAN) endif() if(ENABLE_PQC) list(APPEND CRYPTO_SOURCES - crypto/dilithium.cpp + crypto/dilithium.cpp crypto/dilithium_common.cpp + crypto/sphincsplus.cpp crypto/kyber_common.cpp crypto/kyber.cpp crypto/kyber_ecdh_composite.cpp diff --git a/src/lib/crypto.cpp b/src/lib/crypto.cpp index 23ddfd79..e6bbb8bb 100644 --- a/src/lib/crypto.cpp +++ b/src/lib/crypto.cpp @@ -205,6 +205,17 @@ pgp_generate_seckey(const rnp_keygen_crypto_params_t &crypto, return false; } break; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + if (pgp_sphincsplus_generate(&crypto.ctx->rng, + &seckey.material.sphincsplus, + crypto.sphincsplus.param, + seckey.alg)) { + RNP_LOG("failed to generate SPHINCS+ key for PK alg %d", seckey.alg); + return false; + } + break; #endif default: RNP_LOG("key generation not implemented for PK alg: %d", seckey.alg); @@ -275,6 +286,10 @@ key_material_equal(const pgp_key_material_t *key1, const pgp_key_material_t *key [[fallthrough]]; case PGP_PKA_DILITHIUM5_BP384: return (key1->dilithium_exdsa.pub == key2->dilithium_exdsa.pub); + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + return (key1->sphincsplus.pub == key2->sphincsplus.pub); #endif default: RNP_LOG("unknown public key algorithm: %d", (int) key1->alg); @@ -352,6 +367,10 @@ validate_pgp_key_material(const pgp_key_material_t *material, rnp::RNG *rng) [[fallthrough]]; case PGP_PKA_DILITHIUM5_BP384: return dilithium_exdsa_validate_key(rng, &material->dilithium_exdsa, material->secret); + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + return sphincsplus_validate_key(rng, &material->sphincsplus, material->secret); #endif default: RNP_LOG("unknown public key algorithm: %d", (int) material->alg); diff --git a/src/lib/crypto/common.h b/src/lib/crypto/common.h index 2709f706..cfa73e24 100644 --- a/src/lib/crypto/common.h +++ b/src/lib/crypto/common.h @@ -42,6 +42,7 @@ #if defined(ENABLE_PQC) #include "kyber_ecdh_composite.h" #include "dilithium_exdsa_composite.h" +#include "sphincsplus.h" #endif #if defined(ENABLE_CRYPTO_REFRESH) #include "x25519.h" diff --git a/src/lib/crypto/signatures.cpp b/src/lib/crypto/signatures.cpp index afcc9d85..e4dcf9b1 100644 --- a/src/lib/crypto/signatures.cpp +++ b/src/lib/crypto/signatures.cpp @@ -208,6 +208,11 @@ signature_calculate(pgp_signature_t & sig, ret = seckey.dilithium_exdsa.priv.sign( &ctx.rng, &material.dilithium_exdsa, hash_alg, hval, hlen); break; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + ret = seckey.sphincsplus.priv.sign(&ctx.rng, &material.sphincsplus, hval, hlen); + break; #endif default: RNP_LOG("Unsupported algorithm %d", sig.palg); @@ -246,6 +251,26 @@ signature_validate(const pgp_signature_t & sig, return RNP_ERROR_SIGNATURE_INVALID; } +#if defined(ENABLE_PQC) + /* check that hash matches key requirements */ + bool hash_alg_valid = false; + switch (key.alg) { + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + hash_alg_valid = key.sphincsplus.pub.validate_signature_hash_requirements(hash.alg()); + break; + default: + hash_alg_valid = true; + break; + } + if (!hash_alg_valid) { + RNP_LOG("Signature invalid since hash algorithm requirements are not met for the " + "given key."); + return RNP_ERROR_SIGNATURE_INVALID; + } +#endif + /* Finalize hash */ uint8_t hval[PGP_MAX_HASH_SIZE]; size_t hlen = 0; @@ -326,6 +351,11 @@ signature_validate(const pgp_signature_t & sig, ret = key.dilithium_exdsa.pub.verify(&material.dilithium_exdsa, hash.alg(), hval, hlen); break; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + ret = key.sphincsplus.pub.verify(&material.sphincsplus, hval, hlen); + break; #endif default: RNP_LOG("Unknown algorithm"); diff --git a/src/lib/crypto/sphincsplus.cpp b/src/lib/crypto/sphincsplus.cpp new file mode 100644 index 00000000..f7f9c705 --- /dev/null +++ b/src/lib/crypto/sphincsplus.cpp @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2023, [MTG AG](https://www.mtg.de). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "sphincsplus.h" +#include +#include "logging.h" +#include "types.h" + +namespace { +Botan::Sphincs_Parameter_Set +rnp_sphincsplus_params_to_botan_param(sphincsplus_parameter_t param) +{ + switch (param) { + case sphincsplus_simple_128s: + return Botan::Sphincs_Parameter_Set::Sphincs128Small; + case sphincsplus_simple_128f: + return Botan::Sphincs_Parameter_Set::Sphincs128Fast; + case sphincsplus_simple_192s: + return Botan::Sphincs_Parameter_Set::Sphincs192Small; + case sphincsplus_simple_192f: + return Botan::Sphincs_Parameter_Set::Sphincs192Fast; + case sphincsplus_simple_256s: + return Botan::Sphincs_Parameter_Set::Sphincs256Small; + case sphincsplus_simple_256f: + return Botan::Sphincs_Parameter_Set::Sphincs256Fast; + default: + RNP_LOG("invalid parameter given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} +Botan::Sphincs_Hash_Type +rnp_sphincsplus_hash_func_to_botan_hash_func(sphincsplus_hash_func_t hash_func) +{ + switch (hash_func) { + case sphincsplus_sha256: + return Botan::Sphincs_Hash_Type::Sha256; + case sphinscplus_shake256: + return Botan::Sphincs_Hash_Type::Shake256; + default: + RNP_LOG("invalid parameter given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +sphincsplus_hash_func_t +rnp_sphincsplus_alg_to_hashfunc(pgp_pubkey_alg_t alg) +{ + switch (alg) { + case PGP_PKA_SPHINCSPLUS_SHA2: + return sphincsplus_sha256; + case PGP_PKA_SPHINCSPLUS_SHAKE: + return sphinscplus_shake256; + default: + RNP_LOG("invalid sphincs+ alg id"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +pgp_pubkey_alg_t +rnp_sphincsplus_hashfunc_to_alg(sphincsplus_hash_func_t hashfunc) +{ + switch (hashfunc) { + case sphincsplus_sha256: + return PGP_PKA_SPHINCSPLUS_SHA2; + case sphinscplus_shake256: + return PGP_PKA_SPHINCSPLUS_SHAKE; + default: + RNP_LOG("invalid sphincs+ hashfunc"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} +} // namespace + +pgp_sphincsplus_public_key_t::pgp_sphincsplus_public_key_t(const uint8_t *key_encoded, + size_t key_encoded_len, + sphincsplus_parameter_t param, + sphincsplus_hash_func_t hash_func) + : key_encoded_(key_encoded, key_encoded + key_encoded_len), + pk_alg_(rnp_sphincsplus_hashfunc_to_alg(hash_func)), sphincsplus_param_(param), + sphincsplus_hash_func_(hash_func), is_initialized_(true) +{ +} + +pgp_sphincsplus_public_key_t::pgp_sphincsplus_public_key_t( + std::vector const &key_encoded, + sphincsplus_parameter_t param, + sphincsplus_hash_func_t hash_func) + : key_encoded_(key_encoded), pk_alg_(rnp_sphincsplus_hashfunc_to_alg(hash_func)), + sphincsplus_param_(param), sphincsplus_hash_func_(hash_func), is_initialized_(true) +{ +} + +pgp_sphincsplus_public_key_t::pgp_sphincsplus_public_key_t( + std::vector const &key_encoded, sphincsplus_parameter_t param, pgp_pubkey_alg_t alg) + : key_encoded_(key_encoded), pk_alg_(alg), sphincsplus_param_(param), + sphincsplus_hash_func_(rnp_sphincsplus_alg_to_hashfunc(alg)), is_initialized_(true) +{ +} + +pgp_sphincsplus_private_key_t::pgp_sphincsplus_private_key_t(const uint8_t *key_encoded, + size_t key_encoded_len, + sphincsplus_parameter_t param, + sphincsplus_hash_func_t hash_func) + : key_encoded_(key_encoded, key_encoded + key_encoded_len), + pk_alg_(rnp_sphincsplus_hashfunc_to_alg(hash_func)), sphincsplus_param_(param), + sphincsplus_hash_func_(hash_func), is_initialized_(true) +{ +} + +pgp_sphincsplus_private_key_t::pgp_sphincsplus_private_key_t( + std::vector const &key_encoded, + sphincsplus_parameter_t param, + sphincsplus_hash_func_t hash_func) + : key_encoded_(Botan::secure_vector(key_encoded.begin(), key_encoded.end())), + pk_alg_(rnp_sphincsplus_hashfunc_to_alg(hash_func)), sphincsplus_param_(param), + sphincsplus_hash_func_(hash_func), is_initialized_(true) +{ +} + +pgp_sphincsplus_private_key_t::pgp_sphincsplus_private_key_t( + std::vector const &key_encoded, sphincsplus_parameter_t param, pgp_pubkey_alg_t alg) + : key_encoded_(Botan::secure_vector(key_encoded.begin(), key_encoded.end())), + pk_alg_(alg), sphincsplus_param_(param), + sphincsplus_hash_func_(rnp_sphincsplus_alg_to_hashfunc(alg)), is_initialized_(true) +{ +} + +rnp_result_t +pgp_sphincsplus_private_key_t::sign(rnp::RNG * rng, + pgp_sphincsplus_signature_t *sig, + const uint8_t * msg, + size_t msg_len) const +{ + assert(is_initialized_); + auto priv_key = botan_key(); + + auto signer = Botan::PK_Signer(priv_key, *rng->obj(), ""); + sig->sig = signer.sign_message(msg, msg_len, *rng->obj()); + sig->param = param(); + + return RNP_SUCCESS; +} + +Botan::SphincsPlus_PublicKey +pgp_sphincsplus_public_key_t::botan_key() const +{ + return Botan::SphincsPlus_PublicKey( + key_encoded_, + rnp_sphincsplus_params_to_botan_param(this->sphincsplus_param_), + rnp_sphincsplus_hash_func_to_botan_hash_func(this->sphincsplus_hash_func_)); +} + +Botan::SphincsPlus_PrivateKey +pgp_sphincsplus_private_key_t::botan_key() const +{ + Botan::secure_vector priv_sv(key_encoded_.data(), + key_encoded_.data() + key_encoded_.size()); + return Botan::SphincsPlus_PrivateKey( + priv_sv, + rnp_sphincsplus_params_to_botan_param(this->sphincsplus_param_), + rnp_sphincsplus_hash_func_to_botan_hash_func(this->sphincsplus_hash_func_)); +} + +rnp_result_t +pgp_sphincsplus_public_key_t::verify(const pgp_sphincsplus_signature_t *sig, + const uint8_t * msg, + size_t msg_len) const +{ + assert(is_initialized_); + auto pub_key = botan_key(); + + auto verificator = Botan::PK_Verifier(pub_key, ""); + if (verificator.verify_message(msg, msg_len, sig->sig.data(), sig->sig.size())) { + return RNP_SUCCESS; + } + return RNP_ERROR_SIGNATURE_INVALID; +} + +std::pair +sphincsplus_generate_keypair(rnp::RNG * rng, + sphincsplus_parameter_t sphincsplus_param, + sphincsplus_hash_func_t sphincsplus_hash_func) +{ + Botan::SphincsPlus_PrivateKey priv_key( + *rng->obj(), + rnp_sphincsplus_params_to_botan_param(sphincsplus_param), + rnp_sphincsplus_hash_func_to_botan_hash_func(sphincsplus_hash_func)); + + std::unique_ptr pub_key = priv_key.public_key(); + Botan::secure_vector priv_bits = priv_key.private_key_bits(); + return std::make_pair( + pgp_sphincsplus_public_key_t( + pub_key->public_key_bits(), sphincsplus_param, sphincsplus_hash_func), + pgp_sphincsplus_private_key_t( + priv_bits.data(), priv_bits.size(), sphincsplus_param, sphincsplus_hash_func)); +} + +rnp_result_t +pgp_sphincsplus_generate(rnp::RNG * rng, + pgp_sphincsplus_key_t * material, + sphincsplus_parameter_t param, + pgp_pubkey_alg_t alg) +{ + auto keypair = + sphincsplus_generate_keypair(rng, param, rnp_sphincsplus_alg_to_hashfunc(alg)); + material->pub = keypair.first; + material->priv = keypair.second; + + return RNP_SUCCESS; +} + +bool +pgp_sphincsplus_public_key_t::validate_signature_hash_requirements( + pgp_hash_alg_t hash_alg) const +{ + /* check if key is allowed with the hash algorithm */ + return sphincsplus_hash_allowed(pk_alg_, sphincsplus_param_, hash_alg); +} + +bool +pgp_sphincsplus_public_key_t::is_valid(rnp::RNG *rng) const +{ + if (!is_initialized_) { + return false; + } + + auto key = botan_key(); + return key.check_key(*(rng->obj()), false); +} + +bool +pgp_sphincsplus_private_key_t::is_valid(rnp::RNG *rng) const +{ + if (!is_initialized_) { + return false; + } + + auto key = botan_key(); + return key.check_key(*(rng->obj()), false); +} + +rnp_result_t +sphincsplus_validate_key(rnp::RNG *rng, const pgp_sphincsplus_key_t *key, bool secret) +{ + bool valid; + + valid = key->pub.is_valid(rng); + if (secret) { + valid = valid && key->priv.is_valid(rng); + } + if (!valid) { + return RNP_ERROR_GENERIC; + } + + return RNP_SUCCESS; +} + +size_t +sphincsplus_privkey_size(sphincsplus_parameter_t param) +{ + return 2 * sphincsplus_pubkey_size(param); +} + +size_t +sphincsplus_pubkey_size(sphincsplus_parameter_t param) +{ + switch (param) { + case sphincsplus_simple_128s: + return 32; + case sphincsplus_simple_128f: + return 32; + case sphincsplus_simple_192s: + return 48; + case sphincsplus_simple_192f: + return 48; + case sphincsplus_simple_256s: + return 64; + case sphincsplus_simple_256f: + return 64; + default: + RNP_LOG("invalid sphincs+ parameter identifier"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +size_t +sphincsplus_signature_size(sphincsplus_parameter_t param) +{ + switch (param) { + case sphincsplus_simple_128s: + return 7856; + case sphincsplus_simple_128f: + return 17088; + case sphincsplus_simple_192s: + return 16224; + case sphincsplus_simple_192f: + return 35664; + case sphincsplus_simple_256s: + return 29792; + case sphincsplus_simple_256f: + return 49856; + default: + RNP_LOG("invalid sphincs+ parameter identifier"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +bool +sphincsplus_hash_allowed(pgp_pubkey_alg_t pk_alg, + sphincsplus_parameter_t sphincsplus_param, + pgp_hash_alg_t hash_alg) +{ + switch (pk_alg) { + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + /* draft-wussler-openpgp-pqc-02 Table 14*/ + switch (sphincsplus_param) { + case sphincsplus_simple_128s: + [[fallthrough]]; + case sphincsplus_simple_128f: + if (hash_alg != PGP_HASH_SHA256) { + return false; + } + break; + case sphincsplus_simple_192s: + [[fallthrough]]; + case sphincsplus_simple_192f: + [[fallthrough]]; + case sphincsplus_simple_256s: + [[fallthrough]]; + case sphincsplus_simple_256f: + if (hash_alg != PGP_HASH_SHA512) { + return false; + } + break; + } + default: + break; + } + return true; +} \ No newline at end of file diff --git a/src/lib/crypto/sphincsplus.h b/src/lib/crypto/sphincsplus.h new file mode 100644 index 00000000..c709bb89 --- /dev/null +++ b/src/lib/crypto/sphincsplus.h @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2023, [MTG AG](https://www.mtg.de). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SPHINCSPLUS_H_ +#define SPHINCSPLUS_H_ + +#include "config.h" +#include +#include +#include +#include "crypto/rng.h" +#include +#include + +struct pgp_sphincsplus_key_t; +struct pgp_sphincsplus_signature_t; + +typedef enum { sphincsplus_sha256, sphinscplus_shake256 } sphincsplus_hash_func_t; +typedef enum { + sphincsplus_simple_128s = 1, + sphincsplus_simple_128f = 2, + sphincsplus_simple_192s = 3, + sphincsplus_simple_192f = 4, + sphincsplus_simple_256s = 5, + sphincsplus_simple_256f = 6 +} sphincsplus_parameter_t; + +typedef struct pgp_sphincsplus_signature_t { + std::vector sig; + sphincsplus_parameter_t param; +} pgp_sphincsplus_signature_t; + +class pgp_sphincsplus_private_key_t { + public: + pgp_sphincsplus_private_key_t(const uint8_t * key_encoded, + size_t key_encoded_len, + sphincsplus_parameter_t sphincs_param, + sphincsplus_hash_func_t sphincs_hash_func); + pgp_sphincsplus_private_key_t(std::vector const &key_encoded, + sphincsplus_parameter_t sphincs_param, + sphincsplus_hash_func_t sphincs_hash_func); + pgp_sphincsplus_private_key_t(std::vector const &key_encoded, + sphincsplus_parameter_t param, + pgp_pubkey_alg_t alg); + pgp_sphincsplus_private_key_t() = default; + + bool is_valid(rnp::RNG *rng) const; + + sphincsplus_parameter_t + param() const + { + return sphincsplus_param_; + } + + pgp_pubkey_alg_t + alg() const + { + return pk_alg_; + } + + sphincsplus_hash_func_t + hash_func() const + { + return sphincsplus_hash_func_; + } + + rnp_result_t sign(rnp::RNG * rng, + pgp_sphincsplus_signature_t *sig, + const uint8_t * msg, + size_t msg_len) const; + std::vector + get_encoded() const + { + return Botan::unlock(key_encoded_); + }; + + void + secure_clear() + { + is_initialized_ = false; + Botan::zap(key_encoded_); + }; + + private: + Botan::SphincsPlus_PrivateKey botan_key() const; + + Botan::secure_vector key_encoded_; + pgp_pubkey_alg_t pk_alg_; + sphincsplus_parameter_t sphincsplus_param_; + sphincsplus_hash_func_t sphincsplus_hash_func_; + bool is_initialized_ = false; +}; + +class pgp_sphincsplus_public_key_t { + public: + pgp_sphincsplus_public_key_t(const uint8_t * key_encoded, + size_t key_encoded_len, + sphincsplus_parameter_t sphincs_param, + sphincsplus_hash_func_t sphincs_hash_func); + pgp_sphincsplus_public_key_t(std::vector const &key_encoded, + sphincsplus_parameter_t sphincs_param, + sphincsplus_hash_func_t sphincs_hash_func); + pgp_sphincsplus_public_key_t(std::vector const &key_encoded, + sphincsplus_parameter_t param, + pgp_pubkey_alg_t alg); + pgp_sphincsplus_public_key_t() = default; + + bool + operator==(const pgp_sphincsplus_public_key_t &rhs) const + { + return (sphincsplus_param_ == rhs.sphincsplus_param_) && + (sphincsplus_hash_func_ == rhs.sphincsplus_hash_func_) && + (key_encoded_ == rhs.key_encoded_); + } + + rnp_result_t verify(const pgp_sphincsplus_signature_t *sig, + const uint8_t * msg, + size_t msg_len) const; + + bool is_valid(rnp::RNG *rng) const; + + bool validate_signature_hash_requirements(pgp_hash_alg_t hash_alg) const; + + sphincsplus_parameter_t + param() const + { + return sphincsplus_param_; + } + + pgp_pubkey_alg_t + alg() const + { + return pk_alg_; + } + + std::vector + get_encoded() const + { + return key_encoded_; + }; + + private: + Botan::SphincsPlus_PublicKey botan_key() const; + + std::vector key_encoded_; + pgp_pubkey_alg_t pk_alg_; + sphincsplus_parameter_t sphincsplus_param_; + sphincsplus_hash_func_t sphincsplus_hash_func_; + bool is_initialized_ = false; +}; + +std::pair +sphincsplus_generate_keypair(rnp::RNG * rng, + sphincsplus_parameter_t sphincs_param, + sphincsplus_hash_func_t sphincs_hash_func); + +rnp_result_t pgp_sphincsplus_generate(rnp::RNG * rng, + pgp_sphincsplus_key_t * material, + sphincsplus_parameter_t param, + pgp_pubkey_alg_t alg); + +rnp_result_t sphincsplus_validate_key(rnp::RNG * rng, + const pgp_sphincsplus_key_t *key, + bool secret); + +typedef struct pgp_sphincsplus_key_t { + pgp_sphincsplus_public_key_t pub; + pgp_sphincsplus_private_key_t priv; +} pgp_sphincsplus_key_t; + +size_t sphincsplus_privkey_size(sphincsplus_parameter_t param); +size_t sphincsplus_pubkey_size(sphincsplus_parameter_t param); +size_t sphincsplus_signature_size(sphincsplus_parameter_t param); + +bool sphincsplus_hash_allowed(pgp_pubkey_alg_t pk_alg, + sphincsplus_parameter_t sphincsplus_param, + pgp_hash_alg_t hash_alg); + +#endif \ No newline at end of file diff --git a/src/lib/generate-key.cpp b/src/lib/generate-key.cpp index d61e9f2d..1ca3adaf 100644 --- a/src/lib/generate-key.cpp +++ b/src/lib/generate-key.cpp @@ -72,6 +72,8 @@ static const id_str_pair pubkey_alg_map[] = { {PGP_PKA_DILITHIUM5_P384, "Dilithium-P384"}, {PGP_PKA_DILITHIUM3_BP256, "Dilithium-BP256"}, {PGP_PKA_DILITHIUM5_BP384, "Dilithium-BP384"}, + {PGP_PKA_SPHINCSPLUS_SHA2, "SPHINCS+-SHA2"}, + {PGP_PKA_SPHINCSPLUS_SHAKE, "SPHINCS+-SHAKE"}, #endif {PGP_PKA_PRIVATE00, "Private/Experimental"}, {PGP_PKA_PRIVATE01, "Private/Experimental"}, @@ -301,6 +303,10 @@ get_numbits(const rnp_keygen_crypto_params_t *crypto) [[fallthrough]]; case PGP_PKA_DILITHIUM5_BP384: return pgp_dilithium_exdsa_composite_public_key_t::encoded_size(crypto->key_alg) * 8; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + return sphincsplus_pubkey_size(crypto->sphincsplus.param) * 8; #endif default: return 0; @@ -347,6 +353,26 @@ keygen_primary_merge_defaults(rnp_keygen_primary_desc_t &desc) } } +#if defined(ENABLE_PQC) +static bool +pgp_check_key_hash_requirements(rnp_keygen_crypto_params_t &crypto) +{ + switch (crypto.key_alg) { + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + if (!sphincsplus_hash_allowed( + crypto.key_alg, crypto.sphincsplus.param, crypto.hash_alg)) { + return false; + } + break; + default: + break; + } + return true; +} +#endif + bool pgp_generate_primary_key(rnp_keygen_primary_desc_t &desc, bool merge_defaults, @@ -370,6 +396,14 @@ pgp_generate_primary_key(rnp_keygen_primary_desc_t &desc, return false; } +#if defined(ENABLE_PQC) + // check hash requirements + if (!pgp_check_key_hash_requirements(desc.crypto)) { + RNP_LOG("invalid hash algorithm for the chosen key"); + return false; + } +#endif + // generate the raw key and fill tag/secret fields pgp_key_pkt_t secpkt; if (!pgp_generate_seckey(desc.crypto, secpkt, true, desc.pgp_version)) { @@ -466,6 +500,14 @@ pgp_generate_subkey(rnp_keygen_subkey_desc_t & desc, return false; } +#if defined(ENABLE_PQC) + // check hash requirements + if (!pgp_check_key_hash_requirements(desc.crypto)) { + RNP_LOG("invalid hash algorithm for the chosen key"); + return false; + } +#endif + try { /* decrypt the primary seckey if needed (for signatures) */ rnp::KeyLocker primlock(primary_sec); diff --git a/src/lib/pgp-key.cpp b/src/lib/pgp-key.cpp index 74de9ce0..58e62eef 100644 --- a/src/lib/pgp-key.cpp +++ b/src/lib/pgp-key.cpp @@ -230,6 +230,10 @@ pgp_pk_alg_capabilities(pgp_pubkey_alg_t alg) case PGP_PKA_DILITHIUM3_BP256: [[fallthrough]]; case PGP_PKA_DILITHIUM5_BP384: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: return pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY | PGP_KF_AUTH); #endif @@ -2850,6 +2854,10 @@ pgp_key_material_t::bits() const [[fallthrough]]; case PGP_PKA_DILITHIUM5_BP384: return 8 * dilithium_exdsa.pub.get_encoded().size(); /* public key length*/ + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + return 8 * sphincsplus.pub.get_encoded().size(); /* public key length */ #endif default: RNP_LOG("Unknown public key alg: %d", (int) alg); diff --git a/src/lib/rnp.cpp b/src/lib/rnp.cpp index 2aa40be1..18e6a178 100644 --- a/src/lib/rnp.cpp +++ b/src/lib/rnp.cpp @@ -179,6 +179,8 @@ static const id_str_pair pubkey_alg_map[] = { {PGP_PKA_DILITHIUM5_P384, RNP_ALGNAME_DILITHIUM5_P384}, {PGP_PKA_DILITHIUM3_BP256, RNP_ALGNAME_DILITHIUM3_BP256}, {PGP_PKA_DILITHIUM5_BP384, RNP_ALGNAME_DILITHIUM5_BP384}, + {PGP_PKA_SPHINCSPLUS_SHA2, RNP_ALGNAME_SPHINCSPLUS_SHA2}, + {PGP_PKA_SPHINCSPLUS_SHAKE, RNP_ALGNAME_SPHINCSPLUS_SHAKE}, #endif {0, NULL}}; @@ -357,6 +359,8 @@ pub_alg_supported(int alg) case PGP_PKA_DILITHIUM5_P384: case PGP_PKA_DILITHIUM3_BP256: case PGP_PKA_DILITHIUM5_BP384: + case PGP_PKA_SPHINCSPLUS_SHA2: + case PGP_PKA_SPHINCSPLUS_SHAKE: #endif return true; default: @@ -5280,6 +5284,10 @@ default_key_flags(pgp_pubkey_alg_t alg, bool subkey) case PGP_PKA_DILITHIUM3_BP256: [[fallthrough]]; case PGP_PKA_DILITHIUM5_BP384: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: return subkey ? PGP_KF_SIGN : pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY); #endif default: @@ -5693,6 +5701,39 @@ try { FFI_GUARD #endif +#if defined(ENABLE_PQC) +rnp_result_t +rnp_op_generate_set_sphincsplus_param(rnp_op_generate_t op, const char *param_cstr) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + + sphincsplus_parameter_t param; + std::string param_str = param_cstr; + + if (param_str == "128s") { + param = sphincsplus_simple_128s; + } else if (param_str == "128f") { + param = sphincsplus_simple_128f; + } else if (param_str == "192s") { + param = sphincsplus_simple_192s; + } else if (param_str == "192f") { + param = sphincsplus_simple_192f; + } else if (param_str == "256s") { + param = sphincsplus_simple_256s; + } else if (param_str == "256f") { + param = sphincsplus_simple_256f; + } else { + return RNP_ERROR_BAD_PARAMETERS; + } + + op->crypto.sphincsplus.param = param; + return RNP_SUCCESS; +} +FFI_GUARD +#endif + rnp_result_t rnp_op_generate_execute(rnp_op_generate_t op) try { @@ -7521,6 +7562,10 @@ add_json_public_mpis(json_object *jso, pgp_key_t *key) case PGP_PKA_DILITHIUM3_BP256: [[fallthrough]]; case PGP_PKA_DILITHIUM5_BP384: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: return RNP_SUCCESS; /* TODO */ #endif default: @@ -7615,6 +7660,10 @@ add_json_sig_mpis(json_object *jso, const pgp_signature_t *sig) case PGP_PKA_DILITHIUM3_BP256: [[fallthrough]]; case PGP_PKA_DILITHIUM5_BP384: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: return RNP_SUCCESS; /* TODO */ #endif default: @@ -7858,6 +7907,10 @@ key_to_json(json_object *jso, rnp_key_handle_t handle, uint32_t flags) case PGP_PKA_DILITHIUM3_BP256: [[fallthrough]]; case PGP_PKA_DILITHIUM5_BP384: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: return RNP_SUCCESS; /* TODO */ #endif default: diff --git a/src/lib/types.h b/src/lib/types.h index d2296125..34956814 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -195,6 +195,7 @@ typedef struct pgp_key_material_t { #if defined(ENABLE_PQC) pgp_kyber_ecdh_key_t kyber_ecdh; /* non-trivial type, cannot be in a union */ pgp_dilithium_exdsa_key_t dilithium_exdsa; /* non-trivial type, cannot be in a union */ + pgp_sphincsplus_key_t sphincsplus; /* non-trivial type, cannot be in a union */ #endif pgp_curve_t curve() @@ -220,7 +221,8 @@ typedef struct pgp_signature_material_t { #endif #if defined(ENABLE_PQC) pgp_dilithium_exdsa_signature_t - dilithium_exdsa; // non-trivial type cannot be member in union + dilithium_exdsa; // non-trivial type cannot be member in union + pgp_sphincsplus_signature_t sphincsplus; // non-trivial type cannot be member in union #endif } pgp_signature_material_t; @@ -457,6 +459,12 @@ struct rnp_keygen_elgamal_params_t { size_t key_bitlen; }; +#if defined(ENABLE_PQC) +struct rnp_keygen_sphincsplus_params_t { + sphincsplus_parameter_t param; +}; +#endif + /* structure used to hold context of key generation */ namespace rnp { class SecurityContext; @@ -474,6 +482,9 @@ typedef struct rnp_keygen_crypto_params_t { struct rnp_keygen_rsa_params_t rsa; struct rnp_keygen_dsa_params_t dsa; struct rnp_keygen_elgamal_params_t elgamal; +#if defined(ENABLE_PQC) + struct rnp_keygen_sphincsplus_params_t sphincsplus; +#endif }; } rnp_keygen_crypto_params_t; diff --git a/src/librekey/rnp_key_store.cpp b/src/librekey/rnp_key_store.cpp index e968cca4..2af71534 100644 --- a/src/librekey/rnp_key_store.cpp +++ b/src/librekey/rnp_key_store.cpp @@ -772,6 +772,11 @@ rnp_key_store_get_key_grip(const pgp_key_material_t *key, pgp_key_grip_t &grip) case PGP_PKA_DILITHIUM5_BP384: hash->add(key->dilithium_exdsa.pub.get_encoded()); break; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + hash->add(key->sphincsplus.pub.get_encoded()); + break; #endif default: RNP_LOG("unsupported public-key algorithm %d", (int) key->alg); diff --git a/src/librepgp/stream-dump.cpp b/src/librepgp/stream-dump.cpp index cd1c6732..30cd1ff0 100644 --- a/src/librepgp/stream-dump.cpp +++ b/src/librepgp/stream-dump.cpp @@ -160,6 +160,8 @@ static const id_str_pair pubkey_alg_map[] = { {PGP_PKA_DILITHIUM5_P384, "Dilithium5 + NIST P-384"}, {PGP_PKA_DILITHIUM3_BP256, "Dilithium3 + Brainpool256"}, {PGP_PKA_DILITHIUM5_BP384, "Dilithium5 + Brainpool384"}, + {PGP_PKA_SPHINCSPLUS_SHA2, "SPHINCS+-SHA2"}, + {PGP_PKA_SPHINCSPLUS_SHAKE, "SPHINCS+-SHAKE"}, #endif {0x00, NULL}, }; @@ -838,6 +840,11 @@ stream_dump_signature_pkt(rnp_dump_ctx_t *ctx, pgp_signature_t *sig, pgp_dest_t dst_print_vec( dst, "dilithium-ecdsa/eddsa sig", material.dilithium_exdsa.sig, ctx->dump_mpi); break; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + dst_print_vec(dst, "sphincs+ sig", material.sphincsplus.sig, ctx->dump_mpi); + break; #endif default: dst_printf(dst, "unknown algorithm\n"); @@ -972,6 +979,14 @@ stream_dump_key(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) key.material.dilithium_exdsa.pub.get_encoded(), ctx->dump_mpi); break; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + dst_print_vec(dst, + "sphincs+ encoded pubkey", + key.material.sphincsplus.pub.get_encoded(), + ctx->dump_mpi); + break; #endif default: dst_printf(dst, "unknown public key algorithm\n"); @@ -1984,6 +1999,11 @@ stream_dump_signature_pkt_json(rnp_dump_ctx_t * ctx, case PGP_PKA_DILITHIUM5_BP384: /* TODO */ break; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + /* TODO */ + break; #endif default: break; @@ -2129,6 +2149,11 @@ stream_dump_key_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt) case PGP_PKA_DILITHIUM5_BP384: /* TODO */ break; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + /* TODO */ + break; #endif default: break; diff --git a/src/librepgp/stream-key.cpp b/src/librepgp/stream-key.cpp index b4c9b8a3..ac95d548 100644 --- a/src/librepgp/stream-key.cpp +++ b/src/librepgp/stream-key.cpp @@ -693,6 +693,23 @@ parse_secret_key_mpis(pgp_key_pkt_t &key, const uint8_t *mpis, size_t len) key.material.dilithium_exdsa.priv = pgp_dilithium_exdsa_composite_private_key_t( tmpbuf.data(), tmpbuf.size(), key.alg); break; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: { + uint8_t param; + if (!body.get(param)) { + RNP_LOG("failed to parse sphincs+ secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + tmpbuf.resize(sphincsplus_privkey_size((sphincsplus_parameter_t) param)); + if (!body.get(tmpbuf.data(), tmpbuf.size())) { + RNP_LOG("failed to parse sphincs+ secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + key.material.sphincsplus.priv = + pgp_sphincsplus_private_key_t(tmpbuf, (sphincsplus_parameter_t) param, key.alg); + break; + } #endif default: RNP_LOG("unknown pk alg : %d", (int) key.alg); @@ -850,6 +867,12 @@ write_secret_key_mpis(pgp_packet_body_t &body, pgp_key_pkt_t &key) case PGP_PKA_DILITHIUM5_BP384: body.add(key.material.dilithium_exdsa.priv.get_encoded()); break; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + body.add_byte((uint8_t) key.material.sphincsplus.priv.param()); + body.add(key.material.sphincsplus.priv.get_encoded()); + break; #endif default: RNP_LOG("unknown pk alg : %d", (int) key.alg); @@ -1026,6 +1049,11 @@ forget_secret_key_fields(pgp_key_material_t *key) case PGP_PKA_DILITHIUM5_BP384: key->dilithium_exdsa.priv.secure_clear(); break; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + key->sphincsplus.priv.secure_clear(); + break; #endif default: RNP_LOG("unknown key algorithm: %d", (int) key->alg); @@ -1522,6 +1550,23 @@ pgp_key_pkt_t::parse(pgp_source_t &src) } material.dilithium_exdsa.pub = pgp_dilithium_exdsa_composite_public_key_t(tmpbuf, alg); break; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: { + uint8_t param; + if (!pkt.get(param)) { + RNP_LOG("failed to parse sphincs+ public key data"); + return RNP_ERROR_BAD_FORMAT; + } + tmpbuf.resize(sphincsplus_pubkey_size((sphincsplus_parameter_t) param)); + if (!pkt.get(tmpbuf.data(), tmpbuf.size())) { + RNP_LOG("failed to parse sphincs+ public key data"); + return RNP_ERROR_BAD_FORMAT; + } + material.sphincsplus.pub = + pgp_sphincsplus_public_key_t(tmpbuf, (sphincsplus_parameter_t) param, alg); + break; + } #endif default: RNP_LOG("unknown key algorithm: %d", (int) alg); @@ -1695,6 +1740,12 @@ pgp_key_pkt_t::make_alg_spec_fields_for_public_key(pgp_packet_body_t &hbody) case PGP_PKA_DILITHIUM5_BP384: hbody.add(material.dilithium_exdsa.pub.get_encoded()); break; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + hbody.add_byte((uint8_t) material.sphincsplus.pub.param()); + hbody.add(material.sphincsplus.pub.get_encoded()); + break; #endif default: RNP_LOG("unknown key algorithm: %d", (int) alg); diff --git a/src/librepgp/stream-sig.cpp b/src/librepgp/stream-sig.cpp index a852c9f6..87a9fcba 100644 --- a/src/librepgp/stream-sig.cpp +++ b/src/librepgp/stream-sig.cpp @@ -1583,6 +1583,23 @@ pgp_signature_t::parse_material(pgp_signature_material_t &material) const return false; } break; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: { + uint8_t param; + if (!pkt.get(param)) { + RNP_LOG("failed to parse sphincs+ signature data"); + return false; + } + material.sphincsplus.param = (sphincsplus_parameter_t) param; + material.sphincsplus.sig.resize( + sphincsplus_signature_size(material.sphincsplus.param)); + if (!pkt.get(material.sphincsplus.sig.data(), material.sphincsplus.sig.size())) { + RNP_LOG("failed to parse sphincs+ signature data"); + return false; + } + break; + } #endif default: RNP_LOG("Unknown pk algorithm : %d", (int) palg); @@ -1685,6 +1702,12 @@ pgp_signature_t::write_material(const pgp_signature_material_t &material) case PGP_PKA_DILITHIUM5_BP384: pktbody.add(material.dilithium_exdsa.sig); break; + case PGP_PKA_SPHINCSPLUS_SHA2: + [[fallthrough]]; + case PGP_PKA_SPHINCSPLUS_SHAKE: + pktbody.add_byte((uint8_t) material.sphincsplus.param); + pktbody.add(material.sphincsplus.sig); + break; #endif default: RNP_LOG("Unknown pk algorithm : %d", (int) palg); diff --git a/src/rnp/fficli.cpp b/src/rnp/fficli.cpp index 62f73a0f..72d8f169 100644 --- a/src/rnp/fficli.cpp +++ b/src/rnp/fficli.cpp @@ -1630,6 +1630,14 @@ cli_rnp_generate_key(cli_rnp_t *rnp, const char *username) rnp_op_generate_set_v6_key(genkey); } #endif +#if defined(ENABLE_PQC) + if (cfg.has(CFG_KG_PRIMARY_SPHINCSPLUS_PARAM) && + rnp_op_generate_set_sphincsplus_param( + genkey, cfg.get_cstr(CFG_KG_PRIMARY_SPHINCSPLUS_PARAM))) { + ERR_MSG("Failed to set sphincsplus parameter."); + goto done; + } +#endif fprintf(rnp->userio_out, "Generating a new key...\n"); if (rnp_op_generate_execute(genkey) || rnp_op_generate_get_key(genkey, &primary)) { @@ -1676,6 +1684,14 @@ cli_rnp_generate_key(cli_rnp_t *rnp, const char *username) if (cfg.get_bool(CFG_KG_V6_KEY)) { rnp_op_generate_set_v6_key(genkey); } +#endif +#if defined(ENABLE_PQC) + if (cfg.has(CFG_KG_SUBKEY_SPHINCSPLUS_PARAM) && + rnp_op_generate_set_sphincsplus_param( + genkey, cfg.get_cstr(CFG_KG_PRIMARY_SPHINCSPLUS_PARAM))) { + ERR_MSG("Failed to set sphincsplus parameter."); + goto done; + } #endif if (rnp_op_generate_execute(genkey) || rnp_op_generate_get_key(genkey, &subkey)) { ERR_MSG("Subkey generation failed."); diff --git a/src/rnp/rnpcfg.h b/src/rnp/rnpcfg.h index 9f891ba5..585ca174 100644 --- a/src/rnp/rnpcfg.h +++ b/src/rnp/rnpcfg.h @@ -121,6 +121,10 @@ #define CFG_KG_PROT_ITERATIONS "kg-prot-iterations" #define CFG_KG_V6_KEY \ "kg-v6-key" /* represents a boolean property: non-empty string means 'true' */ +#define CFG_KG_PRIMARY_SPHINCSPLUS_PARAM \ + "kg-primary-sphincsplus-param" /* 128f, 128s, 192f, 192s, 256f, 256s */ +#define CFG_KG_SUBKEY_SPHINCSPLUS_PARAM \ + "kg-subkey-sphincsplus-param" /* 128f, 128s, 192f, 192s, 256f, 256s */ /* rnp CLI config : contains all the system-dependent and specified by the user configuration * options */ diff --git a/src/rnpkeys/tui.cpp b/src/rnpkeys/tui.cpp index f397604f..e9717f71 100644 --- a/src/rnpkeys/tui.cpp +++ b/src/rnpkeys/tui.cpp @@ -231,11 +231,12 @@ rnpkeys_ask_generate_params(rnp_cfg &cfg, FILE *input_fp) if (!check_attempts(attempts)) { return false; } - printf("Please select what kind of key you want:\n" - "\t(1) RSA (Encrypt or Sign)\n" - "\t(16) DSA + ElGamal\n" - "\t(17) DSA + RSA\n" // TODO: See #584 - "\t(19) ECDSA + ECDH\n" + printf( + "Please select what kind of key you want:\n" + "\t(1) RSA (Encrypt or Sign)\n" + "\t(16) DSA + ElGamal\n" + "\t(17) DSA + RSA\n" // TODO: See #584 + "\t(19) ECDSA + ECDH\n" #if defined(ENABLE_CRYPTO_REFRESH) "\t(21) EDDSA + ECDH (v6 key) \n" "\t(22) EDDSA + ECDH (v4 key) \n" @@ -251,6 +252,10 @@ rnpkeys_ask_generate_params(rnp_cfg &cfg, FILE *input_fp) "\t(28) (Dilithium5 + ECDSA-NIST-P-384) + (Kyber1024 + ECDH-NIST-P-384)\n" "\t(29) (Dilithium3 + ECDSA-brainpoolP256r1) + (Kyber768 + ECDH-brainpoolP256r1)\n" "\t(30) (Dilithium5 + ECDSA-brainpoolP384r1) + (Kyber1024 + ECDH-brainpoolP384r1)\n" + "\t(31) SPHINCS+-SHA2-128f + (Kyber768 + X25519)\n" + "\t(32) SPHINCS+-SHAKE-128f + (Kyber768 + X25519)\n" + "\t(33) SPHINCS+-SHA2-256f + (Kyber1024 + ECDH-NIST-P-384)\n" + "\t(34) SPHINCS+-SHAKE-256f + (Kyber1024 + ECDH-NIST-P-384)\n" #endif "\t(99) SM2\n" "> "); @@ -349,6 +354,34 @@ rnpkeys_ask_generate_params(rnp_cfg &cfg, FILE *input_fp) cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_KYBER1024_BP384); cfg.set_str(CFG_KG_V6_KEY, "true"); break; + case 31: + cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_SPHINCSPLUS_SHA2); + cfg.set_str(CFG_KG_HASH, RNP_ALGNAME_SHA256); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_KYBER768_X25519); + cfg.set_str(CFG_KG_PRIMARY_SPHINCSPLUS_PARAM, "128f"); + cfg.set_str(CFG_KG_V6_KEY, "true"); + break; + case 32: + cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_SPHINCSPLUS_SHAKE); + cfg.set_str(CFG_KG_HASH, RNP_ALGNAME_SHA256); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_KYBER768_X25519); + cfg.set_str(CFG_KG_PRIMARY_SPHINCSPLUS_PARAM, "128f"); + cfg.set_str(CFG_KG_V6_KEY, "true"); + break; + case 33: + cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_SPHINCSPLUS_SHA2); + cfg.set_str(CFG_KG_HASH, RNP_ALGNAME_SHA512); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_KYBER1024_P384); + cfg.set_str(CFG_KG_PRIMARY_SPHINCSPLUS_PARAM, "256f"); + cfg.set_str(CFG_KG_V6_KEY, "true"); + break; + case 34: + cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_SPHINCSPLUS_SHAKE); + cfg.set_str(CFG_KG_HASH, RNP_ALGNAME_SHA512); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_KYBER1024_P384); + cfg.set_str(CFG_KG_PRIMARY_SPHINCSPLUS_PARAM, "256f"); + cfg.set_str(CFG_KG_V6_KEY, "true"); + break; #endif case 99: { cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_SM2); diff --git a/src/tests/cipher.cpp b/src/tests/cipher.cpp index 9162f339..9664d38e 100644 --- a/src/tests/cipher.cpp +++ b/src/tests/cipher.cpp @@ -683,6 +683,49 @@ TEST_F(rnp_tests, dilithium_exdsa_signverify_success) assert_rnp_failure(key2->pub.verify(&sig, hash_alg, message, sizeof(message))); } } + +TEST_F(rnp_tests, sphincsplus_signverify_success) +{ + uint8_t message[64]; + pgp_pubkey_alg_t algs[] = {PGP_PKA_SPHINCSPLUS_SHA2, PGP_PKA_SPHINCSPLUS_SHAKE}; + sphincsplus_parameter_t params[] = {sphincsplus_simple_128s, + sphincsplus_simple_128f, + sphincsplus_simple_192s, + sphincsplus_simple_192f, + sphincsplus_simple_256s, + sphincsplus_simple_256f}; + + for (size_t i = 0; i < ARRAY_SIZE(algs); i++) { + for (size_t j = 0; j < ARRAY_SIZE(params); j++) { + // Generate test data. Mainly to make valgrind not to complain about uninitialized + // data + global_ctx.rng.get(message, sizeof(message)); + + pgp_sphincsplus_signature_t sig; + rnp_keygen_crypto_params_t key_desc; + key_desc.key_alg = algs[i]; + key_desc.sphincsplus.param = params[j]; + key_desc.ctx = &global_ctx; + + pgp_key_pkt_t seckey1; + pgp_key_pkt_t seckey2; + + assert_true(pgp_generate_seckey(key_desc, seckey1, true)); + assert_true(pgp_generate_seckey(key_desc, seckey2, true)); + + const pgp_sphincsplus_key_t *key1 = &seckey1.material.sphincsplus; + const pgp_sphincsplus_key_t *key2 = &seckey2.material.sphincsplus; + + assert_rnp_success( + key1->priv.sign(&global_ctx.rng, &sig, message, sizeof(message))); + + assert_rnp_success(key1->pub.verify(&sig, message, sizeof(message))); + + // Fails because of different key used + assert_rnp_failure(key2->pub.verify(&sig, message, sizeof(message))); + } + } +} #endif // platforms known to not have a robust response can compile with diff --git a/src/tests/ffi.cpp b/src/tests/ffi.cpp index 50759a2f..7380cfe9 100644 --- a/src/tests/ffi.cpp +++ b/src/tests/ffi.cpp @@ -3113,7 +3113,7 @@ TEST_F(rnp_tests, test_ffi_supported_features) crypto_refresh_opt = 2; // X25519 + ED25519 #endif #if defined(ENABLE_PQC) - pqc_opt = 10; // kyber+ecc and dilithium+ecc variants + pqc_opt = 12; // kyber+ecc and dilithium+ecc and sphincs+ variants #endif assert_true(check_features( RNP_FEATURE_PK_ALG, features, 6 + has_sm2 + pqc_opt + crypto_refresh_opt)); diff --git a/src/tests/pqc.cpp b/src/tests/pqc.cpp index abb608b0..0b779d49 100644 --- a/src/tests/pqc.cpp +++ b/src/tests/pqc.cpp @@ -30,6 +30,7 @@ #include "rnp_tests.h" #include #include "crypto/dilithium.h" +#include "crypto/sphincsplus.h" #include "crypto/kyber.h" TEST_F(rnp_tests, test_kyber_key_function) @@ -65,6 +66,33 @@ TEST_F(rnp_tests, test_dilithium_key_function) } } +TEST_F(rnp_tests, test_sphincsplus_key_function) +{ + sphincsplus_parameter_t params[] = {sphincsplus_simple_128s, + sphincsplus_simple_128f, + sphincsplus_simple_192s, + sphincsplus_simple_192f, + sphincsplus_simple_256s, + sphincsplus_simple_256f}; + sphincsplus_hash_func_t hash_funcs[] = {sphincsplus_sha256, sphinscplus_shake256}; + + for (sphincsplus_parameter_t param : params) { + for (sphincsplus_hash_func_t hash_func : hash_funcs) { + auto public_and_private_key = + sphincsplus_generate_keypair(&global_ctx.rng, param, hash_func); + + std::array msg{'H', 'e', 'l', 'l', 'o'}; + + pgp_sphincsplus_signature_t sig; + assert_rnp_success(public_and_private_key.second.sign( + &global_ctx.rng, &sig, msg.data(), msg.size())); + + assert_rnp_success( + public_and_private_key.first.verify(&sig, msg.data(), msg.size())); + } + } +} + TEST_F(rnp_tests, test_dilithium_exdsa_direct) { pgp_pubkey_alg_t algs[] = {PGP_PKA_DILITHIUM3_ED25519,