diff --git a/src/lib/rnp.cpp b/src/lib/rnp.cpp index d58060986..a111a13b8 100644 --- a/src/lib/rnp.cpp +++ b/src/lib/rnp.cpp @@ -1144,19 +1144,32 @@ get_feature_sec_value( rnp_ffi_t ffi, const char *stype, const char *sname, rnp::FeatureType &type, int &value) { /* check type */ - if (!rnp::str_case_eq(stype, RNP_FEATURE_HASH_ALG)) { - FFI_LOG(ffi, "Unsupported feature type: %s", stype); - return false; + if (rnp::str_case_eq(stype, RNP_FEATURE_HASH_ALG)) { + type = rnp::FeatureType::Hash; + /* check feature name */ + pgp_hash_alg_t alg = PGP_HASH_UNKNOWN; + if (sname && !str_to_hash_alg(sname, &alg)) { + FFI_LOG(ffi, "Unknown hash algorithm: %s", sname); + return false; + } + value = alg; + return true; } - type = rnp::FeatureType::Hash; - /* check feature name */ - pgp_hash_alg_t alg = PGP_HASH_UNKNOWN; - if (sname && !str_to_hash_alg(sname, &alg)) { - FFI_LOG(ffi, "Unknown hash algorithm: %s", sname); - return false; + + if (rnp::str_case_eq(stype, RNP_FEATURE_SYMM_ALG)) { + type = rnp::FeatureType::Cipher; + /* check feature name */ + pgp_symm_alg_t alg = PGP_SA_UNKNOWN; + if (sname && !str_to_cipher(sname, &alg)) { + FFI_LOG(ffi, "Unknown cipher: %s", sname); + return false; + } + value = alg; + return true; } - value = alg; - return true; + + FFI_LOG(ffi, "Unsupported feature type: %s", stype); + return false; } static bool diff --git a/src/lib/sec_profile.cpp b/src/lib/sec_profile.cpp index f9d0de836..4e7c0be30 100644 --- a/src/lib/sec_profile.cpp +++ b/src/lib/sec_profile.cpp @@ -196,6 +196,13 @@ SecurityContext::SecurityContext() : time_(0), prov_state_(NULL), rng(RNG::Type: SecurityAction::VerifyKey}); /* Mark MD5 insecure since 2012-01-01 */ profile.add_rule({FeatureType::Hash, PGP_HASH_MD5, SecurityLevel::Insecure, 1325376000}); + /* Mark CAST5, 3DES, IDEA, BLOWFISH insecure since 2024-10-01*/ // TODO: tbd + profile.add_rule({FeatureType::Cipher, PGP_SA_CAST5, SecurityLevel::Insecure, 1727730000}); + profile.add_rule( + {FeatureType::Cipher, PGP_SA_TRIPLEDES, SecurityLevel::Insecure, 1727730000}); + profile.add_rule({FeatureType::Cipher, PGP_SA_IDEA, SecurityLevel::Insecure, 1727730000}); + profile.add_rule( + {FeatureType::Cipher, PGP_SA_BLOWFISH, SecurityLevel::Insecure, 1727730000}); } SecurityContext::~SecurityContext() diff --git a/src/rnp/fficli.cpp b/src/rnp/fficli.cpp index 555d957ef..c63896c89 100644 --- a/src/rnp/fficli.cpp +++ b/src/rnp/fficli.cpp @@ -2911,6 +2911,36 @@ cli_rnp_check_weak_hash(cli_rnp_t *rnp) return true; } +bool +cli_rnp_check_old_ciphers(cli_rnp_t *rnp) +{ + if (rnp->cfg().has(CFG_ALLOW_OLD_CIPHERS)) { + return true; + } + + uint32_t security_level = 0; + + if (rnp_get_security_rule(rnp->ffi, + RNP_FEATURE_SYMM_ALG, + rnp->cfg().get_cipher().c_str(), + rnp->cfg().time(), + NULL, + NULL, + &security_level)) { + ERR_MSG("Failed to get security rules for cipher algorithm \'%s\'!", + rnp->cfg().get_cipher().c_str()); + return false; + } + + if (security_level < RNP_SECURITY_DEFAULT) { + ERR_MSG("Cipher algorithm \'%s\' is cryptographically weak!", + rnp->cfg().get_cipher().c_str()); + return false; + } + /* TODO: check other weak algorithms and key sizes */ + return true; +} + bool cli_rnp_protect_file(cli_rnp_t *rnp) { diff --git a/src/rnp/fficli.h b/src/rnp/fficli.h index f025cf282..1a16d6180 100644 --- a/src/rnp/fficli.h +++ b/src/rnp/fficli.h @@ -233,6 +233,7 @@ bool cli_rnp_dump_file(cli_rnp_t *rnp); bool cli_rnp_armor_file(cli_rnp_t *rnp); bool cli_rnp_dearmor_file(cli_rnp_t *rnp); bool cli_rnp_check_weak_hash(cli_rnp_t *rnp); +bool cli_rnp_check_old_ciphers(cli_rnp_t *rnp); bool cli_rnp_setup(cli_rnp_t *rnp); bool cli_rnp_protect_file(cli_rnp_t *rnp); bool cli_rnp_process_file(cli_rnp_t *rnp); diff --git a/src/rnp/rnp.cpp b/src/rnp/rnp.cpp index 8089f17b5..09f2ab835 100644 --- a/src/rnp/rnp.cpp +++ b/src/rnp/rnp.cpp @@ -165,6 +165,7 @@ enum optdefs { OPT_SOURCE, OPT_NOWRAP, OPT_CURTIME, + OPT_ALLOW_OLD_CIPHERS, OPT_SETFNAME, OPT_ALLOW_HIDDEN, OPT_S2K_ITER, @@ -235,6 +236,7 @@ static struct option options[] = { {"source", required_argument, NULL, OPT_SOURCE}, {"no-wrap", no_argument, NULL, OPT_NOWRAP}, {"current-time", required_argument, NULL, OPT_CURTIME}, + {"allow-old-ciphers", no_argument, NULL, OPT_ALLOW_OLD_CIPHERS}, {"set-filename", required_argument, NULL, OPT_SETFNAME}, {"allow-hidden", no_argument, NULL, OPT_ALLOW_HIDDEN}, {"s2k-iterations", required_argument, NULL, OPT_S2K_ITER}, @@ -529,6 +531,9 @@ setoption(rnp_cfg &cfg, int val, const char *arg) case OPT_CURTIME: cfg.set_str(CFG_CURTIME, arg); return true; + case OPT_ALLOW_OLD_CIPHERS: + cfg.set_bool(CFG_ALLOW_OLD_CIPHERS, true); + return true; case OPT_SETFNAME: cfg.set_str(CFG_SETFNAME, arg); return true; @@ -687,6 +692,12 @@ rnp_main(int argc, char **argv) return EXIT_ERROR; } + if (!cli_rnp_check_old_ciphers(&rnp)) { + ERR_MSG("Old cipher detected. Pass --allow-old-ciphers option if you really " + "want to use it."); + return EXIT_ERROR; + } + bool disable_ks = rnp.cfg().get_bool(CFG_KEYSTORE_DISABLED); if (!disable_ks && !rnp.load_keyrings(rnp.cfg().get_bool(CFG_NEEDSSECKEY))) { ERR_MSG("fatal: failed to load keys"); diff --git a/src/rnp/rnpcfg.cpp b/src/rnp/rnpcfg.cpp index e1c35a30a..e96885816 100644 --- a/src/rnp/rnpcfg.cpp +++ b/src/rnp/rnpcfg.cpp @@ -312,6 +312,16 @@ rnp_cfg::get_hashalg() const return DEFAULT_HASH_ALG; } +const std::string +rnp_cfg::get_cipher() const +{ + const std::string cipher_alg = get_str(CFG_CIPHER); + if (!cipher_alg.empty()) { + return cipher_alg; + } + return DEFAULT_SYMM_ALG; +} + bool rnp_cfg::get_expiration(const std::string &key, uint32_t &seconds) const { diff --git a/src/rnp/rnpcfg.h b/src/rnp/rnpcfg.h index eda6960ef..c182429e5 100644 --- a/src/rnp/rnpcfg.h +++ b/src/rnp/rnpcfg.h @@ -96,8 +96,10 @@ #define CFG_ADD_SUBKEY "add-subkey" /* add subkey to existing primary */ #define CFG_SET_KEY_EXPIRE "key-expire" /* set/update key expiration time */ #define CFG_SOURCE "source" /* source for the detached signature */ -#define CFG_NOWRAP "no-wrap" /* do not wrap the output in a literal data packet */ -#define CFG_CURTIME "curtime" /* date or timestamp to override the system's time */ +#define CFG_NOWRAP "no-wrap" /* do not wrap the output in a literal data packet */ +#define CFG_CURTIME "curtime" /* date or timestamp to override the system's time */ +#define CFG_ALLOW_OLD_CIPHERS \ + "allow-old-ciphers" /* Allow to use 64-bit ciphers (CAST5, 3DES, IDEA, BLOWFISH) */ #define CFG_ALLOW_HIDDEN "allow-hidden" /* allow hidden recipients */ /* rnp keyring setup variables */ @@ -196,6 +198,8 @@ class rnp_cfg { int get_pswdtries() const; /** @brief get hash algorithm */ const std::string get_hashalg() const; + /** @brief get cipher algorithm */ + const std::string get_cipher() const; /** @brief Get expiration time from the cfg variable, as value relative to the current * time. As per OpenPGP standard it should fit in 32 bit value, otherwise error is diff --git a/src/rnpkeys/rnpkeys.cpp b/src/rnpkeys/rnpkeys.cpp index 3f2a751f5..6263bd68a 100644 --- a/src/rnpkeys/rnpkeys.cpp +++ b/src/rnpkeys/rnpkeys.cpp @@ -86,6 +86,7 @@ const char *usage = " --overwrite Overwrite output file without a prompt.\n" " --notty Do not write anything to the TTY.\n" " --current-time Override system's time.\n" + " --allow-old-ciphers Allow to use 64-bit ciphers (CAST5, 3DES, IDEA, BLOWFISH).\n" "\n" "See man page for a detailed listing and explanation.\n" "\n"; @@ -142,6 +143,7 @@ struct option options[] = { {"add-subkey", no_argument, NULL, OPT_ADD_SUBKEY}, {"set-expire", required_argument, NULL, OPT_SET_EXPIRE}, {"current-time", required_argument, NULL, OPT_CURTIME}, + {"allow-old-ciphers", no_argument, NULL, OPT_ALLOW_OLD_CIPHERS}, {"allow-weak-hash", no_argument, NULL, OPT_ALLOW_WEAK_HASH}, {"allow-sha1-key-sigs", no_argument, NULL, OPT_ALLOW_SHA1}, {"keyfile", required_argument, NULL, OPT_KEYFILE}, @@ -611,6 +613,9 @@ setoption(rnp_cfg &cfg, optdefs_t *cmd, int val, const char *arg) case OPT_CURTIME: cfg.set_str(CFG_CURTIME, arg); return true; + case OPT_ALLOW_OLD_CIPHERS: + cfg.set_bool(CFG_ALLOW_OLD_CIPHERS, true); + return true; case OPT_ADD_SUBKEY: cfg.set_bool(CFG_ADD_SUBKEY, true); return true; @@ -650,6 +655,12 @@ rnpkeys_init(cli_rnp_t &rnp, const rnp_cfg &cfg) return false; } + if (!cli_rnp_check_old_ciphers(&rnp)) { + ERR_MSG("Old cipher detected. Pass --allow-old-ciphers option if you really " + "want to use it."); + return false; + } + bool disable_ks = rnp.cfg().get_bool(CFG_KEYSTORE_DISABLED); if (!disable_ks && !rnp.load_keyrings(true)) { ERR_MSG("fatal: failed to load keys"); diff --git a/src/rnpkeys/rnpkeys.h b/src/rnpkeys/rnpkeys.h index 95559848e..e34d3711e 100644 --- a/src/rnpkeys/rnpkeys.h +++ b/src/rnpkeys/rnpkeys.h @@ -57,6 +57,7 @@ typedef enum { OPT_FIX_25519_BITS, OPT_CHK_25519_BITS, OPT_CURTIME, + OPT_ALLOW_OLD_CIPHERS, OPT_ADD_SUBKEY, OPT_SET_EXPIRE, OPT_KEYFILE, diff --git a/src/tests/ffi.cpp b/src/tests/ffi.cpp index d84cad8f5..cb13882f9 100644 --- a/src/tests/ffi.cpp +++ b/src/tests/ffi.cpp @@ -5793,7 +5793,7 @@ TEST_F(rnp_tests, test_ffi_security_profile) assert_rnp_failure( rnp_get_security_rule(NULL, RNP_FEATURE_HASH_ALG, "SHA1", 0, &flags, &from, &level)); assert_rnp_failure(rnp_get_security_rule(ffi, NULL, "SHA1", 0, &flags, &from, &level)); - assert_rnp_failure( + assert_rnp_success( rnp_get_security_rule(ffi, RNP_FEATURE_SYMM_ALG, "AES256", 0, &flags, &from, &level)); assert_rnp_failure( rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, "Unknown", 0, &flags, &from, &level)); @@ -5954,12 +5954,14 @@ TEST_F(rnp_tests, test_ffi_security_profile) removed = 0; assert_rnp_failure(rnp_remove_security_rule(ffi, NULL, NULL, 0, 0x17, 0, &removed)); assert_rnp_success(rnp_remove_security_rule(ffi, NULL, NULL, 0, 0, 0, &removed)); - assert_int_equal(removed, 3); + assert_int_equal(removed, 3 /*HASH*/ + 4 /*SYMM*/); rnp_ffi_destroy(ffi); rnp_ffi_create(&ffi, "GPG", "GPG"); /* Remove all rules for hash */ - assert_rnp_failure( + removed = 0; + assert_rnp_success( rnp_remove_security_rule(ffi, RNP_FEATURE_SYMM_ALG, NULL, 0, 0, 0, &removed)); + assert_int_equal(removed, 4); removed = 0; assert_rnp_success( rnp_remove_security_rule(ffi, RNP_FEATURE_HASH_ALG, NULL, 0, 0, 0, &removed));