diff --git a/CMakeLists.txt b/CMakeLists.txt index ecf2191b..522aeaf5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,14 @@ tristate_feature_auto(ENABLE_BLOWFISH "Enable Blowfish cipher support.") tristate_feature_auto(ENABLE_CAST5 "Enable CAST5 cipher support.") tristate_feature_auto(ENABLE_RIPEMD160 "Enable RIPEMD-160 hash support.") +option(ENABLE_CRYPTO_REFRESH "Enable crypto-refresh support (v6)") +option(ENABLE_PQC "Enable PQC support - requires ENABLE_CRYPTO_REFRESH") + +if((NOT ENABLE_CRYPTO_REFRESH) AND ENABLE_PQC) + message(FATAL_ERROR "ENABLE_PQC requires ENABLE_CRYPTO_REFRESH") +endif() + + set(ENABLE_DOC Auto CACHE STRING "Enable building documentation.") set_property(CACHE ENABLE_DOC PROPERTY STRINGS ${TRISTATE_VALUES}) diff --git a/include/repgp/repgp_def.h b/include/repgp/repgp_def.h index 218736c3..16dbae5e 100644 --- a/include/repgp/repgp_def.h +++ b/include/repgp/repgp_def.h @@ -32,6 +32,7 @@ #define REPGP_DEF_H_ #include +#include "config.h" /************************************/ /* Packet Tags - RFC4880, 4.2 */ @@ -94,8 +95,19 @@ #define PGP_KEY_ID_SIZE 8 /* Size of the fingerprint */ -#define PGP_FINGERPRINT_SIZE 20 -#define PGP_FINGERPRINT_HEX_SIZE (PGP_FINGERPRINT_SIZE * 2) + 1 +#define PGP_FINGERPRINT_V4_SIZE 20 +#if defined(ENABLE_CRYPTO_REFRESH) +#define PGP_FINGERPRINT_V6_SIZE 32 +#define PGP_MAX_FINGERPRINT_SIZE PGP_FINGERPRINT_V6_SIZE +#else +#define PGP_MAX_FINGERPRINT_SIZE PGP_FINGERPRINT_V4_SIZE +#endif +#define PGP_MAX_FINGERPRINT_HEX_SIZE (PGP_MAX_FINGERPRINT_SIZE * 2) + 1 + +/* SEIPDv2 salt length */ +#ifdef ENABLE_CRYPTO_REFRESH +#define PGP_SEIPDV2_SALT_LEN 32 +#endif /* Size of the key grip */ #define PGP_KEY_GRIP_SIZE 20 @@ -104,6 +116,11 @@ #define PGP_MARKER_CONTENTS "PGP" #define PGP_MARKER_LEN 3 +/* V6 Signature Salt */ +#if defined(ENABLE_CRYPTO_REFRESH) +#define PGP_MAX_SALT_SIZE_V6_SIG 32 +#endif + /** Old Packet Format Lengths. * Defines the meanings of the 2 bits for length type in the * old packet format. @@ -203,8 +220,43 @@ typedef enum : uint8_t { * (X9.42, as defined for * IETF-S/MIME) */ PGP_PKA_EDDSA = 22, /* EdDSA from draft-ietf-openpgp-rfc4880bis */ - PGP_PKA_SM2 = 99, /* SM2 encryption/signature schemes */ +#if defined(ENABLE_CRYPTO_REFRESH) + PGP_PKA_X25519 = 25, /* v6 / Crypto Refresh */ + PGP_PKA_ED25519 = 27, /* v6 / Crypto Refresh */ +#endif + +#if defined(ENABLE_PQC) + /* PQC-ECC composite */ + PGP_PKA_KYBER768_X25519 = 29, /* Kyber768 + X25519 from draft-wussler-openpgp-pqc-02 */ + // PGP_PKA_KYBER1024_X448 = 30, /* Kyer1024 + X448 from + // draft-wussler-openpgp-pqc-02 */ + PGP_PKA_KYBER768_P256 = 31, /* Kyber768 + NIST P-256 from draft-wussler-openpgp-pqc-02 */ + PGP_PKA_KYBER1024_P384 = 32, /* Kyber1024 + NIST P-384 from draft-wussler-openpgp-pqc-02 */ + PGP_PKA_KYBER768_BP256 = + 33, /* Kyber768 + Brainpool P256r1 from draft-wussler-openpgp-pqc-02 */ + PGP_PKA_KYBER1024_BP384 = + 34, /* Kyber1024 + Brainpool P384r1 from draft-wussler-openpgp-pqc-02 */ + + PGP_PKA_DILITHIUM3_ED25519 = + 35, /* Dilithium 3 + Ed25519 from draft-wussler-openpgp-pqc-02 */ + // PGP_PKA_DILITHIUM5_ED448 = 36, /* Dilithium 5 + Ed448 from + // draft-wussler-openpgp-pqc-02 */ + PGP_PKA_DILITHIUM3_P256 = + 37, /* Dilithium 3 + ECDSA-NIST-P-256 from draft-wussler-openpgp-pqc-02 */ + PGP_PKA_DILITHIUM5_P384 = + 38, /* Dilithium 5 + ECDSA-NIST-P-384 from draft-wussler-openpgp-pqc-02 */ + PGP_PKA_DILITHIUM3_BP256 = + 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 */ PGP_PKA_PRIVATE00 = 100, /* Private/Experimental Algorithm */ PGP_PKA_PRIVATE01 = 101, /* Private/Experimental Algorithm */ PGP_PKA_PRIVATE02 = 102, /* Private/Experimental Algorithm */ @@ -388,7 +440,11 @@ typedef enum { PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE = 32, /* embedded signature */ PGP_SIG_SUBPKT_ISSUER_FPR = 33, /* issuer fingerprint */ PGP_SIG_SUBPKT_PREFERRED_AEAD = 34, /* preferred AEAD algorithms */ - PGP_SIG_SUBPKT_PRIVATE_100 = 100, /* private/experimental subpackets */ +#if defined(ENABLE_CRYPTO_REFRESH) + /* PGP_SIG_SUBPKT_INTENDED_RECIPIENT_FINGERPRINT = 35, */ + PGP_SIG_SUBPKT_PREFERRED_AEAD_CIPHERSUITES = 39, +#endif + PGP_SIG_SUBPKT_PRIVATE_100 = 100, /* private/experimental subpackets */ PGP_SIG_SUBPKT_PRIVATE_101 = 101, PGP_SIG_SUBPKT_PRIVATE_102 = 102, PGP_SIG_SUBPKT_PRIVATE_103 = 103, @@ -423,7 +479,10 @@ typedef enum { typedef enum { PGP_KEY_FEATURE_MDC = 0x01, PGP_KEY_FEATURE_AEAD = 0x02, - PGP_KEY_FEATURE_V5 = 0x04 + PGP_KEY_FEATURE_V5 = 0x04, +#if defined(ENABLE_CRYPTO_REFRESH) + PGP_KEY_FEATURE_SEIPDV2 = 0x08 +#endif } pgp_key_feature_t; /** Types of Compression */ @@ -435,10 +494,19 @@ typedef enum { PGP_C_UNKNOWN = 255 } pgp_compression_type_t; -enum { PGP_SE_IP_DATA_VERSION = 1, PGP_PKSK_V3 = 3, PGP_SKSK_V4 = 4, PGP_SKSK_V5 = 5 }; +enum { PGP_SKSK_V4 = 4, PGP_SKSK_V5 = 5 }; +typedef enum { + PGP_PKSK_V3 = 3, +#if defined(ENABLE_CRYPTO_REFRESH) + PGP_PKSK_V6 = 6 +#endif +} pgp_pkesk_version_t; +typedef enum { PGP_SE_IP_DATA_V1 = 1, PGP_SE_IP_DATA_V2 = 2 } pgp_seipd_version_t; /** Version. * OpenPGP has two different protocol versions: version 3 and version 4. + * Also there is a draft that defines version 5, see + * https://datatracker.ietf.org/doc/draft-ietf-openpgp-crypto-refresh/ * * \see RFC4880 5.2 */ @@ -446,7 +514,10 @@ typedef enum { PGP_VUNKNOWN = 0, PGP_V2 = 2, /* Version 2 (essentially the same as v3) */ PGP_V3 = 3, /* Version 3 */ - PGP_V4 = 4 /* Version 4 */ + PGP_V4 = 4, /* Version 4 */ +#if defined(ENABLE_CRYPTO_REFRESH) + PGP_V6 = 6 /* Version 6 (crypto refresh) */ +#endif } pgp_version_t; typedef enum pgp_op_t { @@ -499,7 +570,15 @@ typedef enum pgp_key_store_format_t { } pgp_key_store_format_t; namespace rnp { -enum class AuthType { None, MDC, AEADv1 }; -} +enum class AuthType { + None, + MDC, + AEADv1, +#ifdef ENABLE_CRYPTO_REFRESH + AEADv2 +#endif +}; + +} // namespace rnp #endif diff --git a/include/rnp/rnp.h b/include/rnp/rnp.h index 8e7971ab..5017bb10 100644 --- a/include/rnp/rnp.h +++ b/include/rnp/rnp.h @@ -1176,6 +1176,31 @@ RNP_API rnp_result_t rnp_op_generate_clear_pref_ciphers(rnp_op_generate_t op); RNP_API rnp_result_t rnp_op_generate_set_pref_keyserver(rnp_op_generate_t op, const char * keyserver); +/** Set the generated key version to v6. + * 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. + * @return RNP_SUCCESS or error code if failed. + */ +RNP_API rnp_result_t rnp_op_generate_set_v6_key(rnp_op_generate_t op); + +/** 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); + /** 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. @@ -1716,6 +1741,14 @@ RNP_API rnp_result_t rnp_uid_remove(rnp_key_handle_t key, rnp_uid_handle_t uid); */ RNP_API rnp_result_t rnp_uid_handle_destroy(rnp_uid_handle_t uid); +/** + * @brief Get key's version as integer. + * + * @param key key handle, should not be NULL + * @return RNP_SUCCESS or error code on failure. + */ +RNP_API rnp_result_t rnp_key_get_version(rnp_key_handle_t handle, uint32_t *version); + /** Get number of the key's subkeys. * * @param key key handle. @@ -2972,6 +3005,17 @@ RNP_API rnp_result_t rnp_op_encrypt_create(rnp_op_encrypt_t *op, */ RNP_API rnp_result_t rnp_op_encrypt_add_recipient(rnp_op_encrypt_t op, rnp_key_handle_t key); +/** + * @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. + * + * @param op opaque encrypting context. Must be allocated and initialized. + * @return RNP_SUCCESS or errorcode if failed. + */ +RNP_API rnp_result_t rnp_op_encrypt_enable_pkesk_v6(rnp_op_encrypt_t op); + /** * @brief Add signature to encrypting context, so data will be encrypted and signed. * @@ -3368,6 +3412,22 @@ RNP_API const char *rnp_backend_version(); #define RNP_ALGNAME_ECDH "ECDH" #define RNP_ALGNAME_ECDSA "ECDSA" #define RNP_ALGNAME_EDDSA "EDDSA" +#define RNP_ALGNAME_ED25519 "ED25519" +#define RNP_ALGNAME_X25519 "X25519" +#define RNP_ALGNAME_KYBER768_X25519 "KYBER768_X25519" +#define RNP_ALGNAME_KYBER1024_X448 "KYBER1024_X448" +#define RNP_ALGNAME_KYBER768_P256 "KYBER768_P256" +#define RNP_ALGNAME_KYBER1024_P384 "KYBER1024_P384" +#define RNP_ALGNAME_KYBER768_BP256 "KYBER768_BP256" +#define RNP_ALGNAME_KYBER1024_BP384 "KYBER1024_BP384" +#define RNP_ALGNAME_DILITHIUM3_ED25519 "DILITHIUM3_ED25519" +#define RNP_ALGNAME_DILITHIUM5_ED448 "DILITHIUM5_ED448" +#define RNP_ALGNAME_DILITHIUM3_P256 "DILITHIUM3_P256" +#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" #define RNP_ALGNAME_IDEA "IDEA" #define RNP_ALGNAME_TRIPLEDES "TRIPLEDES" #define RNP_ALGNAME_CAST5 "CAST5" diff --git a/include/rnp/rnp_err.h b/include/rnp/rnp_err.h index f4ff179e..49bef4f4 100644 --- a/include/rnp/rnp_err.h +++ b/include/rnp/rnp_err.h @@ -57,6 +57,7 @@ enum { RNP_ERROR_KEY_NOT_FOUND, RNP_ERROR_NO_SUITABLE_KEY, RNP_ERROR_DECRYPT_FAILED, + RNP_ERROR_ENCRYPT_FAILED, RNP_ERROR_RNG, RNP_ERROR_SIGNING_FAILED, RNP_ERROR_NO_SIGNATURES_FOUND, diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 40715f99..620e98e5 100755 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -52,6 +52,20 @@ if(CRYPTO_BACKEND_BOTAN3) set(CMAKE_CXX_STANDARD 20) endif() +if(ENABLE_PQC) + if (NOT CRYPTO_BACKEND_BOTAN3) + message(FATAL_ERROR "ENABLE_PQC requires Botan 3 as crypto backend") + endif() + if (NOT ENABLE_CRYPTO_REFRESH) + message(FATAL_ERROR "ENABLE_PQC requires ENABLE_CRYPTO_REFRESH") + endif() +endif() + +# check that AEAD is enabled and not turned off for ENABLE_CRYPTO_REFRESH + if(ENABLE_CRYPTO_REFRESH AND (NOT ENABLE_AEAD)) + message(FATAL_ERROR "ENABLE_CRYPTO_REFRESH requires ENABLE_AEAD, but it's either Off or Auto and got turned off") + endif() + # generate a config.h include(CheckIncludeFileCXX) include(CheckCXXSymbolExists) @@ -173,10 +187,14 @@ if(CRYPTO_BACKEND_BOTAN) resolve_feature_state(ENABLE_AEAD "AEAD_EAX;AEAD_OCB") resolve_feature_state(ENABLE_TWOFISH "TWOFISH") resolve_feature_state(ENABLE_IDEA "IDEA") - # Botan supports Brainpool curves together with SECP via the ECC_GROUP define + resolve_feature_state(ENABLE_CRYPTO_REFRESH "HKDF") + resolve_feature_state(ENABLE_PQC "KMAC;DILITHIUM;KYBER;SPHINCS_PLUS_WITH_SHA2;SPHINCS_PLUS_WITH_SHAKE") resolve_feature_state(ENABLE_BLOWFISH "BLOWFISH") resolve_feature_state(ENABLE_CAST5 "CAST_128") resolve_feature_state(ENABLE_RIPEMD160 "RIPEMD_160") + # Botan supports Brainpool curves together with SECP via the ECC_GROUP define + + set(CMAKE_REQUIRED_INCLUDES) endif() if(CRYPTO_BACKEND_OPENSSL) @@ -212,6 +230,8 @@ if(CRYPTO_BACKEND_OPENSSL) openssl_nope(ENABLE_SM2 "it's on our roadmap, see https://github.com/rnpgp/rnp/issues/1877") #resolve_feature_state(ENABLE_SM2 "SM2;SM3;SM4-ECB") openssl_nope(ENABLE_TWOFISH "Twofish isn't and won't be supported by OpenSSL, see https://github.com/openssl/openssl/issues/2046") + openssl_nope(ENABLE_CRYPTO_REFRESH, "not yet implemented") + openssl_nope(ENABLE_PQC, "not yet implemented") endif() configure_file(config.h.in config.h) @@ -271,6 +291,28 @@ elseif(CRYPTO_BACKEND_BOTAN) if(ENABLE_SM2) list(APPEND CRYPTO_SOURCES crypto/sm2.cpp) endif() + if(ENABLE_CRYPTO_REFRESH) + list(APPEND CRYPTO_SOURCES + crypto/hkdf.cpp + crypto/hkdf_botan.cpp + crypto/ed25519.cpp + crypto/x25519.cpp + crypto/exdsa_ecdhkem.cpp + ) + endif() + if(ENABLE_PQC) + list(APPEND CRYPTO_SOURCES + crypto/dilithium.cpp + crypto/dilithium_common.cpp + crypto/sphincsplus.cpp + crypto/kyber_common.cpp + crypto/kyber.cpp + crypto/kyber_ecdh_composite.cpp + crypto/dilithium_exdsa_composite.cpp + crypto/kmac.cpp + crypto/kmac_botan.cpp + ) + endif() else() message(FATAL_ERROR "Unknown crypto backend: ${CRYPTO_BACKEND}.") endif() @@ -279,6 +321,14 @@ list(APPEND CRYPTO_SOURCES crypto/backend_version.cpp) # sha11collisiondetection sources list(APPEND CRYPTO_SOURCES crypto/hash_sha1cd.cpp crypto/sha1cd/sha1.c crypto/sha1cd/ubc_check.c) + +set(CRYPTO_REFRESH_SOURCES ) +if(ENABLE_CRYPTO_REFRESH) + list(APPEND CRYPTO_REFRESH_SOURCES + ../librepgp/v2_seipd.cpp + ) +endif() + add_library(librnp-obj OBJECT # librepgp ../librepgp/stream-armor.cpp @@ -290,6 +340,7 @@ add_library(librnp-obj OBJECT ../librepgp/stream-parse.cpp ../librepgp/stream-sig.cpp ../librepgp/stream-write.cpp + ${CRYPTO_REFRESH_SOURCES} # librekey ../librekey/key_store_g10.cpp diff --git a/src/lib/config.h.in b/src/lib/config.h.in index 3b31ec99..0e938e9b 100644 --- a/src/lib/config.h.in +++ b/src/lib/config.h.in @@ -60,6 +60,8 @@ #cmakedefine ENABLE_TWOFISH #cmakedefine ENABLE_BRAINPOOL #cmakedefine ENABLE_IDEA +#cmakedefine ENABLE_CRYPTO_REFRESH +#cmakedefine ENABLE_PQC #cmakedefine ENABLE_BLOWFISH #cmakedefine ENABLE_CAST5 #cmakedefine ENABLE_RIPEMD160 @@ -71,3 +73,10 @@ ((defined(__clang__) && (__clang_major__ >= 4)) ) #define RNP_USE_STD_REGEX 1 #endif + +/* do not use the statement for old MSVC versions */ +#if (!defined(_MSVC_LANG) || _MSVC_LANG >= 201703L) +# define FALLTHROUGH_STATEMENT [[fallthrough]]; +#else +# define FALLTHROUGH_STATEMENT +#endif \ No newline at end of file diff --git a/src/lib/crypto.cpp b/src/lib/crypto.cpp index da3cba1c..59126b69 100644 --- a/src/lib/crypto.cpp +++ b/src/lib/crypto.cpp @@ -83,11 +83,16 @@ __RCSID("$NetBSD: crypto.c,v 1.36 2014/02/17 07:39:19 agc Exp $"); bool pgp_generate_seckey(const rnp_keygen_crypto_params_t &crypto, pgp_key_pkt_t & seckey, - bool primary) + bool primary, + pgp_version_t pgp_version) { /* populate pgp key structure */ seckey = {}; +#if defined(ENABLE_CRYPTO_REFRESH) + seckey.version = pgp_version; +#else seckey.version = PGP_V4; +#endif seckey.creation_time = crypto.ctx->time(); seckey.alg = crypto.key_alg; seckey.material.alg = crypto.key_alg; @@ -128,9 +133,7 @@ pgp_generate_seckey(const rnp_keygen_crypto_params_t &crypto, seckey.material.ec.curve = crypto.ecc.curve; break; } -#if (!defined(_MSVC_LANG) || _MSVC_LANG >= 201703L) - [[fallthrough]]; -#endif + FALLTHROUGH_STATEMENT; case PGP_PKA_ECDSA: case PGP_PKA_SM2: if (!curve_supported(crypto.ecc.curve)) { @@ -150,6 +153,70 @@ pgp_generate_seckey(const rnp_keygen_crypto_params_t &crypto, return false; } break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + if (generate_ed25519_native(&crypto.ctx->rng, + seckey.material.ed25519.priv, + seckey.material.ed25519.pub) != RNP_SUCCESS) { + RNP_LOG("failed to generate ED25519 key"); + return false; + } + break; + case PGP_PKA_X25519: + if (generate_x25519_native(&crypto.ctx->rng, + seckey.material.x25519.priv, + seckey.material.x25519.pub) != RNP_SUCCESS) { + RNP_LOG("failed to generate X25519 key"); + return false; + } + break; +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + if (pgp_kyber_ecdh_composite_key_t::gen_keypair( + &crypto.ctx->rng, &seckey.material.kyber_ecdh, seckey.alg)) { + RNP_LOG("failed to generate Kyber-ECDH-composite key for PK alg %d", seckey.alg); + return false; + } + break; + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + if (pgp_dilithium_exdsa_composite_key_t::gen_keypair( + &crypto.ctx->rng, &seckey.material.dilithium_exdsa, seckey.alg)) { + RNP_LOG("failed to generate Dilithium-ecdsa/eddsa-composite key for PK alg %d", + seckey.alg); + return false; + } + break; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + 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); return false; @@ -190,6 +257,40 @@ key_material_equal(const pgp_key_material_t *key1, const pgp_key_material_t *key case PGP_PKA_ECDSA: case PGP_PKA_SM2: return (key1->ec.curve == key2->ec.curve) && mpi_equal(&key1->ec.p, &key2->ec.p); +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + return (key1->ed25519.pub == key2->ed25519.pub); + case PGP_PKA_X25519: + return (key1->x25519.pub == key2->x25519.pub); +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + return (key1->kyber_ecdh.pub == key2->kyber_ecdh.pub); + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + return (key1->dilithium_exdsa.pub == key2->dilithium_exdsa.pub); + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHAKE: + return (key1->sphincsplus.pub == key2->sphincsplus.pub); +#endif default: RNP_LOG("unknown public key algorithm: %d", (int) key1->alg); return false; @@ -237,6 +338,40 @@ validate_pgp_key_material(const pgp_key_material_t *material, rnp::RNG *rng) case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: return elgamal_validate_key(&material->eg, material->secret) ? RNP_SUCCESS : RNP_ERROR_GENERIC; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + return ed25519_validate_key_native(rng, &material->ed25519, material->secret); + case PGP_PKA_X25519: + return x25519_validate_key_native(rng, &material->x25519, material->secret); +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + return kyber_ecdh_validate_key(rng, &material->kyber_ecdh, material->secret); + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + return dilithium_exdsa_validate_key(rng, &material->dilithium_exdsa, material->secret); + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + 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.h b/src/lib/crypto.h index 320daf8d..4fd1f81d 100644 --- a/src/lib/crypto.h +++ b/src/lib/crypto.h @@ -62,7 +62,8 @@ /* raw key generation */ bool pgp_generate_seckey(const rnp_keygen_crypto_params_t ¶ms, pgp_key_pkt_t & seckey, - bool primary); + bool primary, + pgp_version_t pgp_version = PGP_V4); /** generate a new primary key * diff --git a/src/lib/crypto/common.h b/src/lib/crypto/common.h index 3d9b8378..cfa73e24 100644 --- a/src/lib/crypto/common.h +++ b/src/lib/crypto/common.h @@ -39,6 +39,15 @@ #include "ecdsa.h" #include "sm2.h" #include "eddsa.h" +#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" +#include "ed25519.h" +#endif /* symmetric crypto */ #include "symmetric.h" /* hash */ diff --git a/src/lib/crypto/dilithium.cpp b/src/lib/crypto/dilithium.cpp new file mode 100644 index 00000000..bd7f112b --- /dev/null +++ b/src/lib/crypto/dilithium.cpp @@ -0,0 +1,137 @@ +/* + * 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 "dilithium.h" +#include + +namespace { + +Botan::DilithiumMode +rnp_dilithium_param_to_botan_dimension(dilithium_parameter_e mode) +{ + Botan::DilithiumMode result = Botan::DilithiumMode::Dilithium8x7; + if (mode == dilithium_parameter_e::dilithium_L3) { + result = Botan::DilithiumMode::Dilithium6x5; + } + return result; +} + +} // namespace + +std::vector +pgp_dilithium_private_key_t::sign(rnp::RNG *rng, 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(), ""); + std::vector signature = signer.sign_message(msg, msg_len, *rng->obj()); + // std::vector signature; + + return signature; +} + +Botan::Dilithium_PublicKey +pgp_dilithium_public_key_t::botan_key() const +{ + return Botan::Dilithium_PublicKey( + key_encoded_, rnp_dilithium_param_to_botan_dimension(dilithium_param_)); +} + +Botan::Dilithium_PrivateKey +pgp_dilithium_private_key_t::botan_key() const +{ + Botan::secure_vector priv_sv(key_encoded_.data(), + key_encoded_.data() + key_encoded_.size()); + return Botan::Dilithium_PrivateKey( + priv_sv, rnp_dilithium_param_to_botan_dimension(this->dilithium_param_)); +} + +bool +pgp_dilithium_public_key_t::verify_signature(const uint8_t *msg, + size_t msg_len, + const uint8_t *signature, + size_t signature_len) const +{ + assert(is_initialized_); + auto pub_key = botan_key(); + + auto verificator = Botan::PK_Verifier(pub_key, ""); + return verificator.verify_message(msg, msg_len, signature, signature_len); +} + +std::pair +dilithium_generate_keypair(rnp::RNG *rng, dilithium_parameter_e dilithium_param) +{ + Botan::Dilithium_PrivateKey priv_key( + *rng->obj(), rnp_dilithium_param_to_botan_dimension(dilithium_param)); + + std::unique_ptr pub_key = priv_key.public_key(); + Botan::secure_vector priv_bits = priv_key.private_key_bits(); + return std::make_pair( + pgp_dilithium_public_key_t(pub_key->public_key_bits(), dilithium_param), + pgp_dilithium_private_key_t(priv_bits.data(), priv_bits.size(), dilithium_param)); +} + +bool +pgp_dilithium_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_dilithium_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); +} + +bool +dilithium_hash_allowed(pgp_hash_alg_t hash_alg) +{ + switch (hash_alg) { + case PGP_HASH_SHA3_256: + case PGP_HASH_SHA3_512: + return true; + default: + return false; + } +} + +pgp_hash_alg_t +dilithium_default_hash_alg() +{ + return PGP_HASH_SHA3_256; +} \ No newline at end of file diff --git a/src/lib/crypto/dilithium.h b/src/lib/crypto/dilithium.h new file mode 100644 index 00000000..18035ce0 --- /dev/null +++ b/src/lib/crypto/dilithium.h @@ -0,0 +1,116 @@ +/* + * 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 DILITHIUM_H_ +#define DILITHIUM_H_ + +#include "config.h" +#include +#include +#include +#include "crypto/rng.h" +#include +#include + +enum dilithium_parameter_e { dilithium_L3, dilithium_L5 }; + +class pgp_dilithium_private_key_t { + public: + pgp_dilithium_private_key_t(const uint8_t * key_encoded, + size_t key_encoded_len, + dilithium_parameter_e param); + pgp_dilithium_private_key_t(std::vector const &key_encoded, + dilithium_parameter_e param); + pgp_dilithium_private_key_t() = default; + + bool is_valid(rnp::RNG *rng) const; + + dilithium_parameter_e + param() const + { + return dilithium_param_; + } + + std::vector sign(rnp::RNG *rng, const uint8_t *msg, size_t msg_len) const; + std::vector + get_encoded() const + { + return Botan::unlock(key_encoded_); + }; + + private: + Botan::Dilithium_PrivateKey botan_key() const; + + Botan::secure_vector key_encoded_; + dilithium_parameter_e dilithium_param_; + bool is_initialized_ = false; +}; + +class pgp_dilithium_public_key_t { + public: + pgp_dilithium_public_key_t(const uint8_t * key_encoded, + size_t key_encoded_len, + dilithium_parameter_e mode); + pgp_dilithium_public_key_t(std::vector const &key_encoded, + dilithium_parameter_e mode); + pgp_dilithium_public_key_t() = default; + + bool + operator==(const pgp_dilithium_public_key_t &rhs) const + { + return (dilithium_param_ == rhs.dilithium_param_) && + (key_encoded_ == rhs.key_encoded_); + } + + bool verify_signature(const uint8_t *msg, + size_t msg_len, + const uint8_t *signature, + size_t signature_len) const; + + bool is_valid(rnp::RNG *rng) const; + + std::vector + get_encoded() const + { + return key_encoded_; + }; + + private: + Botan::Dilithium_PublicKey botan_key() const; + + std::vector key_encoded_; + dilithium_parameter_e dilithium_param_; + bool is_initialized_ = false; +}; + +std::pair dilithium_generate_keypair( + rnp::RNG *rng, dilithium_parameter_e dilithium_param); + +bool dilithium_hash_allowed(pgp_hash_alg_t hash_alg); + +pgp_hash_alg_t dilithium_default_hash_alg(); + +#endif diff --git a/src/lib/crypto/dilithium_common.cpp b/src/lib/crypto/dilithium_common.cpp new file mode 100644 index 00000000..d28c7b67 --- /dev/null +++ b/src/lib/crypto/dilithium_common.cpp @@ -0,0 +1,100 @@ +/* + * 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 "dilithium.h" +#include "types.h" +#include "logging.h" + +pgp_dilithium_public_key_t::pgp_dilithium_public_key_t(const uint8_t * key_encoded, + size_t key_encoded_len, + dilithium_parameter_e param) + : key_encoded_(key_encoded, key_encoded + key_encoded_len), dilithium_param_(param), + is_initialized_(true) +{ +} + +pgp_dilithium_public_key_t::pgp_dilithium_public_key_t(std::vector const &key_encoded, + dilithium_parameter_e param) + : key_encoded_(key_encoded), dilithium_param_(param), is_initialized_(true) +{ +} + +pgp_dilithium_private_key_t::pgp_dilithium_private_key_t(const uint8_t * key_encoded, + size_t key_encoded_len, + dilithium_parameter_e param) + : key_encoded_(key_encoded, key_encoded + key_encoded_len), dilithium_param_(param), + is_initialized_(true) +{ +} + +pgp_dilithium_private_key_t::pgp_dilithium_private_key_t( + std::vector const &key_encoded, dilithium_parameter_e param) + : key_encoded_(Botan::secure_vector(key_encoded.begin(), key_encoded.end())), + dilithium_param_(param), is_initialized_(true) +{ +} + +size_t +dilithium_privkey_size(dilithium_parameter_e parameter) +{ + switch (parameter) { + case dilithium_L3: + return 4000; + case dilithium_L5: + return 4864; + default: + RNP_LOG("invalid parameter given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +size_t +dilithium_pubkey_size(dilithium_parameter_e parameter) +{ + switch (parameter) { + case dilithium_L3: + return 1952; + case dilithium_L5: + return 2592; + default: + RNP_LOG("invalid parameter given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +size_t +dilithium_signature_size(dilithium_parameter_e parameter) +{ + switch (parameter) { + case dilithium_L3: + return 3293; + case dilithium_L5: + return 4595; + default: + RNP_LOG("invalid parameter given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} diff --git a/src/lib/crypto/dilithium_common.h b/src/lib/crypto/dilithium_common.h new file mode 100644 index 00000000..40b9a961 --- /dev/null +++ b/src/lib/crypto/dilithium_common.h @@ -0,0 +1,36 @@ +/* + * 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 RNP_DILITHIUM_COMMON_H_ +#define RNP_DILITHIUM_COMMON_H_ + +#include "dilithium.h" + +size_t dilithium_privkey_size(dilithium_parameter_e parameter); +size_t dilithium_pubkey_size(dilithium_parameter_e parameter); +size_t dilithium_signature_size(dilithium_parameter_e parameter); + +#endif diff --git a/src/lib/crypto/dilithium_exdsa_composite.cpp b/src/lib/crypto/dilithium_exdsa_composite.cpp new file mode 100644 index 00000000..cd2ed80c --- /dev/null +++ b/src/lib/crypto/dilithium_exdsa_composite.cpp @@ -0,0 +1,454 @@ +/* + * 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 "dilithium_exdsa_composite.h" +#include "types.h" +#include "logging.h" + +pgp_dilithium_exdsa_composite_key_t::~pgp_dilithium_exdsa_composite_key_t() +{ +} + +void +pgp_dilithium_exdsa_composite_key_t::initialized_or_throw() const +{ + if (!is_initialized()) { + RNP_LOG("Trying to use uninitialized dilithium-ecdsa/eddsa key"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } +} + +rnp_result_t +pgp_dilithium_exdsa_composite_key_t::gen_keypair(rnp::RNG * rng, + pgp_dilithium_exdsa_key_t *key, + pgp_pubkey_alg_t alg) +{ + rnp_result_t res; + pgp_curve_t curve = pk_alg_to_curve_id(alg); + dilithium_parameter_e dilithium_id = pk_alg_to_dilithium_id(alg); + + exdsa_key_t exdsa_key_pair; + + res = ec_key_t::generate_exdsa_key_pair(rng, &exdsa_key_pair, curve); + if (res != RNP_SUCCESS) { + RNP_LOG("generating dilithium exdsa composite key failed when generating exdsa key"); + return res; + } + + auto dilithium_key_pair = dilithium_generate_keypair(rng, dilithium_id); + + key->priv = pgp_dilithium_exdsa_composite_private_key_t( + exdsa_key_pair.priv.get_encoded(), dilithium_key_pair.second.get_encoded(), alg); + key->pub = pgp_dilithium_exdsa_composite_public_key_t( + exdsa_key_pair.pub.get_encoded(), dilithium_key_pair.first.get_encoded(), alg); + + return RNP_SUCCESS; +} + +size_t +pgp_dilithium_exdsa_composite_key_t::exdsa_curve_privkey_size(pgp_curve_t curve) +{ + switch (curve) { + case PGP_CURVE_ED25519: + return 32; + /* TODO */ + // case PGP_CURVE_ED448: + // return 56; + case PGP_CURVE_NIST_P_256: + return 32; + case PGP_CURVE_NIST_P_384: + return 48; + case PGP_CURVE_BP256: + return 32; + case PGP_CURVE_BP384: + return 48; + default: + RNP_LOG("invalid curve given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +size_t +pgp_dilithium_exdsa_composite_key_t::exdsa_curve_pubkey_size(pgp_curve_t curve) +{ + switch (curve) { + case PGP_CURVE_ED25519: + return 32; + /* TODO */ + // case PGP_CURVE_ED448: + // return 56; + case PGP_CURVE_NIST_P_256: + return 65; + case PGP_CURVE_NIST_P_384: + return 97; + case PGP_CURVE_BP256: + return 65; + case PGP_CURVE_BP384: + return 97; + default: + RNP_LOG("invalid curve given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +size_t +pgp_dilithium_exdsa_composite_key_t::exdsa_curve_signature_size(pgp_curve_t curve) +{ + switch (curve) { + case PGP_CURVE_ED25519: + return 64; + /* TODO */ + // case PGP_CURVE_ED448: + // return 114; + case PGP_CURVE_NIST_P_256: + return 64; + case PGP_CURVE_NIST_P_384: + return 96; + case PGP_CURVE_BP256: + return 64; + case PGP_CURVE_BP384: + return 96; + default: + RNP_LOG("invalid curve given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +dilithium_parameter_e +pgp_dilithium_exdsa_composite_key_t::pk_alg_to_dilithium_id(pgp_pubkey_alg_t pk_alg) +{ + switch (pk_alg) { + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + return dilithium_L3; + case PGP_PKA_DILITHIUM5_BP384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + return dilithium_L5; + default: + RNP_LOG("invalid PK alg given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +pgp_curve_t +pgp_dilithium_exdsa_composite_key_t::pk_alg_to_curve_id(pgp_pubkey_alg_t pk_alg) +{ + switch (pk_alg) { + case PGP_PKA_DILITHIUM3_ED25519: + return PGP_CURVE_ED25519; + case PGP_PKA_DILITHIUM3_P256: + return PGP_CURVE_NIST_P_256; + case PGP_PKA_DILITHIUM3_BP256: + return PGP_CURVE_BP256; + case PGP_PKA_DILITHIUM5_BP384: + return PGP_CURVE_BP384; + case PGP_PKA_DILITHIUM5_P384: + return PGP_CURVE_NIST_P_384; + /*case PGP_PKA_DILITHIUM5_ED448: + throw rnp::rnp_exception(RNP_ERROR_NOT_IMPLEMENTED);*/ + default: + RNP_LOG("invalid PK alg given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +pgp_dilithium_exdsa_composite_public_key_t::pgp_dilithium_exdsa_composite_public_key_t( + const uint8_t *key_encoded, size_t key_encoded_len, pgp_pubkey_alg_t pk_alg) + : pk_alg_(pk_alg) +{ + parse_component_keys(std::vector(key_encoded, key_encoded + key_encoded_len)); +} + +pgp_dilithium_exdsa_composite_public_key_t::pgp_dilithium_exdsa_composite_public_key_t( + std::vector const &key_encoded, pgp_pubkey_alg_t pk_alg) + : pk_alg_(pk_alg) +{ + parse_component_keys(key_encoded); +} + +pgp_dilithium_exdsa_composite_public_key_t::pgp_dilithium_exdsa_composite_public_key_t( + std::vector const &exdsa_key_encoded, + std::vector const &dilithium_key_encoded, + pgp_pubkey_alg_t pk_alg) + : pk_alg_(pk_alg), dilithium_key_(dilithium_key_encoded, pk_alg_to_dilithium_id(pk_alg)), + exdsa_key_(exdsa_key_encoded, pk_alg_to_curve_id(pk_alg)) +{ + if (exdsa_curve_pubkey_size(pk_alg_to_curve_id(pk_alg)) != exdsa_key_encoded.size() || + dilithium_pubkey_size(pk_alg_to_dilithium_id(pk_alg)) != + dilithium_key_encoded.size()) { + RNP_LOG("exdsa or dilithium key length mismatch"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + is_initialized_ = true; +} + +pgp_dilithium_exdsa_composite_private_key_t::pgp_dilithium_exdsa_composite_private_key_t( + const uint8_t *key_encoded, size_t key_encoded_len, pgp_pubkey_alg_t pk_alg) + : pk_alg_(pk_alg) +{ + parse_component_keys(std::vector(key_encoded, key_encoded + key_encoded_len)); +} + +pgp_dilithium_exdsa_composite_private_key_t::pgp_dilithium_exdsa_composite_private_key_t( + std::vector const &key_encoded, pgp_pubkey_alg_t pk_alg) + : pk_alg_(pk_alg) +{ + parse_component_keys(key_encoded); +} + +/* copy assignment operator is used on key materials struct and thus needs to be defined for + * this class as well */ +pgp_dilithium_exdsa_composite_private_key_t & +pgp_dilithium_exdsa_composite_private_key_t::operator=( + const pgp_dilithium_exdsa_composite_private_key_t &other) +{ + pgp_dilithium_exdsa_composite_key_t::operator=(other); + pk_alg_ = other.pk_alg_; + if (other.is_initialized() && other.dilithium_key_) { + dilithium_key_ = + std::make_unique(pgp_dilithium_private_key_t( + other.dilithium_key_->get_encoded(), other.dilithium_key_->param())); + } + if (other.is_initialized() && other.exdsa_key_) { + exdsa_key_ = std::make_unique( + exdsa_private_key_t(other.exdsa_key_->get_encoded(), other.exdsa_key_->get_curve())); + } + + return *this; +} + +pgp_dilithium_exdsa_composite_private_key_t::pgp_dilithium_exdsa_composite_private_key_t( + std::vector const &exdsa_key_encoded, + std::vector const &dilithium_key_encoded, + pgp_pubkey_alg_t pk_alg) + : pk_alg_(pk_alg) +{ + if (exdsa_curve_privkey_size(pk_alg_to_curve_id(pk_alg)) != exdsa_key_encoded.size() || + dilithium_privkey_size(pk_alg_to_dilithium_id(pk_alg)) != + dilithium_key_encoded.size()) { + RNP_LOG("exdsa or dilithium key length mismatch"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + dilithium_key_ = std::make_unique( + pgp_dilithium_private_key_t(dilithium_key_encoded, pk_alg_to_dilithium_id(pk_alg))); + exdsa_key_ = std::make_unique( + exdsa_private_key_t(exdsa_key_encoded, pk_alg_to_curve_id(pk_alg))); + is_initialized_ = true; +} + +size_t +pgp_dilithium_exdsa_composite_private_key_t::encoded_size(pgp_pubkey_alg_t pk_alg) +{ + dilithium_parameter_e dilithium_param = pk_alg_to_dilithium_id(pk_alg); + pgp_curve_t curve = pk_alg_to_curve_id(pk_alg); + return exdsa_curve_privkey_size(curve) + dilithium_privkey_size(dilithium_param); +} + +void +pgp_dilithium_exdsa_composite_private_key_t::parse_component_keys( + std::vector key_encoded) +{ + if (key_encoded.size() != encoded_size(pk_alg_)) { + RNP_LOG("Dilithium composite key format invalid: length mismatch"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + dilithium_parameter_e dilithium_param = pk_alg_to_dilithium_id(pk_alg_); + pgp_curve_t curve = pk_alg_to_curve_id(pk_alg_); + size_t split_at = exdsa_curve_privkey_size(pk_alg_to_curve_id(pk_alg_)); + + dilithium_key_ = std::make_unique(pgp_dilithium_private_key_t( + key_encoded.data() + split_at, key_encoded.size() - split_at, dilithium_param)); + exdsa_key_ = std::make_unique( + exdsa_private_key_t(key_encoded.data(), split_at, curve)); + + is_initialized_ = true; +} + +std::vector +pgp_dilithium_exdsa_composite_private_key_t::get_encoded() const +{ + initialized_or_throw(); + std::vector result; + std::vector exdsa_key_encoded = exdsa_key_->get_encoded(); + std::vector dilithium_key_encoded = dilithium_key_->get_encoded(); + + result.insert(result.end(), std::begin(exdsa_key_encoded), std::end(exdsa_key_encoded)); + result.insert( + result.end(), std::begin(dilithium_key_encoded), std::end(dilithium_key_encoded)); + return result; +}; + +rnp_result_t +pgp_dilithium_exdsa_composite_private_key_t::sign(rnp::RNG * rng, + pgp_dilithium_exdsa_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * msg, + size_t msg_len) const +{ + initialized_or_throw(); + std::vector dilithium_sig; + std::vector exdsa_sig; + rnp_result_t ret; + + try { + dilithium_sig = dilithium_key_->sign(rng, msg, msg_len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_SIGNING_FAILED; + } + ret = exdsa_key_->sign(rng, exdsa_sig, msg, msg_len, hash_alg); + if (ret != RNP_SUCCESS) { + RNP_LOG("exdsa sign failed"); + return RNP_ERROR_SIGNING_FAILED; + } + + sig->sig.assign(exdsa_sig.data(), exdsa_sig.data() + exdsa_sig.size()); + sig->sig.insert(sig->sig.end(), dilithium_sig.begin(), dilithium_sig.end()); + + return RNP_SUCCESS; +} + +void +pgp_dilithium_exdsa_composite_private_key_t::secure_clear() +{ + // private key buffer is stored in a secure_vector and will be securely erased by the + // destructor. + dilithium_key_.reset(); + exdsa_key_.reset(); + is_initialized_ = false; +} + +size_t +pgp_dilithium_exdsa_composite_public_key_t::encoded_size(pgp_pubkey_alg_t pk_alg) +{ + dilithium_parameter_e dilithium_param = pk_alg_to_dilithium_id(pk_alg); + pgp_curve_t curve = pk_alg_to_curve_id(pk_alg); + return exdsa_curve_pubkey_size(curve) + dilithium_pubkey_size(dilithium_param); +} + +void +pgp_dilithium_exdsa_composite_public_key_t::parse_component_keys( + std::vector key_encoded) +{ + if (key_encoded.size() != encoded_size(pk_alg_)) { + RNP_LOG("Dilithium composite key format invalid: length mismatch"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + dilithium_parameter_e dilithium_param = pk_alg_to_dilithium_id(pk_alg_); + pgp_curve_t curve = pk_alg_to_curve_id(pk_alg_); + size_t split_at = exdsa_curve_pubkey_size(pk_alg_to_curve_id(pk_alg_)); + + dilithium_key_ = pgp_dilithium_public_key_t( + key_encoded.data() + split_at, key_encoded.size() - split_at, dilithium_param); + exdsa_key_ = exdsa_public_key_t(key_encoded.data(), split_at, curve); + + is_initialized_ = true; +} + +std::vector +pgp_dilithium_exdsa_composite_public_key_t::get_encoded() const +{ + initialized_or_throw(); + std::vector result; + std::vector exdsa_key_encoded = exdsa_key_.get_encoded(); + std::vector dilithium_key_encoded = dilithium_key_.get_encoded(); + + result.insert(result.end(), std::begin(exdsa_key_encoded), std::end(exdsa_key_encoded)); + result.insert( + result.end(), std::begin(dilithium_key_encoded), std::end(dilithium_key_encoded)); + return result; +}; + +rnp_result_t +pgp_dilithium_exdsa_composite_public_key_t::verify(const pgp_dilithium_exdsa_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t *hash, + size_t hash_len) const +{ + initialized_or_throw(); + std::vector dilithium_sig; + std::vector exdsa_sig; + + if (sig->sig.size() != sig->composite_signature_size(pk_alg_)) { + RNP_LOG("invalid signature size for dilithium exdsa composite algorithm %d", pk_alg_); + return RNP_ERROR_VERIFICATION_FAILED; + } + + size_t split_at = exdsa_curve_signature_size(pk_alg_to_curve_id(pk_alg_)); + exdsa_sig = std::vector(sig->sig.data(), sig->sig.data() + split_at); + dilithium_sig = + std::vector(sig->sig.data() + split_at, sig->sig.data() + sig->sig.size()); + + if (exdsa_key_.verify(exdsa_sig, hash, hash_len, hash_alg) != RNP_SUCCESS || + !dilithium_key_.verify_signature( + hash, hash_len, dilithium_sig.data(), dilithium_sig.size())) { + RNP_LOG("could not verify composite signature"); + return RNP_ERROR_VERIFICATION_FAILED; + } + + return RNP_SUCCESS; +} + +bool +pgp_dilithium_exdsa_composite_public_key_t::is_valid(rnp::RNG *rng) const +{ + if (!is_initialized()) { + return false; + } + return (exdsa_key_.is_valid(rng) && dilithium_key_.is_valid(rng)); +} + +bool +pgp_dilithium_exdsa_composite_private_key_t::is_valid(rnp::RNG *rng) const +{ + if (!is_initialized()) { + return false; + } + return (exdsa_key_->is_valid(rng) && dilithium_key_->is_valid(rng)); +} + +rnp_result_t +dilithium_exdsa_validate_key(rnp::RNG *rng, const pgp_dilithium_exdsa_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; +} diff --git a/src/lib/crypto/dilithium_exdsa_composite.h b/src/lib/crypto/dilithium_exdsa_composite.h new file mode 100644 index 00000000..6e454342 --- /dev/null +++ b/src/lib/crypto/dilithium_exdsa_composite.h @@ -0,0 +1,184 @@ +/* + * 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 DILITHIUM_EXDSA_COMPOSITE_H_ +#define DILITHIUM_EXDSA_COMPOSITE_H_ + +#include "config.h" +#include +#include +#include +#include "crypto/rng.h" +#include "crypto/dilithium.h" +#include "crypto/dilithium_common.h" +#include "crypto/exdsa_ecdhkem.h" +#include + +struct pgp_dilithium_exdsa_key_t; /* forward declaration */ + +class pgp_dilithium_exdsa_composite_key_t { + public: + virtual ~pgp_dilithium_exdsa_composite_key_t() = 0; + + static rnp_result_t gen_keypair(rnp::RNG * rng, + pgp_dilithium_exdsa_key_t *key, + pgp_pubkey_alg_t alg); + + static size_t exdsa_curve_privkey_size(pgp_curve_t curve); + static size_t exdsa_curve_pubkey_size(pgp_curve_t curve); + static size_t exdsa_curve_signature_size(pgp_curve_t curve); + static pgp_curve_t pk_alg_to_curve_id(pgp_pubkey_alg_t pk_alg); + static dilithium_parameter_e pk_alg_to_dilithium_id(pgp_pubkey_alg_t pk_alg); + + bool + is_initialized() const + { + return is_initialized_; + } + + protected: + bool is_initialized_ = false; + void initialized_or_throw() const; +}; + +typedef struct pgp_dilithium_exdsa_signature_t { + std::vector sig; + + static size_t + composite_signature_size(pgp_pubkey_alg_t pk_alg) + { + return dilithium_signature_size( + pgp_dilithium_exdsa_composite_key_t::pk_alg_to_dilithium_id(pk_alg)) + + pgp_dilithium_exdsa_composite_key_t::exdsa_curve_signature_size( + pgp_dilithium_exdsa_composite_key_t::pk_alg_to_curve_id(pk_alg)); + } +} pgp_dilithium_exdsa_signature_t; + +class pgp_dilithium_exdsa_composite_private_key_t + : public pgp_dilithium_exdsa_composite_key_t { + public: + pgp_dilithium_exdsa_composite_private_key_t(const uint8_t * key_encoded, + size_t key_encoded_len, + pgp_pubkey_alg_t pk_alg); + pgp_dilithium_exdsa_composite_private_key_t( + std::vector const &exdsa_key_encoded, + std::vector const &dilithium_key_encoded, + pgp_pubkey_alg_t pk_alg); + pgp_dilithium_exdsa_composite_private_key_t(std::vector const &key_encoded, + pgp_pubkey_alg_t pk_alg); + pgp_dilithium_exdsa_composite_private_key_t &operator=( + const pgp_dilithium_exdsa_composite_private_key_t &other); + pgp_dilithium_exdsa_composite_private_key_t() = default; + + rnp_result_t sign(rnp::RNG * rng, + pgp_dilithium_exdsa_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * msg, + size_t msg_len) const; + + std::vector get_encoded() const; + + pgp_pubkey_alg_t + pk_alg() const + { + return pk_alg_; + } + + bool is_valid(rnp::RNG *rng) const; + void secure_clear(); + + static size_t encoded_size(pgp_pubkey_alg_t pk_alg); + + private: + void parse_component_keys(std::vector key_encoded); + + pgp_pubkey_alg_t pk_alg_; + + /* dilithium part */ + std::unique_ptr dilithium_key_; + + /* ecc part*/ + std::unique_ptr exdsa_key_; +}; + +class pgp_dilithium_exdsa_composite_public_key_t : public pgp_dilithium_exdsa_composite_key_t { + public: + pgp_dilithium_exdsa_composite_public_key_t(const uint8_t * key_encoded, + size_t key_encoded_len, + pgp_pubkey_alg_t pk_alg); + pgp_dilithium_exdsa_composite_public_key_t( + std::vector const &exdsa_key_encoded, + std::vector const &dilithium_key_encoded, + pgp_pubkey_alg_t pk_alg); + pgp_dilithium_exdsa_composite_public_key_t(std::vector const &key_encoded, + pgp_pubkey_alg_t pk_alg); + pgp_dilithium_exdsa_composite_public_key_t() = default; + + bool + operator==(const pgp_dilithium_exdsa_composite_public_key_t &rhs) const + { + return (pk_alg_ == rhs.pk_alg_) && (dilithium_key_ == rhs.dilithium_key_) && + (exdsa_key_ == rhs.exdsa_key_); + } + + rnp_result_t verify(const pgp_dilithium_exdsa_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len) const; + + std::vector get_encoded() const; + + pgp_pubkey_alg_t + pk_alg() const + { + return pk_alg_; + } + + bool is_valid(rnp::RNG *rng) const; + static size_t encoded_size(pgp_pubkey_alg_t pk_alg); + + private: + void parse_component_keys(std::vector key_encoded); + + pgp_pubkey_alg_t pk_alg_; + + /* dilithium part */ + pgp_dilithium_public_key_t dilithium_key_; + + /* ecc part*/ + exdsa_public_key_t exdsa_key_; +}; + +typedef struct pgp_dilithium_exdsa_key_t { + pgp_dilithium_exdsa_composite_private_key_t priv; + pgp_dilithium_exdsa_composite_public_key_t pub; +} pgp_dilithium_exdsa_key_t; + +rnp_result_t dilithium_exdsa_validate_key(rnp::RNG * rng, + const pgp_dilithium_exdsa_key_t *key, + bool secret); + +#endif diff --git a/src/lib/crypto/ec.cpp b/src/lib/crypto/ec.cpp index 144c362e..8aca0a47 100644 --- a/src/lib/crypto/ec.cpp +++ b/src/lib/crypto/ec.cpp @@ -32,6 +32,13 @@ #include "utils.h" #include "mem.h" #include "bn.h" +#if defined(ENABLE_CRYPTO_REFRESH) +#include "x25519.h" +#include "ed25519.h" +#include "botan/bigint.h" +#include "botan/ecdh.h" +#include +#endif static id_str_pair ec_algo_to_botan[] = { {PGP_PKA_ECDH, "ECDH"}, @@ -185,3 +192,85 @@ ec_generate(rnp::RNG * rng, bn_free(x); return ret; } + +#if defined(ENABLE_CRYPTO_REFRESH) +static bool +is_generic_prime_curve(pgp_curve_t curve) +{ + switch (curve) { + case PGP_CURVE_NIST_P_256: + FALLTHROUGH_STATEMENT; + case PGP_CURVE_NIST_P_384: + FALLTHROUGH_STATEMENT; + case PGP_CURVE_NIST_P_521: + FALLTHROUGH_STATEMENT; + case PGP_CURVE_BP256: + FALLTHROUGH_STATEMENT; + case PGP_CURVE_BP384: + FALLTHROUGH_STATEMENT; + case PGP_CURVE_BP512: + FALLTHROUGH_STATEMENT; + case PGP_CURVE_P256K1: + return true; + default: + return false; + } +} + +static rnp_result_t +ec_generate_generic_native(rnp::RNG * rng, + std::vector &privkey, + std::vector &pubkey, + pgp_curve_t curve, + pgp_pubkey_alg_t alg) +{ + if (!is_generic_prime_curve(curve)) { + RNP_LOG("expected generic prime curve"); + return RNP_ERROR_BAD_PARAMETERS; + } + + const ec_curve_desc_t *ec_desc = get_curve_desc(curve); + const size_t curve_order = BITS_TO_BYTES(ec_desc->bitlen); + + Botan::ECDH_PrivateKey privkey_botan(*(rng->obj()), Botan::EC_Group(ec_desc->botan_name)); + Botan::BigInt pub_x = privkey_botan.public_point().get_affine_x(); + Botan::BigInt pub_y = privkey_botan.public_point().get_affine_y(); + Botan::BigInt x = privkey_botan.private_value(); + + // pubkey: 0x04 || X || Y + pubkey = Botan::unlock(Botan::BigInt::encode_fixed_length_int_pair( + pub_x, pub_y, curve_order)); // zero-pads to the given size + pubkey.insert(pubkey.begin(), 0x04); + + privkey = std::vector(curve_order); + x.binary_encode(privkey.data(), privkey.size()); // zero-pads to the given size + + assert(pubkey.size() == 2 * curve_order + 1); + assert(privkey.size() == curve_order); + + return RNP_SUCCESS; +} + +rnp_result_t +ec_generate_native(rnp::RNG * rng, + std::vector &privkey, + std::vector &pubkey, + pgp_curve_t curve, + pgp_pubkey_alg_t alg) +{ + if (curve == PGP_CURVE_25519) { + return generate_x25519_native(rng, privkey, pubkey); + } else if (curve == PGP_CURVE_ED25519) { + return generate_ed25519_native(rng, privkey, pubkey); + } else if (is_generic_prime_curve(curve)) { + if (alg != PGP_PKA_ECDH && alg != PGP_PKA_ECDSA) { + RNP_LOG("alg and curve mismatch"); + return RNP_ERROR_BAD_PARAMETERS; + } + return ec_generate_generic_native(rng, privkey, pubkey, curve, alg); + } else { + RNP_LOG("invalid curve"); + return RNP_ERROR_BAD_PARAMETERS; + } +} +#endif diff --git a/src/lib/crypto/ec.h b/src/lib/crypto/ec.h index 07cb8e81..aeb84360 100644 --- a/src/lib/crypto/ec.h +++ b/src/lib/crypto/ec.h @@ -35,6 +35,7 @@ #include #include "crypto/rng.h" #include "crypto/mpi.h" +#include #define MAX_CURVE_BIT_SIZE 521 // secp521r1 /* Maximal byte size of elliptic curve order (NIST P-521) */ @@ -89,6 +90,27 @@ typedef struct pgp_ec_signature_t { pgp_mpi_t s; } pgp_ec_signature_t; +#if defined(ENABLE_CRYPTO_REFRESH) +typedef struct pgp_ed25519_key_t { + std::vector pub; // \ native encoding + std::vector priv; // / +} pgp_ed25519_key_t; + +typedef struct pgp_ed25519_signature_t { + std::vector sig; // native encoding +} pgp_ed25519_signature_t; + +typedef struct pgp_x25519_key_t { + std::vector pub; // \ native encoding + std::vector priv; // / +} pgp_x25519_key_t; + +typedef struct pgp_x25519_encrypted_t { + std::vector eph_key; + std::vector enc_sess_key; +} pgp_x25519_encrypted_t; +#endif + /* * @brief Finds curve ID by hex representation of OID * @@ -170,4 +192,20 @@ bool x25519_tweak_bits(pgp_ec_key_t &key); */ bool x25519_bits_tweaked(const pgp_ec_key_t &key); +/* + * @brief Generates EC keys in "native" or SEC1-encoded uncompressed format + * + * @param rng initialized rnp::RNG context* + * @param privkey private key to be generated + * @param pubkey public key to be generated + * @param curve chosen curve + * @param alg algorithm id + * + * @returns RNP_ERROR_BAD_PARAMETERS if the curve or alg parameter is invalid. + */ +rnp_result_t ec_generate_native(rnp::RNG * rng, + std::vector &privkey, + std::vector &pubkey, + pgp_curve_t curve, + pgp_pubkey_alg_t alg); #endif diff --git a/src/lib/crypto/ecdh.cpp b/src/lib/crypto/ecdh.cpp index 574b3b86..31f078ac 100644 --- a/src/lib/crypto/ecdh.cpp +++ b/src/lib/crypto/ecdh.cpp @@ -28,6 +28,7 @@ #include #include "hash_botan.hpp" #include "ecdh.h" +#include "ec.h" #include "ecdh_utils.h" #include "symmetric.h" #include "types.h" @@ -388,3 +389,48 @@ ecdh_decrypt_pkcs5(uint8_t * out, botan_privkey_destroy(prv_key); return ret; } + +#if defined(ENABLE_CRYPTO_REFRESH) +rnp_result_t +ecdh_kem_gen_keypair_native(rnp::RNG * rng, + std::vector &privkey, + std::vector &pubkey, + pgp_curve_t curve) +{ + return ec_generate_native(rng, privkey, pubkey, curve, PGP_PKA_ECDH); +} + +rnp_result_t +exdsa_gen_keypair_native(rnp::RNG * rng, + std::vector &privkey, + std::vector &pubkey, + pgp_curve_t curve) +{ + pgp_pubkey_alg_t alg; + switch (curve) { + case PGP_CURVE_ED25519: + alg = PGP_PKA_EDDSA; + break; + case PGP_CURVE_NIST_P_256: + FALLTHROUGH_STATEMENT; + case PGP_CURVE_NIST_P_384: + FALLTHROUGH_STATEMENT; + case PGP_CURVE_NIST_P_521: + FALLTHROUGH_STATEMENT; + case PGP_CURVE_BP256: + FALLTHROUGH_STATEMENT; + case PGP_CURVE_BP384: + FALLTHROUGH_STATEMENT; + case PGP_CURVE_BP512: + FALLTHROUGH_STATEMENT; + case PGP_CURVE_P256K1: + alg = PGP_PKA_ECDSA; + break; + default: + RNP_LOG("invalid curve for ECDSA/EDDSA"); + return RNP_ERROR_BAD_PARAMETERS; + } + return ec_generate_native(rng, privkey, pubkey, curve, alg); +} + +#endif diff --git a/src/lib/crypto/ecdh.h b/src/lib/crypto/ecdh.h index 017e1e69..7f402035 100644 --- a/src/lib/crypto/ecdh.h +++ b/src/lib/crypto/ecdh.h @@ -28,6 +28,7 @@ #define ECDH_H_ #include "crypto/ec.h" +#include /* Max size of wrapped and obfuscated key size * @@ -114,4 +115,41 @@ rnp_result_t ecdh_decrypt_pkcs5(uint8_t * out, const pgp_ec_key_t * key, const pgp_fingerprint_t & fingerprint); +#if defined(ENABLE_CRYPTO_REFRESH) +/* Generate an ECDH key pair in "native" format, i.e., + * no changes to the format specified in the respective standard + * are applied (uncompressed SEC1 and RFC 7748). + * + * @param rng initialized rnp::RNG object + * @param privkey [out] the generated private key + * @param pubkey [out] the generated public key + * @param curve the curve for which a key pair is generated + * + * @return RNP_SUCCESS on success and output parameters are populated + * @return RNP_ERROR_BAD_PARAMETERS unexpected input provided + */ +rnp_result_t ecdh_kem_gen_keypair_native(rnp::RNG * rng, + std::vector &privkey, + std::vector &pubkey, + pgp_curve_t curve); + +/* Generate an ECDSA or EdDSA key pair in "native" format, i.e., + * no changes to the format specified in the respective standard + * are applied (uncompressed SEC1 and RFC 7748). + * + * @param rng initialized rnp::RNG object + * @param privkey [out] the generated private key + * @param pubkey [out] the generated public key + * @param curve the curve for which a key pair is generated + * + * @return RNP_SUCCESS on success and output parameters are populated + * @return RNP_ERROR_BAD_PARAMETERS unexpected input provided + */ +rnp_result_t exdsa_gen_keypair_native(rnp::RNG * rng, + std::vector &privkey, + std::vector &pubkey, + pgp_curve_t curve); + +#endif + #endif // ECDH_H_ diff --git a/src/lib/crypto/ecdsa.cpp b/src/lib/crypto/ecdsa.cpp index 26816a5c..3a96cae3 100644 --- a/src/lib/crypto/ecdsa.cpp +++ b/src/lib/crypto/ecdsa.cpp @@ -112,7 +112,7 @@ ecdsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) return ret; } -static const char * +const char * ecdsa_padding_str_for(pgp_hash_alg_t hash_alg) { switch (hash_alg) { diff --git a/src/lib/crypto/ecdsa.h b/src/lib/crypto/ecdsa.h index aebfe6a6..86af7ed2 100644 --- a/src/lib/crypto/ecdsa.h +++ b/src/lib/crypto/ecdsa.h @@ -44,6 +44,8 @@ rnp_result_t ecdsa_verify(const pgp_ec_signature_t *sig, size_t hash_len, const pgp_ec_key_t * key); +const char *ecdsa_padding_str_for(pgp_hash_alg_t hash_alg); + /* * @brief Returns hash which should be used with the curve * diff --git a/src/lib/crypto/ed25519.cpp b/src/lib/crypto/ed25519.cpp new file mode 100644 index 00000000..240d8481 --- /dev/null +++ b/src/lib/crypto/ed25519.cpp @@ -0,0 +1,95 @@ +/* + * 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 "ed25519.h" +#include "logging.h" +#include "utils.h" + +#include +#include +#include + +rnp_result_t +generate_ed25519_native(rnp::RNG * rng, + std::vector &privkey, + std::vector &pubkey) +{ + Botan::Ed25519_PrivateKey private_key(*(rng->obj())); + const size_t key_len = 32; + auto priv_pub = Botan::unlock(private_key.raw_private_key_bits()); + assert(priv_pub.size() == 2 * key_len); + privkey = std::vector(priv_pub.begin(), priv_pub.begin() + key_len); + pubkey = std::vector(priv_pub.begin() + key_len, priv_pub.end()); + + return RNP_SUCCESS; +} + +rnp_result_t +ed25519_sign_native(rnp::RNG * rng, + std::vector & sig_out, + const std::vector &key, + const uint8_t * hash, + size_t hash_len) +{ + Botan::Ed25519_PrivateKey priv_key(Botan::secure_vector(key.begin(), key.end())); + auto signer = Botan::PK_Signer(priv_key, *(rng->obj()), "Pure"); + sig_out = signer.sign_message(hash, hash_len, *(rng->obj())); + + return RNP_SUCCESS; +} + +rnp_result_t +ed25519_verify_native(const std::vector &sig, + const std::vector &key, + const uint8_t * hash, + size_t hash_len) +{ + Botan::Ed25519_PublicKey pub_key(key); + auto verifier = Botan::PK_Verifier(pub_key, "Pure"); + if (verifier.verify_message(hash, hash_len, sig.data(), sig.size())) { + return RNP_SUCCESS; + } + return RNP_ERROR_VERIFICATION_FAILED; +} + +rnp_result_t +ed25519_validate_key_native(rnp::RNG *rng, const pgp_ed25519_key_t *key, bool secret) +{ + Botan::Ed25519_PublicKey pub_key(key->pub); + if (!pub_key.check_key(*(rng->obj()), false)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (secret) { + Botan::Ed25519_PrivateKey priv_key( + Botan::secure_vector(key->priv.begin(), key->priv.end())); + if (!priv_key.check_key(*(rng->obj()), false)) { + return RNP_ERROR_SIGNING_FAILED; + } + } + + return RNP_SUCCESS; +} diff --git a/src/lib/crypto/ed25519.h b/src/lib/crypto/ed25519.h new file mode 100644 index 00000000..5ab109d0 --- /dev/null +++ b/src/lib/crypto/ed25519.h @@ -0,0 +1,58 @@ +/* + * 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 ED25519_H_ +#define ED25519_H_ + +#include "config.h" +#include +#include +#include +#include "crypto/rng.h" +#include "crypto/ec.h" + +/* implements ED25519 with native format (V6 and PQC) */ + +rnp_result_t generate_ed25519_native(rnp::RNG * rng, + std::vector &privkey, + std::vector &pubkey); + +rnp_result_t ed25519_sign_native(rnp::RNG * rng, + std::vector & sig_out, + const std::vector &key, + const uint8_t * hash, + size_t hash_len); + +rnp_result_t ed25519_verify_native(const std::vector &sig, + const std::vector &key, + const uint8_t * hash, + size_t hash_len); + +rnp_result_t ed25519_validate_key_native(rnp::RNG * rng, + const pgp_ed25519_key_t *key, + bool secret); + +#endif diff --git a/src/lib/crypto/exdsa_ecdhkem.cpp b/src/lib/crypto/exdsa_ecdhkem.cpp new file mode 100644 index 00000000..4be3db0f --- /dev/null +++ b/src/lib/crypto/exdsa_ecdhkem.cpp @@ -0,0 +1,314 @@ +/* + * 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 "exdsa_ecdhkem.h" +#include "ecdh.h" +#include "ecdsa.h" +#include "ec.h" +#include "types.h" +#include "logging.h" +#include "string.h" +#include "utils.h" +#include + +ec_key_t::~ec_key_t() +{ +} + +ec_key_t::ec_key_t(pgp_curve_t curve) : curve_(curve) +{ +} + +ecdh_kem_public_key_t::ecdh_kem_public_key_t(uint8_t * key_buf, + size_t key_buf_len, + pgp_curve_t curve) + : ec_key_t(curve), key_(std::vector(key_buf, key_buf + key_buf_len)) +{ +} +ecdh_kem_public_key_t::ecdh_kem_public_key_t(std::vector key, pgp_curve_t curve) + : ec_key_t(curve), key_(key) +{ +} + +ecdh_kem_private_key_t::ecdh_kem_private_key_t(uint8_t * key_buf, + size_t key_buf_len, + pgp_curve_t curve) + : ec_key_t(curve), key_(key_buf, key_buf + key_buf_len) +{ +} +ecdh_kem_private_key_t::ecdh_kem_private_key_t(std::vector key, pgp_curve_t curve) + : ec_key_t(curve), key_(Botan::secure_vector(key.begin(), key.end())) +{ +} + +Botan::ECDH_PrivateKey +ecdh_kem_private_key_t::botan_key_ecdh(rnp::RNG *rng) const +{ + assert(curve_ >= PGP_CURVE_NIST_P_256 && curve_ <= PGP_CURVE_P256K1); + const ec_curve_desc_t *ec_desc = get_curve_desc(curve_); + return Botan::ECDH_PrivateKey( + *(rng->obj()), Botan::EC_Group(ec_desc->botan_name), Botan::BigInt(key_)); +} + +Botan::ECDH_PublicKey +ecdh_kem_public_key_t::botan_key_ecdh(rnp::RNG *rng) const +{ + assert(curve_ >= PGP_CURVE_NIST_P_256 && curve_ <= PGP_CURVE_P256K1); + + const ec_curve_desc_t *ec_desc = get_curve_desc(curve_); + Botan::EC_Group group(ec_desc->botan_name); + const size_t curve_order = BITS_TO_BYTES(ec_desc->bitlen); + Botan::BigInt x(key_.data() + 1, curve_order); + Botan::BigInt y(key_.data() + 1 + curve_order, curve_order); + return Botan::ECDH_PublicKey(group, group.point(x, y)); +} + +Botan::Curve25519_PrivateKey +ecdh_kem_private_key_t::botan_key_x25519() const +{ + assert(curve_ == PGP_CURVE_25519); + return Botan::Curve25519_PrivateKey(key_); +} + +Botan::Curve25519_PublicKey +ecdh_kem_public_key_t::botan_key_x25519() const +{ + assert(curve_ == PGP_CURVE_25519); + return Botan::Curve25519_PublicKey(key_); +} + +std::vector +ecdh_kem_private_key_t::get_pubkey_encoded(rnp::RNG *rng) const +{ + if (curve_ == PGP_CURVE_25519) { + Botan::X25519_PrivateKey botan_key = botan_key_x25519(); + return botan_key.public_value(); + } else { + Botan::ECDH_PrivateKey botan_key = botan_key_ecdh(rng); + return botan_key.public_value(); + } +} + +rnp_result_t +ecdh_kem_public_key_t::encapsulate(rnp::RNG * rng, + std::vector &ciphertext, + std::vector &symmetric_key) +{ + if (curve_ == PGP_CURVE_25519) { + Botan::Curve25519_PrivateKey eph_prv_key(*(rng->obj())); + ciphertext = eph_prv_key.public_value(); + Botan::PK_Key_Agreement key_agreement(eph_prv_key, *(rng->obj()), "Raw"); + symmetric_key = Botan::unlock(key_agreement.derive_key(0, key_).bits_of()); + } else { + const ec_curve_desc_t *curve_desc = get_curve_desc(curve_); + if (!curve_desc) { + RNP_LOG("unknown curve"); + return RNP_ERROR_NOT_SUPPORTED; + } + + Botan::EC_Group domain(curve_desc->botan_name); + Botan::ECDH_PrivateKey eph_prv_key(*(rng->obj()), domain); + Botan::PK_Key_Agreement key_agreement(eph_prv_key, *(rng->obj()), "Raw"); + ciphertext = eph_prv_key.public_value(); + symmetric_key = Botan::unlock(key_agreement.derive_key(0, key_).bits_of()); + } + return RNP_SUCCESS; +} + +rnp_result_t +ecdh_kem_private_key_t::decapsulate(rnp::RNG * rng, + const std::vector &ciphertext, + std::vector & plaintext) +{ + if (curve_ == PGP_CURVE_25519) { + Botan::Curve25519_PrivateKey priv_key = botan_key_x25519(); + Botan::PK_Key_Agreement key_agreement(priv_key, *(rng->obj()), "Raw"); + plaintext = Botan::unlock(key_agreement.derive_key(0, ciphertext).bits_of()); + } else { + Botan::ECDH_PrivateKey priv_key = botan_key_ecdh(rng); + Botan::PK_Key_Agreement key_agreement(priv_key, *(rng->obj()), "Raw"); + plaintext = Botan::unlock(key_agreement.derive_key(0, ciphertext).bits_of()); + } + return RNP_SUCCESS; +} + +rnp_result_t +ec_key_t::generate_ecdh_kem_key_pair(rnp::RNG *rng, ecdh_kem_key_t *out, pgp_curve_t curve) +{ + std::vector pub, priv; + rnp_result_t result = ecdh_kem_gen_keypair_native(rng, priv, pub, curve); + if (result != RNP_SUCCESS) { + RNP_LOG("error when generating EC key pair"); + return result; + } + + out->priv = ecdh_kem_private_key_t(priv, curve); + out->pub = ecdh_kem_public_key_t(pub, curve); + + return RNP_SUCCESS; +} + +exdsa_public_key_t::exdsa_public_key_t(uint8_t *key_buf, size_t key_buf_len, pgp_curve_t curve) + : ec_key_t(curve), key_(key_buf, key_buf + key_buf_len) +{ +} +exdsa_public_key_t::exdsa_public_key_t(std::vector key, pgp_curve_t curve) + : ec_key_t(curve), key_(key) +{ +} + +exdsa_private_key_t::exdsa_private_key_t(uint8_t * key_buf, + size_t key_buf_len, + pgp_curve_t curve) + : ec_key_t(curve), key_(key_buf, key_buf + key_buf_len) +{ +} +exdsa_private_key_t::exdsa_private_key_t(std::vector key, pgp_curve_t curve) + : ec_key_t(curve), key_(Botan::secure_vector(key.begin(), key.end())) +{ +} + +rnp_result_t +ec_key_t::generate_exdsa_key_pair(rnp::RNG *rng, exdsa_key_t *out, pgp_curve_t curve) +{ + std::vector pub, priv; + rnp_result_t result = exdsa_gen_keypair_native(rng, priv, pub, curve); + if (result != RNP_SUCCESS) { + RNP_LOG("error when generating EC key pair"); + return result; + } + + out->priv = exdsa_private_key_t(priv, curve); + out->pub = exdsa_public_key_t(pub, curve); + + return RNP_SUCCESS; +} + +Botan::ECDSA_PrivateKey +exdsa_private_key_t::botan_key(rnp::RNG *rng) const +{ + const ec_curve_desc_t * ec_desc = get_curve_desc(curve_); + Botan::ECDSA_PrivateKey priv_key( + *(rng->obj()), Botan::EC_Group(ec_desc->botan_name), Botan::BigInt(key_)); + return priv_key; +} + +Botan::ECDSA_PublicKey +exdsa_public_key_t::botan_key() const +{ + // format: 04 | X | Y + const ec_curve_desc_t *ec_desc = get_curve_desc(curve_); + Botan::EC_Group group(ec_desc->botan_name); + const size_t curve_order = BITS_TO_BYTES(ec_desc->bitlen); + Botan::BigInt x(key_.data() + 1, curve_order); + Botan::BigInt y(key_.data() + 1 + curve_order, curve_order); + return Botan::ECDSA_PublicKey(group, group.point(x, y)); +} + +/* NOTE hash_alg unused for ed25519/x25519 curves */ +rnp_result_t +exdsa_private_key_t::sign(rnp::RNG * rng, + std::vector &sig_out, + const uint8_t * hash, + size_t hash_len, + pgp_hash_alg_t hash_alg) const +{ + if (curve_ == PGP_CURVE_ED25519) { + return ed25519_sign_native(rng, sig_out, Botan::unlock(key_), hash, hash_len); + } else { + Botan::ECDSA_PrivateKey priv_key = botan_key(rng); + auto signer = + Botan::PK_Signer(priv_key, *(rng->obj()), ecdsa_padding_str_for(hash_alg)); + sig_out = signer.sign_message(hash, hash_len, *(rng->obj())); + } + return RNP_SUCCESS; +} + +rnp_result_t +exdsa_public_key_t::verify(const std::vector &sig, + const uint8_t * hash, + size_t hash_len, + pgp_hash_alg_t hash_alg) const +{ + if (curve_ == PGP_CURVE_ED25519) { + return ed25519_verify_native(sig, key_, hash, hash_len); + } else { + Botan::ECDSA_PublicKey pub_key = botan_key(); + auto verifier = Botan::PK_Verifier(pub_key, ecdsa_padding_str_for(hash_alg)); + if (verifier.verify_message(hash, hash_len, sig.data(), sig.size())) { + return RNP_SUCCESS; + } + } + return RNP_ERROR_VERIFICATION_FAILED; +} + +bool +exdsa_public_key_t::is_valid(rnp::RNG *rng) const +{ + if (curve_ == PGP_CURVE_ED25519) { + Botan::Ed25519_PublicKey pub_key(key_); + return pub_key.check_key(*(rng->obj()), false); + } else { + Botan::ECDSA_PublicKey pub_key = botan_key(); + return pub_key.check_key(*(rng->obj()), false); + } +} + +bool +exdsa_private_key_t::is_valid(rnp::RNG *rng) const +{ + if (curve_ == PGP_CURVE_ED25519) { + Botan::Ed25519_PrivateKey priv_key(key_); + return priv_key.check_key(*(rng->obj()), false); + } else { + auto priv_key = botan_key(rng); + return priv_key.check_key(*(rng->obj()), false); + } +} + +bool +ecdh_kem_public_key_t::is_valid(rnp::RNG *rng) const +{ + if (curve_ == PGP_CURVE_25519) { + auto pub_key = botan_key_x25519(); + return pub_key.check_key(*(rng->obj()), false); + } else { + auto pub_key = botan_key_ecdh(rng); + return pub_key.check_key(*(rng->obj()), false); + } +} + +bool +ecdh_kem_private_key_t::is_valid(rnp::RNG *rng) const +{ + if (curve_ == PGP_CURVE_25519) { + auto priv_key = botan_key_x25519(); + return priv_key.check_key(*(rng->obj()), false); + } else { + auto priv_key = botan_key_ecdh(rng); + return priv_key.check_key(*(rng->obj()), false); + } +} diff --git a/src/lib/crypto/exdsa_ecdhkem.h b/src/lib/crypto/exdsa_ecdhkem.h new file mode 100644 index 00000000..03fe025d --- /dev/null +++ b/src/lib/crypto/exdsa_ecdhkem.h @@ -0,0 +1,194 @@ +/* + * 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 ECDH_KEM_H_ +#define ECDH_KEM_H_ + +#include "config.h" +#include +#include +#include +#include "crypto/rng.h" +#include +#include "botan/secmem.h" +#include +#include +#include +#include +#include + +struct ecdh_kem_key_t; /* forward declaration */ +struct exdsa_key_t; /* forward declaration */ + +class ec_key_t { + public: + virtual ~ec_key_t() = 0; + ec_key_t(pgp_curve_t curve); + ec_key_t() = default; + + static rnp_result_t generate_ecdh_kem_key_pair(rnp::RNG * rng, + ecdh_kem_key_t *out, + pgp_curve_t curve); + static rnp_result_t generate_exdsa_key_pair(rnp::RNG * rng, + exdsa_key_t *out, + pgp_curve_t curve); + + pgp_curve_t + get_curve() const + { + return curve_; + } + + protected: + pgp_curve_t curve_; +}; + +class ecdh_kem_public_key_t : public ec_key_t { + public: + ecdh_kem_public_key_t(uint8_t *key_buf, size_t key_buf_len, pgp_curve_t curve); + ecdh_kem_public_key_t(std::vector key_buf, pgp_curve_t curve); + ecdh_kem_public_key_t() = default; + + bool + operator==(const ecdh_kem_public_key_t &rhs) const + { + return (curve_ == rhs.curve_) && (key_ == rhs.key_); + } + + bool is_valid(rnp::RNG *rng) const; + + std::vector + get_encoded() const + { + return key_; + } + + rnp_result_t encapsulate(rnp::RNG * rng, + std::vector &ciphertext, + std::vector &symmetric_key); + + private: + Botan::ECDH_PublicKey botan_key_ecdh(rnp::RNG *rng) const; + Botan::Curve25519_PublicKey botan_key_x25519() const; + + std::vector key_; +}; + +class ecdh_kem_private_key_t : public ec_key_t { + public: + ecdh_kem_private_key_t(uint8_t *key_buf, size_t key_buf_len, pgp_curve_t curve); + ecdh_kem_private_key_t(std::vector key_buf, pgp_curve_t curve); + ecdh_kem_private_key_t() = default; + + bool is_valid(rnp::RNG *rng) const; + + std::vector + get_encoded() const + { + return Botan::unlock(key_); + } + + std::vector get_pubkey_encoded(rnp::RNG *rng) const; + + rnp_result_t decapsulate(rnp::RNG * rng, + const std::vector &ciphertext, + std::vector & plaintext); + + private: + Botan::ECDH_PrivateKey botan_key_ecdh(rnp::RNG *rng) const; + Botan::Curve25519_PrivateKey botan_key_x25519() const; + + Botan::secure_vector key_; +}; + +typedef struct ecdh_kem_key_t { + ecdh_kem_private_key_t priv; + ecdh_kem_public_key_t pub; +} ecdh_kem_key_t; + +class exdsa_public_key_t : public ec_key_t { + public: + exdsa_public_key_t(uint8_t *key_buf, size_t key_buf_len, pgp_curve_t curve); + exdsa_public_key_t(std::vector key_buf, pgp_curve_t curve); + exdsa_public_key_t() = default; + + bool + operator==(const exdsa_public_key_t &rhs) const + { + return (curve_ == rhs.curve_) && (key_ == rhs.key_); + } + + bool is_valid(rnp::RNG *rng) const; + + std::vector + get_encoded() const + { + return key_; + } + + rnp_result_t verify(const std::vector &sig, + const uint8_t * hash, + size_t hash_len, + pgp_hash_alg_t hash_alg) const; + + private: + Botan::ECDSA_PublicKey botan_key() const; + + std::vector key_; +}; + +class exdsa_private_key_t : public ec_key_t { + public: + exdsa_private_key_t(uint8_t *key_buf, size_t key_buf_len, pgp_curve_t curve); + exdsa_private_key_t(std::vector key_buf, pgp_curve_t curve); + exdsa_private_key_t() = default; + + bool is_valid(rnp::RNG *rng) const; + + std::vector + get_encoded() const + { + return Botan::unlock(key_); + } + + rnp_result_t sign(rnp::RNG * rng, + std::vector &sig_out, + const uint8_t * hash, + size_t hash_len, + pgp_hash_alg_t hash_alg) const; + + private: + Botan::ECDSA_PrivateKey botan_key(rnp::RNG *rng) const; + + Botan::secure_vector key_; +}; + +typedef struct exdsa_key_t { + exdsa_private_key_t priv; + exdsa_public_key_t pub; +} exdsa_key_t; + +#endif diff --git a/src/lib/crypto/hash.hpp b/src/lib/crypto/hash.hpp index 7fcb8177..7bf763e6 100644 --- a/src/lib/crypto/hash.hpp +++ b/src/lib/crypto/hash.hpp @@ -57,6 +57,7 @@ class Hash { virtual std::unique_ptr clone() const = 0; virtual void add(const void *buf, size_t len) = 0; + virtual void add(const std::vector &val); virtual void add(uint32_t val); virtual void add(const pgp_mpi_t &mpi); virtual size_t finish(uint8_t *digest = NULL) = 0; diff --git a/src/lib/crypto/hash_common.cpp b/src/lib/crypto/hash_common.cpp index 194cb07a..8a45f229 100644 --- a/src/lib/crypto/hash_common.cpp +++ b/src/lib/crypto/hash_common.cpp @@ -100,6 +100,12 @@ CRC24::create() #endif } +void +Hash::add(const std::vector &val) +{ + add(val.data(), val.size()); +} + void Hash::add(uint32_t val) { diff --git a/src/lib/crypto/hkdf.cpp b/src/lib/crypto/hkdf.cpp new file mode 100644 index 00000000..1ddac874 --- /dev/null +++ b/src/lib/crypto/hkdf.cpp @@ -0,0 +1,72 @@ +/* + * 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 "config.h" + +#if defined(ENABLE_CRYPTO_REFRESH) + +#include "hkdf.hpp" + +#if defined(CRYPTO_BACKEND_BOTAN) +#include "hkdf_botan.hpp" +#endif +#if defined(CRYPTO_BACKEND_OPENSSL) +#error HKDF not implemented for OpenSSL Backend +#endif + +namespace rnp { +std::unique_ptr +Hkdf::create(pgp_hash_alg_t alg) +{ +#if defined(CRYPTO_BACKEND_OPENSSL) +#error HKDF not implemented for OpenSSL + // return Hash_OpenSSL::create(alg); +#elif defined(CRYPTO_BACKEND_BOTAN) + return Hkdf_Botan::create(alg); +#else +#error "Crypto backend not specified" +#endif +} + +size_t +Hkdf::size() const +{ + return size_; +} + +pgp_hash_alg_t +Hkdf::alg() const +{ + return hash_alg_; +} + +Hkdf::~Hkdf() +{ +} + +} // namespace rnp + +#endif diff --git a/src/lib/crypto/hkdf.hpp b/src/lib/crypto/hkdf.hpp new file mode 100644 index 00000000..e7d9c67d --- /dev/null +++ b/src/lib/crypto/hkdf.hpp @@ -0,0 +1,74 @@ +/* + * 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 CRYPTO_HKDF_H_ +#define CRYPTO_HKDF_H_ + +#include "config.h" + +#ifdef ENABLE_CRYPTO_REFRESH + +#include +#include "types.h" + +namespace rnp { +class Hkdf { + /*HKDF is used with SHA256 as hash algorithm, the session key as Initial Keying Material + * (IKM), the salt as salt, and the Packet Tag in OpenPGP format encoding (bits 7 and 6 + * set, bits 5-0 carry the packet tag), version number, cipher algorithm octet, AEAD + * algorithm octet, and chunk size octet as info parameter.*/ + + protected: + pgp_hash_alg_t hash_alg_; + size_t size_; + Hkdf(pgp_hash_alg_t hash_alg) : hash_alg_(hash_alg) + { + size_ = Hash::size(hash_alg); + }; + + public: + static std::unique_ptr create(pgp_hash_alg_t alg); + + pgp_hash_alg_t alg() const; + size_t size() const; + + virtual void extract_expand(const uint8_t *salt, + size_t salt_len, + const uint8_t *ikm, + size_t ikm_len, + const uint8_t *info, + size_t info_len, + uint8_t * output_buf, + size_t output_length) = 0; + + virtual ~Hkdf(); +}; + +} // namespace rnp + +#endif + +#endif diff --git a/src/lib/crypto/hkdf_botan.cpp b/src/lib/crypto/hkdf_botan.cpp new file mode 100644 index 00000000..8e700c34 --- /dev/null +++ b/src/lib/crypto/hkdf_botan.cpp @@ -0,0 +1,76 @@ +/* + * 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 "config.h" + +#ifdef ENABLE_CRYPTO_REFRESH + +#include "hkdf_botan.hpp" +#include "hash_botan.hpp" + +namespace rnp { + +Hkdf_Botan::Hkdf_Botan(pgp_hash_alg_t hash_alg) : Hkdf(hash_alg) +{ +} + +std::unique_ptr +Hkdf_Botan::create(pgp_hash_alg_t alg) +{ + return std::unique_ptr(new Hkdf_Botan(alg)); +} + +std::string +Hkdf_Botan::alg() const +{ + return std::string("HKDF(") + Hash_Botan::name_backend(Hkdf::alg()) + ")"; +} + +void +Hkdf_Botan::extract_expand(const uint8_t *salt, + size_t salt_len, + const uint8_t *ikm, + size_t ikm_len, + const uint8_t *info, + size_t info_len, + uint8_t * output_buf, + size_t output_length) +{ + std::unique_ptr kdf = Botan::KDF::create_or_throw(Hkdf_Botan::alg(), ""); + + Botan::secure_vector OKM; + OKM = kdf->derive_key(output_length, ikm, ikm_len, salt, salt_len, info, info_len); + + memcpy(output_buf, Botan::unlock(OKM).data(), output_length); +} + +Hkdf_Botan::~Hkdf_Botan() +{ +} + +} // namespace rnp + +#endif diff --git a/src/lib/crypto/hkdf_botan.hpp b/src/lib/crypto/hkdf_botan.hpp new file mode 100644 index 00000000..19f5f5ee --- /dev/null +++ b/src/lib/crypto/hkdf_botan.hpp @@ -0,0 +1,67 @@ +/* + * 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 CRYPTO_HKDF_BOTAN_HPP_ +#define CRYPTO_HKDF_BOTAN_HPP_ + +#include "config.h" + +#ifdef ENABLE_CRYPTO_REFRESH + +#include "hkdf.hpp" +#include "botan/kdf.h" + +namespace rnp { + +class Hkdf_Botan : public Hkdf { + private: + std::string alg() const; + + public: + // std::unique_ptr fn_; + + Hkdf_Botan(pgp_hash_alg_t alg); + Hkdf_Botan(const Hkdf_Botan &src); + + virtual ~Hkdf_Botan(); + + static std::unique_ptr create(pgp_hash_alg_t alg); + + void extract_expand(const uint8_t *salt, + size_t salt_len, + const uint8_t *ikm, + size_t ikm_len, + const uint8_t *info, + size_t info_len, + uint8_t * output_buf, + size_t output_length); +}; + +} // namespace rnp + +#endif + +#endif diff --git a/src/lib/crypto/kmac.cpp b/src/lib/crypto/kmac.cpp new file mode 100644 index 00000000..3b8f1b72 --- /dev/null +++ b/src/lib/crypto/kmac.cpp @@ -0,0 +1,113 @@ +/* + * 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 "config.h" +#include "kmac.hpp" + +#if defined(CRYPTO_BACKEND_BOTAN) +#include "kmac_botan.hpp" +#endif +#if defined(CRYPTO_BACKEND_OPENSSL) +#error KMAC256 not implemented for OpenSSL Backend +#endif + +namespace rnp { +std::unique_ptr +KMAC256::create() +{ +#if defined(CRYPTO_BACKEND_OPENSSL) +#error KMAC256 not implemented for OpenSSL + // return Hash_OpenSSL::create(); +#elif defined(CRYPTO_BACKEND_BOTAN) + return KMAC256_Botan::create(); +#else +#error "Crypto backend not specified" +#endif +} + +std::vector +KMAC256::domSeparation() const +{ + return domSeparation_; +} + +std::vector +KMAC256::customizationString() const +{ + return customizationString_; +} + +std::vector +KMAC256::counter() const +{ + return counter_; +} + +/* + // Input: + // algID - the algorithm ID encoded as octet + + fixedInfo = algID +*/ +std::vector +KMAC256::fixedInfo(pgp_pubkey_alg_t alg_id) +{ + std::vector result(static_cast(alg_id)); + return result; +} + +std::vector +KMAC256::encData(const std::vector &ecc_key_share, + const std::vector &ecc_ciphertext, + const std::vector &kyber_key_share, + const std::vector &kyber_ciphertext, + pgp_pubkey_alg_t alg_id) +{ + std::vector enc_data; + std::vector counter_vec = counter(); + std::vector fixedInfo_vec = fixedInfo(alg_id); + + /* draft-wussler-openpgp-pqc-02: + + eccKemData = eccKeyShare || eccCipherText + kyberKemData = kyberKeyShare || kyberCipherText + encData = counter || eccKemData || kyberKemData || fixedInfo + */ + enc_data.insert(enc_data.end(), counter_vec.begin(), counter_vec.end()); + enc_data.insert(enc_data.end(), ecc_key_share.begin(), ecc_key_share.end()); + enc_data.insert(enc_data.end(), ecc_ciphertext.begin(), ecc_ciphertext.end()); + enc_data.insert(enc_data.end(), kyber_key_share.begin(), kyber_key_share.end()); + enc_data.insert(enc_data.end(), kyber_ciphertext.begin(), kyber_ciphertext.end()); + enc_data.insert(enc_data.end(), fixedInfo_vec.begin(), fixedInfo_vec.end()); + + return enc_data; +} + +KMAC256::~KMAC256() +{ +} + +} // namespace rnp diff --git a/src/lib/crypto/kmac.hpp b/src/lib/crypto/kmac.hpp new file mode 100644 index 00000000..4daa6a52 --- /dev/null +++ b/src/lib/crypto/kmac.hpp @@ -0,0 +1,88 @@ +/* + * 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 CRYPTO_KMAC_H_ +#define CRYPTO_KMAC_H_ + +#include +#include "types.h" +#include "config.h" +#include "pgp-key.h" + +namespace rnp { +class KMAC256 { + /* KDF for PQC key combiner according to + * https://datatracker.ietf.org/doc/html/draft-wussler-openpgp-pqc-02 */ + + protected: + /* The value of domSeparation is the UTF-8 encoding of the string + "OpenPGPCompositeKeyDerivationFunction" and MUST be the following octet sequence: + + domSeparation := 4F 70 65 6E 50 47 50 43 6F 6D 70 6F 73 69 74 65 + 4B 65 79 44 65 72 69 76 61 74 69 6F 6E 46 75 6E + 63 74 69 6F 6E + + */ + const std::vector domSeparation_ = std::vector( + {0x4F, 0x70, 0x65, 0x6E, 0x50, 0x47, 0x50, 0x43, 0x6F, 0x6D, 0x70, 0x6F, 0x73, + 0x69, 0x74, 0x65, 0x4B, 0x65, 0x79, 0x44, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, + 0x69, 0x6F, 0x6E, 0x46, 0x75, 0x6E, 0x63, 0x74, 0x69, 0x6F, 0x6E}); + + /* customizationString := 4B 44 46 */ + const std::vector customizationString_ = std::vector({0x4B, 0x44, 0x46}); + + /* counter - a 4 byte counter set to the value 1 */ + const std::vector counter_ = std::vector({0x00, 0x00, 0x00, 0x01}); + + std::vector domSeparation() const; + std::vector customizationString() const; + std::vector counter() const; + std::vector fixedInfo(pgp_pubkey_alg_t alg_id); + std::vector encData(const std::vector &ecc_key_share, + const std::vector &ecc_ciphertext, + const std::vector &kyber_key_share, + const std::vector &kyber_ciphertext, + pgp_pubkey_alg_t alg_id); + + KMAC256(){}; + + public: + static std::unique_ptr create(); + + /* KMAC interface for OpenPGP PQC composite algorithms */ + virtual void compute(const std::vector &ecc_key_share, + const std::vector &ecc_key_ciphertext, + const std::vector &kyber_key_share, + const std::vector &kyber_ciphertext, + const pgp_pubkey_alg_t alg_id, + std::vector & out) = 0; + + virtual ~KMAC256(); +}; + +} // namespace rnp + +#endif diff --git a/src/lib/crypto/kmac_botan.cpp b/src/lib/crypto/kmac_botan.cpp new file mode 100644 index 00000000..161eecd0 --- /dev/null +++ b/src/lib/crypto/kmac_botan.cpp @@ -0,0 +1,70 @@ +/* + * 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 "kmac_botan.hpp" +#include "hash_botan.hpp" +#include "botan/mac.h" + +namespace rnp { + +KMAC256_Botan::KMAC256_Botan() : KMAC256() +{ +} + +std::unique_ptr +KMAC256_Botan::create() +{ + return std::unique_ptr(new KMAC256_Botan()); +} + +void +KMAC256_Botan::compute(const std::vector &ecc_key_share, + const std::vector &ecc_ciphertext, + const std::vector &kyber_key_share, + const std::vector &kyber_ciphertext, + const pgp_pubkey_alg_t alg_id, + std::vector & out) +{ + auto kmac = Botan::MessageAuthenticationCode::create_or_throw("KMAC-256(256)"); + + /* the mapping between the KEM Combiner and the MAC interface is: + * key <> domSeparation + * nonce <> customizationString + * message <> encData + */ + + kmac->set_key(domSeparation()); + kmac->start(customizationString()); // set nonce + kmac->update( + encData(ecc_key_share, ecc_ciphertext, kyber_key_share, kyber_ciphertext, alg_id)); + out = kmac->final_stdvec(); +} + +KMAC256_Botan::~KMAC256_Botan() +{ +} + +} // namespace rnp diff --git a/src/lib/crypto/kmac_botan.hpp b/src/lib/crypto/kmac_botan.hpp new file mode 100644 index 00000000..fb1711a8 --- /dev/null +++ b/src/lib/crypto/kmac_botan.hpp @@ -0,0 +1,54 @@ +/* + * 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 CRYPTO_KMAC_BOTAN_HPP_ +#define CRYPTO_KMAC_BOTAN_HPP_ + +#include "kmac.hpp" + +namespace rnp { + +class KMAC256_Botan : public KMAC256 { + private: + public: + KMAC256_Botan(); + KMAC256_Botan(const KMAC256_Botan &src); + + virtual ~KMAC256_Botan(); + + static std::unique_ptr create(); + + void compute(const std::vector &ecc_key_share, + const std::vector &ecc_ciphertext, + const std::vector &kyber_key_share, + const std::vector &kyber_ciphertext, + const pgp_pubkey_alg_t alg_id, + std::vector & out) override; +}; + +} // namespace rnp + +#endif diff --git a/src/lib/crypto/kyber.cpp b/src/lib/crypto/kyber.cpp new file mode 100644 index 00000000..7b975f14 --- /dev/null +++ b/src/lib/crypto/kyber.cpp @@ -0,0 +1,141 @@ +/* + * 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 "kyber.h" +#include +#include +#include + +namespace { +Botan::KyberMode +rnp_kyber_param_to_botan_kyber_mode(kyber_parameter_e mode) +{ + Botan::KyberMode result = Botan::KyberMode::Kyber1024; + if (mode == kyber_768) { + result = Botan::KyberMode::Kyber768; + } + return result; +} + +uint32_t +key_share_size_from_kyber_param(kyber_parameter_e param) +{ + if (param == kyber_768) { + return 24; + } + return 32; // kyber_1024 +} +} // namespace + +std::pair +kyber_generate_keypair(rnp::RNG *rng, kyber_parameter_e kyber_param) +{ + Botan::Kyber_PrivateKey kyber_priv(*rng->obj(), + rnp_kyber_param_to_botan_kyber_mode(kyber_param)); + + Botan::secure_vector encoded_private_key = kyber_priv.private_key_bits(); + std::unique_ptr kyber_pub = kyber_priv.public_key(); + + std::vector encoded_public_key = kyber_priv.public_key_bits(); + return std::make_pair(pgp_kyber_public_key_t(encoded_public_key, kyber_param), + pgp_kyber_private_key_t(encoded_private_key.data(), + encoded_private_key.size(), + kyber_param)); +} + +Botan::Kyber_PublicKey +pgp_kyber_public_key_t::botan_key() const +{ + return Botan::Kyber_PublicKey(key_encoded_, + rnp_kyber_param_to_botan_kyber_mode(kyber_mode_)); +} + +Botan::Kyber_PrivateKey +pgp_kyber_private_key_t::botan_key() const +{ + Botan::secure_vector key_sv(key_encoded_.data(), + key_encoded_.data() + key_encoded_.size()); + return Botan::Kyber_PrivateKey(key_sv, rnp_kyber_param_to_botan_kyber_mode(kyber_mode_)); +} + +kyber_encap_result_t +pgp_kyber_public_key_t::encapsulate(rnp::RNG *rng) +{ + assert(is_initialized_); + auto decoded_kyber_pub = botan_key(); + + Botan::PK_KEM_Encryptor kem_enc(decoded_kyber_pub, "Raw", "base"); + Botan::secure_vector encap_key; // this has to go over the wire + Botan::secure_vector data_encryption_key; // this is the key used for + // encryption of the payload data + kem_enc.encrypt(encap_key, + data_encryption_key, + *rng->obj(), + key_share_size_from_kyber_param(kyber_mode_)); + kyber_encap_result_t result; + result.ciphertext.insert( + result.ciphertext.end(), encap_key.data(), encap_key.data() + encap_key.size()); + result.symmetric_key.insert(result.symmetric_key.end(), + data_encryption_key.data(), + data_encryption_key.data() + data_encryption_key.size()); + return result; +} + +std::vector +pgp_kyber_private_key_t::decapsulate(rnp::RNG * rng, + const uint8_t *ciphertext, + size_t ciphertext_len) +{ + assert(is_initialized_); + auto decoded_kyber_priv = botan_key(); + Botan::PK_KEM_Decryptor kem_dec(decoded_kyber_priv, *rng->obj(), "Raw", "base"); + Botan::secure_vector dec_shared_key = kem_dec.decrypt( + ciphertext, ciphertext_len, key_share_size_from_kyber_param(kyber_mode_)); + return std::vector(dec_shared_key.data(), + dec_shared_key.data() + dec_shared_key.size()); +} + +bool +pgp_kyber_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_kyber_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); +} diff --git a/src/lib/crypto/kyber.h b/src/lib/crypto/kyber.h new file mode 100644 index 00000000..24122ce8 --- /dev/null +++ b/src/lib/crypto/kyber.h @@ -0,0 +1,112 @@ +/* + * 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 KYBER_H_ +#define KYBER_H_ + +#include "config.h" +#include +#include +#include +#include "crypto/rng.h" +#include +#include + +enum kyber_parameter_e { kyber_768, kyber_1024 }; + +struct kyber_encap_result_t { + std::vector ciphertext; + std::vector symmetric_key; +}; + +class pgp_kyber_private_key_t { + public: + pgp_kyber_private_key_t(const uint8_t * key_encoded, + size_t key_encoded_len, + kyber_parameter_e mode); + pgp_kyber_private_key_t(std::vector const &key_encoded, kyber_parameter_e mode); + pgp_kyber_private_key_t() = default; + + bool is_valid(rnp::RNG *rng) const; + + std::vector decapsulate(rnp::RNG * rng, + const uint8_t *ciphertext, + size_t ciphertext_len); + std::vector + get_encoded() const + { + return Botan::unlock(key_encoded_); + }; + + kyber_parameter_e + param() const + { + return kyber_mode_; + } + + private: + Botan::Kyber_PrivateKey botan_key() const; + + Botan::secure_vector key_encoded_; + kyber_parameter_e kyber_mode_; + bool is_initialized_ = false; +}; + +class pgp_kyber_public_key_t { + public: + pgp_kyber_public_key_t(const uint8_t * key_encoded, + size_t key_encoded_len, + kyber_parameter_e mode); + pgp_kyber_public_key_t(std::vector const &key_encoded, kyber_parameter_e mode); + pgp_kyber_public_key_t() = default; + kyber_encap_result_t encapsulate(rnp::RNG *rng); + + bool + operator==(const pgp_kyber_public_key_t &rhs) const + { + return (kyber_mode_ == rhs.kyber_mode_) && (key_encoded_ == rhs.key_encoded_); + } + + bool is_valid(rnp::RNG *rng) const; + + std::vector + get_encoded() const + { + return key_encoded_; + }; + + private: + Botan::Kyber_PublicKey botan_key() const; + + std::vector key_encoded_; + kyber_parameter_e kyber_mode_; + bool is_initialized_ = false; +}; + +std::pair kyber_generate_keypair( + rnp::RNG *rng, kyber_parameter_e kyber_param); + +#endif diff --git a/src/lib/crypto/kyber_common.cpp b/src/lib/crypto/kyber_common.cpp new file mode 100644 index 00000000..0d70a157 --- /dev/null +++ b/src/lib/crypto/kyber_common.cpp @@ -0,0 +1,114 @@ +/* + * 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 "kyber_common.h" +#include "types.h" +#include "logging.h" + +size_t +kyber_privkey_size(kyber_parameter_e parameter) +{ + switch (parameter) { + case kyber_768: + return 2400; + case kyber_1024: + return 3168; + default: + RNP_LOG("invalid parameter given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +size_t +kyber_pubkey_size(kyber_parameter_e parameter) +{ + switch (parameter) { + case kyber_768: + return 1184; + case kyber_1024: + return 1568; + default: + RNP_LOG("invalid parameter given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +size_t +kyber_keyshare_size(kyber_parameter_e parameter) +{ + switch (parameter) { + case kyber_768: + return 24; + case kyber_1024: + return 32; + default: + RNP_LOG("invalid parameter given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +size_t +kyber_ciphertext_size(kyber_parameter_e parameter) +{ + switch (parameter) { + case kyber_768: + return 1088; + case kyber_1024: + return 1568; + default: + RNP_LOG("invalid parameter given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +pgp_kyber_public_key_t::pgp_kyber_public_key_t(const uint8_t * key_encoded, + size_t key_encoded_len, + kyber_parameter_e mode) + : key_encoded_(key_encoded, key_encoded + key_encoded_len), kyber_mode_(mode), + is_initialized_(true) +{ +} + +pgp_kyber_public_key_t::pgp_kyber_public_key_t(std::vector const &key_encoded, + kyber_parameter_e mode) + : key_encoded_(key_encoded), kyber_mode_(mode), is_initialized_(true) +{ +} + +pgp_kyber_private_key_t::pgp_kyber_private_key_t(const uint8_t * key_encoded, + size_t key_encoded_len, + kyber_parameter_e mode) + : key_encoded_(key_encoded, key_encoded + key_encoded_len), kyber_mode_(mode), + is_initialized_(true) +{ +} + +pgp_kyber_private_key_t::pgp_kyber_private_key_t(std::vector const &key_encoded, + kyber_parameter_e mode) + : key_encoded_(Botan::secure_vector(key_encoded.begin(), key_encoded.end())), + kyber_mode_(mode), is_initialized_(true) +{ +} diff --git a/src/lib/crypto/kyber_common.h b/src/lib/crypto/kyber_common.h new file mode 100644 index 00000000..4b7978ef --- /dev/null +++ b/src/lib/crypto/kyber_common.h @@ -0,0 +1,37 @@ +/* + * 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 RNP_KYBER_COMMON_H_ +#define RNP_KYBER_COMMON_H_ + +#include "kyber.h" + +size_t kyber_privkey_size(kyber_parameter_e parameter); +size_t kyber_pubkey_size(kyber_parameter_e parameter); +size_t kyber_keyshare_size(kyber_parameter_e parameter); +size_t kyber_ciphertext_size(kyber_parameter_e parameter); + +#endif diff --git a/src/lib/crypto/kyber_ecdh_composite.cpp b/src/lib/crypto/kyber_ecdh_composite.cpp new file mode 100644 index 00000000..b6955621 --- /dev/null +++ b/src/lib/crypto/kyber_ecdh_composite.cpp @@ -0,0 +1,591 @@ +/* + * 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 "kyber_ecdh_composite.h" +#include "logging.h" +#include "types.h" +#include "ecdh_utils.h" +#include "kmac.hpp" +#include +#include +#include + +pgp_kyber_ecdh_composite_key_t::~pgp_kyber_ecdh_composite_key_t() +{ +} + +void +pgp_kyber_ecdh_composite_key_t::initialized_or_throw() const +{ + if (!is_initialized()) { + RNP_LOG("Trying to use uninitialized kyber-ecdh key"); + throw rnp::rnp_exception(RNP_ERROR_GENERIC); /* TODO better return error */ + } +} + +rnp_result_t +pgp_kyber_ecdh_composite_key_t::gen_keypair(rnp::RNG * rng, + pgp_kyber_ecdh_key_t *key, + pgp_pubkey_alg_t alg) +{ + rnp_result_t res; + pgp_curve_t curve = pk_alg_to_curve_id(alg); + kyber_parameter_e kyber_id = pk_alg_to_kyber_id(alg); + + ecdh_kem_key_t ecdh_key_pair; + + res = ec_key_t::generate_ecdh_kem_key_pair(rng, &ecdh_key_pair, curve); + if (res != RNP_SUCCESS) { + RNP_LOG("generating kyber ecdh composite key failed when generating ecdh key"); + return res; + } + + auto kyber_key_pair = kyber_generate_keypair(rng, kyber_id); + + key->priv = pgp_kyber_ecdh_composite_private_key_t( + ecdh_key_pair.priv.get_encoded(), kyber_key_pair.second.get_encoded(), alg); + key->pub = pgp_kyber_ecdh_composite_public_key_t( + ecdh_key_pair.pub.get_encoded(), kyber_key_pair.first.get_encoded(), alg); + + return RNP_SUCCESS; +} + +size_t +pgp_kyber_ecdh_composite_key_t::ecdh_curve_privkey_size(pgp_curve_t curve) +{ + switch (curve) { + case PGP_CURVE_25519: + return 32; + /* TODO */ + // case PGP_CURVE_X448: + // return 56; + case PGP_CURVE_NIST_P_256: + return 32; + case PGP_CURVE_NIST_P_384: + return 48; + case PGP_CURVE_BP256: + return 32; + case PGP_CURVE_BP384: + return 48; + default: + RNP_LOG("invalid curve given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +size_t +pgp_kyber_ecdh_composite_key_t::ecdh_curve_pubkey_size(pgp_curve_t curve) +{ + switch (curve) { + case PGP_CURVE_25519: + return 32; + /* TODO */ + // case PGP_CURVE_X448: + // return 56; + case PGP_CURVE_NIST_P_256: + return 65; + case PGP_CURVE_NIST_P_384: + return 97; + case PGP_CURVE_BP256: + return 65; + case PGP_CURVE_BP384: + return 97; + default: + RNP_LOG("invalid curve given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +size_t +pgp_kyber_ecdh_composite_key_t::ecdh_curve_ephemeral_size(pgp_curve_t curve) +{ + switch (curve) { + case PGP_CURVE_25519: + return 32; + /* TODO */ + // case PGP_CURVE_X448: + // return 56; + case PGP_CURVE_NIST_P_256: + return 65; + case PGP_CURVE_NIST_P_384: + return 97; + case PGP_CURVE_BP256: + return 65; + case PGP_CURVE_BP384: + return 97; + default: + RNP_LOG("invalid curve given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +size_t +pgp_kyber_ecdh_composite_key_t::ecdh_curve_keyshare_size(pgp_curve_t curve) +{ + switch (curve) { + case PGP_CURVE_25519: + return 32; + /* TODO */ + // case PGP_CURVE_X448: + // return 56; + case PGP_CURVE_NIST_P_256: + return 32; + case PGP_CURVE_NIST_P_384: + return 48; + case PGP_CURVE_BP256: + return 32; + case PGP_CURVE_BP384: + return 48; + default: + RNP_LOG("invalid curve given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +kyber_parameter_e +pgp_kyber_ecdh_composite_key_t::pk_alg_to_kyber_id(pgp_pubkey_alg_t pk_alg) +{ + switch (pk_alg) { + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + return kyber_768; + case PGP_PKA_KYBER1024_BP384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + return kyber_1024; + default: + RNP_LOG("invalid PK alg given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +pgp_curve_t +pgp_kyber_ecdh_composite_key_t::pk_alg_to_curve_id(pgp_pubkey_alg_t pk_alg) +{ + switch (pk_alg) { + case PGP_PKA_KYBER768_X25519: + return PGP_CURVE_25519; + case PGP_PKA_KYBER768_P256: + return PGP_CURVE_NIST_P_256; + case PGP_PKA_KYBER768_BP256: + return PGP_CURVE_BP256; + case PGP_PKA_KYBER1024_BP384: + return PGP_CURVE_BP384; + case PGP_PKA_KYBER1024_P384: + return PGP_CURVE_NIST_P_384; + /*case PGP_PKA_KYBER1024_X448: + return ... NOT_IMPLEMENTED*/ + default: + RNP_LOG("invalid PK alg given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +pgp_kyber_ecdh_composite_private_key_t::pgp_kyber_ecdh_composite_private_key_t( + const uint8_t *key_encoded, size_t key_encoded_len, pgp_pubkey_alg_t pk_alg) + : pk_alg_(pk_alg) +{ + parse_component_keys(std::vector(key_encoded, key_encoded + key_encoded_len)); +} + +pgp_kyber_ecdh_composite_private_key_t::pgp_kyber_ecdh_composite_private_key_t( + std::vector const &key_encoded, pgp_pubkey_alg_t pk_alg) + : pk_alg_(pk_alg) +{ + parse_component_keys(key_encoded); +} + +pgp_kyber_ecdh_composite_private_key_t::pgp_kyber_ecdh_composite_private_key_t( + std::vector const &ecdh_key_encoded, + std::vector const &kyber_key_encoded, + pgp_pubkey_alg_t pk_alg) + : pk_alg_(pk_alg) +{ + if (ecdh_curve_privkey_size(pk_alg_to_curve_id(pk_alg)) != ecdh_key_encoded.size() || + kyber_privkey_size(pk_alg_to_kyber_id(pk_alg)) != kyber_key_encoded.size()) { + RNP_LOG("ecdh or kyber key length mismatch"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + kyber_key_ = std::make_unique( + pgp_kyber_private_key_t(kyber_key_encoded, pk_alg_to_kyber_id(pk_alg))); + ecdh_key_ = std::make_unique( + ecdh_kem_private_key_t(ecdh_key_encoded, pk_alg_to_curve_id(pk_alg))); + + is_initialized_ = true; +} + +/* copy assignment operator is used on key materials struct and thus needs to be defined for + * this class as well */ +pgp_kyber_ecdh_composite_private_key_t & +pgp_kyber_ecdh_composite_private_key_t::operator=( + const pgp_kyber_ecdh_composite_private_key_t &other) +{ + pgp_kyber_ecdh_composite_key_t::operator=(other); + pk_alg_ = other.pk_alg_; + if (other.is_initialized() && other.kyber_key_) { + kyber_key_ = std::make_unique( + pgp_kyber_private_key_t(other.kyber_key_->get_encoded(), other.kyber_key_->param())); + } + if (other.is_initialized() && other.ecdh_key_) { + ecdh_key_ = std::make_unique(ecdh_kem_private_key_t( + other.ecdh_key_->get_encoded(), other.ecdh_key_->get_curve())); + } + + return *this; +} + +size_t +pgp_kyber_ecdh_composite_private_key_t::encoded_size(pgp_pubkey_alg_t pk_alg) +{ + kyber_parameter_e kyber_param = pk_alg_to_kyber_id(pk_alg); + pgp_curve_t curve = pk_alg_to_curve_id(pk_alg); + return ecdh_curve_privkey_size(curve) + kyber_privkey_size(kyber_param); +} + +void +pgp_kyber_ecdh_composite_private_key_t::parse_component_keys(std::vector key_encoded) +{ + if (key_encoded.size() != encoded_size(pk_alg_)) { + RNP_LOG("Kyber composite key format invalid: length mismatch"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + kyber_parameter_e kyber_param = pk_alg_to_kyber_id(pk_alg_); + pgp_curve_t ecdh_curve = pk_alg_to_curve_id(pk_alg_); + size_t split_at = ecdh_curve_privkey_size(pk_alg_to_curve_id(pk_alg_)); + + kyber_key_ = std::make_unique(pgp_kyber_private_key_t( + key_encoded.data() + split_at, key_encoded.size() - split_at, kyber_param)); + ecdh_key_ = std::make_unique( + ecdh_kem_private_key_t(key_encoded.data(), split_at, ecdh_curve)); + + is_initialized_ = true; +} + +namespace { +std::vector +hashed_ecc_keyshare(const std::vector &key_share, + const std::vector &ciphertext, + const std::vector &ecc_pubkey, + pgp_pubkey_alg_t alg_id) +{ + /* SHA3-256(X || eccCipherText) or SHA3-512(X || eccCipherText) depending on algorithm */ + + std::vector digest; + pgp_hash_alg_t hash_alg; + + switch (alg_id) { + case PGP_PKA_KYBER768_X25519: + case PGP_PKA_KYBER768_BP256: + case PGP_PKA_KYBER768_P256: + hash_alg = PGP_HASH_SHA3_256; + break; + // case PGP_PKA_KYBER1024_X448: + case PGP_PKA_KYBER1024_P384: + case PGP_PKA_KYBER1024_BP384: + hash_alg = PGP_HASH_SHA3_512; + break; + default: + RNP_LOG("key combiner does not support this algorithm"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + auto hash = rnp::Hash::create(hash_alg); + hash->add(key_share); + hash->add(ciphertext); + hash->add(ecc_pubkey); + + digest.resize(rnp::Hash::size(hash_alg)); + hash->finish(digest.data()); + + return digest; +} +} // namespace + +rnp_result_t +pgp_kyber_ecdh_composite_private_key_t::decrypt(rnp::RNG * rng, + uint8_t * out, + size_t * out_len, + const pgp_kyber_ecdh_encrypted_t *enc) +{ + initialized_or_throw(); + rnp_result_t res; + std::vector ecdh_keyshare; + std::vector hashed_ecdh_keyshare; + std::vector kyber_keyshare; + + if (((enc->wrapped_sesskey.size() % 8) != 0) || (enc->wrapped_sesskey.size() < 16)) { + RNP_LOG("invalid wrapped AES key length (size is a multiple of 8 octets with 8 octets " + "integrity check)"); + return RNP_ERROR_BAD_PARAMETERS; + } + + // Compute (eccKeyShare) := eccKem.decap(eccCipherText, eccPrivateKey) + pgp_curve_t curve = pk_alg_to_curve_id(pk_alg_); + std::vector ecdh_encapsulated_keyshare = std::vector( + enc->composite_ciphertext.data(), + enc->composite_ciphertext.data() + ecdh_curve_ephemeral_size(curve)); + res = ecdh_key_->decapsulate(rng, ecdh_encapsulated_keyshare, ecdh_keyshare); + if (res) { + RNP_LOG("error when decrypting kyber-ecdh encrypted session key"); + return res; + } + hashed_ecdh_keyshare = hashed_ecc_keyshare( + ecdh_keyshare, ecdh_encapsulated_keyshare, ecdh_key_->get_pubkey_encoded(rng), pk_alg_); + + // Compute (kyberKeyShare) := kyberKem.decap(kyberCipherText, kyberPrivateKey) + std::vector kyber_encapsulated_keyshare = std::vector( + enc->composite_ciphertext.begin() + ecdh_curve_ephemeral_size(curve), + enc->composite_ciphertext.end()); + kyber_keyshare = kyber_key_->decapsulate( + rng, kyber_encapsulated_keyshare.data(), kyber_encapsulated_keyshare.size()); + if (res) { + RNP_LOG("error when decrypting kyber-ecdh encrypted session key"); + return res; + } + + // Compute KEK := multiKeyCombine(eccKeyShare, kyberKeyShare, fixedInfo) as defined in + // Section 4.2.2 + std::vector kek_vec; + auto kmac = rnp::KMAC256::create(); + kmac->compute(hashed_ecdh_keyshare, + ecdh_encapsulated_keyshare, + kyber_keyshare, + kyber_encapsulated_keyshare, + pk_alg(), + kek_vec); + Botan::SymmetricKey kek(kek_vec); + + // Compute sessionKey := AESKeyUnwrap(KEK, C) with AES-256 as per [RFC3394], aborting if + // the 64 bit integrity check fails + Botan::secure_vector tmp_out; + try { + tmp_out = + Botan::rfc3394_keyunwrap(Botan::secure_vector(enc->wrapped_sesskey.begin(), + enc->wrapped_sesskey.end()), + kek); + } catch (const std::exception &e) { + RNP_LOG("Keyunwrap failed: %s", e.what()); + return RNP_ERROR_DECRYPT_FAILED; + } + + if (*out_len < tmp_out.size()) { + RNP_LOG("buffer for decryption result too small"); + return RNP_ERROR_DECRYPT_FAILED; + } + *out_len = tmp_out.size(); + memcpy(out, tmp_out.data(), *out_len); + + return RNP_SUCCESS; +} + +void +pgp_kyber_ecdh_composite_private_key_t::secure_clear() +{ + // private key buffer is stored in a secure_vector and will be securely erased by the + // destructor. + kyber_key_.reset(); + ecdh_key_.reset(); + is_initialized_ = false; +} + +std::vector +pgp_kyber_ecdh_composite_private_key_t::get_encoded() const +{ + initialized_or_throw(); + std::vector result; + std::vector ecdh_key_encoded = ecdh_key_->get_encoded(); + std::vector kyber_key_encoded = kyber_key_->get_encoded(); + + result.insert(result.end(), std::begin(ecdh_key_encoded), std::end(ecdh_key_encoded)); + result.insert(result.end(), std::begin(kyber_key_encoded), std::end(kyber_key_encoded)); + return result; +}; + +pgp_kyber_ecdh_composite_public_key_t::pgp_kyber_ecdh_composite_public_key_t( + const uint8_t *key_encoded, size_t key_encoded_len, pgp_pubkey_alg_t pk_alg) + : pk_alg_(pk_alg) +{ + parse_component_keys(std::vector(key_encoded, key_encoded + key_encoded_len)); +} + +pgp_kyber_ecdh_composite_public_key_t::pgp_kyber_ecdh_composite_public_key_t( + std::vector const &key_encoded, pgp_pubkey_alg_t pk_alg) + : pk_alg_(pk_alg) +{ + parse_component_keys(key_encoded); +} + +pgp_kyber_ecdh_composite_public_key_t::pgp_kyber_ecdh_composite_public_key_t( + std::vector const &ecdh_key_encoded, + std::vector const &kyber_key_encoded, + pgp_pubkey_alg_t pk_alg) + : pk_alg_(pk_alg), kyber_key_(kyber_key_encoded, pk_alg_to_kyber_id(pk_alg)), + ecdh_key_(ecdh_key_encoded, pk_alg_to_curve_id(pk_alg)) +{ + if (ecdh_curve_pubkey_size(pk_alg_to_curve_id(pk_alg)) != ecdh_key_encoded.size() || + kyber_pubkey_size(pk_alg_to_kyber_id(pk_alg)) != kyber_key_encoded.size()) { + RNP_LOG("ecdh or kyber key length mismatch"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + is_initialized_ = true; +} + +size_t +pgp_kyber_ecdh_composite_public_key_t::encoded_size(pgp_pubkey_alg_t pk_alg) +{ + kyber_parameter_e kyber_param = pk_alg_to_kyber_id(pk_alg); + pgp_curve_t curve = pk_alg_to_curve_id(pk_alg); + return ecdh_curve_pubkey_size(curve) + kyber_pubkey_size(kyber_param); +} + +void +pgp_kyber_ecdh_composite_public_key_t::parse_component_keys(std::vector key_encoded) +{ + if (key_encoded.size() != encoded_size(pk_alg_)) { + RNP_LOG("Kyber composite key format invalid: length mismatch"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + kyber_parameter_e kyber_param = pk_alg_to_kyber_id(pk_alg_); + pgp_curve_t ecdh_curve = pk_alg_to_curve_id(pk_alg_); + size_t split_at = ecdh_curve_pubkey_size(pk_alg_to_curve_id(pk_alg_)); + + kyber_key_ = pgp_kyber_public_key_t( + key_encoded.data() + split_at, key_encoded.size() - split_at, kyber_param); + ecdh_key_ = ecdh_kem_public_key_t(key_encoded.data(), split_at, ecdh_curve); + + is_initialized_ = true; +} + +rnp_result_t +pgp_kyber_ecdh_composite_public_key_t::encrypt(rnp::RNG * rng, + pgp_kyber_ecdh_encrypted_t *out, + const uint8_t * session_key, + size_t session_key_len) +{ + initialized_or_throw(); + + rnp_result_t res; + std::vector ecdh_ciphertext; + std::vector ecdh_symmetric_key; + std::vector ecdh_hashed_symmetric_key; + + if ((session_key_len % 8) != 0) { + RNP_LOG("AES key wrap requires a multiple of 8 octets as input key"); + return RNP_ERROR_BAD_PARAMETERS; + } + + // Compute (eccCipherText, eccKeyShare) := eccKem.encap(eccPublicKey) + res = ecdh_key_.encapsulate(rng, ecdh_ciphertext, ecdh_symmetric_key); + if (res) { + RNP_LOG("error when encapsulating with ECDH"); + return res; + } + ecdh_hashed_symmetric_key = hashed_ecc_keyshare( + ecdh_symmetric_key, ecdh_ciphertext, ecdh_key_.get_encoded(), pk_alg_); + + // Compute (kyberCipherText, kyberKeyShare) := kyberKem.encap(kyberPublicKey) + kyber_encap_result_t kyber_encap = kyber_key_.encapsulate(rng); + + // Compute KEK := multiKeyCombine(eccKeyShare, kyberKeyShare, fixedInfo) as defined in + // Section 4.2.2 + std::vector kek_vec; + auto kmac = rnp::KMAC256::create(); + kmac->compute(ecdh_hashed_symmetric_key, + ecdh_ciphertext, + kyber_encap.symmetric_key, + kyber_encap.ciphertext, + pk_alg(), + kek_vec); + Botan::SymmetricKey kek(kek_vec); + + // Compute C := AESKeyWrap(KEK, sessionKey) with AES-256 as per [RFC3394] that includes a + // 64 bit integrity check + try { + out->wrapped_sesskey = Botan::unlock(Botan::rfc3394_keywrap( + Botan::secure_vector(session_key, session_key + session_key_len), kek)); + } catch (const std::exception &e) { + RNP_LOG("Keywrap failed: %s", e.what()); + return RNP_ERROR_ENCRYPT_FAILED; + } + + out->composite_ciphertext.assign(ecdh_ciphertext.begin(), ecdh_ciphertext.end()); + out->composite_ciphertext.insert(out->composite_ciphertext.end(), + kyber_encap.ciphertext.begin(), + kyber_encap.ciphertext.end()); + return RNP_SUCCESS; +} + +std::vector +pgp_kyber_ecdh_composite_public_key_t::get_encoded() const +{ + initialized_or_throw(); + std::vector result; + std::vector ecdh_key_encoded = ecdh_key_.get_encoded(); + std::vector kyber_key_encoded = kyber_key_.get_encoded(); + + result.insert(result.end(), std::begin(ecdh_key_encoded), std::end(ecdh_key_encoded)); + result.insert(result.end(), std::begin(kyber_key_encoded), std::end(kyber_key_encoded)); + return result; +}; + +bool +pgp_kyber_ecdh_composite_public_key_t::is_valid(rnp::RNG *rng) const +{ + if (!is_initialized()) { + return false; + } + return (ecdh_key_.is_valid(rng) && kyber_key_.is_valid(rng)); +} + +bool +pgp_kyber_ecdh_composite_private_key_t::is_valid(rnp::RNG *rng) const +{ + if (!is_initialized()) { + return false; + } + return (ecdh_key_->is_valid(rng) && kyber_key_->is_valid(rng)); +} + +rnp_result_t +kyber_ecdh_validate_key(rnp::RNG *rng, const pgp_kyber_ecdh_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; +} diff --git a/src/lib/crypto/kyber_ecdh_composite.h b/src/lib/crypto/kyber_ecdh_composite.h new file mode 100644 index 00000000..37f87833 --- /dev/null +++ b/src/lib/crypto/kyber_ecdh_composite.h @@ -0,0 +1,183 @@ +/* + * 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 RNP_KYBER_ECDH_COMPOSITE_H_ +#define RNP_KYBER_ECDH_COMPOSITE_H_ + +#include "config.h" +#include +#include +#include +#include "crypto/rng.h" +#include "crypto/kyber.h" +#include "crypto/kyber_common.h" +#include "crypto/ecdh.h" +#include "crypto/exdsa_ecdhkem.h" +#include + +struct pgp_kyber_ecdh_key_t; /* forward declaration */ + +class pgp_kyber_ecdh_composite_key_t { + public: + virtual ~pgp_kyber_ecdh_composite_key_t() = 0; + + static rnp_result_t gen_keypair(rnp::RNG * rng, + pgp_kyber_ecdh_key_t *key, + pgp_pubkey_alg_t alg); + + static size_t ecdh_curve_privkey_size(pgp_curve_t curve); + static size_t ecdh_curve_pubkey_size(pgp_curve_t curve); + static size_t ecdh_curve_ephemeral_size(pgp_curve_t curve); + static size_t ecdh_curve_keyshare_size(pgp_curve_t curve); + static pgp_curve_t pk_alg_to_curve_id(pgp_pubkey_alg_t pk_alg); + static kyber_parameter_e pk_alg_to_kyber_id(pgp_pubkey_alg_t pk_alg); + + bool + is_initialized() const + { + return is_initialized_; + } + + protected: + bool is_initialized_ = false; + void initialized_or_throw() const; +}; + +typedef struct pgp_kyber_ecdh_encrypted_t { + std::vector composite_ciphertext; + std::vector wrapped_sesskey; + + static size_t + composite_ciphertext_size(pgp_pubkey_alg_t pk_alg) + { + return kyber_ciphertext_size( + pgp_kyber_ecdh_composite_key_t::pk_alg_to_kyber_id(pk_alg)) + + pgp_kyber_ecdh_composite_key_t::ecdh_curve_ephemeral_size( + pgp_kyber_ecdh_composite_key_t::pk_alg_to_curve_id(pk_alg)); + } +} pgp_kyber_ecdh_encrypted_t; + +class pgp_kyber_ecdh_composite_private_key_t : public pgp_kyber_ecdh_composite_key_t { + public: + pgp_kyber_ecdh_composite_private_key_t(const uint8_t * key_encoded, + size_t key_encoded_len, + pgp_pubkey_alg_t pk_alg); + pgp_kyber_ecdh_composite_private_key_t(std::vector const &ecdh_key_encoded, + std::vector const &kyber_key_encoded, + pgp_pubkey_alg_t pk_alg); + pgp_kyber_ecdh_composite_private_key_t(std::vector const &key_encoded, + pgp_pubkey_alg_t pk_alg); + pgp_kyber_ecdh_composite_private_key_t &operator=( + const pgp_kyber_ecdh_composite_private_key_t &other); + pgp_kyber_ecdh_composite_private_key_t() = default; + + rnp_result_t decrypt(rnp::RNG * rng, + uint8_t * out, + size_t * out_len, + const pgp_kyber_ecdh_encrypted_t *enc); + + bool is_valid(rnp::RNG *rng) const; + std::vector get_encoded() const; + + pgp_pubkey_alg_t + pk_alg() const + { + return pk_alg_; + } + + void secure_clear(); + + static size_t encoded_size(pgp_pubkey_alg_t pk_alg); + + private: + void parse_component_keys(std::vector key_encoded); + + pgp_pubkey_alg_t pk_alg_; + + /* kyber part */ + std::unique_ptr kyber_key_; + + /* ecc part*/ + std::unique_ptr ecdh_key_; +}; + +class pgp_kyber_ecdh_composite_public_key_t : public pgp_kyber_ecdh_composite_key_t { + public: + pgp_kyber_ecdh_composite_public_key_t(const uint8_t * key_encoded, + size_t key_encoded_len, + pgp_pubkey_alg_t pk_alg); + pgp_kyber_ecdh_composite_public_key_t(std::vector const &ecdh_key_encoded, + std::vector const &kyber_key_encoded, + pgp_pubkey_alg_t pk_alg); + pgp_kyber_ecdh_composite_public_key_t(std::vector const &key_encoded, + pgp_pubkey_alg_t pk_alg); + pgp_kyber_ecdh_composite_public_key_t() = default; + + bool + operator==(const pgp_kyber_ecdh_composite_public_key_t &rhs) const + { + return (pk_alg_ == rhs.pk_alg_) && (kyber_key_ == rhs.kyber_key_) && + (ecdh_key_ == rhs.ecdh_key_); + } + + rnp_result_t encrypt(rnp::RNG * rng, + pgp_kyber_ecdh_encrypted_t *out, + const uint8_t * in, + size_t in_len); + + bool is_valid(rnp::RNG *rng) const; + std::vector get_encoded() const; + + pgp_pubkey_alg_t + pk_alg() const + { + return pk_alg_; + } + + static size_t encoded_size(pgp_pubkey_alg_t pk_alg); + + private: + void parse_component_keys(std::vector key_encoded); + + pgp_pubkey_alg_t pk_alg_; + + /* kyber part */ + pgp_kyber_public_key_t kyber_key_; + + /* ecc part*/ + ecdh_kem_public_key_t ecdh_key_; +}; + +typedef struct pgp_kyber_ecdh_key_t { + pgp_kyber_ecdh_composite_private_key_t priv; + pgp_kyber_ecdh_composite_public_key_t pub; +} pgp_kyber_ecdh_key_t; + +rnp_result_t kyber_ecdh_validate_key(rnp::RNG * rng, + const pgp_kyber_ecdh_key_t *key, + bool secret); + +#endif diff --git a/src/lib/crypto/rng.cpp b/src/lib/crypto/rng.cpp index bf5bfadc..63a16e00 100644 --- a/src/lib/crypto/rng.cpp +++ b/src/lib/crypto/rng.cpp @@ -35,6 +35,13 @@ RNG::RNG(Type type) if (botan_rng_init(&botan_rng, type == Type::DRBG ? "user" : NULL)) { throw rnp::rnp_exception(RNP_ERROR_RNG); } +#if defined(ENABLE_CRYPTO_REFRESH) + if (type == Type::DRBG) { + botan_rng_obj.reset(new Botan::AutoSeeded_RNG); + } else { + botan_rng_obj.reset(new Botan::System_RNG); + } +#endif } RNG::~RNG() @@ -56,4 +63,12 @@ RNG::handle() { return botan_rng; } + +#if defined(ENABLE_CRYPTO_REFRESH) +Botan::RandomNumberGenerator * +RNG::obj() const +{ + return botan_rng_obj.get(); +} +#endif } // namespace rnp diff --git a/src/lib/crypto/rng.h b/src/lib/crypto/rng.h index f452bd9e..00da52c8 100644 --- a/src/lib/crypto/rng.h +++ b/src/lib/crypto/rng.h @@ -34,6 +34,11 @@ #ifdef CRYPTO_BACKEND_BOTAN typedef struct botan_rng_struct *botan_rng_t; + +#if defined(ENABLE_CRYPTO_REFRESH) +#include +#include +#endif #endif namespace rnp { @@ -41,6 +46,9 @@ class RNG { private: #ifdef CRYPTO_BACKEND_BOTAN struct botan_rng_struct *botan_rng; +#if defined(ENABLE_CRYPTO_REFRESH) + std::unique_ptr botan_rng_obj; +#endif #endif public: enum Type { DRBG, System }; @@ -71,6 +79,15 @@ class RNG { * internal error NULL is returned */ struct botan_rng_struct *handle(); + +#if defined(ENABLE_CRYPTO_REFRESH) + /** + * @brief Returns the Botan RNG C++ object + * Note: It is planned to move away from the FFI handle. + * For the transition phase, both approaches are implemented. + */ + Botan::RandomNumberGenerator *obj() const; +#endif #endif }; } // namespace rnp diff --git a/src/lib/crypto/signatures.cpp b/src/lib/crypto/signatures.cpp index 8531360d..8255ca68 100644 --- a/src/lib/crypto/signatures.cpp +++ b/src/lib/crypto/signatures.cpp @@ -28,6 +28,7 @@ #include "crypto/signatures.h" #include "librepgp/stream-packet.h" #include "librepgp/stream-sig.h" +#include "librepgp/stream-key.h" #include "utils.h" #include "sec_profile.hpp" @@ -44,21 +45,30 @@ static void signature_hash_finish(const pgp_signature_t &sig, rnp::Hash &hash, uint8_t *hbuf, size_t &hlen) { hash.add(sig.hashed_data, sig.hashed_len); - if (sig.version > PGP_V3) { - uint8_t trailer[6] = {0x04, 0xff, 0x00, 0x00, 0x00, 0x00}; + if (sig.version >= PGP_V4) { + uint8_t trailer[6] = {0x00, 0xff, 0x00, 0x00, 0x00, 0x00}; + trailer[0] = sig.version; write_uint32(&trailer[2], sig.hashed_len); + hash.add(trailer, 6); } hlen = hash.finish(hbuf); } std::unique_ptr -signature_init(const pgp_key_material_t &key, pgp_hash_alg_t hash_alg) +signature_init(const pgp_key_pkt_t &key, const pgp_signature_t &sig) { - auto hash = rnp::Hash::create(hash_alg); - if (key.alg == PGP_PKA_SM2) { + auto hash = rnp::Hash::create(sig.halg); + +#if defined(ENABLE_CRYPTO_REFRESH) + if (key.version == PGP_V6) { + hash->add(sig.salt, sig.salt_size); + } +#endif + + if (key.material.alg == PGP_PKA_SM2) { #if defined(ENABLE_SM2) - rnp_result_t r = sm2_compute_za(key.ec, *hash); + rnp_result_t r = sm2_compute_za(key.material.ec, *hash); if (r != RNP_SUCCESS) { RNP_LOG("failed to compute SM2 ZA field"); throw rnp::rnp_exception(r); @@ -125,6 +135,15 @@ signature_calculate(pgp_signature_t & sig, RNP_LOG("eddsa signing failed"); } break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + ret = + ed25519_sign_native(&ctx.rng, material.ed25519.sig, seckey.ed25519.priv, hval, hlen); + if (ret) { + RNP_LOG("ed25519 signing failed"); + } + break; +#endif case PGP_PKA_DSA: ret = dsa_sign(&ctx.rng, &material.dsa, hval, hlen, &seckey.dsa); if (ret != RNP_SUCCESS) { @@ -175,6 +194,26 @@ signature_calculate(pgp_signature_t & sig, } break; } +#if defined(ENABLE_PQC) + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + ret = seckey.dilithium_exdsa.priv.sign( + &ctx.rng, &material.dilithium_exdsa, hash_alg, hval, hlen); + break; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + 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); break; @@ -212,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_STATEMENT; + 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; @@ -244,6 +303,11 @@ signature_validate(const pgp_signature_t & sig, case PGP_PKA_EDDSA: ret = eddsa_verify(&material.ecc, hval, hlen, &key.ec); break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + ret = ed25519_verify_native(material.ed25519.sig, key.ed25519.pub, hval, hlen); + break; +#endif case PGP_PKA_SM2: #if defined(ENABLE_SM2) ret = sm2_verify(&material.ecc, hash.alg(), hval, hlen, &key.ec); @@ -273,6 +337,26 @@ signature_validate(const pgp_signature_t & sig, RNP_LOG("ElGamal are considered as invalid."); ret = RNP_ERROR_SIGNATURE_INVALID; break; +#if defined(ENABLE_PQC) + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + ret = + key.dilithium_exdsa.pub.verify(&material.dilithium_exdsa, hash.alg(), hval, hlen); + break; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHAKE: + ret = key.sphincsplus.pub.verify(&material.sphincsplus, hval, hlen); + break; +#endif default: RNP_LOG("Unknown algorithm"); ret = RNP_ERROR_BAD_PARAMETERS; diff --git a/src/lib/crypto/signatures.h b/src/lib/crypto/signatures.h index 6ba64ce1..ae69f72d 100644 --- a/src/lib/crypto/signatures.h +++ b/src/lib/crypto/signatures.h @@ -35,8 +35,8 @@ * @param hash_alg the digest algo to be used * @param hash digest object that will be initialized */ -std::unique_ptr signature_init(const pgp_key_material_t &key, - pgp_hash_alg_t hash_alg); +std::unique_ptr signature_init(const pgp_key_pkt_t & key, + const pgp_signature_t &sig); /** * @brief Calculate signature with pre-populated hash diff --git a/src/lib/crypto/sphincsplus.cpp b/src/lib/crypto/sphincsplus.cpp new file mode 100644 index 00000000..d948ddbe --- /dev/null +++ b/src/lib/crypto/sphincsplus.cpp @@ -0,0 +1,425 @@ +/* + * 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) +{ + /* draft-wussler-openpgp-pqc-02 Table 14*/ + switch (pk_alg) { + case PGP_PKA_SPHINCSPLUS_SHA2: + switch (sphincsplus_param) { + case sphincsplus_simple_128s: + FALLTHROUGH_STATEMENT; + case sphincsplus_simple_128f: + if (hash_alg != PGP_HASH_SHA256) { + return false; + } + break; + case sphincsplus_simple_192s: + FALLTHROUGH_STATEMENT; + case sphincsplus_simple_192f: + FALLTHROUGH_STATEMENT; + case sphincsplus_simple_256s: + FALLTHROUGH_STATEMENT; + case sphincsplus_simple_256f: + if (hash_alg != PGP_HASH_SHA512) { + return false; + } + break; + } + break; + case PGP_PKA_SPHINCSPLUS_SHAKE: + switch (sphincsplus_param) { + case sphincsplus_simple_128s: + FALLTHROUGH_STATEMENT; + case sphincsplus_simple_128f: + if (hash_alg != PGP_HASH_SHA3_256) { + return false; + } + break; + case sphincsplus_simple_192s: + FALLTHROUGH_STATEMENT; + case sphincsplus_simple_192f: + FALLTHROUGH_STATEMENT; + case sphincsplus_simple_256s: + FALLTHROUGH_STATEMENT; + case sphincsplus_simple_256f: + if (hash_alg != PGP_HASH_SHA3_512) { + return false; + } + break; + } + break; + default: + break; + } + return true; +} + +pgp_hash_alg_t +sphincsplus_default_hash_alg(pgp_pubkey_alg_t pk_alg, + sphincsplus_parameter_t sphincsplus_param) +{ + switch (sphincsplus_param) { + case sphincsplus_simple_128s: + FALLTHROUGH_STATEMENT; + case sphincsplus_simple_128f: + switch (pk_alg) { + case PGP_PKA_SPHINCSPLUS_SHA2: + return PGP_HASH_SHA256; + case PGP_PKA_SPHINCSPLUS_SHAKE: + return PGP_HASH_SHA3_256; + default: + RNP_LOG("invalid parameter given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + case sphincsplus_simple_192s: + FALLTHROUGH_STATEMENT; + case sphincsplus_simple_192f: + FALLTHROUGH_STATEMENT; + case sphincsplus_simple_256s: + FALLTHROUGH_STATEMENT; + case sphincsplus_simple_256f: + switch (pk_alg) { + case PGP_PKA_SPHINCSPLUS_SHA2: + return PGP_HASH_SHA512; + case PGP_PKA_SPHINCSPLUS_SHAKE: + return PGP_HASH_SHA3_512; + default: + RNP_LOG("invalid parameter given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + default: + RNP_LOG("invalid parameter given"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} diff --git a/src/lib/crypto/sphincsplus.h b/src/lib/crypto/sphincsplus.h new file mode 100644 index 00000000..17d61c8e --- /dev/null +++ b/src/lib/crypto/sphincsplus.h @@ -0,0 +1,205 @@ +/* + * 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); + +pgp_hash_alg_t sphincsplus_default_hash_alg(pgp_pubkey_alg_t pk_alg, + sphincsplus_parameter_t sphincsplus_param); + +#endif \ No newline at end of file diff --git a/src/lib/crypto/symmetric.h b/src/lib/crypto/symmetric.h index a50fe9a1..ab502b27 100644 --- a/src/lib/crypto/symmetric.h +++ b/src/lib/crypto/symmetric.h @@ -69,6 +69,12 @@ /* Maximum AEAD nonce length */ #define PGP_AEAD_MAX_NONCE_LEN 16 +#ifdef ENABLE_CRYPTO_REFRESH +#define PGP_AEAD_MAX_NONCE_OR_SALT_LEN PGP_SEIPDV2_SALT_LEN +#else +#define PGP_AEAD_MAX_NONCE_OR_SALT_LEN PGP_AEAD_MAX_NONCE_LEN +#endif + /* Authentication tag len for AEAD/EAX and AEAD/OCB */ #define PGP_AEAD_EAX_OCB_TAG_LEN 16 diff --git a/src/lib/crypto/x25519.cpp b/src/lib/crypto/x25519.cpp new file mode 100644 index 00000000..f7932784 --- /dev/null +++ b/src/lib/crypto/x25519.cpp @@ -0,0 +1,181 @@ +/* + * 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 "x25519.h" +#include "exdsa_ecdhkem.h" +#include "hkdf.hpp" +#include "utils.h" +#include "botan/rfc3394.h" + +static void +x25519_hkdf(std::vector & derived_key, + const std::vector &ephemeral_pubkey_material, + const std::vector &recipient_pubkey_material, + const std::vector &shared_key) +{ + /* The shared secret is passed to HKDF (see {{RFC5869}}) using SHA256, and the + * UTF-8-encoded string "OpenPGP X25519" as the info parameter. */ + static const std::vector info = { + 'O', 'p', 'e', 'n', 'P', 'G', 'P', ' ', 'X', '2', '5', '5', '1', '9'}; + auto kdf = rnp::Hkdf::create(PGP_HASH_SHA256); + derived_key.resize(pgp_key_size(PGP_SA_AES_128)); // 128-bit AES key wrap + + std::vector kdf_input; + kdf_input.insert(kdf_input.end(), + std::begin(ephemeral_pubkey_material), + std::end(ephemeral_pubkey_material)); + kdf_input.insert(kdf_input.end(), + std::begin(recipient_pubkey_material), + std::end(recipient_pubkey_material)); + kdf_input.insert(kdf_input.end(), std::begin(shared_key), std::end(shared_key)); + + kdf->extract_expand(NULL, + 0, // no salt + kdf_input.data(), + kdf_input.size(), + info.data(), + info.size(), + derived_key.data(), + derived_key.size()); +} + +rnp_result_t +generate_x25519_native(rnp::RNG * rng, + std::vector &privkey, + std::vector &pubkey) +{ + Botan::Curve25519_PrivateKey priv_key(*(rng->obj())); + pubkey = priv_key.public_value(); + privkey = Botan::unlock(priv_key.raw_private_key_bits()); + + return RNP_SUCCESS; +} + +rnp_result_t +x25519_native_encrypt(rnp::RNG * rng, + const std::vector &pubkey, + const uint8_t * in, + size_t in_len, + pgp_x25519_encrypted_t * encrypted) +{ + rnp_result_t ret; + std::vector shared_key; + std::vector derived_key; + + if (!in_len || (in_len % 8) != 0) { + RNP_LOG("incorrect size of in, AES key wrap requires a multiple of 8 bytes"); + return RNP_ERROR_BAD_FORMAT; + } + + /* encapsulation */ + ecdh_kem_public_key_t ecdhkem_pubkey(pubkey, PGP_CURVE_25519); + ret = ecdhkem_pubkey.encapsulate(rng, encrypted->eph_key, shared_key); + if (ret != RNP_SUCCESS) { + RNP_LOG("encapsulation failed"); + return ret; + } + + x25519_hkdf(derived_key, encrypted->eph_key, pubkey, shared_key); + + Botan::SymmetricKey kek(derived_key); + try { + encrypted->enc_sess_key = Botan::unlock( + Botan::rfc3394_keywrap(Botan::secure_vector(in, in + in_len), kek)); + } catch (const std::exception &e) { + RNP_LOG("Keywrap failed: %s", e.what()); + return RNP_ERROR_ENCRYPT_FAILED; + } + + return RNP_SUCCESS; +} + +rnp_result_t +x25519_native_decrypt(rnp::RNG * rng, + const pgp_x25519_key_t & keypair, + const pgp_x25519_encrypted_t *encrypted, + uint8_t * decbuf, + size_t * decbuf_len) +{ + rnp_result_t ret; + std::vector shared_key; + std::vector derived_key; + + static const size_t x25519_pubkey_size = 32; + if (encrypted->eph_key.size() != x25519_pubkey_size) { + RNP_LOG("Wrong ephemeral public key size"); + return RNP_ERROR_BAD_FORMAT; + } + if (!encrypted->enc_sess_key.size()) { + // TODO: could do a check for possible sizes + RNP_LOG("No encrypted session key provided"); + return RNP_ERROR_BAD_FORMAT; + } + + /* decapsulate */ + ecdh_kem_private_key_t ecdhkem_privkey(keypair.priv, PGP_CURVE_25519); + ret = ecdhkem_privkey.decapsulate(rng, encrypted->eph_key, shared_key); + if (ret != RNP_SUCCESS) { + RNP_LOG("decapsulation failed"); + return ret; + } + + x25519_hkdf(derived_key, encrypted->eph_key, keypair.pub, shared_key); + + Botan::SymmetricKey kek(derived_key); + auto tmp_out = + Botan::rfc3394_keyunwrap(Botan::secure_vector(encrypted->enc_sess_key.begin(), + encrypted->enc_sess_key.end()), + kek); + if (*decbuf_len < tmp_out.size()) { + RNP_LOG("buffer for decryption result too small"); + return RNP_ERROR_DECRYPT_FAILED; + } + *decbuf_len = tmp_out.size(); + memcpy(decbuf, tmp_out.data(), tmp_out.size()); + + return RNP_SUCCESS; +} + +rnp_result_t +x25519_validate_key_native(rnp::RNG *rng, const pgp_x25519_key_t *key, bool secret) +{ + bool valid_pub; + bool valid_priv; + + Botan::Curve25519_PublicKey pub_key(key->priv); + valid_pub = pub_key.check_key(*(rng->obj()), false); + + if (secret) { + Botan::Curve25519_PrivateKey priv_key( + Botan::secure_vector(key->priv.begin(), key->priv.end())); + valid_priv = priv_key.check_key(*(rng->obj()), false); + } else { + valid_priv = true; + } + + // check key returns true for successful check + return (valid_pub && valid_priv) ? RNP_SUCCESS : RNP_ERROR_BAD_PARAMETERS; +} diff --git a/src/lib/crypto/x25519.h b/src/lib/crypto/x25519.h new file mode 100644 index 00000000..0e6317b0 --- /dev/null +++ b/src/lib/crypto/x25519.h @@ -0,0 +1,57 @@ +/* + * 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 X25519_H_ +#define X25519_H_ + +#include "config.h" +#include +#include +#include +#include "crypto/rng.h" +#include "crypto/ec.h" + +rnp_result_t generate_x25519_native(rnp::RNG * rng, + std::vector &privkey, + std::vector &pubkey); + +rnp_result_t x25519_native_encrypt(rnp::RNG * rng, + const std::vector &pubkey, + const uint8_t * in, + size_t in_len, + pgp_x25519_encrypted_t * encrypted); + +rnp_result_t x25519_native_decrypt(rnp::RNG * rng, + const pgp_x25519_key_t & keypair, + const pgp_x25519_encrypted_t *encrypted, + uint8_t * decbuf, + size_t * decbuf_len); + +rnp_result_t x25519_validate_key_native(rnp::RNG * rng, + const pgp_x25519_key_t *key, + bool secret); + +#endif diff --git a/src/lib/ffi-priv-types.h b/src/lib/ffi-priv-types.h index beb624c9..924343d7 100644 --- a/src/lib/ffi-priv-types.h +++ b/src/lib/ffi-priv-types.h @@ -129,6 +129,7 @@ struct rnp_op_generate_st { rnp_key_protection_params_t protection{}; rnp_selfsig_cert_info_t cert{}; rnp_selfsig_binding_info_t binding{}; + pgp_version_t pgp_version = PGP_V4; }; struct rnp_op_sign_signature_st { @@ -196,7 +197,7 @@ struct rnp_op_encrypt_st { }; #define RNP_LOCATOR_MAX_SIZE (MAX_ID_LENGTH + 1) -static_assert(RNP_LOCATOR_MAX_SIZE > PGP_FINGERPRINT_SIZE * 2, "Locator size mismatch."); +static_assert(RNP_LOCATOR_MAX_SIZE > PGP_MAX_FINGERPRINT_SIZE * 2, "Locator size mismatch."); static_assert(RNP_LOCATOR_MAX_SIZE > PGP_KEY_ID_SIZE * 2, "Locator size mismatch."); static_assert(RNP_LOCATOR_MAX_SIZE > PGP_KEY_GRIP_SIZE * 2, "Locator size mismatch."); static_assert(RNP_LOCATOR_MAX_SIZE > MAX_ID_LENGTH, "Locator size mismatch."); diff --git a/src/lib/fingerprint.cpp b/src/lib/fingerprint.cpp index c937c749..a1b002d4 100644 --- a/src/lib/fingerprint.cpp +++ b/src/lib/fingerprint.cpp @@ -57,13 +57,28 @@ pgp_fingerprint(pgp_fingerprint_t &fp, const pgp_key_pkt_t &key) } } - if (key.version != PGP_V4) { + switch (key.version) { +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_V6: + FALLTHROUGH_STATEMENT; +#endif + case PGP_V4: + break; + default: RNP_LOG("unsupported key version"); return RNP_ERROR_NOT_SUPPORTED; } + std::unique_ptr hash; + if (key.version == PGP_V4) { + hash = rnp::Hash::create(PGP_HASH_SHA1); + } +#if defined(ENABLE_CRYPTO_REFRESH) + else if (key.version == PGP_V6) { + hash = rnp::Hash::create(PGP_HASH_SHA256); + } +#endif try { - auto hash = rnp::Hash::create(PGP_HASH_SHA1); signature_hash_key(key, *hash); fp.length = hash->finish(fp.fingerprint); return RNP_SUCCESS; @@ -100,7 +115,19 @@ pgp_keyid(pgp_key_id_t &keyid, const pgp_key_pkt_t &key) if ((ret = pgp_fingerprint(fp, key))) { return ret; } - (void) memcpy(keyid.data(), fp.fingerprint + fp.length - keyid.size(), keyid.size()); + + switch (key.version) { + case PGP_V4: + (void) memcpy(keyid.data(), fp.fingerprint + fp.length - keyid.size(), keyid.size()); + break; +#ifdef ENABLE_CRYPTO_REFRESH + case PGP_V6: + (void) memcpy(keyid.data(), fp.fingerprint, keyid.size()); + break; +#endif + default: + return RNP_ERROR_BAD_STATE; + } return RNP_SUCCESS; } diff --git a/src/lib/generate-key.cpp b/src/lib/generate-key.cpp index dfd5556d..52481f97 100644 --- a/src/lib/generate-key.cpp +++ b/src/lib/generate-key.cpp @@ -55,6 +55,26 @@ static const id_str_pair pubkey_alg_map[] = { {PGP_PKA_RESERVED_DH, "Reserved for Diffie-Hellman (X9.42)"}, {PGP_PKA_EDDSA, "EdDSA"}, {PGP_PKA_SM2, "SM2"}, +#if defined(ENABLE_CRYPTO_REFRESH) + {PGP_PKA_ED25519, "ED25519"}, + {PGP_PKA_X25519, "X25519"}, +#endif +#if defined(ENABLE_PQC) + {PGP_PKA_KYBER768_X25519, "Kyber-X25519"}, + //{PGP_PKA_KYBER1024_X448, "Kyber-X448"}, + {PGP_PKA_KYBER768_P256, "Kyber-P256"}, + {PGP_PKA_KYBER1024_P384, "Kyber-P384"}, + {PGP_PKA_KYBER768_BP256, "Kyber-BP256"}, + {PGP_PKA_KYBER1024_BP384, "Kyber-BP384"}, + {PGP_PKA_DILITHIUM3_ED25519, "Dilithium-ED25519"}, + //{PGP_PKA_DILITHIUM5_ED448, "Dilithium-ED448"}, + {PGP_PKA_DILITHIUM3_P256, "Dilithium-P256"}, + {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"}, {PGP_PKA_PRIVATE02, "Private/Experimental"}, @@ -249,11 +269,45 @@ get_numbits(const rnp_keygen_crypto_params_t *crypto) return 0; } } +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + return 255; + case PGP_PKA_X25519: + return 255; +#endif case PGP_PKA_DSA: return crypto->dsa.p_bitlen; case PGP_PKA_ELGAMAL: case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: return crypto->elgamal.key_bitlen; +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + return pgp_kyber_ecdh_composite_public_key_t::encoded_size(crypto->key_alg) * 8; + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + 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_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHAKE: + return sphincsplus_pubkey_size(crypto->sphincsplus.param) * 8; +#endif default: return 0; } @@ -299,6 +353,40 @@ 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_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHAKE: + if (!sphincsplus_hash_allowed( + crypto.key_alg, crypto.sphincsplus.param, crypto.hash_alg)) { + return false; + } + break; + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + if (!dilithium_hash_allowed(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, @@ -322,9 +410,17 @@ 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)) { + if (!pgp_generate_seckey(desc.crypto, secpkt, true, desc.pgp_version)) { return false; } @@ -418,6 +514,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); @@ -428,7 +532,7 @@ pgp_generate_subkey(rnp_keygen_subkey_desc_t & desc, } /* generate the raw subkey */ pgp_key_pkt_t secpkt; - if (!pgp_generate_seckey(desc.crypto, secpkt, false)) { + if (!pgp_generate_seckey(desc.crypto, secpkt, false, desc.pgp_version)) { return false; } pgp_key_pkt_t pubpkt = pgp_key_pkt_t(secpkt, true); diff --git a/src/lib/pgp-key.cpp b/src/lib/pgp-key.cpp index 43003315..aadd1054 100644 --- a/src/lib/pgp-key.cpp +++ b/src/lib/pgp-key.cpp @@ -193,6 +193,13 @@ pgp_pk_alg_capabilities(pgp_pubkey_alg_t alg) case PGP_PKA_EDDSA: return pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY | PGP_KF_AUTH); +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + return pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY | PGP_KF_AUTH); + case PGP_PKA_X25519: + return PGP_KF_ENCRYPT; +#endif + case PGP_PKA_SM2: return pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY | PGP_KF_AUTH | PGP_KF_ENCRYPT); @@ -200,6 +207,36 @@ pgp_pk_alg_capabilities(pgp_pubkey_alg_t alg) case PGP_PKA_ELGAMAL: return PGP_KF_ENCRYPT; +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + return PGP_KF_ENCRYPT; + + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHAKE: + return pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY | PGP_KF_AUTH); +#endif + default: RNP_LOG("unknown pk alg: %d\n", alg); return PGP_KF_NONE; @@ -483,6 +520,28 @@ find_suitable_key(pgp_op_t op, pgp_hash_alg_t pgp_hash_adjust_alg_to_key(pgp_hash_alg_t hash, const pgp_key_pkt_t *pubkey) { +#if defined(ENABLE_PQC) + switch (pubkey->alg) { + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHAKE: + return sphincsplus_default_hash_alg(pubkey->alg, + pubkey->material.sphincsplus.pub.param()); + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + return dilithium_default_hash_alg(); + default: + break; + } +#endif + if ((pubkey->alg != PGP_PKA_DSA) && (pubkey->alg != PGP_PKA_ECDSA)) { return hash; } @@ -1159,6 +1218,12 @@ pgp_key_t::curve() const case PGP_PKA_EDDSA: case PGP_PKA_SM2: return pkt_.material.ec.curve; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + return PGP_CURVE_ED25519; + case PGP_PKA_X25519: + return PGP_CURVE_25519; +#endif default: return PGP_CURVE_UNKNOWN; } @@ -1667,7 +1732,7 @@ pgp_key_t::write_xfer(pgp_dest_t &dst, const rnp_key_store_t *keyring) const for (auto &fp : subkey_fps_) { const pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(keyring, fp); if (!subkey) { - char fphex[PGP_FINGERPRINT_SIZE * 2 + 1] = {0}; + char fphex[PGP_MAX_FINGERPRINT_SIZE * 2 + 1] = {0}; rnp::hex_encode( fp.fingerprint, fp.length, fphex, sizeof(fphex), rnp::HEX_LOWERCASE); RNP_LOG("Warning! Subkey %s not found.", fphex); @@ -2276,14 +2341,27 @@ pgp_key_t::mark_valid() } void -pgp_key_t::sign_init(pgp_signature_t &sig, pgp_hash_alg_t hash, uint64_t creation) const +pgp_key_t::sign_init(rnp::RNG & rng, + pgp_signature_t &sig, + pgp_hash_alg_t hash, + uint64_t creation, + pgp_version_t version) const { - sig.version = PGP_V4; + sig.version = version; sig.halg = pgp_hash_adjust_alg_to_key(hash, &pkt_); sig.palg = alg(); sig.set_keyfp(fp()); sig.set_creation(creation); - sig.set_keyid(keyid()); + if (version == PGP_V4) { + // for v6 issuing keys, this MUST NOT be included + sig.set_keyid(keyid()); + } +#if defined(ENABLE_CRYPTO_REFRESH) + if (version == PGP_V6) { + sig.salt_size = rnp::Hash::size(sig.halg) / 2; + rng.get(sig.salt, sig.salt_size); + } +#endif } void @@ -2325,7 +2403,7 @@ pgp_key_t::gen_revocation(const pgp_revoke_t & revoke, pgp_signature_t & sig, rnp::SecurityContext &ctx) { - sign_init(sig, hash, ctx.time()); + sign_init(ctx.rng, sig, hash, ctx.time(), key.version); sig.set_type(is_primary_key_pkt(key.tag) ? PGP_SIG_REV_KEY : PGP_SIG_REV_SUBKEY); sig.set_revocation_reason(revoke.code, revoke.reason); @@ -2349,7 +2427,7 @@ pgp_key_t::sign_subkey_binding(pgp_key_t & sub, /* add primary key binding subpacket if requested */ if (subsign) { pgp_signature_t embsig; - sub.sign_init(embsig, sig.halg, ctx.time()); + sub.sign_init(ctx.rng, embsig, sig.halg, ctx.time(), sub.version()); embsig.set_type(PGP_SIG_PRIMARY); sub.sign_binding(pkt(), embsig, ctx); sig.set_embedded_sig(embsig); @@ -2396,7 +2474,7 @@ pgp_key_t::add_uid_cert(rnp_selfsig_cert_info_t &cert, /* Fill the transferable userid */ pgp_userid_pkt_t uid; pgp_signature_t sig; - sign_init(sig, hash, ctx.time()); + sign_init(ctx.rng, sig, hash, ctx.time(), pkt().version); cert.populate(uid, sig); try { sign_cert(pkt_, uid, sig, ctx); @@ -2430,7 +2508,7 @@ pgp_key_t::add_sub_binding(pgp_key_t & subsec, /* populate signature */ pgp_signature_t sig; - sign_init(sig, hash, ctx.time()); + sign_init(ctx.rng, sig, hash, ctx.time(), version()); sig.set_type(PGP_SIG_SUBKEY); if (binding.key_expiration) { sig.set_key_expiration(binding.key_expiration); @@ -2722,6 +2800,29 @@ pgp_key_t::merge(const pgp_key_t &src, pgp_key_t *primary) return true; } +pgp_curve_t +pgp_key_material_t::curve() const +{ + switch (alg) { + case PGP_PKA_ECDH: + FALLTHROUGH_STATEMENT; + case PGP_PKA_ECDSA: + FALLTHROUGH_STATEMENT; + case PGP_PKA_EDDSA: + FALLTHROUGH_STATEMENT; + case PGP_PKA_SM2: + return ec.curve; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + return PGP_CURVE_ED25519; + case PGP_PKA_X25519: + return PGP_CURVE_25519; +#endif + default: + return PGP_CURVE_UNKNOWN; + } +} + size_t pgp_key_material_t::bits() const { @@ -2736,13 +2837,50 @@ pgp_key_material_t::bits() const case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: return 8 * mpi_bytes(&eg.y); case PGP_PKA_ECDH: + FALLTHROUGH_STATEMENT; case PGP_PKA_ECDSA: + FALLTHROUGH_STATEMENT; case PGP_PKA_EDDSA: + FALLTHROUGH_STATEMENT; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + FALLTHROUGH_STATEMENT; + case PGP_PKA_X25519: + FALLTHROUGH_STATEMENT; +#endif case PGP_PKA_SM2: { - // bn_num_bytes returns value <= curve order - const ec_curve_desc_t *curve = get_curve_desc(ec.curve); - return curve ? curve->bitlen : 0; - } + /* handle ecc cases */ + const ec_curve_desc_t *curve_desc = get_curve_desc(curve()); + return curve_desc ? curve_desc->bitlen : 0; + } +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + return 8 * kyber_ecdh.pub.get_encoded().size(); /* public key length */ + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + return 8 * dilithium_exdsa.pub.get_encoded().size(); /* public key length*/ + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + 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); return 0; diff --git a/src/lib/pgp-key.h b/src/lib/pgp-key.h index aa088bb4..6e82d963 100644 --- a/src/lib/pgp-key.h +++ b/src/lib/pgp-key.h @@ -236,6 +236,7 @@ struct pgp_key_t { bool is_secret() const; bool is_primary() const; bool is_subkey() const; + bool is_seipdv2_capable() const; /** @brief check if a key is currently locked, i.e. secret fields are not decrypted. * Note: Key locking does not apply to unprotected keys. */ @@ -457,8 +458,14 @@ struct pgp_key_t { * @param hash hash algorithm to use (may be changed if it is not suitable for public key * algorithm). * @param creation signature's creation time. + * @param version signature version */ - void sign_init(pgp_signature_t &sig, pgp_hash_alg_t hash, uint64_t creation) const; + void sign_init(rnp::RNG & rng, + pgp_signature_t &sig, + pgp_hash_alg_t hash, + uint64_t creation, + pgp_version_t version) const; + /** * @brief Calculate a certification and fill signature material. * Note: secret key must be unlocked before calling this function. diff --git a/src/lib/rnp.cpp b/src/lib/rnp.cpp index 2f08bc4a..11ea7ec5 100644 --- a/src/lib/rnp.cpp +++ b/src/lib/rnp.cpp @@ -162,6 +162,26 @@ static const id_str_pair pubkey_alg_map[] = { {PGP_PKA_ECDSA, RNP_ALGNAME_ECDSA}, {PGP_PKA_EDDSA, RNP_ALGNAME_EDDSA}, {PGP_PKA_SM2, RNP_ALGNAME_SM2}, +#if defined(ENABLE_CRYPTO_REFRESH) + {PGP_PKA_ED25519, RNP_ALGNAME_ED25519}, + {PGP_PKA_X25519, RNP_ALGNAME_X25519}, +#endif +#if defined(ENABLE_PQC) + {PGP_PKA_KYBER768_X25519, RNP_ALGNAME_KYBER768_X25519}, + //{PGP_PKA_KYBER1024_X448, RNP_ALGNAME_KYBER1024_X448}, + {PGP_PKA_KYBER768_P256, RNP_ALGNAME_KYBER768_P256}, + {PGP_PKA_KYBER1024_P384, RNP_ALGNAME_KYBER1024_P384}, + {PGP_PKA_KYBER768_BP256, RNP_ALGNAME_KYBER768_BP256}, + {PGP_PKA_KYBER1024_BP384, RNP_ALGNAME_KYBER1024_BP384}, + {PGP_PKA_DILITHIUM3_ED25519, RNP_ALGNAME_DILITHIUM3_ED25519}, + //{PGP_PKA_DILITHIUM5_ED448, RNP_ALGNAME_DILITHIUM5_ED448}, + {PGP_PKA_DILITHIUM3_P256, RNP_ALGNAME_DILITHIUM3_P256}, + {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}}; static const id_str_pair symm_alg_map[] = {{PGP_SA_IDEA, RNP_ALGNAME_IDEA}, @@ -321,6 +341,26 @@ pub_alg_supported(int alg) case PGP_PKA_EDDSA: #if defined(ENABLE_SM2) case PGP_PKA_SM2: +#endif +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_X25519: + case PGP_PKA_ED25519: +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + // case PGP_PKA_KYBER1024_X448: + case PGP_PKA_KYBER768_P256: + case PGP_PKA_KYBER1024_P384: + case PGP_PKA_KYBER768_BP256: + case PGP_PKA_KYBER1024_BP384: + case PGP_PKA_DILITHIUM3_ED25519: + // case PGP_PKA_DILITHIUM5_ED448: + case PGP_PKA_DILITHIUM3_P256: + 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: @@ -765,6 +805,8 @@ rnp_result_to_string(rnp_result_t result) return "No suitable key"; case RNP_ERROR_DECRYPT_FAILED: return "Decryption failed"; + case RNP_ERROR_ENCRYPT_FAILED: + return "Encryption failed"; case RNP_ERROR_RNG: return "Failure of random number generator"; case RNP_ERROR_SIGNING_FAILED: @@ -2533,6 +2575,22 @@ try { } FFI_GUARD +rnp_result_t +rnp_op_encrypt_enable_pkesk_v6(rnp_op_encrypt_t op) +try { +#if defined(ENABLE_CRYPTO_REFRESH) + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + + op->rnpctx.enable_pkesk_v6 = true; + return RNP_SUCCESS; +#else + return RNP_ERROR_NOT_IMPLEMENTED; +#endif +} +FFI_GUARD + rnp_result_t rnp_op_encrypt_add_signature(rnp_op_encrypt_t op, rnp_key_handle_t key, @@ -2660,6 +2718,15 @@ try { FFI_LOG(op->ffi, "Invalid AEAD algorithm: %s", alg); return RNP_ERROR_BAD_PARAMETERS; } +#ifdef ENABLE_CRYPTO_REFRESH + if (op->rnpctx.aalg == PGP_AEAD_NONE && op->rnpctx.enable_pkesk_v6) { + FFI_LOG(op->ffi, + "Setting AEAD algorithm to PGP_AEAD_NONE (%s) would contradict the previously " + "enabled PKESKv6 setting", + alg); + return RNP_ERROR_BAD_PARAMETERS; + } +#endif return RNP_SUCCESS; } FFI_GUARD @@ -3727,14 +3794,17 @@ str_to_locator(rnp_ffi_t ffi, } } break; case PGP_KEY_SEARCH_FINGERPRINT: { - // TODO: support v5 fingerprints - // Note: v2/v3 fingerprint are 16 bytes (32 chars) long. - if ((strlen(identifier) != (PGP_FINGERPRINT_SIZE * 2)) && (strlen(identifier) != 32)) { + // Note: v2/v3 fingerprint are 16 bytes (32 chars) long + if (strlen(identifier) != (PGP_FINGERPRINT_V4_SIZE * 2) && +#if defined(ENABLE_CRYPTO_REFRESH) + strlen(identifier) != (PGP_FINGERPRINT_V6_SIZE * 2) && +#endif + (strlen(identifier) != 32)) { FFI_LOG(ffi, "Invalid fingerprint: %s", identifier); return RNP_ERROR_BAD_PARAMETERS; } locator->by.fingerprint.length = rnp::hex_decode( - identifier, locator->by.fingerprint.fingerprint, PGP_FINGERPRINT_SIZE); + identifier, locator->by.fingerprint.fingerprint, PGP_MAX_FINGERPRINT_SIZE); if (!locator->by.fingerprint.length) { FFI_LOG(ffi, "Invalid fingerprint: %s", identifier); return RNP_ERROR_BAD_PARAMETERS; @@ -5188,6 +5258,40 @@ default_key_flags(pgp_pubkey_alg_t alg, bool subkey) case PGP_PKA_ECDH: case PGP_PKA_ELGAMAL: return PGP_KF_ENCRYPT; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + return subkey ? PGP_KF_SIGN : pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY); + case PGP_PKA_X25519: + return PGP_KF_ENCRYPT; +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + return PGP_KF_ENCRYPT; + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHAKE: + return subkey ? PGP_KF_SIGN : pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY); +#endif default: return PGP_KF_NONE; } @@ -5586,6 +5690,56 @@ try { } FFI_GUARD +rnp_result_t +rnp_op_generate_set_v6_key(rnp_op_generate_t op) +try { +#if defined(ENABLE_CRYPTO_REFRESH) + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + op->pgp_version = PGP_V6; + return RNP_SUCCESS; +#else + return RNP_ERROR_NOT_IMPLEMENTED; +#endif +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_sphincsplus_param(rnp_op_generate_t op, const char *param_cstr) +try { +#if defined(ENABLE_PQC) + 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; +#else + return RNP_ERROR_NOT_IMPLEMENTED; +#endif +} +FFI_GUARD + rnp_result_t rnp_op_generate_execute(rnp_op_generate_t op) try { @@ -5602,6 +5756,7 @@ try { rnp_keygen_primary_desc_t keygen = {}; keygen.crypto = op->crypto; keygen.cert = op->cert; + keygen.pgp_version = op->pgp_version; op->cert.prefs = {}; /* generate call will free prefs */ if (!pgp_generate_primary_key(keygen, true, sec, pub, op->ffi->secring->format)) { @@ -5612,6 +5767,7 @@ try { rnp_keygen_subkey_desc_t keygen = {}; keygen.crypto = op->crypto; keygen.binding = op->binding; + keygen.pgp_version = op->pgp_version; if (!pgp_generate_subkey(keygen, true, *op->primary_sec, @@ -6407,6 +6563,18 @@ try { } FFI_GUARD +rnp_result_t +rnp_key_get_version(rnp_key_handle_t handle, uint32_t *version) +try { + if (!handle || !version) { + return RNP_ERROR_NULL_POINTER; + } + + *version = get_key_prefer_public(handle)->version(); + return RNP_SUCCESS; +} +FFI_GUARD + rnp_result_t rnp_key_get_subkey_count(rnp_key_handle_t handle, size_t *count) try { @@ -7373,6 +7541,39 @@ add_json_public_mpis(json_object *jso, pgp_key_t *key) case PGP_PKA_EDDSA: case PGP_PKA_SM2: return add_json_mpis(jso, "point", &km.ec.p, NULL); +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + case PGP_PKA_X25519: + return RNP_SUCCESS; /* TODO */ +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + return RNP_SUCCESS; /* TODO */ + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHAKE: + return RNP_SUCCESS; /* TODO */ +#endif default: return RNP_ERROR_NOT_SUPPORTED; } @@ -7399,6 +7600,24 @@ add_json_secret_mpis(json_object *jso, pgp_key_t *key) case PGP_PKA_EDDSA: case PGP_PKA_SM2: return add_json_mpis(jso, "x", &km.ec.x, NULL); +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + case PGP_PKA_X25519: + return RNP_SUCCESS; /* TODO */ +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + return RNP_SUCCESS; /* TODO */ +#endif default: return RNP_ERROR_NOT_SUPPORTED; } @@ -7431,6 +7650,28 @@ add_json_sig_mpis(json_object *jso, const pgp_signature_t *sig) case PGP_PKA_EDDSA: case PGP_PKA_SM2: return add_json_mpis(jso, "r", &material.ecc.r, "s", &material.ecc.s, NULL); +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + case PGP_PKA_X25519: + return RNP_SUCCESS; /* TODO */ +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHAKE: + return RNP_SUCCESS; /* TODO */ +#endif default: // TODO: we could use info->unknown and add a hex string of raw data here return RNP_ERROR_NOT_SUPPORTED; @@ -7633,10 +7874,7 @@ key_to_json(json_object *jso, rnp_key_handle_t handle, uint32_t flags) return RNP_ERROR_OUT_OF_MEMORY; } } - -#if (!defined(_MSVC_LANG) || _MSVC_LANG >= 201703L) - [[fallthrough]]; -#endif + FALLTHROUGH_STATEMENT; case PGP_PKA_ECDSA: case PGP_PKA_EDDSA: case PGP_PKA_SM2: { @@ -7648,6 +7886,39 @@ key_to_json(json_object *jso, rnp_key_handle_t handle, uint32_t flags) return RNP_ERROR_OUT_OF_MEMORY; } } break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + case PGP_PKA_X25519: + return RNP_SUCCESS; /* TODO */ +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + return RNP_SUCCESS; /* TODO */ + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHAKE: + return RNP_SUCCESS; /* TODO */ +#endif default: break; } @@ -7661,7 +7932,7 @@ key_to_json(json_object *jso, rnp_key_handle_t handle, uint32_t flags) return RNP_ERROR_OUT_OF_MEMORY; } // fingerprint - char fpr[PGP_FINGERPRINT_SIZE * 2 + 1]; + char fpr[PGP_MAX_FINGERPRINT_SIZE * 2 + 1]; if (!rnp::hex_encode(key->fp().fingerprint, key->fp().length, fpr, sizeof(fpr))) { return RNP_ERROR_GENERIC; } diff --git a/src/lib/types.h b/src/lib/types.h index 5a67d422..34956814 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -102,7 +102,7 @@ class id_str_pair { /** pgp_fingerprint_t */ typedef struct pgp_fingerprint_t { - uint8_t fingerprint[PGP_FINGERPRINT_SIZE]; + uint8_t fingerprint[PGP_MAX_FINGERPRINT_SIZE]; unsigned length; bool operator==(const pgp_fingerprint_t &src) const; bool operator!=(const pgp_fingerprint_t &src) const; @@ -117,9 +117,10 @@ template <> struct hash { { /* since fingerprint value is hash itself, we may use its low bytes */ size_t res = 0; - static_assert(sizeof(fp.fingerprint) == PGP_FINGERPRINT_SIZE, + static_assert(sizeof(fp.fingerprint) == PGP_MAX_FINGERPRINT_SIZE, + "pgp_fingerprint_t size mismatch"); + static_assert(PGP_MAX_FINGERPRINT_SIZE >= sizeof(res), "pgp_fingerprint_t size mismatch"); - static_assert(PGP_FINGERPRINT_SIZE >= sizeof(res), "pgp_fingerprint_t size mismatch"); std::memcpy(&res, fp.fingerprint, sizeof(res)); return res; } @@ -187,7 +188,18 @@ typedef struct pgp_key_material_t { pgp_eg_key_t eg; pgp_ec_key_t ec; }; - +#if defined(ENABLE_CRYPTO_REFRESH) + pgp_ed25519_key_t ed25519; /* non-trivial type, cannot be in a union */ + pgp_x25519_key_t x25519; /* non-trivial type, cannot be in a union */ +#endif +#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() + const; /* return curve for EC algorithms, PGP_CURVE_UNKNOWN otherwise */ size_t bits() const; size_t qbits() const; void validate(rnp::SecurityContext &ctx, bool reset = true); @@ -204,6 +216,14 @@ typedef struct pgp_signature_material_t { pgp_ec_signature_t ecc; pgp_eg_signature_t eg; }; +#if defined(ENABLE_CRYPTO_REFRESH) + pgp_ed25519_signature_t ed25519; // non-trivial type cannot be member in union +#endif +#if defined(ENABLE_PQC) + pgp_dilithium_exdsa_signature_t + 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; /** @@ -216,6 +236,12 @@ typedef struct pgp_encrypted_material_t { pgp_sm2_encrypted_t sm2; pgp_ecdh_encrypted_t ecdh; }; +#if defined(ENABLE_CRYPTO_REFRESH) + pgp_x25519_encrypted_t x25519; // non-trivial type cannot be member in union +#endif +#if defined(ENABLE_PQC) + pgp_kyber_ecdh_encrypted_t kyber_ecdh; // non-trivial type cannot be member in union +#endif } pgp_encrypted_material_t; typedef struct pgp_s2k_t { @@ -362,6 +388,16 @@ typedef struct pgp_aead_hdr_t { } } pgp_aead_hdr_t; +#ifdef ENABLE_CRYPTO_REFRESH +typedef struct pgp_seipdv2_hdr_t { + pgp_seipd_version_t version; /* version of the SEIPD packet */ + pgp_symm_alg_t cipher_alg; /* underlying symmetric algorithm */ + pgp_aead_alg_t aead_alg; /* AEAD algorithm, i.e. EAX, OCB, etc */ + uint8_t chunk_size_octet; /* chunk size octet */ + uint8_t salt[PGP_SEIPDV2_SALT_LEN]; /* SEIPDv2 salt value */ +} pgp_seipdv2_hdr_t; +#endif + /** litdata_type_t */ typedef enum { PGP_LDT_BINARY = 'b', @@ -423,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; @@ -440,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; @@ -465,11 +510,13 @@ typedef struct rnp_selfsig_binding_info_t { typedef struct rnp_keygen_primary_desc_t { rnp_keygen_crypto_params_t crypto{}; rnp_selfsig_cert_info_t cert{}; + pgp_version_t pgp_version = PGP_V4; } rnp_keygen_primary_desc_t; typedef struct rnp_keygen_subkey_desc_t { rnp_keygen_crypto_params_t crypto; rnp_selfsig_binding_info_t binding; + pgp_version_t pgp_version = PGP_V4; } rnp_keygen_subkey_desc_t; typedef struct rnp_key_protection_params_t { diff --git a/src/librekey/kbx_blob.hpp b/src/librekey/kbx_blob.hpp index 274413c6..5c6b3ee7 100644 --- a/src/librekey/kbx_blob.hpp +++ b/src/librekey/kbx_blob.hpp @@ -90,7 +90,7 @@ class kbx_header_blob_t : public kbx_blob_t { }; typedef struct { - uint8_t fp[PGP_FINGERPRINT_SIZE]; + uint8_t fp[PGP_MAX_FINGERPRINT_SIZE]; uint32_t keyid_offset; uint16_t flags; } kbx_pgp_key_t; diff --git a/src/librekey/key_store_kbx.cpp b/src/librekey/key_store_kbx.cpp index 67756724..954e49df 100644 --- a/src/librekey/key_store_kbx.cpp +++ b/src/librekey/key_store_kbx.cpp @@ -525,7 +525,7 @@ rnp_key_store_kbx_write_pgp(rnp_key_store_t *key_store, pgp_key_t *key, pgp_dest return false; } - if (!pbuf(&mem.dst(), key->fp().fingerprint, PGP_FINGERPRINT_SIZE) || + if (!pbuf(&mem.dst(), key->fp().fingerprint, key->fp().length) || !pu32(&mem.dst(), mem.writeb() - 8) || // offset to keyid (part of fpr for V4) !pu16(&mem.dst(), 0) || // flags, not used by GnuPG !pu16(&mem.dst(), 0)) { // RFU @@ -536,7 +536,9 @@ rnp_key_store_kbx_write_pgp(rnp_key_store_t *key_store, pgp_key_t *key, pgp_dest std::vector subkey_sig_expirations; for (auto &sfp : key->subkey_fps()) { pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(key_store, sfp); - if (!subkey || !pbuf(&mem.dst(), subkey->fp().fingerprint, PGP_FINGERPRINT_SIZE) || + if (!subkey || !pbuf(&mem.dst(), subkey->fp().fingerprint, key->fp().length) || + // if (!subkey || !pbuf(&mem.dst(), subkey->fp().fingerprint, PGP_FINGERPRINT_SIZE) + // || // from upstream during merge 2023-03-20 !pu32(&mem.dst(), mem.writeb() - 8) || // offset to keyid (part of fpr for V4) !pu16(&mem.dst(), 0) || // flags, not used by GnuPG !pu16(&mem.dst(), 0)) { // RFU diff --git a/src/librekey/rnp_key_store.cpp b/src/librekey/rnp_key_store.cpp index 002a51e7..0918d155 100644 --- a/src/librekey/rnp_key_store.cpp +++ b/src/librekey/rnp_key_store.cpp @@ -154,7 +154,7 @@ rnp_key_store_write_to_path(rnp_key_store_t *key_store) } for (auto &key : key_store->keys) { - char grip[PGP_FINGERPRINT_HEX_SIZE] = {0}; + char grip[PGP_MAX_FINGERPRINT_HEX_SIZE] = {0}; rnp::hex_encode(key.grip().data(), key.grip().size(), grip, sizeof(grip)); snprintf(path, sizeof(path), "%s/%s.key", key_store->path.c_str(), grip); @@ -739,6 +739,45 @@ rnp_key_store_get_key_grip(const pgp_key_material_t *key, pgp_key_grip_t &grip) case PGP_PKA_SM2: grip_hash_ec(*hash, key->ec); break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + hash->add(key->ed25519.pub); + break; + case PGP_PKA_X25519: + hash->add(key->x25519.pub); + break; +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + hash->add(key->kyber_ecdh.pub.get_encoded()); + break; + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + hash->add(key->dilithium_exdsa.pub.get_encoded()); + break; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + 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); return false; diff --git a/src/librepgp/stream-common.cpp b/src/librepgp/stream-common.cpp index 334f93b5..f1f2270a 100644 --- a/src/librepgp/stream-common.cpp +++ b/src/librepgp/stream-common.cpp @@ -1210,3 +1210,31 @@ dst_write_src(pgp_source_t *src, pgp_dest_t *dst, uint64_t limit) dst_flush(dst); return dst->werr; } + +#if defined(ENABLE_CRYPTO_REFRESH) +bool +have_pkesk_checksum(pgp_pubkey_alg_t alg) +{ + switch (alg) { + case PGP_PKA_X25519: +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + // case PGP_PKA_KYBER1024_X448: + case PGP_PKA_KYBER768_P256: + case PGP_PKA_KYBER1024_P384: + case PGP_PKA_KYBER768_BP256: + case PGP_PKA_KYBER1024_BP384: +#endif + return false; + default: + return true; + } +} + +bool +do_encrypt_pkesk_v3_alg_id(pgp_pubkey_alg_t alg) +{ + /* matches the same algorithms */ + return have_pkesk_checksum(alg); +} +#endif diff --git a/src/librepgp/stream-common.h b/src/librepgp/stream-common.h index 02279d3b..32d19fbd 100644 --- a/src/librepgp/stream-common.h +++ b/src/librepgp/stream-common.h @@ -553,4 +553,9 @@ class MemoryDest : public Dest { }; } // namespace rnp +#if defined(ENABLE_CRYPTO_REFRESH) +bool have_pkesk_checksum(pgp_pubkey_alg_t alg); +bool do_encrypt_pkesk_v3_alg_id(pgp_pubkey_alg_t alg); +#endif + #endif diff --git a/src/librepgp/stream-ctx.cpp b/src/librepgp/stream-ctx.cpp index 28b5444f..96edbd44 100644 --- a/src/librepgp/stream-ctx.cpp +++ b/src/librepgp/stream-ctx.cpp @@ -67,3 +67,16 @@ rnp_ctx_t::add_encryption_password(const std::string &password, passwords.push_back(info); return RNP_SUCCESS; } + +#if defined(ENABLE_CRYPTO_REFRESH) +bool +rnp_ctx_t::pkeskv6_capable() +{ + for (pgp_key_t *key : recipients) { + if (key->version() < PGP_V6) { + return false; + } + } + return true; +} +#endif diff --git a/src/librepgp/stream-ctx.h b/src/librepgp/stream-ctx.h index b9e0c105..12639bc4 100644 --- a/src/librepgp/stream-ctx.h +++ b/src/librepgp/stream-ctx.h @@ -70,8 +70,12 @@ typedef struct rnp_symmetric_pass_info_t { * - halg : hash algorithm used during key derivation for password-based encryption * - ealg, aalg, abits : symmetric encryption algorithm and AEAD parameters if used * - recipients : list of key ids used to encrypt data to + * - enable_pkesk_v6 : if true and each recipient in the list of recipients has the + * capability, allows PKESKv5/SEIPDv2 * - passwords : list of passwords used for password-based encryption * - filename, filemtime, zalg, zlevel : see previous + * - pkeskv6_capable() : returns true if all keys support PKESKv6+SEIPDv2, false otherwise + * (will use PKESKv3 + SEIPDv1) * * For signing of any kind (attached, detached, cleartext): * - clearsign, detached : controls kind of the signed data. Both are mutually-exclusive. @@ -102,6 +106,9 @@ typedef struct rnp_ctx_t { bool overwrite{}; /* allow to overwrite output file if exists */ bool armor{}; /* whether to use ASCII armor on output */ bool no_wrap{}; /* do not wrap source in literal data packet */ +#if defined(ENABLE_CRYPTO_REFRESH) + bool enable_pkesk_v6{}; /* allows pkesk v6 if list of recipients is suitable */ +#endif std::list recipients{}; /* recipients of the encrypted message */ std::list passwords{}; /* passwords to encrypt message */ std::list signers{}; /* keys to which sign message */ @@ -118,6 +125,10 @@ typedef struct rnp_ctx_t { pgp_hash_alg_t halg, pgp_symm_alg_t ealg, size_t iterations = 0); + +#if defined(ENABLE_CRYPTO_REFRESH) + bool pkeskv6_capable(); +#endif } rnp_ctx_t; #endif diff --git a/src/librepgp/stream-dump.cpp b/src/librepgp/stream-dump.cpp index 4976e0d6..644af5b6 100644 --- a/src/librepgp/stream-dump.cpp +++ b/src/librepgp/stream-dump.cpp @@ -143,6 +143,26 @@ static const id_str_pair pubkey_alg_map[] = { {PGP_PKA_RESERVED_DH, "Reserved for DH (X9.42)"}, {PGP_PKA_EDDSA, "EdDSA"}, {PGP_PKA_SM2, "SM2"}, +#if defined(ENABLE_CRYPTO_REFRESH) + {PGP_PKA_ED25519, "Ed25519"}, + {PGP_PKA_X25519, "X25519"}, +#endif +#if defined(ENABLE_PQC) + {PGP_PKA_KYBER768_X25519, "Kyber768 + X25519"}, + //{PGP_PKA_KYBER1024_X448, "Kyber1024 + X448"}, + {PGP_PKA_KYBER768_P256, "Kyber768 + NIST P-256"}, + {PGP_PKA_KYBER1024_P384, "Kyber1024 + NIST P-384"}, + {PGP_PKA_KYBER768_BP256, "Kyber768 + Brainpool256"}, + {PGP_PKA_KYBER1024_BP384, "Kyber1024 + Brainpool384"}, + {PGP_PKA_DILITHIUM3_ED25519, "Dilithium3 + ED25519"}, + //{PGP_PKA_DILITHIUM5_ED448, "Dilithium + X448"}, + {PGP_PKA_DILITHIUM3_P256, "Dilithium3 + NIST P-256"}, + {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}, }; @@ -320,6 +340,23 @@ dst_print_mpi(pgp_dest_t *dst, const char *name, pgp_mpi_t *mpi, bool dumpbin) } } +#if defined(ENABLE_CRYPTO_REFRESH) +static void +dst_print_vec(pgp_dest_t * dst, + const char * name, + std::vector const &data, + bool dumpbin) +{ + std::vector hex(2 * data.size()); + if (!dumpbin) { + dst_printf(dst, "%s\n", name); + } else { + vsnprinthex(hex.data(), hex.size(), data.data(), data.size()); + dst_printf(dst, "%s, %s\n", name, hex.data()); + } +} +#endif + static void dst_print_palg(pgp_dest_t *dst, const char *name, pgp_pubkey_alg_t palg) { @@ -434,6 +471,17 @@ dst_print_keyid(pgp_dest_t *dst, const char *name, const pgp_key_id_t &keyid) dst_print_hex(dst, name, keyid.data(), keyid.size(), false); } +#if defined(ENABLE_CRYPTO_REFRESH) +static void +dst_print_fp(pgp_dest_t *dst, const char *name, const pgp_fingerprint_t &fp) +{ + if (!name) { + name = "fingerprint"; + } + dst_print_hex(dst, name, fp.fingerprint, fp.length, true); +} +#endif + static void dst_print_s2k(pgp_dest_t *dst, pgp_s2k_t *s2k) { @@ -574,7 +622,7 @@ signature_dump_subpacket(rnp_dump_ctx_t *ctx, pgp_dest_t *dst, const pgp_sig_sub dst_printf(dst, "class: %d\n", (int) subpkt.fields.revocation_key.klass); dst_print_palg(dst, NULL, subpkt.fields.revocation_key.pkalg); dst_print_hex( - dst, "fingerprint", subpkt.fields.revocation_key.fp, PGP_FINGERPRINT_SIZE, true); + dst, "fingerprint", subpkt.fields.revocation_key.fp, PGP_FINGERPRINT_V4_SIZE, true); break; case PGP_SIG_SUBPKT_ISSUER_KEY_ID: dst_print_hex(dst, sname, subpkt.fields.issuer, PGP_KEY_ID_SIZE, false); @@ -655,6 +703,10 @@ signature_dump_subpacket(rnp_dump_ctx_t *ctx, pgp_dest_t *dst, const pgp_sig_sub dst_printf(dst, "%s", subpkt.fields.features & PGP_KEY_FEATURE_MDC ? "mdc " : ""); dst_printf(dst, "%s", subpkt.fields.features & PGP_KEY_FEATURE_AEAD ? "aead " : ""); dst_printf(dst, "%s", subpkt.fields.features & PGP_KEY_FEATURE_V5 ? "v5 keys " : ""); +#if defined(ENABLE_CRYPTO_REFRESH) + dst_printf( + dst, "%s", subpkt.fields.features & PGP_KEY_FEATURE_SEIPDV2 ? "SEIPD v2 " : ""); +#endif dst_printf(dst, ")\n"); break; case PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE: @@ -769,6 +821,31 @@ stream_dump_signature_pkt(rnp_dump_ctx_t *ctx, pgp_signature_t *sig, pgp_dest_t dst_print_mpi(dst, "eg r", &material.eg.r, ctx->dump_mpi); dst_print_mpi(dst, "eg s", &material.eg.s, ctx->dump_mpi); break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + dst_print_vec(dst, "ed25519 sig", material.ed25519.sig, ctx->dump_mpi); + break; +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + dst_print_vec( + dst, "dilithium-ecdsa/eddsa sig", material.dilithium_exdsa.sig, ctx->dump_mpi); + break; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + 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"); } @@ -863,6 +940,54 @@ stream_dump_key(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) dst_printf(dst, "ecdh key wrap algorithm: %d\n", (int) key.material.ec.key_wrap_alg); break; } +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + dst_print_vec(dst, "ed25519", key.material.ed25519.pub, ctx->dump_mpi); + break; + case PGP_PKA_X25519: + dst_print_vec(dst, "x25519", key.material.x25519.pub, ctx->dump_mpi); + break; +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + dst_print_vec(dst, + "kyber-ecdh encoded pubkey", + key.material.kyber_ecdh.pub.get_encoded(), + ctx->dump_mpi); + break; + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + dst_print_vec(dst, + "dilithium-ecdsa/eddsa encodced pubkey", + key.material.dilithium_exdsa.pub.get_encoded(), + ctx->dump_mpi); + break; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + 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"); } @@ -991,7 +1116,15 @@ stream_dump_pk_session_key(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *d indent_dest_increase(dst); dst_printf(dst, "version: %d\n", (int) pkey.version); +#if defined(ENABLE_CRYPTO_REFRESH) + if (pkey.version == PGP_PKSK_V6) { + dst_print_fp(dst, NULL, pkey.fp); + } else { + dst_print_keyid(dst, NULL, pkey.key_id); + } +#else dst_print_keyid(dst, NULL, pkey.key_id); +#endif dst_print_palg(dst, NULL, pkey.alg); dst_printf(dst, "encrypted material:\n"); indent_dest_increase(dst); @@ -1018,6 +1151,35 @@ stream_dump_pk_session_key(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *d dst_printf(dst, "ecdh m: %d bytes\n", (int) material.ecdh.mlen); } break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_X25519: + dst_print_vec( + dst, "x25519 ephemeral public key", material.x25519.eph_key, ctx->dump_mpi); + dst_print_vec( + dst, "x25519 encrypted session key", material.x25519.enc_sess_key, ctx->dump_mpi); + break; +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + dst_print_vec(dst, + "kyber-ecdh composite ciphertext", + material.kyber_ecdh.composite_ciphertext, + ctx->dump_mpi); + dst_print_vec(dst, + "kyber-ecdh wrapped session key", + material.kyber_ecdh.wrapped_sesskey, + ctx->dump_mpi); + break; +#endif default: dst_printf(dst, "unknown public key algorithm\n"); } @@ -1601,7 +1763,7 @@ signature_dump_subpacket_json(rnp_dump_ctx_t * ctx, return json_add(obj, "class", (int) subpkt.fields.revocation_key.klass) && json_add(obj, "algorithm", (int) subpkt.fields.revocation_key.pkalg) && json_add_hex( - obj, "fingerprint", subpkt.fields.revocation_key.fp, PGP_FINGERPRINT_SIZE); + obj, "fingerprint", subpkt.fields.revocation_key.fp, PGP_FINGERPRINT_V4_SIZE); case PGP_SIG_SUBPKT_ISSUER_KEY_ID: return json_add_hex(obj, "issuer keyid", subpkt.fields.issuer, PGP_KEY_ID_SIZE); case PGP_SIG_SUBPKT_KEYSERV_PREFS: @@ -1819,6 +1981,30 @@ stream_dump_signature_pkt_json(rnp_dump_ctx_t * ctx, return RNP_ERROR_OUT_OF_MEMORY; } break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + /* TODO */ + break; +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + /* TODO */ + break; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHAKE: + /* TODO */ + break; +#endif default: break; } @@ -1932,6 +2118,43 @@ stream_dump_key_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt) } break; } +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + case PGP_PKA_X25519: + /* TODO */ + break; +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + // TODO + break; + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + /* TODO */ + break; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHAKE: + /* TODO */ + break; +#endif default: break; } @@ -2061,6 +2284,26 @@ stream_dump_pk_session_key_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_obj return RNP_ERROR_OUT_OF_MEMORY; } break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + case PGP_PKA_X25519: + /* TODO */ + break; +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + // TODO + break; +#endif default:; } diff --git a/src/librepgp/stream-key.cpp b/src/librepgp/stream-key.cpp index 8090ff7d..7c9a5c4e 100644 --- a/src/librepgp/stream-key.cpp +++ b/src/librepgp/stream-key.cpp @@ -48,6 +48,7 @@ #include "crypto/signatures.h" #include "crypto/mem.h" #include "../librekey/key_store_pgp.h" +#include "str-utils.h" #include #include #include @@ -540,6 +541,12 @@ parse_secret_key_mpis(pgp_key_pkt_t &key, const uint8_t *mpis, size_t len) /* check the cleartext data */ switch (key.sec_protection.s2k.usage) { case PGP_S2KU_NONE: +#if defined(ENABLE_CRYPTO_REFRESH) + if (key.version == PGP_V6) { + break; /* checksum removed for v6 and usage byte zero */ + } + FALLTHROUGH_STATEMENT; +#endif case PGP_S2KU_ENCRYPTED: { /* calculate and check sum16 of the cleartext */ if (len < 2) { @@ -591,6 +598,10 @@ parse_secret_key_mpis(pgp_key_pkt_t &key, const uint8_t *mpis, size_t len) /* parse mpis depending on algorithm */ pgp_packet_body_t body(mpis, len); +#if defined(ENABLE_CRYPTO_REFRESH) + std::vector tmpbuf; +#endif + switch (key.alg) { case PGP_PKA_RSA: case PGP_PKA_RSA_ENCRYPT_ONLY: @@ -623,6 +634,83 @@ parse_secret_key_mpis(pgp_key_pkt_t &key, const uint8_t *mpis, size_t len) return RNP_ERROR_BAD_FORMAT; } break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: { + const ec_curve_desc_t *ec_desc = get_curve_desc(PGP_CURVE_ED25519); + tmpbuf.resize(BITS_TO_BYTES(ec_desc->bitlen)); + if (!body.get(tmpbuf.data(), tmpbuf.size())) { + RNP_LOG("failed to parse Ed25519 secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + key.material.ed25519.priv = tmpbuf; + break; + } + case PGP_PKA_X25519: { + const ec_curve_desc_t *ec_desc = get_curve_desc(PGP_CURVE_25519); + tmpbuf.resize(BITS_TO_BYTES(ec_desc->bitlen)); + if (!body.get(tmpbuf.data(), tmpbuf.size())) { + RNP_LOG("failed to parse X25519 secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + key.material.x25519.priv = tmpbuf; + break; + } +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + tmpbuf.resize(pgp_kyber_ecdh_composite_private_key_t::encoded_size(key.alg)); + if (!body.get(tmpbuf.data(), tmpbuf.size())) { + RNP_LOG("failed to parse kyber-ecdh secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + key.material.kyber_ecdh.priv = + pgp_kyber_ecdh_composite_private_key_t(tmpbuf.data(), tmpbuf.size(), key.alg); + break; + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + tmpbuf.resize(pgp_dilithium_exdsa_composite_private_key_t::encoded_size(key.alg)); + if (!body.get(tmpbuf.data(), tmpbuf.size())) { + RNP_LOG("failed to parse dilithium-ecdsa/eddsa secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + 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_STATEMENT; + 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); return RNP_ERROR_BAD_PARAMETERS; @@ -700,6 +788,10 @@ decrypt_secret_key(pgp_key_pkt_t *key, const char *password) } ret = decrypt_secret_key_v3(&crypt, decdata.data(), key->sec_data, key->sec_len); break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_V6: + FALLTHROUGH_STATEMENT; +#endif case PGP_V4: pgp_cipher_cfb_decrypt(&crypt, decdata.data(), key->sec_data, key->sec_len); ret = RNP_SUCCESS; @@ -746,11 +838,57 @@ write_secret_key_mpis(pgp_packet_body_t &body, pgp_key_pkt_t &key) case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: body.add(key.material.eg.x); break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + body.add(key.material.ed25519.priv); + break; + case PGP_PKA_X25519: + body.add(key.material.x25519.priv); + break; +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + body.add(key.material.kyber_ecdh.priv.get_encoded()); + break; + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + body.add(key.material.dilithium_exdsa.priv.get_encoded()); + break; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + 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); throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); } +#if defined(ENABLE_CRYPTO_REFRESH) + if (key.version == PGP_V6 && key.sec_protection.s2k.usage == PGP_S2KU_NONE) { + return; /* checksum removed for v6 and usage byte zero */ + } +#endif + /* add sum16 if sha1 is not used */ if (key.sec_protection.s2k.usage != PGP_S2KU_ENCRYPTED_AND_HASHED) { uint16_t sum = 0; @@ -883,6 +1021,47 @@ forget_secret_key_fields(pgp_key_material_t *key) case PGP_PKA_ECDH: mpi_forget(&key->ec.x); break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + secure_clear(key->ed25519.priv.data(), key->ed25519.priv.size()); + key->ed25519.priv.clear(); + break; + case PGP_PKA_X25519: + secure_clear(key->x25519.priv.data(), key->x25519.priv.size()); + key->x25519.priv.clear(); + break; +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + key->kyber_ecdh.priv.secure_clear(); + break; + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + key->dilithium_exdsa.priv.secure_clear(); + break; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + case PGP_PKA_SPHINCSPLUS_SHAKE: + key->sphincsplus.priv.secure_clear(); + break; +#endif default: RNP_LOG("unknown key algorithm: %d", (int) key->alg); } @@ -1145,6 +1324,54 @@ pgp_key_pkt_t::~pgp_key_pkt_t() free(sec_data); } +uint8_t +pgp_key_pkt_t::s2k_specifier_len(pgp_s2k_specifier_t specifier) +{ + switch (specifier) { + case PGP_S2KS_SIMPLE: + return 2; + case PGP_S2KS_SALTED: + return 10; + case PGP_S2KS_ITERATED_AND_SALTED: + return 11; + default: + RNP_LOG("invalid specifier"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +void +pgp_key_pkt_t::make_s2k_params(pgp_packet_body_t &hbody) +{ + switch (sec_protection.s2k.usage) { + case PGP_S2KU_NONE: + break; + case PGP_S2KU_ENCRYPTED_AND_HASHED: + case PGP_S2KU_ENCRYPTED: { + hbody.add_byte(sec_protection.symm_alg); +#if defined(ENABLE_CRYPTO_REFRESH) + if (version == PGP_V6) { + // V6 packages contain length of the following field + hbody.add_byte(s2k_specifier_len(sec_protection.s2k.specifier)); + } +#endif + hbody.add(sec_protection.s2k); + if (sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) { + size_t blsize = pgp_block_size(sec_protection.symm_alg); + if (!blsize) { + RNP_LOG("wrong block size"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + hbody.add(sec_protection.iv, blsize); + } + break; + } + default: + RNP_LOG("wrong s2k usage"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + void pgp_key_pkt_t::write(pgp_dest_t &dst) { @@ -1172,27 +1399,16 @@ pgp_key_pkt_t::write(pgp_dest_t &dst) } pktbody.add_byte(sec_protection.s2k.usage); - switch (sec_protection.s2k.usage) { - case PGP_S2KU_NONE: - break; - case PGP_S2KU_ENCRYPTED_AND_HASHED: - case PGP_S2KU_ENCRYPTED: { - pktbody.add_byte(sec_protection.symm_alg); - pktbody.add(sec_protection.s2k); - if (sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) { - size_t blsize = pgp_block_size(sec_protection.symm_alg); - if (!blsize) { - RNP_LOG("wrong block size"); - throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); - } - pktbody.add(sec_protection.iv, blsize); - } - break; - } - default: - RNP_LOG("wrong s2k usage"); - throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + pgp_packet_body_t s2k_params(tag); + make_s2k_params(s2k_params); +#if defined(ENABLE_CRYPTO_REFRESH) + if ((version == PGP_V6) && (sec_protection.s2k.usage != PGP_S2KU_NONE)) { + // V6 packages contain the count of the optional 1-byte parameters + pktbody.add_byte(s2k_params.size()); } +#endif + pktbody.add(s2k_params.data(), s2k_params.size()); + if (sec_len) { /* if key is stored on card, or exported via gpg --export-secret-subkeys, then * sec_data is empty */ @@ -1211,6 +1427,10 @@ pgp_key_pkt_t::parse(pgp_source_t &src) return RNP_ERROR_BAD_FORMAT; } +#if defined(ENABLE_CRYPTO_REFRESH) + std::vector tmpbuf; +#endif + pgp_packet_body_t pkt((pgp_pkt_type_t) atag); /* Read the packet into memory */ rnp_result_t res = pkt.read(src); @@ -1221,7 +1441,22 @@ pgp_key_pkt_t::parse(pgp_source_t &src) tag = (pgp_pkt_type_t) atag; /* version */ uint8_t ver = 0; - if (!pkt.get(ver) || (ver < PGP_V2) || (ver > PGP_V4)) { + if (!pkt.get(ver)) { + RNP_LOG("unable to retrieve key packet version"); + return RNP_ERROR_BAD_FORMAT; + } + switch (ver) { + case PGP_V2: + FALLTHROUGH_STATEMENT; + case PGP_V3: + FALLTHROUGH_STATEMENT; + case PGP_V4: + break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_V6: + break; +#endif + default: RNP_LOG("wrong key packet version"); return RNP_ERROR_BAD_FORMAT; } @@ -1246,6 +1481,15 @@ pgp_key_pkt_t::parse(pgp_source_t &src) RNP_LOG("wrong v3 pk algorithm"); return RNP_ERROR_BAD_FORMAT; } +#if defined(ENABLE_CRYPTO_REFRESH) + /* v6 length field for public key material */ + if (version == PGP_V6) { + uint32_t material_len; + if (!pkt.get(material_len)) { + return RNP_ERROR_BAD_FORMAT; + } + } +#endif /* algorithm specific fields */ switch (alg) { case PGP_PKA_RSA: @@ -1293,6 +1537,81 @@ pgp_key_pkt_t::parse(pgp_source_t &src) material.ec.key_wrap_alg = (pgp_symm_alg_t) walg; break; } +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: { + const ec_curve_desc_t *ec_desc = get_curve_desc(PGP_CURVE_ED25519); + tmpbuf.resize(BITS_TO_BYTES(ec_desc->bitlen)); + if (!pkt.get(tmpbuf.data(), tmpbuf.size())) { + RNP_LOG("failed to parse Ed25519 public key data"); + return RNP_ERROR_BAD_FORMAT; + } + material.ed25519.pub = tmpbuf; + break; + } + case PGP_PKA_X25519: { + const ec_curve_desc_t *ec_desc = get_curve_desc(PGP_CURVE_25519); + tmpbuf.resize(BITS_TO_BYTES(ec_desc->bitlen)); + if (!pkt.get(tmpbuf.data(), tmpbuf.size())) { + RNP_LOG("failed to parse X25519 public key data"); + return RNP_ERROR_BAD_FORMAT; + } + material.x25519.pub = tmpbuf; + break; + } +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + tmpbuf.resize(pgp_kyber_ecdh_composite_public_key_t::encoded_size(alg)); + if (!pkt.get(tmpbuf.data(), tmpbuf.size())) { + RNP_LOG("failed to parse kyber-ecdh public key data"); + return RNP_ERROR_BAD_FORMAT; + } + material.kyber_ecdh.pub = pgp_kyber_ecdh_composite_public_key_t(tmpbuf, alg); + break; + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + tmpbuf.resize(pgp_dilithium_exdsa_composite_public_key_t::encoded_size(alg)); + if (!pkt.get(tmpbuf.data(), tmpbuf.size())) { + RNP_LOG("failed to parse dilithium-ecdsa/eddsa public key data"); + return RNP_ERROR_BAD_FORMAT; + } + material.dilithium_exdsa.pub = pgp_dilithium_exdsa_composite_public_key_t(tmpbuf, alg); + break; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + 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); return RNP_ERROR_BAD_FORMAT; @@ -1312,9 +1631,26 @@ pgp_key_pkt_t::parse(pgp_source_t &src) RNP_LOG("failed to read key protection"); return RNP_ERROR_BAD_FORMAT; } +#if defined(ENABLE_CRYPTO_REFRESH) + if (version == PGP_V6 && usage == 255) { + RNP_LOG( + "Error when parsing S2K usage: A version 6 packet MUST NOT use the value 255."); + return RNP_ERROR_BAD_FORMAT; + } +#endif sec_protection.s2k.usage = (pgp_s2k_usage_t) usage; sec_protection.cipher_mode = PGP_CIPHER_MODE_CFB; +#if defined(ENABLE_CRYPTO_REFRESH) + if (version == PGP_V6 && sec_protection.s2k.usage != PGP_S2KU_NONE) { + // V6 packages contain the count of the optional 1-byte parameters + uint8_t s2k_params_count; + if (!pkt.get(s2k_params_count)) { + RNP_LOG("failed to read key protection"); + } + } +#endif + switch (sec_protection.s2k.usage) { case PGP_S2KU_NONE: break; @@ -1322,8 +1658,21 @@ pgp_key_pkt_t::parse(pgp_source_t &src) case PGP_S2KU_ENCRYPTED_AND_HASHED: { /* we have s2k */ uint8_t salg = 0; - if (!pkt.get(salg) || !pkt.get(sec_protection.s2k)) { - RNP_LOG("failed to read key protection"); + if (!pkt.get(salg)) { + RNP_LOG("failed to read key protection (symmetric alg)"); + return RNP_ERROR_BAD_FORMAT; + } +#if defined(ENABLE_CRYPTO_REFRESH) + if (version == PGP_V6) { + // V6 packages contain the length of the following field + uint8_t s2k_specifier_len; + if (!pkt.get(s2k_specifier_len)) { + RNP_LOG("failed to read key protection (s2k specifier length)"); + } + } +#endif + if (!pkt.get(sec_protection.s2k)) { + RNP_LOG("failed to read key protection (s2k)"); return RNP_ERROR_BAD_FORMAT; } sec_protection.symm_alg = (pgp_symm_alg_t) salg; @@ -1371,19 +1720,8 @@ pgp_key_pkt_t::parse(pgp_source_t &src) } void -pgp_key_pkt_t::fill_hashed_data() +pgp_key_pkt_t::make_alg_spec_fields_for_public_key(pgp_packet_body_t &hbody) { - /* we don't have a need to write v2-v3 signatures */ - if (version != PGP_V4) { - RNP_LOG("unknown key version %d", (int) version); - throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); - } - - pgp_packet_body_t hbody(PGP_PKT_RESERVED); - hbody.add_byte(version); - hbody.add_uint32(creation_time); - hbody.add_byte(alg); - /* Algorithm specific fields */ switch (alg) { case PGP_PKA_RSA: case PGP_PKA_RSA_ENCRYPT_ONLY: @@ -1417,10 +1755,82 @@ pgp_key_pkt_t::fill_hashed_data() hbody.add_byte(material.ec.kdf_hash_alg); hbody.add_byte(material.ec.key_wrap_alg); break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + hbody.add(material.ed25519.pub); + break; + case PGP_PKA_X25519: + hbody.add(material.x25519.pub); + break; +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + hbody.add(material.kyber_ecdh.pub.get_encoded()); + break; + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + hbody.add(material.dilithium_exdsa.pub.get_encoded()); + break; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + 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); throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); } +} + +void +pgp_key_pkt_t::fill_hashed_data() +{ + /* we don't have a need to write v2-v3 signatures */ + switch (version) { + case PGP_V4: + break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_V6: + break; +#endif + default: + RNP_LOG("unknown key version %d", (int) version); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + pgp_packet_body_t hbody(PGP_PKT_RESERVED); + hbody.add_byte(version); + hbody.add_uint32(creation_time); + hbody.add_byte(alg); + + /* Algorithm specific fields */ + pgp_packet_body_t alg_spec_fields(PGP_PKT_RESERVED); + make_alg_spec_fields_for_public_key(alg_spec_fields); +#if defined(ENABLE_CRYPTO_REFRESH) + if (version == PGP_V6) { + hbody.add_uint32(alg_spec_fields.size()); + } +#endif + hbody.add(alg_spec_fields.data(), alg_spec_fields.size()); hashed_data = (uint8_t *) malloc(hbody.size()); if (!hashed_data) { diff --git a/src/librepgp/stream-key.h b/src/librepgp/stream-key.h index a19a9864..8974a231 100644 --- a/src/librepgp/stream-key.h +++ b/src/librepgp/stream-key.h @@ -69,6 +69,12 @@ typedef struct pgp_key_pkt_t { * pgp_key_pkt_t::write() on the newly generated key */ void fill_hashed_data(); bool equals(const pgp_key_pkt_t &key, bool pubonly = false) const noexcept; + + private: + /* create the contents of the algorithm specific public key fields in a separate packet */ + void make_alg_spec_fields_for_public_key(pgp_packet_body_t &hbody); + void make_s2k_params(pgp_packet_body_t &hbody); + uint8_t s2k_specifier_len(pgp_s2k_specifier_t specifier); } pgp_key_pkt_t; /* userid/userattr with all the corresponding signatures */ diff --git a/src/librepgp/stream-packet.cpp b/src/librepgp/stream-packet.cpp index 729d2f26..7342a326 100644 --- a/src/librepgp/stream-packet.cpp +++ b/src/librepgp/stream-packet.cpp @@ -673,6 +673,12 @@ pgp_packet_body_t::add(const void *data, size_t len) data_.insert(data_.end(), (uint8_t *) data, (uint8_t *) data + len); } +void +pgp_packet_body_t::add(const std::vector &data) +{ + add(data.data(), data.size()); +} + void pgp_packet_body_t::add_byte(uint8_t bt) { @@ -745,7 +751,19 @@ pgp_packet_body_t::add_subpackets(const pgp_signature_t &sig, bool hashed) if (spbody.data_.size() > 0xffff) { throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); } - add_uint16(spbody.data_.size()); + switch (sig.version) { + case PGP_V4: + add_uint16(spbody.data_.size()); + break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_V6: + add_uint32(spbody.data_.size()); + break; +#endif + default: + RNP_LOG("should not reach this code"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } add(spbody.data_.data(), spbody.data_.size()); } @@ -1013,8 +1031,23 @@ pgp_pk_sesskey_t::write(pgp_dest_t &dst) const { pgp_packet_body_t pktbody(PGP_PKT_PK_SESSION_KEY); pktbody.add_byte(version); - pktbody.add(key_id); +#if defined(ENABLE_CRYPTO_REFRESH) + if (version == PGP_PKSK_V3) { +#endif + pktbody.add(key_id); +#if defined(ENABLE_CRYPTO_REFRESH) + } else { // PGP_PKSK_V6 + pktbody.add_byte(1 + fp.length); // A one-octet size of the following two fields. + pktbody.add_byte((fp.length == PGP_FINGERPRINT_V6_SIZE) ? PGP_V6 : PGP_V4); + pktbody.add(fp.fingerprint, fp.length); + } +#endif pktbody.add_byte(alg); +#if defined(ENABLE_CRYPTO_REFRESH) + if ((version == PGP_PKSK_V3) && !do_encrypt_pkesk_v3_alg_id(alg)) { + pktbody.add_byte(salg); /* added as plaintext */ + } +#endif pktbody.add(material_buf.data(), material_buf.size()); pktbody.write(dst); } @@ -1029,16 +1062,75 @@ pgp_pk_sesskey_t::parse(pgp_source_t &src) } /* version */ uint8_t bt = 0; - if (!pkt.get(bt) || (bt != PGP_PKSK_V3)) { - RNP_LOG("wrong packet version"); + if (!pkt.get(bt)) { + RNP_LOG("Error when reading packet version"); return RNP_ERROR_BAD_FORMAT; } - version = bt; - /* key id */ - if (!pkt.get(key_id)) { - RNP_LOG("failed to get key id"); +#if defined(ENABLE_CRYPTO_REFRESH) + if ((bt != PGP_PKSK_V3) && (bt != PGP_PKSK_V6)) { +#else + if ((bt != PGP_PKSK_V3)) { +#endif + RNP_LOG("wrong packet version"); return RNP_ERROR_BAD_FORMAT; } + version = (pgp_pkesk_version_t) bt; + +#if defined(ENABLE_CRYPTO_REFRESH) + if (version == PGP_PKSK_V3) +#endif + { + /* key id */ + if (!pkt.get(key_id)) { + RNP_LOG("failed to get key id"); + return RNP_ERROR_BAD_FORMAT; + } + } +#if defined(ENABLE_CRYPTO_REFRESH) + else { // PGP_PKSK_V6 + uint8_t fp_and_key_ver_len; // A one-octet size of the following two fields. + if (!pkt.get(fp_and_key_ver_len)) { + RNP_LOG("Error when reading length of next two fields"); + return RNP_ERROR_BAD_FORMAT; + } + if ((fp_and_key_ver_len != 1 + PGP_FINGERPRINT_V4_SIZE) && + (fp_and_key_ver_len != 1 + PGP_FINGERPRINT_V6_SIZE)) { + RNP_LOG("Invalid size for key version + length field"); + return RNP_ERROR_BAD_FORMAT; + } + + size_t fp_len; + uint8_t fp_key_version; + if (!pkt.get(fp_key_version)) { + RNP_LOG("Error when reading key version"); + return RNP_ERROR_BAD_FORMAT; + } + switch (fp_key_version) { + case 0: // anonymous + fp_len = 0; + break; + case PGP_V4: + fp_len = PGP_FINGERPRINT_V4_SIZE; + break; + case PGP_V6: + fp_len = PGP_FINGERPRINT_V6_SIZE; + break; + default: + RNP_LOG("wrong key version used with PKESK v6"); + return RNP_ERROR_BAD_FORMAT; + } + fp.length = fp_len; + if (fp.length && (fp.length != fp_and_key_ver_len - 1)) { + RNP_LOG("size mismatch (fingerprint size and fp+key version length field)"); + return RNP_ERROR_BAD_FORMAT; + } + if (!pkt.get(fp.fingerprint, fp.length)) { + RNP_LOG("Error when reading fingerprint"); + return RNP_ERROR_BAD_FORMAT; + } + } +#endif + /* public key algorithm */ if (!pkt.get(bt)) { RNP_LOG("failed to get palg"); @@ -1046,6 +1138,16 @@ pgp_pk_sesskey_t::parse(pgp_source_t &src) } alg = (pgp_pubkey_alg_t) bt; +#if defined(ENABLE_CRYPTO_REFRESH) + if ((version == PGP_PKSK_V3) && !do_encrypt_pkesk_v3_alg_id(alg)) { + if (!pkt.get(bt)) { + RNP_LOG("failed to get salg"); + return RNP_ERROR_BAD_FORMAT; + } + } + salg = (pgp_symm_alg_t) bt; +#endif + /* raw signature material */ if (!pkt.left()) { RNP_LOG("No encrypted material"); @@ -1118,6 +1220,59 @@ pgp_pk_sesskey_t::parse_material(pgp_encrypted_material_t &material) const } break; } +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_X25519: { + const ec_curve_desc_t *ec_desc = get_curve_desc(PGP_CURVE_25519); + material.x25519.eph_key.resize(BITS_TO_BYTES(ec_desc->bitlen)); + if (!pkt.get(material.x25519.eph_key.data(), material.x25519.eph_key.size())) { + RNP_LOG("failed to parse X25519 PKESK (eph. pubkey)"); + return false; + } + uint8_t enc_sesskey_len; + if (!pkt.get(enc_sesskey_len)) { + RNP_LOG("failed to parse X25519 PKESK (enc sesskey length)"); + return false; + } + material.x25519.enc_sess_key.resize(enc_sesskey_len); + if (!pkt.get(material.x25519.enc_sess_key.data(), enc_sesskey_len)) { + RNP_LOG("failed to parse X25519 PKESK (enc sesskey)"); + return false; + } + break; + } +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: { + uint8_t wrapped_key_len = 0; + material.kyber_ecdh.composite_ciphertext.resize( + pgp_kyber_ecdh_encrypted_t::composite_ciphertext_size(alg)); + if (!pkt.get(material.kyber_ecdh.composite_ciphertext.data(), + material.kyber_ecdh.composite_ciphertext.size())) { + RNP_LOG("failed to get kyber-ecdh ciphertext"); + return false; + } + if (!pkt.get(wrapped_key_len)) { + RNP_LOG("failed to get kyber-ecdh wrapped session key length"); + return false; + } + material.kyber_ecdh.wrapped_sesskey.resize(wrapped_key_len); + if (!pkt.get(material.kyber_ecdh.wrapped_sesskey.data(), + material.kyber_ecdh.wrapped_sesskey.size())) { + RNP_LOG("failed to get kyber-ecdh session key"); + return false; + } + break; + } +#endif default: RNP_LOG("unknown pk alg %d", (int) alg); return false; @@ -1152,6 +1307,29 @@ pgp_pk_sesskey_t::write_material(const pgp_encrypted_material_t &material) pktbody.add(material.eg.g); pktbody.add(material.eg.m); break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_X25519: + pktbody.add(material.x25519.eph_key); + pktbody.add_byte(static_cast(material.x25519.enc_sess_key.size())); + pktbody.add(material.x25519.enc_sess_key); + break; +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + pktbody.add(material.kyber_ecdh.composite_ciphertext); + pktbody.add_byte(static_cast(material.kyber_ecdh.wrapped_sesskey.size())); + pktbody.add(material.kyber_ecdh.wrapped_sesskey); + break; +#endif default: RNP_LOG("Unknown pk alg: %d", (int) alg); throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); diff --git a/src/librepgp/stream-packet.h b/src/librepgp/stream-packet.h index 72b1c48e..b32f0ef0 100644 --- a/src/librepgp/stream-packet.h +++ b/src/librepgp/stream-packet.h @@ -119,6 +119,8 @@ typedef struct pgp_packet_body_t { bool get(pgp_s2k_t &s2k) noexcept; /** @brief append some bytes to the packet body */ void add(const void *data, size_t len); + /** @brief append some bytes to the packet body */ + void add(const std::vector &data); /** @brief append single byte to the packet body */ void add_byte(uint8_t bt); /** @brief append big endian 16-bit value to the packet body */ @@ -155,11 +157,19 @@ typedef struct pgp_packet_body_t { /** public-key encrypted session key packet */ typedef struct pgp_pk_sesskey_t { - unsigned version{}; - pgp_key_id_t key_id{}; + pgp_pkesk_version_t version{}; pgp_pubkey_alg_t alg{}; std::vector material_buf{}; + /* v3 PKESK */ + pgp_key_id_t key_id{}; + pgp_symm_alg_t salg; + +#if defined(ENABLE_CRYPTO_REFRESH) + /* v6 PKESK */ + pgp_fingerprint_t fp{}; +#endif + void write(pgp_dest_t &dst) const; rnp_result_t parse(pgp_source_t &src); /** @@ -182,7 +192,7 @@ typedef struct pgp_sk_sesskey_t { pgp_s2k_t s2k{}; uint8_t enckey[PGP_MAX_KEY_SIZE + PGP_AEAD_MAX_TAG_LEN + 1]{}; unsigned enckeylen{}; - /* v5 specific fields */ + /* v5/v6 specific fields */ pgp_aead_alg_t aalg{}; uint8_t iv[PGP_MAX_BLOCK_SIZE]{}; unsigned ivlen{}; diff --git a/src/librepgp/stream-parse.cpp b/src/librepgp/stream-parse.cpp index 07e663ce..aed70530 100644 --- a/src/librepgp/stream-parse.cpp +++ b/src/librepgp/stream-parse.cpp @@ -48,6 +48,10 @@ #include "crypto/signatures.h" #include "fingerprint.h" #include "pgp-key.h" +#ifdef ENABLE_CRYPTO_REFRESH +#include "crypto/hkdf.hpp" +#include "v2_seipd.h" +#endif #ifdef HAVE_ZLIB_H #include @@ -100,6 +104,9 @@ typedef struct pgp_source_encrypted_param_t { size_t aead_adlen{}; /* length of the additional data */ pgp_symm_alg_t salg; /* data encryption algorithm */ pgp_parse_handler_t * handler{}; /* parsing handler with callbacks */ +#ifdef ENABLE_CRYPTO_REFRESH + pgp_seipdv2_hdr_t seipdv2_hdr; /* SEIPDv2 encryption parameters */ +#endif pgp_source_encrypted_param_t() : auth_type(rnp::AuthType::None), salg(PGP_SA_UNKNOWN) { @@ -108,8 +115,21 @@ typedef struct pgp_source_encrypted_param_t { bool use_cfb() { - return auth_type != rnp::AuthType::AEADv1; + return (auth_type != rnp::AuthType::AEADv1 +#ifdef ENABLE_CRYPTO_REFRESH + && auth_type != rnp::AuthType::AEADv2 +#endif + ); + } + +#ifdef ENABLE_CRYPTO_REFRESH + bool + is_v2_seipd() const + { + return auth_type == rnp::AuthType::AEADv2; } +#endif + } pgp_source_encrypted_param_t; typedef struct pgp_source_signed_param_t { @@ -164,6 +184,23 @@ typedef struct pgp_source_partial_param_t { bool last; /* current part is last */ } pgp_source_partial_param_t; +namespace { + +bool +is_valid_seipd_version(uint8_t version) +{ + if (version == 1 +#ifdef ENABLE_CRYPTO_REFRESH + || version == 2 +#endif + ) { + return true; + } + return false; +} + +} // namespace + static bool is_pgp_source(pgp_source_t &src) { @@ -447,8 +484,7 @@ encrypted_start_aead_chunk(pgp_source_encrypted_param_t *param, size_t idx, bool uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN]; size_t nlen; - /* set chunk index for additional data */ - write_uint64(param->aead_ad + param->aead_adlen - 8, idx); + size_t default_ad_len = param->aead_adlen; if (last) { uint64_t total = idx * param->chunklen; @@ -464,9 +500,47 @@ encrypted_start_aead_chunk(pgp_source_encrypted_param_t *param, size_t idx, bool param->aead_adlen += 8; } - if (!pgp_cipher_aead_set_ad(¶m->decrypt, param->aead_ad, param->aead_adlen)) { - RNP_LOG("failed to set ad"); - return false; + switch (param->auth_type) { + case rnp::AuthType::MDC: + break; // cannot happen + case rnp::AuthType::None: + break; // cannot happen + case rnp::AuthType::AEADv1: + /* set chunk index for additional data */ + write_uint64(param->aead_ad + default_ad_len - 8, idx); + + if (!pgp_cipher_aead_set_ad(¶m->decrypt, param->aead_ad, param->aead_adlen)) { + RNP_LOG("failed to set ad"); + return false; + } + break; + +#ifdef ENABLE_CRYPTO_REFRESH + case rnp::AuthType::AEADv2: + /* set chunk index for additional data */ + write_uint64(param->aead_ad + default_ad_len - 8, idx); + std::vector add_data_seipd_v2 = { + static_cast(PGP_PKT_SE_IP_DATA | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT), + static_cast(param->seipdv2_hdr.version), + static_cast(param->seipdv2_hdr.cipher_alg), + static_cast(param->seipdv2_hdr.aead_alg), + param->seipdv2_hdr.chunk_size_octet}; + + if (param->is_v2_seipd()) { + if (last) { + std::copy(¶m->aead_ad[5], + ¶m->aead_ad[5 + 8], + std::back_inserter(add_data_seipd_v2)); + } + if (!pgp_cipher_aead_set_ad( + ¶m->decrypt, add_data_seipd_v2.data(), add_data_seipd_v2.size())) { + RNP_LOG("failed to set ad"); + return false; + } + } + break; + +#endif } /* setup chunk */ @@ -1388,6 +1462,21 @@ encrypted_start_aead(pgp_source_encrypted_param_t *param, pgp_symm_alg_t alg, ui if (alg != param->aead_hdr.ealg) { return false; } +#ifdef ENABLE_CRYPTO_REFRESH + std::vector seipd_v2_key; + if (param->is_v2_seipd()) { + seipd_v2_aead_fields_t aead_fields = + seipd_v2_key_and_nonce_derivation(param->seipdv2_hdr, key); + seipd_v2_key = aead_fields.key; + key = std::move(seipd_v2_key.data()); + // param->seipd_v2_nonce = std::move(aead_fields.nonce); + if (aead_fields.nonce.size() > sizeof(param->aead_hdr.iv)) { + // signalling error would be better here + aead_fields.nonce.resize(sizeof(param->aead_hdr.iv)); + } + memcpy(param->aead_hdr.iv, aead_fields.nonce.data(), aead_fields.nonce.size()); + } +#endif /* initialize cipher with key */ if (!pgp_cipher_aead_init( @@ -1405,6 +1494,34 @@ encrypted_start_aead(pgp_source_encrypted_param_t *param, pgp_symm_alg_t alg, ui #endif } +#if defined(ENABLE_CRYPTO_REFRESH) +/* The crypto refresh mandates that for a X25519/X448 PKESKv3, AES MUST be used. + The same is true for the PQC algorithms defined in draft-wussler-openpgp-pqc-02. + */ +static bool +do_enforce_aes_v3pkesk(pgp_pubkey_alg_t alg) +{ + switch (alg) { +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + FALLTHROUGH_STATEMENT; +#endif + case PGP_PKA_X25519: + return true; + default: + return false; + } +} +#endif + static bool encrypted_try_key(pgp_source_encrypted_param_t *param, pgp_pk_sesskey_t * sesskey, @@ -1426,6 +1543,32 @@ encrypted_try_key(pgp_source_encrypted_param_t *param, return false; } +#if defined(ENABLE_CRYPTO_REFRESH) + /* Crypto Refresh: + - The payload following any v6 PKESK or v6 SKESK packet MUST be a v2 SEIPD. + - implementations MUST NOT precede a v2 SEIPD payload with either v3 PKESK or v4 + SKESK packets. */ + if ((param->is_v2_seipd() && !(sesskey->version == PGP_PKSK_V6)) || + (param->auth_type == rnp::AuthType::MDC && !(sesskey->version == PGP_PKSK_V3))) { + RNP_LOG("Attempt to mix SEIPD v1 with PKESK v6 or SEIPD v2 with PKESK v3"); + return false; + } + + /* check that AES is used when mandated by the standard */ + if (do_enforce_aes_v3pkesk(sesskey->alg) && sesskey->version == PGP_PKSK_V3) { + switch (sesskey->salg) { + case PGP_SA_AES_128: + case PGP_SA_AES_192: + case PGP_SA_AES_256: + break; + default: + RNP_LOG("For the given asymmetric encryption algorithm in the PKESK, only AES is " + "allowed but another algorithm has been detected."); + return false; + } + } +#endif + rnp::secure_array decbuf; /* Decrypting session key value */ rnp_result_t err; @@ -1488,54 +1631,137 @@ encrypted_try_key(pgp_source_encrypted_param_t *param, } break; } +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_X25519: + declen = decbuf.size(); + err = x25519_native_decrypt( + &ctx.rng, keymaterial->x25519, &encmaterial.x25519, decbuf.data(), &declen); + if (err != RNP_SUCCESS) { + RNP_LOG("X25519 decryption error %u", err); + return false; + } + break; +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: { + pgp_key_t key(*seckey, true); /* make public-key `pgp_key_t` object from seckey */ + declen = decbuf.size(); + err = keymaterial->kyber_ecdh.priv.decrypt( + &ctx.rng, decbuf.data(), &declen, &encmaterial.kyber_ecdh); + if (err != RNP_SUCCESS) { + RNP_LOG("Kyber ECC decryption failure"); + return false; + } + break; + } +#endif default: RNP_LOG("unsupported public key algorithm %d\n", seckey->alg); return false; } - /* Check algorithm and key length */ - if (!pgp_is_sa_supported(decbuf[0])) { - RNP_LOG("Unsupported symmetric algorithm %" PRIu8, decbuf[0]); - return false; + uint8_t *decbuf_sesskey = decbuf.data(); + size_t decbuf_sesskey_len = declen; +#if defined(ENABLE_CRYPTO_REFRESH) + if (do_encrypt_pkesk_v3_alg_id(sesskey->alg)) +#endif + { + sesskey->salg = static_cast(decbuf[0]); } + size_t keylen = pgp_key_size(sesskey->salg); + if (sesskey->version == PGP_PKSK_V3) { + /* Check algorithm and key length */ + if (!pgp_is_sa_supported(sesskey->salg)) { + RNP_LOG("Unsupported symmetric algorithm %" PRIu8, sesskey->salg); + return false; + } - pgp_symm_alg_t salg = static_cast(decbuf[0]); - size_t keylen = pgp_key_size(salg); - if (declen != keylen + 3) { - RNP_LOG("invalid symmetric key length"); - return false; - } +#if defined(ENABLE_CRYPTO_REFRESH) + size_t alg_id_bytes = do_encrypt_pkesk_v3_alg_id(sesskey->alg) ? 1 : 0; + size_t checksum_bytes = have_pkesk_checksum(sesskey->alg) ? 2 : 0; +#else + size_t alg_id_bytes = 1; + size_t checksum_bytes = 2; +#endif - /* Validate checksum */ - rnp::secure_array checksum; - for (unsigned i = 1; i <= keylen; i++) { - checksum[0] += decbuf[i]; + if (decbuf_sesskey_len != keylen + alg_id_bytes + checksum_bytes) { + RNP_LOG("invalid symmetric key length"); + return false; + } + + /* skip over the first byte (if present) to aligns the code for all cases. */ + decbuf_sesskey += alg_id_bytes; + } +#if defined(ENABLE_CRYPTO_REFRESH) + else { // V6 PKESK + /* compute the expected key length from the decbuf_sesskey_len and check */ + keylen = + have_pkesk_checksum(sesskey->alg) ? decbuf_sesskey_len - 2 : decbuf_sesskey_len; + if (pgp_key_size(param->aead_hdr.ealg) != keylen) { + RNP_LOG("invalid symmetric key length"); + return false; + } } +#endif - if ((checksum[0] & 0xffff) != - (decbuf[keylen + 2] | ((unsigned) decbuf[keylen + 1] << 8))) { - RNP_LOG("wrong checksum\n"); - return false; +#if defined(ENABLE_CRYPTO_REFRESH) + if (have_pkesk_checksum(sesskey->alg)) +#endif + { + /* Validate checksum */ + rnp::secure_array checksum; + for (unsigned i = 0; i < keylen; i++) { + checksum[0] += decbuf_sesskey[i]; + } + + if ((checksum[0] & 0xffff) != + (decbuf_sesskey[keylen + 1] | ((unsigned) decbuf_sesskey[keylen] << 8))) { + RNP_LOG("wrong checksum\n"); + return false; + } } - if (param->use_cfb()) { - /* Decrypt header */ - res = encrypted_decrypt_cfb_header(param, salg, &decbuf[1]); - } else { - /* Start AEAD decrypting, assuming we have correct key */ - res = encrypted_start_aead(param, salg, &decbuf[1]); +#if defined(ENABLE_CRYPTO_REFRESH) + if (sesskey->version == PGP_PKSK_V3) +#endif + { + if (param->use_cfb()) { + /* Decrypt header */ + res = encrypted_decrypt_cfb_header(param, sesskey->salg, decbuf_sesskey); + } else { + /* Start AEAD decrypting, assuming we have correct key */ + res = encrypted_start_aead(param, sesskey->salg, decbuf_sesskey); + } + if (res) { + param->salg = sesskey->salg; + } + return res; } - if (res) { - param->salg = salg; +#if defined(ENABLE_CRYPTO_REFRESH) + else { // PGP_PKSK_V6 + pgp_symm_alg_t salg = + param->aead_hdr.ealg; // NOTEMTG: salg not part of the v6 PKESK, assignment here + // just to make the following call "happy" + return encrypted_start_aead(param, salg, decbuf_sesskey); } - return res; +#endif } #if defined(ENABLE_AEAD) static bool encrypted_sesk_set_ad(pgp_crypt_t *crypt, pgp_sk_sesskey_t *skey) { - /* TODO: this method is exact duplicate as in stream-write.c. Not sure where to put it */ + /* TODO: this method is exact duplicate as in stream-write.c. Not sure where to put it + */ uint8_t ad_data[4]; ad_data[0] = PGP_PKT_SK_SESSION_KEY | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT; @@ -1877,6 +2103,39 @@ get_compressed_src_alg(pgp_source_t *src, uint8_t *alg) return true; } +static bool +parse_aead_chunk_size(uint8_t chunk_size_octet, size_t &chunk_size) +{ + if (chunk_size_octet > 56) { + RNP_LOG("too large chunk size: %d", chunk_size_octet); + return false; + } + if (chunk_size_octet > 16) { + RNP_LOG("Warning: AEAD chunk bits > 16."); + } + chunk_size = 1L << (chunk_size_octet + 6); + return true; +} + +#if defined(ENABLE_CRYPTO_REFRESH) +bool +get_seipdv2_src_hdr(pgp_source_t *src, pgp_seipdv2_hdr_t *hdr) +{ + uint8_t hdrbt[3 + PGP_SEIPDV2_SALT_LEN] = {0}; + + if (!src_read_eq(src, hdrbt, sizeof(hdrbt))) { + return false; + } + + hdr->version = PGP_SE_IP_DATA_V2; + hdr->cipher_alg = (pgp_symm_alg_t) hdrbt[0]; + hdr->aead_alg = (pgp_aead_alg_t) hdrbt[1]; + hdr->chunk_size_octet = hdrbt[2]; + std::memcpy(hdr->salt, &hdrbt[3], PGP_SEIPDV2_SALT_LEN); + return true; +} +#endif + bool get_aead_src_hdr(pgp_source_t *src, pgp_aead_hdr_t *hdr) { @@ -1989,7 +2248,7 @@ encrypted_read_packet_data(pgp_source_encrypted_param_t *param) } /* check AEAD encrypted data packet header */ - if (param->aead_hdr.version != 1) { + if (!is_valid_seipd_version(param->aead_hdr.version)) { RNP_LOG("unknown aead ver: %d", param->aead_hdr.version); return RNP_ERROR_BAD_FORMAT; } @@ -1997,14 +2256,11 @@ encrypted_read_packet_data(pgp_source_encrypted_param_t *param) RNP_LOG("unknown aead alg: %d", (int) param->aead_hdr.aalg); return RNP_ERROR_BAD_FORMAT; } - if (param->aead_hdr.csize > 56) { - RNP_LOG("too large chunk size: %d", param->aead_hdr.csize); + + /* parse chunk size */ + if (!parse_aead_chunk_size(param->aead_hdr.csize, param->chunklen)) { return RNP_ERROR_BAD_FORMAT; } - if (param->aead_hdr.csize > 16) { - RNP_LOG("Warning: AEAD chunk bits > 16."); - } - param->chunklen = 1L << (param->aead_hdr.csize + 6); /* build additional data */ param->aead_adlen = 13; @@ -2012,16 +2268,65 @@ encrypted_read_packet_data(pgp_source_encrypted_param_t *param) memcpy(param->aead_ad + 1, hdr, 4); memset(param->aead_ad + 5, 0, 8); } else if (ptype == PGP_PKT_SE_IP_DATA) { - uint8_t mdcver; - if (!src_read_eq(param->pkt.readsrc, &mdcver, 1)) { + uint8_t SEIPD_version; + if (!src_read_eq(param->pkt.readsrc, &SEIPD_version, 1)) { return RNP_ERROR_READ; } - if (mdcver != 1) { - RNP_LOG("unknown mdc ver: %d", (int) mdcver); + if (SEIPD_version == PGP_SE_IP_DATA_V1) { + param->auth_type = rnp::AuthType::MDC; + param->auth_validated = false; + } +#ifdef ENABLE_CRYPTO_REFRESH + else if (SEIPD_version == PGP_SE_IP_DATA_V2) { + /* SKESK v6 is not yet implemented, thus we must not attempt to decrypt + SEIPDv2 here + TODO: Once SKESK v6 is implemented, replace this check with a check for + consistency between SEIPD and SKESK version + */ + if (param->symencs.size() > 0) { + RNP_LOG("SEIPDv2 not usable with SKESK version"); + return RNP_ERROR_BAD_FORMAT; + } + + param->auth_type = rnp::AuthType::AEADv2; + param->seipdv2_hdr.version = PGP_SE_IP_DATA_V2; + uint8_t hdr[4]; + if (!src_peek_eq(param->pkt.readsrc, hdr, 4)) { + return RNP_ERROR_READ; + } + + if (!get_seipdv2_src_hdr(param->pkt.readsrc, ¶m->seipdv2_hdr)) { + RNP_LOG("failed to read SEIPDv2 header"); + return RNP_ERROR_READ; + } + + /* check SEIPDv2 packet header */ + if ((param->seipdv2_hdr.aead_alg != PGP_AEAD_EAX) && + (param->seipdv2_hdr.aead_alg != PGP_AEAD_OCB)) { + RNP_LOG("unknown AEAD alg: %d", (int) param->seipdv2_hdr.aead_alg); + return RNP_ERROR_BAD_FORMAT; + } + + /* parse chunk size */ + if (!parse_aead_chunk_size(param->seipdv2_hdr.chunk_size_octet, param->chunklen)) { + return RNP_ERROR_BAD_FORMAT; + } + + /* build additional data */ + param->aead_adlen = 5; + param->aead_ad[0] = param->pkt.hdr.hdr[0]; + memcpy(param->aead_ad + 1, hdr, 4); + + param->aead_hdr.aalg = param->seipdv2_hdr.aead_alg; + param->aead_hdr.csize = param->seipdv2_hdr.chunk_size_octet; // needed? + param->aead_hdr.ealg = param->seipdv2_hdr.cipher_alg; + } +#endif + else { + RNP_LOG("unknown SEIPD version: %d", (int) SEIPD_version); return RNP_ERROR_BAD_FORMAT; } - param->auth_type = rnp::AuthType::MDC; } param->auth_validated = false; @@ -2054,7 +2359,13 @@ init_encrypted_src(pgp_parse_handler_t *handler, pgp_source_t *src, pgp_source_t goto finish; } - src->read = !param->use_cfb() ? encrypted_src_read_aead : encrypted_src_read_cfb; + src->read = (!param->use_cfb() +#ifdef ENABLE_CRYPTO_REFRESH + || param->is_v2_seipd() +#endif + ) ? + encrypted_src_read_aead : + encrypted_src_read_cfb; /* Obtaining the symmetric key */ if (!handler->password_provider) { @@ -2085,7 +2396,17 @@ init_encrypted_src(pgp_parse_handler_t *handler, pgp_source_t *src, pgp_source_t errcode = RNP_ERROR_NO_SUITABLE_KEY; while (pubidx < param->pubencs.size()) { auto &pubenc = param->pubencs[pubidx]; - keyctx.search.by.keyid = pubenc.key_id; +#if defined(ENABLE_CRYPTO_REFRESH) + if (pubenc.version == PGP_PKSK_V3) { +#endif + keyctx.search.by.keyid = pubenc.key_id; +#if defined(ENABLE_CRYPTO_REFRESH) + } else { // PGP_PKSK_V6 + keyctx.search.by.fingerprint = pubenc.fp; + keyctx.search.type = PGP_KEY_SEARCH_FINGERPRINT; + } +#endif + /* Get the key if any */ pgp_key_t *seckey = pgp_request_key(handler->key_provider, &keyctx); if (!seckey) { @@ -2093,7 +2414,16 @@ init_encrypted_src(pgp_parse_handler_t *handler, pgp_source_t *src, pgp_source_t continue; } /* Check whether key fits our needs */ - bool hidden = pubenc.key_id == pgp_key_id_t({}); + bool hidden; +#if defined(ENABLE_CRYPTO_REFRESH) + if (pubenc.version == PGP_PKSK_V3) { +#endif + hidden = (pubenc.key_id == pgp_key_id_t({})); +#if defined(ENABLE_CRYPTO_REFRESH) + } else { // PGP_PKSK_V6 + hidden = (pubenc.fp.length == 0); + } +#endif if (!hidden || (++hidden_tries >= MAX_HIDDEN_TRIES)) { pubidx++; } diff --git a/src/librepgp/stream-parse.h b/src/librepgp/stream-parse.h index 4f22b9a6..1bee9fd8 100644 --- a/src/librepgp/stream-parse.h +++ b/src/librepgp/stream-parse.h @@ -113,6 +113,15 @@ rnp_result_t init_literal_src(pgp_source_t *src, pgp_source_t *readsrc); */ bool get_literal_src_hdr(pgp_source_t *src, pgp_literal_hdr_t *hdr); +#if defined(ENABLE_CRYPTO_REFRESH) +/* @brief Get the SEIPDv2 packet information fields (not the OpenPGP packet header) + * @param src SEIPDv2-encrypted data source (starting from packet data itself, not the header) + * @param hdr pointer to header structure, where result will be stored + * @return true on success or false otherwise + */ +bool get_seipdv2_src_hdr(pgp_source_t *src, pgp_seipdv2_hdr_t *hdr); +#endif + /* @brief Get the AEAD-encrypted packet information fields (not the OpenPGP packet header) * @param src AEAD-encrypted data source (starting from packet data itself, not the header) * @param hdr pointer to header structure, where result will be stored diff --git a/src/librepgp/stream-sig.cpp b/src/librepgp/stream-sig.cpp index 74828594..0b15fdfc 100644 --- a/src/librepgp/stream-sig.cpp +++ b/src/librepgp/stream-sig.cpp @@ -48,12 +48,36 @@ void signature_hash_key(const pgp_key_pkt_t &key, rnp::Hash &hash) { - uint8_t hdr[3] = {0x99, 0x00, 0x00}; - if (key.hashed_data) { - write_uint16(hdr + 1, key.hashed_len); - hash.add(hdr, 3); - hash.add(key.hashed_data, key.hashed_len); - return; + switch (key.version) { + case PGP_V2: + FALLTHROUGH_STATEMENT; + case PGP_V3: + FALLTHROUGH_STATEMENT; + case PGP_V4: { + uint8_t hdr[3] = {0x99, 0x00, 0x00}; + if (key.hashed_data) { + write_uint16(hdr + 1, key.hashed_len); + hash.add(hdr, sizeof(hdr)); + hash.add(key.hashed_data, key.hashed_len); + return; + } + break; + } +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_V6: { + uint8_t hdr[5] = {0x9b, 0x00, 0x00, 0x00, 0x00}; + if (key.hashed_data) { + write_uint32(hdr + 1, key.hashed_len); + hash.add(hdr, sizeof(hdr)); + hash.add(key.hashed_data, key.hashed_len); + return; + } + break; + } +#endif + default: + RNP_LOG("should not reach this code"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); } /* call self recursively if hashed data is not filled, to overcome const restriction */ @@ -92,7 +116,7 @@ signature_hash_certification(const pgp_signature_t & sig, const pgp_key_pkt_t & key, const pgp_userid_pkt_t &userid) { - auto hash = signature_init(key.material, sig.halg); + auto hash = signature_init(key, sig); signature_hash_key(key, *hash); signature_hash_userid(userid, *hash, sig.version); return hash; @@ -103,7 +127,7 @@ signature_hash_binding(const pgp_signature_t &sig, const pgp_key_pkt_t & key, const pgp_key_pkt_t & subkey) { - auto hash = signature_init(key.material, sig.halg); + auto hash = signature_init(key, sig); signature_hash_key(key, *hash); signature_hash_key(subkey, *hash); return hash; @@ -112,7 +136,7 @@ signature_hash_binding(const pgp_signature_t &sig, std::unique_ptr signature_hash_direct(const pgp_signature_t &sig, const pgp_key_pkt_t &key) { - auto hash = signature_init(key.material, sig.halg); + auto hash = signature_init(key, sig); signature_hash_key(key, *hash); return hash; } @@ -373,6 +397,11 @@ pgp_sig_subpkt_t::parse() fields.issuer_fp.len = len - 1; } break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_SIG_SUBPKT_PREFERRED_AEAD_CIPHERSUITES: + // TODO-V6: needs implementation + break; +#endif case PGP_SIG_SUBPKT_PRIVATE_100: case PGP_SIG_SUBPKT_PRIVATE_101: case PGP_SIG_SUBPKT_PRIVATE_102: @@ -429,6 +458,12 @@ pgp_signature_t::pgp_signature_t(const pgp_signature_t &src) palg = src.palg; halg = src.halg; memcpy(lbits, src.lbits, sizeof(src.lbits)); +#if defined(ENABLE_CRYPTO_REFRESH) + if (version == PGP_V6) { + salt_size = src.salt_size; + memcpy(salt, src.salt, salt_size); + } +#endif creation_time = src.creation_time; signer = src.signer; @@ -458,6 +493,12 @@ pgp_signature_t::pgp_signature_t(pgp_signature_t &&src) palg = src.palg; halg = src.halg; memcpy(lbits, src.lbits, sizeof(src.lbits)); +#if defined(ENABLE_CRYPTO_REFRESH) + if (version == PGP_V6) { + salt_size = src.salt_size; + memcpy(salt, src.salt, salt_size); + } +#endif creation_time = src.creation_time; signer = src.signer; hashed_len = src.hashed_len; @@ -508,6 +549,12 @@ pgp_signature_t::operator=(const pgp_signature_t &src) palg = src.palg; halg = src.halg; memcpy(lbits, src.lbits, sizeof(src.lbits)); +#if defined(ENABLE_CRYPTO_REFRESH) + if (version == PGP_V6) { + salt_size = src.salt_size; + memcpy(salt, src.salt, salt_size); + } +#endif creation_time = src.creation_time; signer = src.signer; @@ -540,6 +587,7 @@ pgp_signature_t::operator==(const pgp_signature_t &src) const if ((lbits[0] != src.lbits[0]) || (lbits[1] != src.lbits[1])) { return false; } + // TODO-V6: could also compare salt if ((hashed_len != src.hashed_len) || memcmp(hashed_data, src.hashed_data, hashed_len)) { return false; } @@ -642,11 +690,30 @@ pgp_signature_t::keyid() const noexcept memcpy(res.data(), subpkt->fields.issuer, PGP_KEY_ID_SIZE); return res; } - if ((subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR))) { - memcpy(res.data(), - subpkt->fields.issuer_fp.fp + subpkt->fields.issuer_fp.len - PGP_KEY_ID_SIZE, - PGP_KEY_ID_SIZE); - return res; + switch (version) { + case PGP_V4: { + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_KEY_ID, false); + if (subpkt) { + memcpy(res.data(), subpkt->fields.issuer, PGP_KEY_ID_SIZE); + } else if ((subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR))) { + memcpy(res.data(), + subpkt->fields.issuer_fp.fp + subpkt->fields.issuer_fp.len - + PGP_KEY_ID_SIZE, + PGP_KEY_ID_SIZE); + } + break; + } +#ifdef ENABLE_CRYPTO_REFRESH + case PGP_V6: { + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR); + if (subpkt) { + memcpy(res.data(), subpkt->fields.issuer_fp.fp, PGP_KEY_ID_SIZE); + } + break; + } +#endif + default: + break; } return res; } @@ -676,7 +743,7 @@ pgp_signature_t::has_keyfp() const return false; } const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR); - return subpkt && (subpkt->fields.issuer_fp.len <= PGP_FINGERPRINT_SIZE); + return subpkt && (subpkt->fields.issuer_fp.len <= PGP_MAX_FINGERPRINT_SIZE); } pgp_fingerprint_t @@ -704,7 +771,11 @@ pgp_signature_t::set_keyfp(const pgp_fingerprint_t &fp) pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR, 1 + fp.length, true); subpkt.parsed = true; subpkt.hashed = true; +#if defined(ENABLE_CRYPTO_REFRESH) + subpkt.data[0] = (uint8_t) version; +#else subpkt.data[0] = 4; +#endif memcpy(subpkt.data + 1, fp.fingerprint, fp.length); subpkt.fields.issuer_fp.len = fp.length; subpkt.fields.issuer_fp.version = subpkt.data[0]; @@ -1143,9 +1214,9 @@ pgp_signature_t::matches_onepass(const pgp_one_pass_sig_t &onepass) const } rnp_result_t -pgp_signature_t::parse_v3(pgp_packet_body_t &pkt) +pgp_signature_t::parse_v2v3(pgp_packet_body_t &pkt) { - /* parse v3-specific fields, not the whole signature */ + /* parse v2/v3-specific fields, not the whole signature */ uint8_t buf[16] = {}; if (!pkt.get(buf, 16)) { RNP_LOG("cannot get enough bytes"); @@ -1249,12 +1320,12 @@ pgp_signature_t::parse_subpackets(uint8_t *buf, size_t len, bool hashed) } rnp_result_t -pgp_signature_t::parse_v4(pgp_packet_body_t &pkt) +pgp_signature_t::parse_v4up(pgp_packet_body_t &pkt) { - /* parse v4-specific fields, not the whole signature */ - uint8_t buf[5]; - if (!pkt.get(buf, 5)) { - RNP_LOG("cannot get first 5 bytes"); + /* parse v4 (and up) specific fields, not the whole signature */ + uint8_t buf[3]; + if (!pkt.get(buf, 3)) { + RNP_LOG("cannot get first 3 bytes"); return RNP_ERROR_BAD_FORMAT; } @@ -1265,34 +1336,86 @@ pgp_signature_t::parse_v4(pgp_packet_body_t &pkt) /* hash algorithm */ halg = (pgp_hash_alg_t) buf[2]; /* hashed subpackets length */ - uint16_t splen = read_uint16(&buf[3]); - /* hashed subpackets length + 2 bytes of length of unhashed subpackets */ - if (pkt.left() < (size_t)(splen + 2)) { + + size_t splen; + size_t splen_size = 2; +#if defined(ENABLE_CRYPTO_REFRESH) + uint8_t splen_buf[4]; + switch (version) { + case PGP_V4: + splen_size = 2; + break; + case PGP_V6: + splen_size = 4; + break; + default: + RNP_LOG("unsupported signature version: %d", (int) version); + return RNP_ERROR_BAD_FORMAT; + } +#else + uint8_t splen_buf[2]; +#endif + + if (!pkt.get(splen_buf, splen_size)) { + RNP_LOG("cannot get hashed len"); + return RNP_ERROR_BAD_FORMAT; + } + switch (version) { + case PGP_V4: + splen = read_uint16(splen_buf); + break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_V6: + splen = read_uint32(splen_buf); + break; +#endif + default: + RNP_LOG("unsupported signature version: %d", (int) version); + return RNP_ERROR_BAD_FORMAT; + } + + /* hashed subpackets length + splen_size bytes of length of unhashed subpackets */ + if (pkt.left() < (size_t)(splen + splen_size)) { RNP_LOG("wrong packet or hashed subpackets length"); return RNP_ERROR_BAD_FORMAT; } /* building hashed data */ free(hashed_data); - if (!(hashed_data = (uint8_t *) malloc(splen + 6))) { + if (!(hashed_data = (uint8_t *) malloc(splen + 4 + splen_size))) { RNP_LOG("allocation failed"); return RNP_ERROR_OUT_OF_MEMORY; } hashed_data[0] = version; - memcpy(hashed_data + 1, buf, 5); + memcpy(hashed_data + 1, buf, sizeof(buf)); + memcpy(hashed_data + 1 + sizeof(buf), splen_buf, splen_size); - if (!pkt.get(hashed_data + 6, splen)) { + if (!pkt.get(hashed_data + 4 + splen_size, splen)) { RNP_LOG("cannot get hashed subpackets data"); return RNP_ERROR_BAD_FORMAT; } - hashed_len = splen + 6; + hashed_len = splen + 4 + splen_size; /* parsing hashed subpackets */ - if (!parse_subpackets(hashed_data + 6, splen, true)) { + if (!parse_subpackets(hashed_data + 4 + splen_size, splen, true)) { RNP_LOG("failed to parse hashed subpackets"); return RNP_ERROR_BAD_FORMAT; } + /* reading unhashed subpackets */ - if (!pkt.get(splen)) { - RNP_LOG("cannot get unhashed len"); + if (!pkt.get(splen_buf, splen_size)) { + RNP_LOG("cannot get hashed len"); + return RNP_ERROR_BAD_FORMAT; + } + switch (version) { + case PGP_V4: + splen = read_uint16(splen_buf); + break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_V6: + splen = read_uint32(splen_buf); + break; +#endif + default: + RNP_LOG("unsupported signature version: %d", (int) version); return RNP_ERROR_BAD_FORMAT; } if (pkt.left() < splen) { @@ -1320,13 +1443,22 @@ pgp_signature_t::parse(pgp_packet_body_t &pkt) } version = (pgp_version_t) ver; - /* v3 or v4 signature body */ + /* v3 or v4 or v6 signature body */ rnp_result_t res; - if ((ver == PGP_V2) || (ver == PGP_V3)) { - res = parse_v3(pkt); - } else if (ver == PGP_V4) { - res = parse_v4(pkt); - } else { + switch (ver) { + case PGP_V2: + FALLTHROUGH_STATEMENT; + case PGP_V3: + res = parse_v2v3(pkt); + break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_V6: + FALLTHROUGH_STATEMENT; +#endif + case PGP_V4: + res = parse_v4up(pkt); + break; + default: RNP_LOG("unknown signature version: %d", (int) ver); res = RNP_ERROR_BAD_FORMAT; } @@ -1340,6 +1472,24 @@ pgp_signature_t::parse(pgp_packet_body_t &pkt) RNP_LOG("not enough data for hash left bits"); return RNP_ERROR_BAD_FORMAT; } + +#if defined(ENABLE_CRYPTO_REFRESH) + if (ver == PGP_V6) { + if (!pkt.get(salt_size)) { + RNP_LOG("not enough data for v6 salt size octet"); + return RNP_ERROR_BAD_FORMAT; + } + if (salt_size != rnp::Hash::size(halg) / 2) { + RNP_LOG("invalid salt size"); + return RNP_ERROR_BAD_FORMAT; + } + if (!pkt.get(salt, salt_size)) { + RNP_LOG("not enough data for v6 signature salt"); + return RNP_ERROR_BAD_FORMAT; + } + } +#endif + /* raw signature material */ material_len = pkt.left(); if (!material_len) { @@ -1393,9 +1543,7 @@ pgp_signature_t::parse_material(pgp_signature_material_t &material) const if (version < PGP_V4) { RNP_LOG("Warning! v3 EdDSA signature."); } -#if (!defined(_MSVC_LANG) || _MSVC_LANG >= 201703L) - [[fallthrough]]; -#endif + FALLTHROUGH_STATEMENT; case PGP_PKA_ECDSA: case PGP_PKA_SM2: case PGP_PKA_ECDH: @@ -1409,6 +1557,54 @@ pgp_signature_t::parse_material(pgp_signature_material_t &material) const return false; } break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: { + const ec_curve_desc_t *ec_desc = get_curve_desc(PGP_CURVE_25519); + material.ed25519.sig.resize(2 * BITS_TO_BYTES(ec_desc->bitlen)); + if (!pkt.get(material.ed25519.sig.data(), material.ed25519.sig.size())) { + RNP_LOG("failed to parse ED25519 signature data"); + return false; + } + break; + } +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + material.dilithium_exdsa.sig.resize( + pgp_dilithium_exdsa_signature_t::composite_signature_size(palg)); + if (!pkt.get(material.dilithium_exdsa.sig.data(), + material.dilithium_exdsa.sig.size())) { + RNP_LOG("failed to get dilithium-ecdsa/eddsa signature"); + return false; + } + break; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + 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); return false; @@ -1424,7 +1620,18 @@ pgp_signature_t::parse_material(pgp_signature_material_t &material) const void pgp_signature_t::write(pgp_dest_t &dst) const { - if ((version < PGP_V2) || (version > PGP_V4)) { + switch (version) { + case PGP_V2: + FALLTHROUGH_STATEMENT; + case PGP_V3: + FALLTHROUGH_STATEMENT; + case PGP_V4: + break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_V6: + break; +#endif + default: RNP_LOG("don't know version %d", (int) version); throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); } @@ -1445,6 +1652,12 @@ pgp_signature_t::write(pgp_dest_t &dst) const pktbody.add_subpackets(*this, false); } pktbody.add(lbits, 2); +#if defined(ENABLE_CRYPTO_REFRESH) + if (version == PGP_V6) { + pktbody.add_byte(salt_size); + pktbody.add(salt, salt_size); + } +#endif /* write mpis */ pktbody.add(material_buf, material_len); pktbody.write(dst); @@ -1475,6 +1688,31 @@ pgp_signature_t::write_material(const pgp_signature_material_t &material) pktbody.add(material.eg.r); pktbody.add(material.eg.s); break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_ED25519: + pktbody.add(material.ed25519.sig); + break; +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_DILITHIUM3_ED25519: + FALLTHROUGH_STATEMENT; + // TODO: add case PGP_PKA_DILITHIUM5_ED448: FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM3_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_DILITHIUM5_BP384: + pktbody.add(material.dilithium_exdsa.sig); + break; + case PGP_PKA_SPHINCSPLUS_SHA2: + FALLTHROUGH_STATEMENT; + 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); throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); @@ -1493,7 +1731,18 @@ void pgp_signature_t::fill_hashed_data() { /* we don't have a need to write v2-v3 signatures */ - if ((version < PGP_V2) || (version > PGP_V4)) { + switch (version) { + case PGP_V2: + FALLTHROUGH_STATEMENT; + case PGP_V3: + FALLTHROUGH_STATEMENT; + case PGP_V4: + break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_V6: + break; +#endif + default: RNP_LOG("don't know version %d", (int) version); throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); } diff --git a/src/librepgp/stream-sig.h b/src/librepgp/stream-sig.h index 4f36c381..a980dc1b 100644 --- a/src/librepgp/stream-sig.h +++ b/src/librepgp/stream-sig.h @@ -39,8 +39,8 @@ typedef struct pgp_signature_t { pgp_sig_type_t type_; std::vector preferred(pgp_sig_subpacket_type_t type) const; void set_preferred(const std::vector &data, pgp_sig_subpacket_type_t type); - rnp_result_t parse_v3(pgp_packet_body_t &pkt); - rnp_result_t parse_v4(pgp_packet_body_t &pkt); + rnp_result_t parse_v2v3(pgp_packet_body_t &pkt); + rnp_result_t parse_v4up(pgp_packet_body_t &pkt); bool parse_subpackets(uint8_t *buf, size_t len, bool hashed); public: @@ -58,9 +58,15 @@ typedef struct pgp_signature_t { uint32_t creation_time; pgp_key_id_t signer; - /* v4 - only fields */ + /* common v4 and v6 fields */ std::vector subpkts; +#if defined(ENABLE_CRYPTO_REFRESH) + /* v6 - only fields */ + uint8_t salt[PGP_MAX_SALT_SIZE_V6_SIG]; + uint8_t salt_size; +#endif + pgp_signature_t() : type_(PGP_SIG_BINARY), version(PGP_VUNKNOWN), palg(PGP_PKA_NOTHING), halg(PGP_HASH_UNKNOWN), hashed_data(NULL), hashed_len(0), material_buf(NULL), diff --git a/src/librepgp/stream-write.cpp b/src/librepgp/stream-write.cpp index b17ef65d..acf122f9 100644 --- a/src/librepgp/stream-write.cpp +++ b/src/librepgp/stream-write.cpp @@ -25,6 +25,7 @@ */ #include "config.h" +#include "repgp/repgp_def.h" #include #include #include @@ -55,6 +56,9 @@ #include "defaults.h" #include #include +#ifdef ENABLE_CRYPTO_REFRESH +#include "v2_seipd.h" +#endif /* 8192 bytes, as GnuPG */ #define PGP_PARTIAL_PKT_SIZE_BITS (13) @@ -98,6 +102,34 @@ typedef struct pgp_dest_encrypted_param_t { size_t chunkidx; /* index of the current AEAD chunk */ size_t cachelen; /* how many bytes are in cache, for AEAD */ uint8_t cache[PGP_AEAD_CACHE_LEN]; /* pre-allocated cache for encryption */ +#ifdef ENABLE_CRYPTO_REFRESH + std::array v2_seipd_salt; /* SEIPDv2 salt value */ +#endif + + bool + is_aead_auth() + { + switch (this->auth_type) { + case rnp::AuthType::AEADv1: +#ifdef ENABLE_CRYPTO_REFRESH + case rnp::AuthType::AEADv2: +#endif + return true; + break; + case rnp::AuthType::MDC: + case rnp::AuthType::None: + return false; + } + throw rnp::rnp_exception(RNP_ERROR_GENERIC); + }; + +#ifdef ENABLE_CRYPTO_REFRESH + bool + is_v2_seipd() const + { + return this->auth_type == rnp::AuthType::AEADv2; + } +#endif } pgp_dest_encrypted_param_t; typedef struct pgp_dest_signer_info_t { @@ -343,7 +375,9 @@ encrypted_start_aead_chunk(pgp_dest_encrypted_param_t *param, size_t idx, bool l } /* set chunk index for additional data */ - write_uint64(param->ad + param->adlen - 8, idx); + if (param->auth_type == rnp::AuthType::AEADv1) { + write_uint64(param->ad + param->adlen - 8, idx); + } if (last) { if (!(param->chunkout + param->cachelen)) { @@ -370,6 +404,9 @@ encrypted_start_aead_chunk(pgp_dest_encrypted_param_t *param, size_t idx, bool l /* set chunk index for nonce */ nlen = pgp_cipher_aead_nonce(param->aalg, param->iv, nonce, idx); + if (!nlen) { + RNP_LOG("ERROR: when starting encrypted AEAD chunk: could not determine nonce length"); + } /* start cipher */ res = pgp_cipher_aead_start(¶m->encrypt, nonce, nlen); @@ -456,7 +493,7 @@ encrypted_dst_finish(pgp_dest_t *dst) { pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param; - if (param->auth_type == rnp::AuthType::AEADv1) { + if (param->is_aead_auth()) { #if !defined(ENABLE_AEAD) RNP_LOG("AEAD is not enabled."); rnp_result_t res = RNP_ERROR_NOT_IMPLEMENTED; @@ -502,7 +539,7 @@ encrypted_dst_close(pgp_dest_t *dst, bool discard) return; } - if (param->auth_type == rnp::AuthType::AEADv1) { + if (param->is_aead_auth()) { #if defined(ENABLE_AEAD) pgp_cipher_aead_destroy(¶m->encrypt); #endif @@ -519,7 +556,8 @@ encrypted_add_recipient(pgp_write_handler_t *handler, pgp_dest_t * dst, pgp_key_t * userkey, const uint8_t * key, - const unsigned keylen) + const unsigned keylen, + pgp_pkesk_version_t pkesk_version) { pgp_pk_sesskey_t pkey; pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param; @@ -531,24 +569,79 @@ encrypted_add_recipient(pgp_write_handler_t *handler, return RNP_ERROR_NO_SUITABLE_KEY; } +#if defined(ENABLE_CRYPTO_REFRESH) + /* Crypto Refresh: For X25519/X448 PKESKv3, AES is mandated */ + if (userkey->alg() == PGP_PKA_X25519 && pkesk_version == PGP_PKSK_V3) { + switch (param->ctx->ealg) { + case PGP_SA_AES_128: + case PGP_SA_AES_192: + case PGP_SA_AES_256: + break; + default: + RNP_LOG("attempting to use X25519 and v3 PKESK in combination with a symmetric " + "algorithm that is not AES."); + return RNP_ERROR_DECRYPT_FAILED; + } + } +#endif + /* Fill pkey */ - pkey.version = PGP_PKSK_V3; + pkey.version = pkesk_version; pkey.alg = userkey->alg(); + /* set key_id (used for PKESK v3) and fingerprint (used for PKESK v6) */ pkey.key_id = userkey->keyid(); +#if defined(ENABLE_CRYPTO_REFRESH) + if (pkey.version == (uint8_t) PGP_V6) { + pkey.fp = userkey->fp(); + } +#endif /* Encrypt the session key */ rnp::secure_array enckey; - enckey[0] = param->ctx->ealg; - memcpy(&enckey[1], key, keylen); + uint8_t *sesskey = enckey.data(); /* pointer to the actual session key */ + size_t enckey_len = keylen; + + pkey.salg = param->ctx->ealg; + +#if defined(ENABLE_CRYPTO_REFRESH) + if (pkey.version == PGP_PKSK_V3) { + size_t key_offset; + if (do_encrypt_pkesk_v3_alg_id(pkey.alg)) { + /* for pre-crypto-refresh algorithms, algorithm ID is part of the session key */ + key_offset = 1; + enckey[0] = pkey.salg; + } else { + key_offset = 0; + } +#else + enckey[0] = pkey.salg; + size_t key_offset = 1; +#endif + memcpy(&enckey[key_offset], key, keylen); + sesskey += key_offset; + enckey_len += key_offset; +#if defined(ENABLE_CRYPTO_REFRESH) + } else { // PGP_PKSK_V6 + memcpy(&enckey[0], key, keylen); + } +#endif + +#if defined(ENABLE_CRYPTO_REFRESH) + if (have_pkesk_checksum(pkey.alg)) +#endif + { + /* Calculate checksum */ + rnp::secure_array checksum; - /* Calculate checksum */ - rnp::secure_array checksum; + for (unsigned i = 0; i < keylen; i++) { + checksum[0] += sesskey[i]; + } + sesskey[keylen] = (checksum[0] >> 8) & 0xff; + sesskey[keylen + 1] = checksum[0] & 0xff; - for (unsigned i = 1; i <= keylen; i++) { - checksum[0] += enckey[i]; + /* increment enckey_len by checksum */ + enckey_len += 2; } - enckey[keylen + 1] = (checksum[0] >> 8) & 0xff; - enckey[keylen + 2] = checksum[0] & 0xff; pgp_encrypted_material_t material; @@ -558,7 +651,7 @@ encrypted_add_recipient(pgp_write_handler_t *handler, ret = rsa_encrypt_pkcs1(&handler->ctx->ctx->rng, &material.rsa, enckey.data(), - keylen + 3, + enckey_len, &userkey->material().rsa); if (ret) { RNP_LOG("rsa_encrypt_pkcs1 failed"); @@ -571,7 +664,7 @@ encrypted_add_recipient(pgp_write_handler_t *handler, ret = sm2_encrypt(&handler->ctx->ctx->rng, &material.sm2, enckey.data(), - keylen + 3, + enckey_len, PGP_HASH_SM3, &userkey->material().ec); if (ret) { @@ -593,7 +686,7 @@ encrypted_add_recipient(pgp_write_handler_t *handler, ret = ecdh_encrypt_pkcs5(&handler->ctx->ctx->rng, &material.ecdh, enckey.data(), - keylen + 3, + enckey_len, &userkey->material().ec, userkey->fp()); if (ret) { @@ -606,7 +699,7 @@ encrypted_add_recipient(pgp_write_handler_t *handler, ret = elgamal_encrypt_pkcs1(&handler->ctx->ctx->rng, &material.eg, enckey.data(), - keylen + 3, + enckey_len, &userkey->material().eg); if (ret) { RNP_LOG("pgp_elgamal_public_encrypt failed"); @@ -614,6 +707,38 @@ encrypted_add_recipient(pgp_write_handler_t *handler, } break; } +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_PKA_X25519: + ret = x25519_native_encrypt(&handler->ctx->ctx->rng, + userkey->material().x25519.pub, + enckey.data(), + enckey_len, + &material.x25519); + if (ret) { + RNP_LOG("x25519 encryption failed"); + return ret; + } + break; +#endif +#if defined(ENABLE_PQC) + case PGP_PKA_KYBER768_X25519: + FALLTHROUGH_STATEMENT; + // TODO add case PGP_PKA_KYBER1024_X448: FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_P256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_P384: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER768_BP256: + FALLTHROUGH_STATEMENT; + case PGP_PKA_KYBER1024_BP384: + ret = userkey->material().kyber_ecdh.pub.encrypt( + &handler->ctx->ctx->rng, &material.kyber_ecdh, enckey.data(), enckey_len); + if (ret) { + RNP_LOG("Kyber ECC Encrypt failed"); + return ret; + } + break; +#endif default: RNP_LOG("unsupported alg: %d", (int) userkey->alg()); return ret; @@ -793,7 +918,7 @@ encrypted_start_aead(pgp_dest_encrypted_param_t *param, uint8_t *enckey) RNP_LOG("AEAD support is not enabled."); return RNP_ERROR_NOT_IMPLEMENTED; #else - uint8_t hdr[4 + PGP_AEAD_MAX_NONCE_LEN]; + uint8_t hdr[4 + PGP_AEAD_MAX_NONCE_OR_SALT_LEN]; size_t nlen; if (pgp_block_size(param->ctx->ealg) != 16) { @@ -801,32 +926,74 @@ encrypted_start_aead(pgp_dest_encrypted_param_t *param, uint8_t *enckey) } /* fill header */ +#ifdef ENABLE_CRYPTO_REFRESH + hdr[0] = param->auth_type == rnp::AuthType::AEADv2 ? 2 : 1; +#else hdr[0] = 1; +#endif hdr[1] = param->ctx->ealg; hdr[2] = param->ctx->aalg; hdr[3] = param->ctx->abits; /* generate iv */ nlen = pgp_cipher_aead_nonce_len(param->ctx->aalg); + uint8_t *iv_or_salt = param->iv; + size_t iv_or_salt_len = nlen; +#ifdef ENABLE_CRYPTO_REFRESH + if (param->auth_type == rnp::AuthType::AEADv2) { + iv_or_salt = param->v2_seipd_salt.data(); + iv_or_salt_len = param->v2_seipd_salt.size(); + } +#endif try { - param->ctx->ctx->rng.get(param->iv, nlen); + param->ctx->ctx->rng.get(iv_or_salt, iv_or_salt_len); } catch (const std::exception &e) { return RNP_ERROR_RNG; } - memcpy(hdr + 4, param->iv, nlen); - + memcpy(hdr + 4, iv_or_salt, iv_or_salt_len); /* output header */ - dst_write(param->pkt.writedst, hdr, 4 + nlen); + dst_write(param->pkt.writedst, hdr, 4 + iv_or_salt_len); /* initialize encryption */ param->chunklen = 1L << (hdr[3] + 6); param->chunkout = 0; /* fill additional/authenticated data */ - param->ad[0] = PGP_PKT_AEAD_ENCRYPTED | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT; + uint8_t raw_packet_tag = PGP_PKT_AEAD_ENCRYPTED; +#ifdef ENABLE_CRYPTO_REFRESH + if (param->auth_type == rnp::AuthType::AEADv2) { + raw_packet_tag = PGP_PKT_SE_IP_DATA; + } +#endif + param->ad[0] = raw_packet_tag | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT; memcpy(param->ad + 1, hdr, 4); - memset(param->ad + 5, 0, 8); - param->adlen = 13; +#ifdef ENABLE_CRYPTO_REFRESH + if (param->auth_type == rnp::AuthType::AEADv2) { + param->adlen = 5; + } else { +#endif + memset(param->ad + 5, 0, 8); + param->adlen = 13; +#ifdef ENABLE_CRYPTO_REFRESH + } + seipd_v2_aead_fields_t s2_fields; + if (param->auth_type == rnp::AuthType::AEADv2) { + param->aalg = param->ctx->aalg; + pgp_seipdv2_hdr_t v2_seipd_hdr; + v2_seipd_hdr.cipher_alg = param->ctx->ealg; + v2_seipd_hdr.aead_alg = param->ctx->aalg; + v2_seipd_hdr.chunk_size_octet = param->ctx->abits; + v2_seipd_hdr.version = PGP_SE_IP_DATA_V2; + memcpy(v2_seipd_hdr.salt, iv_or_salt, PGP_SEIPDV2_SALT_LEN); + s2_fields = seipd_v2_key_and_nonce_derivation(v2_seipd_hdr, enckey); + enckey = s2_fields.key.data(); + if (s2_fields.nonce.size() > sizeof(param->iv)) { + // would be better to indicate an error + s2_fields.nonce.resize(sizeof(param->iv)); + } + std::memcpy(param->iv, s2_fields.nonce.data(), s2_fields.nonce.size()); + } +#endif /* initialize cipher */ if (!pgp_cipher_aead_init( @@ -883,18 +1050,26 @@ init_encrypted_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *wr } param->auth_type = handler->ctx->aalg == PGP_AEAD_NONE ? rnp::AuthType::MDC : rnp::AuthType::AEADv1; + + pkeycount = handler->ctx->recipients.size(); + skeycount = handler->ctx->passwords.size(); + +#if defined(ENABLE_CRYPTO_REFRESH) + /* in the case of PKESK (pkeycount > 0) and all keys are PKESKv6/SEIPDv2 capable, ugprade + * to AEADv2 */ + if (handler->ctx->enable_pkesk_v6 && handler->ctx->pkeskv6_capable() && pkeycount > 0) { + param->auth_type = rnp::AuthType::AEADv2; + } +#endif param->aalg = handler->ctx->aalg; param->ctx = handler->ctx; param->pkt.origdst = writedst; - dst->write = param->auth_type == rnp::AuthType::AEADv1 ? encrypted_dst_write_aead : - encrypted_dst_write_cfb; + // the following assignment is covered for the v2 SEIPD case further below + dst->write = param->is_aead_auth() ? encrypted_dst_write_aead : encrypted_dst_write_cfb; dst->finish = encrypted_dst_finish; dst->close = encrypted_dst_close; dst->type = PGP_STREAM_ENCRYPTED; - pkeycount = handler->ctx->recipients.size(); - skeycount = handler->ctx->passwords.size(); - rnp::secure_array enckey; /* content encryption key */ if (!pkeycount && !skeycount) { RNP_LOG("no recipients"); @@ -902,7 +1077,7 @@ init_encrypted_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *wr goto finish; } - if ((pkeycount > 0) || (skeycount > 1) || (param->auth_type == rnp::AuthType::AEADv1)) { + if ((pkeycount > 0) || (skeycount > 1) || param->is_aead_auth()) { try { handler->ctx->ctx->rng.get(enckey.data(), keylen); } catch (const std::exception &e) { @@ -914,7 +1089,19 @@ init_encrypted_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *wr /* Configuring and writing pk-encrypted session keys */ for (auto recipient : handler->ctx->recipients) { - ret = encrypted_add_recipient(handler, dst, recipient, enckey.data(), keylen); + pgp_pkesk_version_t pkesk_version = PGP_PKSK_V3; +#if defined(ENABLE_CRYPTO_REFRESH) + if (param->auth_type == rnp::AuthType::AEADv2) { + pkesk_version = PGP_PKSK_V6; + } + if (handler->ctx->aalg == PGP_AEAD_NONE) { + // set default AEAD if not set + // TODO-V6: is this the right place to set the default algorithm? + param->ctx->aalg = DEFAULT_AEAD_ALG; + } +#endif + ret = encrypted_add_recipient( + handler, dst, recipient, enckey.data(), keylen, pkesk_version); if (ret) { goto finish; } @@ -937,6 +1124,11 @@ init_encrypted_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *wr /* We do not generate PGP_PKT_SE_DATA, leaving this just in case */ param->pkt.tag = param->auth_type == rnp::AuthType::MDC ? PGP_PKT_SE_IP_DATA : PGP_PKT_SE_DATA; +#ifdef ENABLE_CRYPTO_REFRESH + if (param->auth_type == rnp::AuthType::AEADv2) { + param->pkt.tag = PGP_PKT_SE_IP_DATA; + } +#endif } /* initializing partial data length writer */ @@ -947,7 +1139,7 @@ init_encrypted_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *wr goto finish; } - if (param->auth_type == rnp::AuthType::AEADv1) { + if (param->is_aead_auth()) { /* initialize AEAD encryption */ ret = encrypted_start_aead(param, enckey.data()); } else { @@ -1134,11 +1326,19 @@ signed_write_signature(pgp_dest_signed_param_t *param, try { pgp_signature_t sig; if (signer->onepass.version) { - signer->key->sign_init(sig, signer->onepass.halg, param->ctx->ctx->time()); + signer->key->sign_init(param->ctx->ctx->rng, + sig, + signer->onepass.halg, + param->ctx->ctx->time(), + signer->key->version()); sig.palg = signer->onepass.palg; sig.set_type(signer->onepass.type); } else { - signer->key->sign_init(sig, signer->halg, param->ctx->ctx->time()); + signer->key->sign_init(param->ctx->ctx->rng, + sig, + signer->halg, + param->ctx->ctx->time(), + signer->key->version()); /* line below should be checked */ sig.set_type(param->ctx->detached ? PGP_SIG_BINARY : PGP_SIG_TEXT); } diff --git a/src/librepgp/v2_seipd.cpp b/src/librepgp/v2_seipd.cpp new file mode 100644 index 00000000..4bb2ee19 --- /dev/null +++ b/src/librepgp/v2_seipd.cpp @@ -0,0 +1,84 @@ +/* + * 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 "config.h" + +#ifdef ENABLE_CRYPTO_REFRESH + +#include "v2_seipd.h" +#include "logging.h" +#include "crypto/hkdf.hpp" + +seipd_v2_aead_fields_t +seipd_v2_key_and_nonce_derivation(pgp_seipdv2_hdr_t &hdr, uint8_t *sesskey) +{ + auto hkdf = rnp::Hkdf::create(PGP_HASH_SHA256); + + size_t sesskey_len = pgp_key_size(hdr.cipher_alg); + uint32_t nonce_size = 0; + uint32_t key_size = pgp_key_size(hdr.cipher_alg); + + switch (hdr.aead_alg) { + case PGP_AEAD_EAX: + nonce_size = 16; + break; + case PGP_AEAD_OCB: + nonce_size = 15; + break; + case PGP_AEAD_NONE: + case PGP_AEAD_UNKNOWN: + RNP_LOG("only EAX and OCB is supported for v2 SEIPD packets"); + throw rnp::rnp_exception(RNP_ERROR_NOT_SUPPORTED); + } + const uint8_t info[5] = { + static_cast(PGP_PKT_SE_IP_DATA | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT), + static_cast(hdr.version), + static_cast(hdr.cipher_alg), + static_cast(hdr.aead_alg), + hdr.chunk_size_octet}; + uint32_t out_size = key_size + nonce_size - 8; + std::vector hkdf_out(out_size); + hkdf->extract_expand(hdr.salt, + sizeof(hdr.salt), + sesskey, + sesskey_len, + info, + sizeof(info), + hkdf_out.data(), + hkdf_out.size()); + + seipd_v2_aead_fields_t result; + result.key = std::vector(hkdf_out.data(), hkdf_out.data() + key_size); + result.nonce = + std::vector(hkdf_out.data() + key_size, hkdf_out.data() + hkdf_out.size()); + for (uint32_t i = 0; i < 8; i++) { + // fill up the nonce with zero octets + result.nonce.push_back(0); + } + return result; +} + +#endif diff --git a/src/librepgp/v2_seipd.h b/src/librepgp/v2_seipd.h new file mode 100644 index 00000000..dc3cf4ff --- /dev/null +++ b/src/librepgp/v2_seipd.h @@ -0,0 +1,36 @@ +/* + * 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 +#include +#include "types.h" + +struct seipd_v2_aead_fields_t { + std::vector key, nonce; +}; + +seipd_v2_aead_fields_t seipd_v2_key_and_nonce_derivation(pgp_seipdv2_hdr_t &hdr, + uint8_t * sesskey); diff --git a/src/rnp/fficli.cpp b/src/rnp/fficli.cpp index d4cd4c38..62ecd701 100644 --- a/src/rnp/fficli.cpp +++ b/src/rnp/fficli.cpp @@ -25,6 +25,7 @@ */ #include "config.h" +#include "rnpcfg.h" #include #include #include @@ -1624,6 +1625,20 @@ cli_rnp_generate_key(cli_rnp_t *rnp, const char *username) goto done; } +#if defined(ENABLE_CRYPTO_REFRESH) + 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_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)) { ERR_MSG("Primary key generation failed."); @@ -1665,6 +1680,19 @@ cli_rnp_generate_key(cli_rnp_t *rnp, const char *username) ERR_MSG("Failed to set hash algorithm."); goto done; } +#if defined(ENABLE_CRYPTO_REFRESH) + 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."); goto done; @@ -1747,7 +1775,16 @@ key_matches_string(rnp_key_handle_t handle, const std::string &str) } /* check fingerprint */ - if (len == RNP_FP_SIZE * 2) { + size_t fp_size = RNP_FP_V4_SIZE; +#if defined(ENABLE_CRYPTO_REFRESH) + uint32_t key_version; + rnp_key_get_version(handle, &key_version); + + if (key_version == RNP_PGP_VER_6) { + fp_size = RNP_FP_V6_SIZE; + } +#endif + if (len == fp_size * 2) { if (rnp_key_get_fprint(handle, &id)) { goto done; } @@ -2811,6 +2848,13 @@ cli_rnp_encrypt_and_sign(const rnp_cfg &cfg, } } +#if defined(ENABLE_CRYPTO_REFRESH) + /* enable or disable v6 PKESK creation*/ + if (!cfg.get_bool(CFG_V3_PKESK_ONLY)) { + rnp_op_encrypt_enable_pkesk_v6(op); + } +#endif + /* adding signatures if encrypt-and-sign is used */ if (cfg.get_bool(CFG_SIGN_NEEDED)) { rnp_op_encrypt_set_creation_time(op, cfg.get_sig_creation()); diff --git a/src/rnp/fficli.h b/src/rnp/fficli.h index 7db29dc1..7e1b6a47 100644 --- a/src/rnp/fficli.h +++ b/src/rnp/fficli.h @@ -268,8 +268,12 @@ void rnp_win_clear_args(int argc, char **argv); #endif /* TODO: we should decide what to do with functions/constants/defines below */ +#define RNP_FP_V4_SIZE 20 +#if defined(ENABLE_CRYPTO_REFRESH) +#define RNP_PGP_VER_6 6 +#define RNP_FP_V6_SIZE 32 +#endif #define RNP_KEYID_SIZE 8 -#define RNP_FP_SIZE 20 #define RNP_GRIP_SIZE 20 #define ERR_MSG(...) \ diff --git a/src/rnp/rnp.cpp b/src/rnp/rnp.cpp index 5dddaca8..a4b41f1e 100644 --- a/src/rnp/rnp.cpp +++ b/src/rnp/rnp.cpp @@ -60,6 +60,10 @@ static const char *usage = " -V, --version Print RNP version information.\n" " -e, --encrypt Encrypt data using the public key(s).\n" " -r, --recipient Specify recipient's key via uid/keyid/fingerprint.\n" +#if defined(ENABLE_CRYPTO_REFRESH) + " --v3-pkesk-only Only create v3 PKESK (otherwise v6 will be created if " + "appropriate).\n" +#endif " --cipher name Specify symmetric cipher, used for encryption.\n" " --aead[=EAX, OCB] Use AEAD for encryption.\n" " -z 0..9 Set the compression level.\n" @@ -125,6 +129,9 @@ enum optdefs { OPT_KEY_STORE_FORMAT, OPT_USERID, OPT_RECIPIENT, +#if defined(ENABLE_CRYPTO_REFRESH) + OPT_V3_PKESK_ONLY, +#endif OPT_ARMOR, OPT_HOMEDIR, OPT_DETACHED, @@ -189,6 +196,9 @@ static struct option options[] = { {"keystore-format", required_argument, NULL, OPT_KEY_STORE_FORMAT}, {"userid", required_argument, NULL, OPT_USERID}, {"recipient", required_argument, NULL, OPT_RECIPIENT}, +#if defined(ENABLE_CRYPTO_REFRESH) + {"v3-pkesk-only", optional_argument, NULL, OPT_V3_PKESK_ONLY}, +#endif {"home", required_argument, NULL, OPT_HOMEDIR}, {"homedir", required_argument, NULL, OPT_HOMEDIR}, {"keyfile", required_argument, NULL, OPT_KEYFILE}, @@ -297,9 +307,7 @@ setcmd(rnp_cfg &cfg, int cmd, const char *arg) break; case CMD_CLEARSIGN: cfg.set_bool(CFG_CLEARTEXT, true); -#if (!defined(_MSVC_LANG) || _MSVC_LANG >= 201703L) - [[fallthrough]]; -#endif + FALLTHROUGH_STATEMENT; case CMD_SIGN: cfg.set_bool(CFG_NEEDSSECKEY, true); cfg.set_bool(CFG_SIGN_NEEDED, true); @@ -316,9 +324,7 @@ setcmd(rnp_cfg &cfg, int cmd, const char *arg) case CMD_VERIFY: /* single verify will discard output, decrypt will not */ cfg.set_bool(CFG_NO_OUTPUT, true); -#if (!defined(_MSVC_LANG) || _MSVC_LANG >= 201703L) - [[fallthrough]]; -#endif + FALLTHROUGH_STATEMENT; case CMD_VERIFY_CAT: newcmd = CMD_PROCESS; break; @@ -401,6 +407,11 @@ setoption(rnp_cfg &cfg, int val, const char *arg) case OPT_RECIPIENT: cfg.add_str(CFG_RECIPIENTS, arg); return true; +#if defined(ENABLE_CRYPTO_REFRESH) + case OPT_V3_PKESK_ONLY: + cfg.set_bool(CFG_V3_PKESK_ONLY, true); + return true; +#endif case OPT_ARMOR: cfg.set_bool(CFG_ARMOR, true); return true; @@ -592,9 +603,7 @@ set_short_option(rnp_cfg &cfg, int ch, const char *arg) cfg.set_bool(CFG_KEYSTORE_DISABLED, true); break; case 'h': -#if (!defined(_MSVC_LANG) || _MSVC_LANG >= 201703L) - [[fallthrough]]; -#endif + FALLTHROUGH_STATEMENT; default: return setcmd(cfg, CMD_HELP, optarg); } diff --git a/src/rnp/rnpcfg.h b/src/rnp/rnpcfg.h index c9478bbd..585ca174 100644 --- a/src/rnp/rnpcfg.h +++ b/src/rnp/rnpcfg.h @@ -50,8 +50,11 @@ #define CFG_NEEDSSECKEY "needsseckey" /* needs secret key for the ongoing operation */ #define CFG_USERID "userid" /* userid for the ongoing operation */ #define CFG_RECIPIENTS "recipients" /* list of encrypted data recipients */ -#define CFG_SIGNERS "signers" /* list of signers */ -#define CFG_HOMEDIR "homedir" /* home directory - folder with keyrings and so on */ +#if defined(ENABLE_CRYPTO_REFRESH) +#define CFG_V3_PKESK_ONLY "v3-pkesk-only" /* disable v6 PKESK */ +#endif +#define CFG_SIGNERS "signers" /* list of signers */ +#define CFG_HOMEDIR "homedir" /* home directory - folder with keyrings and so on */ #define CFG_KEYFILE "keyfile" /* path to the file with key(s), used instead of keyring */ #define CFG_PASSFD "pass-fd" /* password file descriptor */ #define CFG_PASSWD "password" /* password as command-line constant */ @@ -116,6 +119,12 @@ #define CFG_KG_PROT_HASH "kg-prot-hash" #define CFG_KG_PROT_ALG "kg-prot-alg" #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/main.cpp b/src/rnpkeys/main.cpp index 3dd088cc..75e3efe5 100644 --- a/src/rnpkeys/main.cpp +++ b/src/rnpkeys/main.cpp @@ -52,9 +52,7 @@ get_short_cmd(int ch) case 'l': return CMD_LIST_KEYS; case 'h': -#if (!defined(_MSVC_LANG) || _MSVC_LANG >= 201703L) - [[fallthrough]]; -#endif + FALLTHROUGH_STATEMENT; default: return CMD_HELP; } diff --git a/src/rnpkeys/tui.cpp b/src/rnpkeys/tui.cpp index 6454f8e3..cd9724a7 100644 --- a/src/rnpkeys/tui.cpp +++ b/src/rnpkeys/tui.cpp @@ -231,14 +231,34 @@ 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" - "\t(22) EDDSA + X25519\n" - "\t(99) SM2\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" +#else + "\t(22) EDDSA + ECDH\n" +#endif +#if defined(ENABLE_CRYPTO_REFRESH) + "\t(23) ED25519 + X25519 (v6 key) \n" +#endif +#if defined(ENABLE_PQC) + "\t(25) (Dilithium3 + Ed25519) + (Kyber768 + X25519)\n" + "\t(27) (Dilithium3 + ECDSA-NIST-P-256) + (Kyber768 + ECDH-NIST-P-256)\n" + "\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" + "> "); if (!rnp_secure_get_long_from_fd(input_fp, option, false)) { option = 0; continue; @@ -288,12 +308,86 @@ rnpkeys_ask_generate_params(rnp_cfg &cfg, FILE *input_fp) cfg.set_str(CFG_KG_SUBKEY_CURVE, curve); break; } +#if defined(ENABLE_CRYPTO_REFRESH) + case 21: { + cfg.set_str(CFG_KG_V6_KEY, "true"); + FALLTHROUGH_STATEMENT; + } +#endif case 22: { cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_EDDSA); cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ECDH); cfg.set_str(CFG_KG_SUBKEY_CURVE, "Curve25519"); break; } +#if defined(ENABLE_CRYPTO_REFRESH) + case 23: { + cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_ED25519); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_X25519); + cfg.set_str(CFG_KG_V6_KEY, "true"); + break; + } +#endif +#if defined(ENABLE_PQC) + case 25: + cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_DILITHIUM3_ED25519); + cfg.set_str(CFG_KG_HASH, RNP_ALGNAME_SHA3_256); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_KYBER768_X25519); + cfg.set_str(CFG_KG_V6_KEY, "true"); + break; + case 27: + cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_DILITHIUM3_P256); + cfg.set_str(CFG_KG_HASH, RNP_ALGNAME_SHA3_256); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_KYBER768_P256); + cfg.set_str(CFG_KG_V6_KEY, "true"); + break; + case 28: + cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_DILITHIUM5_P384); + cfg.set_str(CFG_KG_HASH, RNP_ALGNAME_SHA3_256); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_KYBER1024_P384); + cfg.set_str(CFG_KG_V6_KEY, "true"); + break; + case 29: + cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_DILITHIUM3_BP256); + cfg.set_str(CFG_KG_HASH, RNP_ALGNAME_SHA3_256); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_KYBER768_BP256); + cfg.set_str(CFG_KG_V6_KEY, "true"); + break; + case 30: + cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_DILITHIUM5_BP384); + cfg.set_str(CFG_KG_HASH, RNP_ALGNAME_SHA3_256); + 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_SHA3_256); + 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_SHA3_512); + 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); cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_SM2); @@ -327,6 +421,14 @@ rnpkeys_ask_generate_params_subkey(rnp_cfg &cfg, FILE *input_fp) "\t(18) ECDH\n" "\t(19) ECDSA\n" "\t(22) EDDSA\n" +#if defined(ENABLE_PQC) + "\t(25) Kyber768 + X25519\n" + "\t(26) Kyber1024 + X448\n" + "\t(27) Kyber768 + ECDH-NIST-P-256\n" + "\t(28) Kyber1024 + ECDH-NIST-P-384\n" + "\t(29) Kyber768 + ECDH-brainpoolP256r1\n" + "\t(30) Kyber1024 + ECDH-brainpoolP384r1\n" +#endif "\t(99) SM2" "> "); if (!rnp_secure_get_long_from_fd(input_fp, option, false)) { @@ -383,6 +485,26 @@ rnpkeys_ask_generate_params_subkey(rnp_cfg &cfg, FILE *input_fp) cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_EDDSA); break; } +#if defined(ENABLE_PQC) + case 25: + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_KYBER768_X25519); + break; + case 26: + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_KYBER1024_X448); + break; + case 27: + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_KYBER768_P256); + break; + case 28: + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_KYBER1024_P384); + break; + case 29: + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_KYBER768_BP256); + break; + case 30: + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_KYBER1024_BP384); + break; +#endif case 99: { cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_SM2); if (!cfg.has(CFG_KG_HASH)) { diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index c539d761..c4ba8561 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -82,7 +82,7 @@ if(CRYPTO_BACKEND_BOTAN3) set(CMAKE_CXX_STANDARD 20) endif() -add_executable(rnp_tests +set(RNP_TEST_SOURCES ../rnp/rnpcfg.cpp ../rnp/fficli.cpp ../rnp/rnp.cpp @@ -134,6 +134,7 @@ add_executable(rnp_tests user-prefs.cpp utils-hex2bin.cpp utils-rnpcfg.cpp + exdsa_ecdhkem.cpp issues/1030.cpp issues/1115.cpp issues/1171.cpp @@ -146,7 +147,19 @@ add_executable(rnp_tests fuzz_dump.cpp fuzz_verify_detached.cpp fuzz_verify.cpp -) + ) + +if(ENABLE_CRYPTO_REFRESH) + list(APPEND RNP_TEST_SOURCES + hkdf.cpp) +endif() +if(ENABLE_PQC) + list(APPEND RNP_TEST_SOURCES + pqc.cpp) +endif() + +add_executable(rnp_tests ${RNP_TEST_SOURCES}) + if(MSVC) find_package(WindowsSDK) GetUMWindowsSDKLibraryDir(WIN_LIBRARY_DIR) diff --git a/src/tests/cipher.cpp b/src/tests/cipher.cpp index 25b98bfa..9664d38e 100644 --- a/src/tests/cipher.cpp +++ b/src/tests/cipher.cpp @@ -602,6 +602,132 @@ TEST_F(rnp_tests, test_dsa_verify_negative) assert_int_equal(dsa_verify(&sig, message, h_size, key1), RNP_ERROR_SIGNATURE_INVALID); } +#if defined(ENABLE_PQC) +TEST_F(rnp_tests, kyber_ecdh_roundtrip) +{ + pgp_pubkey_alg_t algs[] = {PGP_PKA_KYBER768_X25519, + /* PGP_PKA_KYBER1024_X448, */ // X448 not yet implemented + PGP_PKA_KYBER1024_P384, + PGP_PKA_KYBER768_BP256, + PGP_PKA_KYBER1024_BP384}; + + pgp_kyber_ecdh_encrypted_t enc; + uint8_t plaintext[32] = {0}; + size_t plaintext_len = sizeof(plaintext); + uint8_t result[32] = {0}; + size_t result_len = sizeof(result); + + for (size_t i = 0; i < plaintext_len; i++) { + plaintext[i] = i; // assures that we do not have a special case with all-zeroes + } + + for (size_t i = 0; i < ARRAY_SIZE(algs); i++) { + rnp_keygen_crypto_params_t key_desc; + key_desc.key_alg = algs[i]; + key_desc.hash_alg = PGP_HASH_SHA512; + key_desc.ctx = &global_ctx; + + pgp_key_pkt_t key_pkt; + assert_true(pgp_generate_seckey(key_desc, key_pkt, true)); + + pgp_fingerprint_t key_fpr = {}; + assert_rnp_success(pgp_fingerprint(key_fpr, key_pkt)); + + pgp_key_t key(key_pkt); + assert_rnp_success(key_pkt.material.kyber_ecdh.pub.encrypt( + &global_ctx.rng, &enc, plaintext, plaintext_len)); + assert_rnp_success(key_pkt.material.kyber_ecdh.priv.decrypt( + &global_ctx.rng, result, &result_len, &enc)); + + assert_int_equal(plaintext_len, result_len); + assert_int_equal(memcmp(plaintext, result, result_len), 0); + } +} + +TEST_F(rnp_tests, dilithium_exdsa_signverify_success) +{ + uint8_t message[64]; + const pgp_hash_alg_t hash_alg = PGP_HASH_SHA512; + + pgp_pubkey_alg_t algs[] = {PGP_PKA_DILITHIUM3_ED25519, + /* PGP_PKA_DILITHIUM5_ED448,*/ PGP_PKA_DILITHIUM3_P256, + PGP_PKA_DILITHIUM5_P384, + PGP_PKA_DILITHIUM3_BP256, + PGP_PKA_DILITHIUM5_BP384}; + + for (size_t i = 0; i < ARRAY_SIZE(algs); i++) { + // Generate test data. Mainly to make valgrind not to complain about uninitialized data + global_ctx.rng.get(message, sizeof(message)); + + pgp_dilithium_exdsa_signature_t sig; + rnp_keygen_crypto_params_t key_desc; + key_desc.key_alg = algs[i]; + key_desc.hash_alg = hash_alg; + 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_dilithium_exdsa_key_t *key1 = &seckey1.material.dilithium_exdsa; + const pgp_dilithium_exdsa_key_t *key2 = &seckey2.material.dilithium_exdsa; + + assert_rnp_success( + key1->priv.sign(&global_ctx.rng, &sig, hash_alg, message, sizeof(message))); + + assert_rnp_success(key1->pub.verify(&sig, hash_alg, message, sizeof(message))); + + // Fails because of different key used + 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 // -DS2K_MINIMUM_TUNING_RATIO=2 (or whatever they need) #ifndef S2K_MINIMUM_TUNING_RATIO diff --git a/src/tests/data/test_v6_valid_data/transferable_pubkey_v6.asc b/src/tests/data/test_v6_valid_data/transferable_pubkey_v6.asc new file mode 100644 index 00000000..7cdbdda5 --- /dev/null +++ b/src/tests/data/test_v6_valid_data/transferable_pubkey_v6.asc @@ -0,0 +1,12 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xioGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laPCsQYf +GwoAAABCBYJjh3/jAwsJBwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxy +KwwfHifBilZwj2Ul7Ce62azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lw +gyU2kCcUmKfvBXbAf6rhRYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaE +QsiPlR4zxP/TP7mhfVEe7XWPxtnMUMtf15OyA51YBM4qBmOHf+MZAAAAIIaTJINn ++eUBXbki+PSAld2nhJh/LVmFsS+60WyvXkQ1wpsGGBsKAAAALAWCY4d/4wKbDCIh +BssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce62azJAAAAAAQBIKbpGG2dWTX8 +j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDEM0g12vYxoWM8Y81W+bHBw805 +I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUrk0mXubZvyl4GBg== +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/tests/data/test_v6_valid_data/transferable_seckey_v6.asc b/src/tests/data/test_v6_valid_data/transferable_seckey_v6.asc new file mode 100644 index 00000000..976b460c --- /dev/null +++ b/src/tests/data/test_v6_valid_data/transferable_seckey_v6.asc @@ -0,0 +1,14 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB +exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ +BwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 +2azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lwgyU2kCcUmKfvBXbAf6rh +RYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaEQsiPlR4zxP/TP7mhfVEe +7XWPxtnMUMtf15OyA51YBMdLBmOHf+MZAAAAIIaTJINn+eUBXbki+PSAld2nhJh/ +LVmFsS+60WyvXkQ1AE1gCk95TUR3XFeibg/u/tVY6a//1q0NWC1X+yui3O24wpsG +GBsKAAAALAWCY4d/4wKbDCIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 +2azJAAAAAAQBIKbpGG2dWTX8j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDE +M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr +k0mXubZvyl4GBg== +-----END PGP PRIVATE KEY BLOCK----- diff --git a/src/tests/data/test_v6_valid_data/v6pkesk.asc b/src/tests/data/test_v6_valid_data/v6pkesk.asc new file mode 100644 index 00000000..f0d0c211 --- /dev/null +++ b/src/tests/data/test_v6_valid_data/v6pkesk.asc @@ -0,0 +1,8 @@ +-----BEGIN PGP MESSAGE----- + +wV0GIQYSyD8ecG9jCP4VGkF3Q6HwM3kOk+mXhIjR2zeNqZMIhRmHzxjV8bU/gXzO +WgBM85PMiVi93AZfJfhK9QmxfdNnZBjeo1VDeVZheQHgaVf7yopqR6W1FT6NOrfS +aQIHAgZhZBZTW+CwcW1g4FKlbExAf56zaw76/prQoN+bAzxpohup69LA7JW/Vp0l +yZnuSj3hcFj0DfqLTGgr4/u717J+sPWbtQBfgMfG9AOIwwrUBqsFE9zW+f1zdlYo +bhF30A+IitsxxA== +-----END PGP MESSAGE----- \ No newline at end of file diff --git a/src/tests/exdsa_ecdhkem.cpp b/src/tests/exdsa_ecdhkem.cpp new file mode 100644 index 00000000..0e635698 --- /dev/null +++ b/src/tests/exdsa_ecdhkem.cpp @@ -0,0 +1,107 @@ +/* + * 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. + */ + +#if defined(ENABLE_CRYPTO_REFRESH) + +#include "rnp_tests.h" +#include "crypto/exdsa_ecdhkem.h" +#include "crypto/bn.h" + +TEST_F(rnp_tests, test_ecdh_kem) +{ + std::vector ciphertext; + std::vector symmetric_key; + std::vector symmetric_key2; + ecdh_kem_key_t key_pair; + pgp_curve_t curve_list[] = {PGP_CURVE_NIST_P_256, + PGP_CURVE_NIST_P_384, + PGP_CURVE_NIST_P_521, + PGP_CURVE_BP256, + PGP_CURVE_BP384, + PGP_CURVE_BP512, + PGP_CURVE_25519}; + + for (auto curve : curve_list) { + /* keygen */ + assert_rnp_success( + ec_key_t::generate_ecdh_kem_key_pair(&global_ctx.rng, &key_pair, curve)); + + /* kem encaps / decaps */ + assert_rnp_success( + key_pair.pub.encapsulate(&global_ctx.rng, ciphertext, symmetric_key)); + assert_rnp_success( + key_pair.priv.decapsulate(&global_ctx.rng, ciphertext, symmetric_key2)); + + /* both parties should have the same key share */ + assert_int_equal(symmetric_key.size(), symmetric_key2.size()); + assert_memory_equal(symmetric_key.data(), symmetric_key2.data(), symmetric_key.size()); + + /* test invalid ciphertext */ + ciphertext.data()[4] += 1; + if (curve != PGP_CURVE_25519) { // Curve25519 accepts any 32-byte array + assert_throw( + key_pair.priv.decapsulate(&global_ctx.rng, ciphertext, symmetric_key)); + } + } +} + +TEST_F(rnp_tests, test_exdsa) +{ + pgp_hash_alg_t hash_alg = PGP_HASH_SHA256; + std::vector msg(32); + exdsa_key_t key_pair; + pgp_curve_t curve_list[] = {PGP_CURVE_NIST_P_256, + PGP_CURVE_NIST_P_384, + PGP_CURVE_NIST_P_521, + PGP_CURVE_BP256, + PGP_CURVE_BP384, + PGP_CURVE_BP512, + PGP_CURVE_ED25519}; + // pgp_curve_t curve_list[] = {PGP_CURVE_ED25519}; + + for (auto curve : curve_list) { + /* keygen */ + assert_rnp_success( + ec_key_t::generate_exdsa_key_pair(&global_ctx.rng, &key_pair, curve)); + + /* sign and verify */ + std::vector sig; + assert_rnp_success( + key_pair.priv.sign(&global_ctx.rng, sig, msg.data(), msg.size(), hash_alg)); + assert_rnp_success(key_pair.pub.verify(sig, msg.data(), msg.size(), hash_alg)); + + /* test invalid msg / hash */ + msg.data()[4] -= 1; + assert_rnp_failure(key_pair.pub.verify(sig, msg.data(), msg.size(), hash_alg)); + + /* test invalid sig */ + msg.data()[4] += 1; + sig.data()[4] -= 1; + assert_rnp_failure(key_pair.pub.verify(sig, msg.data(), msg.size(), hash_alg)); + } +} + +#endif diff --git a/src/tests/ffi-enc.cpp b/src/tests/ffi-enc.cpp index 6aebf472..b37cb689 100644 --- a/src/tests/ffi-enc.cpp +++ b/src/tests/ffi-enc.cpp @@ -735,6 +735,115 @@ TEST_F(rnp_tests, test_ffi_decrypt_pk_unlocked) rnp_ffi_destroy(ffi); } +#if defined(ENABLE_CRYPTO_REFRESH) +TEST_F(rnp_tests, test_ffi_decrypt_v6_pkesk_test_vector) +{ + rnp_ffi_t ffi = NULL; + rnp_input_t input = NULL; + rnp_output_t output = NULL; + + assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); + assert_true(import_all_keys(ffi, "data/test_v6_valid_data/transferable_seckey_v6.asc")); + + assert_rnp_success(rnp_input_from_path(&input, "data/test_v6_valid_data/v6pkesk.asc")); + assert_non_null(input); + assert_rnp_success(rnp_output_to_null(&output)); + assert_rnp_success(rnp_decrypt(ffi, input, output)); + + // cleanup + rnp_input_destroy(input); + rnp_output_destroy(output); +} + +TEST_F(rnp_tests, test_ffi_encrypt_pk_with_v6_key) +{ + rnp_ffi_t ffi = NULL; + rnp_input_t input = NULL; + rnp_output_t output = NULL; + rnp_op_encrypt_t op = NULL; + const char * plaintext = "data1"; + + // setup FFI + assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); + + assert_true(import_all_keys(ffi, "data/test_v6_valid_data/transferable_seckey_v6.asc")); + + std::vector ciphers = {"AES128", "AES192", "AES256"}; + std::vector aead_modes = {"None", "EAX", "OCB"}; + std::vector enable_pkeskv6_modes = {true, false}; + + for (auto enable_pkeskv6 : enable_pkeskv6_modes) + for (auto aead : aead_modes) + for (auto cipher : ciphers) { + // write out some data + FILE *fp = fopen("plaintext", "wb"); + assert_non_null(fp); + assert_int_equal(1, fwrite(plaintext, strlen(plaintext), 1, fp)); + assert_int_equal(0, fclose(fp)); + + // create input+output + assert_rnp_success(rnp_input_from_path(&input, "plaintext")); + assert_non_null(input); + assert_rnp_success(rnp_output_to_path(&output, "encrypted")); + assert_non_null(output); + // create encrypt operation + assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output)); + // add recipients + rnp_key_handle_t key = NULL; + assert_rnp_success(rnp_locate_key(ffi, "keyid", "12c83f1e706f6308", &key)); + assert_non_null(key); + + assert_rnp_failure(rnp_op_encrypt_add_recipient(op, NULL)); // what for ? + assert_rnp_success(rnp_op_encrypt_add_recipient(op, key)); + if (enable_pkeskv6) { + assert_rnp_success(rnp_op_encrypt_enable_pkesk_v6(op)); + } + rnp_key_handle_destroy(key); + key = NULL; + + // set the data encryption cipher + if ((aead == "None") && enable_pkeskv6) { + // already enabled v6 pkesk, does not make any sense to set AEAD to None + // explicitly. + assert_rnp_failure(rnp_op_encrypt_set_aead(op, aead.c_str())); + } else { + assert_rnp_success(rnp_op_encrypt_set_aead(op, aead.c_str())); + } + assert_rnp_success(rnp_op_encrypt_set_cipher(op, cipher.c_str())); + + // execute the operation + assert_rnp_success(rnp_op_encrypt_execute(op)); + + // make sure the output file was created + assert_true(rnp_file_exists("encrypted")); + + // cleanup + assert_rnp_success(rnp_input_destroy(input)); + input = NULL; + assert_rnp_success(rnp_output_destroy(output)); + output = NULL; + assert_rnp_success(rnp_op_encrypt_destroy(op)); + op = NULL; + + /* decrypt */ + + // decrypt + assert_rnp_success(rnp_input_from_path(&input, "encrypted")); + assert_non_null(input); + assert_rnp_success(rnp_output_to_path(&output, "decrypted")); + assert_non_null(output); + assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL)); + assert_rnp_success(rnp_decrypt(ffi, input, output)); + // cleanup + rnp_input_destroy(input); + input = NULL; + rnp_output_destroy(output); + output = NULL; + } + rnp_ffi_destroy(ffi); +} +#endif + TEST_F(rnp_tests, test_ffi_encrypt_pk_key_provider) { rnp_ffi_t ffi = NULL; diff --git a/src/tests/ffi-key.cpp b/src/tests/ffi-key.cpp index 7b7c5d6b..11d9ffdd 100644 --- a/src/tests/ffi-key.cpp +++ b/src/tests/ffi-key.cpp @@ -3155,6 +3155,99 @@ TEST_F(rnp_tests, test_ffi_malformed_keys_import) rnp_ffi_destroy(ffi); } +#if defined(ENABLE_CRYPTO_REFRESH) +TEST_F(rnp_tests, test_ffi_v6_sig_subpackets) +{ + rnp_ffi_t ffi = NULL; + + assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); + assert_rnp_success(rnp_ffi_set_key_provider(ffi, unused_getkeycb, NULL)); + assert_rnp_success(rnp_ffi_set_pass_provider(ffi, unused_getpasscb, NULL)); + + rnp_op_generate_t op = NULL; + assert_rnp_success(rnp_op_generate_create(&op, ffi, "EDDSA")); + assert_rnp_success(rnp_op_generate_set_v6_key(op)); + assert_rnp_success(rnp_op_generate_set_userid(op, "test")); + assert_rnp_success(rnp_op_generate_add_usage(op, "sign")); + assert_rnp_success(rnp_op_generate_add_usage(op, "certify")); + assert_rnp_success(rnp_op_generate_set_expiration(op, 0)); + assert_rnp_success(rnp_op_generate_execute(op)); + rnp_key_handle_t primary = NULL; + assert_rnp_success(rnp_op_generate_get_key(op, &primary)); + + assert_true(primary->pub->get_sig(0).sig.has_subpkt( + PGP_SIG_SUBPKT_ISSUER_FPR, false)); // MUST NOT have issuer key id extension + assert_false(primary->pub->get_sig(0).sig.has_subpkt( + PGP_SIG_SUBPKT_ISSUER_KEY_ID, false)); // SHOULD have issuer fingerprint + + rnp_op_generate_destroy(op); +} + +TEST_F(rnp_tests, test_ffi_v6_cert_import) +{ + rnp_ffi_t ffi = NULL; + rnp_input_t input = NULL; + size_t keycount = 255; + + assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); + assert_rnp_success( + rnp_input_from_path(&input, "data/test_v6_valid_data/transferable_pubkey_v6.asc")); + assert_rnp_success( + rnp_import_keys(ffi, + input, + RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SINGLE | RNP_LOAD_SAVE_BASE64, + NULL)); + rnp_input_destroy(input); + assert_rnp_success(rnp_get_public_key_count(ffi, &keycount)); + assert_int_equal(keycount, 2); + assert_rnp_success(rnp_get_secret_key_count(ffi, &keycount)); + assert_int_equal(keycount, 0); + + /* check that fingerprint is correct by checking the fingerprint in the signature (coming + from the correct input data) vs the computed fingerprint value of the primary key. + Issuer fingerprint is the priamry's key fingerprint for the primary and its subkeys */ + pgp_fingerprint_t primary_fp; + for (pgp_key_t key : ffi->pubring->keys) { + if (key.is_primary()) { + primary_fp = key.fp(); + } + } + + for (pgp_key_t key : ffi->pubring->keys) { + /* get first sig and its issuer fpr subpacket */ + pgp_subsig_t subsig = key.get_sig(0); + const pgp_sig_subpkt_t *issuer_fpr = + subsig.sig.get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR, false); + assert_non_null(issuer_fpr); + + /* check that fingerprints match */ + assert_int_equal(key.fp().length, PGP_FINGERPRINT_V6_SIZE); + assert_memory_equal(issuer_fpr->data + 1, + primary_fp.fingerprint, + primary_fp.length); // first byte in data is the version - skip + } +} + +TEST_F(rnp_tests, test_ffi_v6_seckey_import) +{ + rnp_ffi_t ffi = NULL; + rnp_input_t input = NULL; + size_t keycount = 255; + + assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); + assert_rnp_success( + rnp_input_from_path(&input, "data/test_v6_valid_data/transferable_seckey_v6.asc")); + assert_rnp_success( + rnp_import_keys(ffi, + input, + RNP_LOAD_SAVE_SECRET_KEYS | RNP_LOAD_SAVE_SINGLE | RNP_LOAD_SAVE_BASE64, + NULL)); + rnp_input_destroy(input); + assert_rnp_success(rnp_get_secret_key_count(ffi, &keycount)); + assert_int_equal(keycount, 2); +} +#endif + TEST_F(rnp_tests, test_ffi_iterated_key_import) { rnp_ffi_t ffi = NULL; diff --git a/src/tests/ffi.cpp b/src/tests/ffi.cpp index 781fdfd2..9e40cd88 100644 --- a/src/tests/ffi.cpp +++ b/src/tests/ffi.cpp @@ -3109,7 +3109,16 @@ TEST_F(rnp_tests, test_ffi_supported_features) /* public key algorithm */ assert_rnp_success(rnp_supported_features(RNP_FEATURE_PK_ALG, &features)); assert_non_null(features); - assert_true(check_features(RNP_FEATURE_PK_ALG, features, 6 + has_sm2)); + size_t pqc_opt = 0; + size_t crypto_refresh_opt = 0; +#if defined(ENABLE_CRYPTO_REFRESH) + crypto_refresh_opt = 2; // X25519 + ED25519 +#endif +#if defined(ENABLE_PQC) + 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)); rnp_buffer_destroy(features); assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "RSA", &supported)); assert_true(supported); diff --git a/src/tests/generatekey.cpp b/src/tests/generatekey.cpp index b846ebb4..bf948b7b 100644 --- a/src/tests/generatekey.cpp +++ b/src/tests/generatekey.cpp @@ -1148,6 +1148,9 @@ TEST_F(rnp_tests, test_generated_key_sigs) pgp_signature_info_t ssiginfo = {}; memset(&desc, 0, sizeof(desc)); +#if defined(ENABLE_CRYPTO_REFRESH) + desc.pgp_version = PGP_V4; +#endif desc.crypto.key_alg = PGP_PKA_RSA; desc.crypto.rsa.modulus_bit_len = 1024; desc.crypto.ctx = &global_ctx; diff --git a/src/tests/hkdf.cpp b/src/tests/hkdf.cpp new file mode 100644 index 00000000..abf2633b --- /dev/null +++ b/src/tests/hkdf.cpp @@ -0,0 +1,46 @@ +#include "rnp_tests.h" + +#if defined(CRYPTO_BACKEND_BOTAN) && defined(ENABLE_CRYPTO_REFRESH) +#include "crypto/hkdf_botan.hpp" + +TEST_F(rnp_tests, hkdf_test_case_1) +{ + /* rfc5869 Test Case 1 + Hash = SHA-256 + IKM = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b (22 octets) + salt = 0x000102030405060708090a0b0c (13 octets) + info = 0xf0f1f2f3f4f5f6f7f8f9 (10 octets) + L = 42 + + PRK = 0x077709362c2e32df0ddc3f0dc47bba63 + 90b6c73bb50f9c3122ec844ad7c2b3e5 (32 octets) + OKM = 0x3cb25f25faacd57a90434f64d0362f2a + 2d2d0a90cf1a5a4c5db02d56ecc4c5bf + 34007208d5b887185865 (42 octets) + */ + + std::vector IKM = {0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b}; + std::vector salt = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c}; + std::vector info = {0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9}; + std::vector PRK_expected = {0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf, + 0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63, + 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31, + 0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5}; + std::vector OKM_expected = { + 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, + 0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56, + 0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65}; + size_t L = 42; + + std::unique_ptr hkdf = rnp::Hkdf::create(PGP_HASH_SHA256); + + uint8_t OKM[L]; + hkdf->extract_expand( + salt.data(), salt.size(), IKM.data(), IKM.size(), info.data(), info.size(), OKM, L); + assert_memory_equal(OKM, OKM_expected.data(), OKM_expected.size()); +} + +#endif diff --git a/src/tests/key-store-search.cpp b/src/tests/key-store-search.cpp index 4070b0da..aabd4bd0 100644 --- a/src/tests/key-store-search.cpp +++ b/src/tests/key-store-search.cpp @@ -69,9 +69,9 @@ TEST_F(rnp_tests, test_key_store_search) // and fingerprint pgp_fingerprint_t &fp = (pgp_fingerprint_t &) key.fp(); assert_true( - rnp::hex_decode(testdata[i].keyid, fp.fingerprint, PGP_FINGERPRINT_SIZE)); + rnp::hex_decode(testdata[i].keyid, fp.fingerprint, PGP_FINGERPRINT_V4_SIZE)); fp.fingerprint[0] = (uint8_t) n; - fp.length = PGP_FINGERPRINT_SIZE; + fp.length = PGP_FINGERPRINT_V4_SIZE; // set the userids for (size_t uidn = 0; testdata[i].userids[uidn]; uidn++) { pgp_transferable_userid_t tuid; diff --git a/src/tests/pqc.cpp b/src/tests/pqc.cpp new file mode 100644 index 00000000..0b779d49 --- /dev/null +++ b/src/tests/pqc.cpp @@ -0,0 +1,137 @@ +/* + * 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 "config.h" +#if defined(ENABLE_PQC) + +#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) +{ + kyber_parameter_e params[2] = {kyber_768, kyber_1024}; + for (kyber_parameter_e param : params) { + auto public_and_private_key = kyber_generate_keypair(&global_ctx.rng, param); + + kyber_encap_result_t encap_res = + public_and_private_key.first.encapsulate(&global_ctx.rng); + + std::vector decrypted = public_and_private_key.second.decapsulate( + &global_ctx.rng, encap_res.ciphertext.data(), encap_res.ciphertext.size()); + assert_int_equal(encap_res.symmetric_key.size(), decrypted.size()); + assert_memory_equal( + encap_res.symmetric_key.data(), decrypted.data(), decrypted.size()); + } +} + +TEST_F(rnp_tests, test_dilithium_key_function) +{ + dilithium_parameter_e params[2] = {dilithium_L3, dilithium_L5}; + for (dilithium_parameter_e param : params) { + auto public_and_private_key = dilithium_generate_keypair(&global_ctx.rng, param); + + std::array msg{'H', 'e', 'l', 'l', 'o'}; + + std::vector signature = + public_and_private_key.second.sign(&global_ctx.rng, msg.data(), msg.size()); + + assert_true(public_and_private_key.first.verify_signature( + msg.data(), msg.size(), signature.data(), signature.size())); + } +} + +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, + /* PGP_PKA_DILITHIUM5_ED448,*/ PGP_PKA_DILITHIUM3_P256, + PGP_PKA_DILITHIUM5_P384, + PGP_PKA_DILITHIUM3_BP256, + PGP_PKA_DILITHIUM5_BP384}; + + for (size_t i = 0; i < ARRAY_SIZE(algs); i++) { + uint8_t message[64]; + const pgp_hash_alg_t hash_alg = PGP_HASH_SHA512; + // Generate test data. Mainly to make valgrind not to complain about uninitialized data + global_ctx.rng.get(message, sizeof(message)); + + pgp_dilithium_exdsa_key_t key; + pgp_dilithium_exdsa_signature_t sig; + + assert_rnp_success( + pgp_dilithium_exdsa_composite_key_t::gen_keypair(&global_ctx.rng, &key, algs[i])); + + assert_rnp_success( + key.priv.sign(&global_ctx.rng, &sig, hash_alg, message, sizeof(message))); + assert_rnp_success(key.pub.verify(&sig, hash_alg, message, sizeof(message))); + + // Fails because message won't verify + message[0] = ~message[0]; + assert_rnp_failure(key.pub.verify(&sig, hash_alg, message, sizeof(message))); + message[0] = ~message[0]; + + // Fails because first sig won't verify + sig.sig.data()[0] = ~sig.sig.data()[0]; + assert_rnp_failure(key.pub.verify(&sig, hash_alg, message, sizeof(message))); + sig.sig.data()[0] = ~sig.sig.data()[0]; + + // Fails because second sig won't verify + sig.sig.data()[sig.sig.size() - 1] = ~sig.sig.data()[sig.sig.size() - 1]; + assert_rnp_failure(key.pub.verify(&sig, hash_alg, message, sizeof(message))); + sig.sig.data()[sig.sig.size() - 1] = ~sig.sig.data()[sig.sig.size() - 1]; + } +} + +#endif diff --git a/src/tests/support.cpp b/src/tests/support.cpp index 2867304d..4b333c37 100644 --- a/src/tests/support.cpp +++ b/src/tests/support.cpp @@ -807,9 +807,9 @@ rnp_tests_get_key_by_fpr(rnp_key_store_t *keyring, const std::string &keyid) if (!keyring || keyid.empty() || !rnp::is_hex(keyid)) { return NULL; } - std::vector keyid_bin(PGP_FINGERPRINT_SIZE, 0); + std::vector keyid_bin(PGP_MAX_FINGERPRINT_SIZE, 0); size_t binlen = rnp::hex_decode(keyid.c_str(), keyid_bin.data(), keyid_bin.size()); - if (binlen > PGP_FINGERPRINT_SIZE) { + if (binlen > PGP_MAX_FINGERPRINT_SIZE) { return NULL; } pgp_fingerprint_t fp = {{}, static_cast(binlen)};