diff --git a/src/rnp/fficli.cpp b/src/rnp/fficli.cpp index 5a65c699a3..555d957efb 100644 --- a/src/rnp/fficli.cpp +++ b/src/rnp/fficli.cpp @@ -649,6 +649,19 @@ cli_rnp_t::init(const rnp_cfg &cfg) return false; } + if (cfg_.has(CFG_ALLOW_SHA1)) { + auto now = time(NULL); + uint64_t from = 0; + uint32_t level = 0; + rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, "SHA1", now, NULL, &from, &level); + rnp_add_security_rule(ffi, + RNP_FEATURE_HASH_ALG, + "SHA1", + RNP_SECURITY_OVERRIDE | RNP_SECURITY_VERIFY_KEY, + from, + RNP_SECURITY_DEFAULT); + } + // by default use stdin password provider if (rnp_ffi_set_pass_provider(ffi, ffi_pass_callback_stdin, this)) { goto done; diff --git a/src/rnp/rnp.1.adoc b/src/rnp/rnp.1.adoc index 86ced1c613..793224ea36 100644 --- a/src/rnp/rnp.1.adoc +++ b/src/rnp/rnp.1.adoc @@ -117,6 +117,12 @@ You may want to use *--hash* option to override default hash algorithm settings. + Compression options also apply here. Since the secret key is usually stored encrypted, you will be asked for the password to decrypt it via _stdin_/_tty_ unless *--password* or *--pass-fd* is specified. +*--allow-weak-hash*::: +Allow usage of a weak hash algorithm. + +*--allow-sha1-key-sigs*::: +Allow usage of a SHA-1 key signatures. + *--clearsign*:: Digitally sign text data, producing human-readable output with the signature attached. + + diff --git a/src/rnp/rnp.cpp b/src/rnp/rnp.cpp index a4b41f1ec3..8089f17b55 100644 --- a/src/rnp/rnp.cpp +++ b/src/rnp/rnp.cpp @@ -56,52 +56,53 @@ static const char *usage = "Sign, verify, encrypt, decrypt, inspect OpenPGP data.\n" "Usage: rnp --command [options] [files]\n" "Commands:\n" - " -h, --help This help message.\n" - " -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" + " -h, --help This help message.\n" + " -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 " + " --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" - " --[zip,zlib,bzip] Use the corresponding compression algorithm.\n" - " --armor Apply ASCII armor to the encryption/signing output.\n" - " --no-wrap Do not wrap the output in a literal data packet.\n" - " -c, --symmetric Encrypt data using the password(s).\n" - " --passwords num Encrypt to the specified number of passwords.\n" - " -s, --sign Sign data. May be combined with encryption.\n" - " --detach Produce detached signature.\n" - " -u, --userid Specify signing key(s) via uid/keyid/fingerprint.\n" - " --hash Specify hash algorithm, used during signing.\n" - " --allow-weak-hash Allow usage of a weak hash algorithm.\n" - " --clearsign Cleartext-sign data.\n" - " -d, --decrypt Decrypt and output data, verifying signatures.\n" - " -v, --verify Verify signatures, without outputting data.\n" - " --source Specify source for the detached signature.\n" - " --dearmor Strip ASCII armor from the data, outputting binary.\n" - " --enarmor Add ASCII armor to the data.\n" - " --list-packets List OpenPGP packets from the input.\n" - " --json Use JSON output instead of human-readable.\n" - " --grips Dump key fingerprints and grips.\n" - " --mpi Dump MPI values from packets.\n" - " --raw Dump raw packet contents as well.\n" + " --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" + " --[zip,zlib,bzip] Use the corresponding compression algorithm.\n" + " --armor Apply ASCII armor to the encryption/signing output.\n" + " --no-wrap Do not wrap the output in a literal data packet.\n" + " -c, --symmetric Encrypt data using the password(s).\n" + " --passwords num Encrypt to the specified number of passwords.\n" + " -s, --sign Sign data. May be combined with encryption.\n" + " --detach Produce detached signature.\n" + " -u, --userid Specify signing key(s) via uid/keyid/fingerprint.\n" + " --hash Specify hash algorithm, used during signing.\n" + " --allow-weak-hash Allow usage of a weak hash algorithm.\n" + " --allow-sha1-key-sigs Allow usage of a SHA-1 key signatures.\n" + " --clearsign Cleartext-sign data.\n" + " -d, --decrypt Decrypt and output data, verifying signatures.\n" + " -v, --verify Verify signatures, without outputting data.\n" + " --source Specify source for the detached signature.\n" + " --dearmor Strip ASCII armor from the data, outputting binary.\n" + " --enarmor Add ASCII armor to the data.\n" + " --list-packets List OpenPGP packets from the input.\n" + " --json Use JSON output instead of human-readable.\n" + " --grips Dump key fingerprints and grips.\n" + " --mpi Dump MPI values from packets.\n" + " --raw Dump raw packet contents as well.\n" "\n" "Other options:\n" - " --homedir path Override home directory (default is ~/.rnp/).\n" - " -f, --keyfile Load key(s) only from the file specified.\n" - " --output [file, -] Write data to the specified file or stdout.\n" - " --overwrite Overwrite output file without a prompt.\n" - " --password Password used during operation.\n" - " --pass-fd num Read password(s) from the file descriptor.\n" - " --s2k-iterations Set the number of iterations for the S2K process.\n" - " --s2k-msec Calculate S2K iterations value based on a provided time in " + " --homedir path Override home directory (default is ~/.rnp/).\n" + " -f, --keyfile Load key(s) only from the file specified.\n" + " --output [file, -] Write data to the specified file or stdout.\n" + " --overwrite Overwrite output file without a prompt.\n" + " --password Password used during operation.\n" + " --pass-fd num Read password(s) from the file descriptor.\n" + " --s2k-iterations Set the number of iterations for the S2K process.\n" + " --s2k-msec Calculate S2K iterations value based on a provided time in " "milliseconds.\n" - " --notty Do not output anything to the TTY.\n" - " --current-time Override system's time.\n" - " --set-filename Override file name, stored inside of OpenPGP message.\n" + " --notty Do not output anything to the TTY.\n" + " --current-time Override system's time.\n" + " --set-filename Override file name, stored inside of OpenPGP message.\n" "\n" "See man page for a detailed listing and explanation.\n" "\n"; @@ -137,6 +138,7 @@ enum optdefs { OPT_DETACHED, OPT_HASH_ALG, OPT_ALLOW_WEAK_HASH, + OPT_ALLOW_SHA1, OPT_OUTPUT, OPT_RESULTS, OPT_COREDUMPS, @@ -238,6 +240,7 @@ static struct option options[] = { {"s2k-iterations", required_argument, NULL, OPT_S2K_ITER}, {"s2k-msec", required_argument, NULL, OPT_S2K_MSEC}, {"allow-weak-hash", no_argument, NULL, OPT_ALLOW_WEAK_HASH}, + {"allow-sha1-key-sigs", no_argument, NULL, OPT_ALLOW_SHA1}, {NULL, 0, NULL, 0}, }; @@ -430,6 +433,9 @@ setoption(rnp_cfg &cfg, int val, const char *arg) case OPT_ALLOW_WEAK_HASH: cfg.set_bool(CFG_WEAK_HASH, true); return true; + case OPT_ALLOW_SHA1: + cfg.set_bool(CFG_ALLOW_SHA1, true); + return true; case OPT_PASSWDFD: cfg.set_str(CFG_PASSFD, arg); return true; diff --git a/src/rnp/rnpcfg.h b/src/rnp/rnpcfg.h index bdd909a25c..97b37d8289 100644 --- a/src/rnp/rnpcfg.h +++ b/src/rnp/rnpcfg.h @@ -66,6 +66,7 @@ #define CFG_CIPHER "cipher" /* symmetric encryption algorithm as string */ #define CFG_HASH "hash" /* hash algorithm used, string like 'SHA1'*/ #define CFG_WEAK_HASH "weak-hash" /* allow weak algorithms */ +#define CFG_ALLOW_SHA1 "allow-sha1" /* allow SHA-1 key signatures */ #define CFG_S2K_ITER "s2k-iter" /* number of S2K hash iterations to perform */ #define CFG_S2K_MSEC "s2k-msec" /* number of milliseconds S2K should target */ #define CFG_ENCRYPT_PK "encrypt_pk" /* public key should be used during encryption */ diff --git a/src/rnpkeys/rnpkeys.1.adoc b/src/rnpkeys/rnpkeys.1.adoc index 2b09d1794d..0a68e5e21d 100644 --- a/src/rnpkeys/rnpkeys.1.adoc +++ b/src/rnpkeys/rnpkeys.1.adoc @@ -150,6 +150,12 @@ would take _NUMBER_ of milliseconds on the current system. + For example, setting it to _2000_ would mean that each secret key decryption operation would take around 2 seconds (on the current machine). +*--allow-weak-hash*::: +Allow usage of a weak hash algorithm. + +*--allow-sha1-key-sigs*::: +Allow usage of a SHA-1 key signatures. + === KEY/SIGNATURE IMPORT diff --git a/src/rnpkeys/rnpkeys.cpp b/src/rnpkeys/rnpkeys.cpp index d4e602dfca..3f2a751f5d 100644 --- a/src/rnpkeys/rnpkeys.cpp +++ b/src/rnpkeys/rnpkeys.cpp @@ -45,46 +45,47 @@ const char *usage = "Manipulate OpenPGP keys and keyrings.\n" "Usage: rnpkeys --command [options] [files]\n" "Commands:\n" - " -h, --help This help message.\n" - " -V, --version Print RNP version information.\n" - " -g, --generate-key Generate a new keypair (default is RSA).\n" - " --userid Specify key's userid.\n" - " --expert Select key type, size, and additional parameters.\n" - " --numbits Override default key size (2048).\n" - " --expiration Set key and subkey expiration time.\n" - " --cipher Set cipher used to encrypt a secret key.\n" - " --hash Set hash which is used for key derivation.\n" - " --allow-weak-hash Allow usage of a weak hash algorithm.\n" - " -l, --list-keys List keys in the keyrings.\n" - " --secret List secret keys instead of public ones.\n" - " --with-sigs List signatures as well.\n" - " --import Import keys or signatures.\n" - " --import-keys Import keys.\n" - " --import-sigs Import signatures.\n" - " --permissive Skip erroring keys/sigs instead of failing.\n" - " --export-key Export a key.\n" - " --secret Export a secret key instead of a public.\n" - " --export-rev Export a key's revocation.\n" - " --rev-type Set revocation type.\n" - " --rev-reason Human-readable reason for revocation.\n" - " --revoke-key Revoke a key specified.\n" - " --remove-key Remove a key specified.\n" - " --edit-key Edit key properties.\n" - " --add-subkey Add new subkey.\n" - " --check-cv25519-bits Check whether Cv25519 subkey bits are correct.\n" - " --fix-cv25519-bits Fix Cv25519 subkey bits.\n" - " --set-expire Set key expiration time.\n" + " -h, --help This help message.\n" + " -V, --version Print RNP version information.\n" + " -g, --generate-key Generate a new keypair (default is RSA).\n" + " --userid Specify key's userid.\n" + " --expert Select key type, size, and additional parameters.\n" + " --numbits Override default key size (2048).\n" + " --expiration Set key and subkey expiration time.\n" + " --cipher Set cipher used to encrypt a secret key.\n" + " --hash Set hash which is used for key derivation.\n" + " --allow-weak-hash Allow usage of a weak hash algorithm.\n" + " --allow-sha1-key-sigs Allow usage of a SHA-1 key signatures.\n" + " -l, --list-keys List keys in the keyrings.\n" + " --secret List secret keys instead of public ones.\n" + " --with-sigs List signatures as well.\n" + " --import Import keys or signatures.\n" + " --import-keys Import keys.\n" + " --import-sigs Import signatures.\n" + " --permissive Skip erroring keys/sigs instead of failing.\n" + " --export-key Export a key.\n" + " --secret Export a secret key instead of a public.\n" + " --export-rev Export a key's revocation.\n" + " --rev-type Set revocation type.\n" + " --rev-reason Human-readable reason for revocation.\n" + " --revoke-key Revoke a key specified.\n" + " --remove-key Remove a key specified.\n" + " --edit-key Edit key properties.\n" + " --add-subkey Add new subkey.\n" + " --check-cv25519-bits Check whether Cv25519 subkey bits are correct.\n" + " --fix-cv25519-bits Fix Cv25519 subkey bits.\n" + " --set-expire Set key expiration time.\n" "\n" "Other options:\n" - " --homedir Override home directory (default is ~/.rnp/).\n" - " --password Password, which should be used during operation.\n" - " --pass-fd Read password(s) from the file descriptor.\n" - " --force Force operation (like secret key removal).\n" - " --keyfile Load key(s) only from the file specified.\n" - " --output [file, -] Write data to the specified file or stdout.\n" - " --overwrite Overwrite output file without a prompt.\n" - " --notty Do not write anything to the TTY.\n" - " --current-time Override system's time.\n" + " --homedir Override home directory (default is ~/.rnp/).\n" + " --password Password, which should be used during operation.\n" + " --pass-fd Read password(s) from the file descriptor.\n" + " --force Force operation (like secret key removal).\n" + " --keyfile Load key(s) only from the file specified.\n" + " --output [file, -] Write data to the specified file or stdout.\n" + " --overwrite Overwrite output file without a prompt.\n" + " --notty Do not write anything to the TTY.\n" + " --current-time Override system's time.\n" "\n" "See man page for a detailed listing and explanation.\n" "\n"; @@ -142,6 +143,7 @@ struct option options[] = { {"set-expire", required_argument, NULL, OPT_SET_EXPIRE}, {"current-time", required_argument, NULL, OPT_CURTIME}, {"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}, {NULL, 0, NULL, 0}, }; @@ -517,6 +519,9 @@ setoption(rnp_cfg &cfg, optdefs_t *cmd, int val, const char *arg) case OPT_ALLOW_WEAK_HASH: cfg.set_bool(CFG_WEAK_HASH, true); return true; + case OPT_ALLOW_SHA1: + cfg.set_bool(CFG_ALLOW_SHA1, true); + return true; case OPT_HASH_ALG: return cli_rnp_set_hash(cfg, arg); case OPT_S2K_ITER: { diff --git a/src/rnpkeys/rnpkeys.h b/src/rnpkeys/rnpkeys.h index de212ade3f..95559848ee 100644 --- a/src/rnpkeys/rnpkeys.h +++ b/src/rnpkeys/rnpkeys.h @@ -34,6 +34,7 @@ typedef enum { OPT_HOMEDIR, OPT_NUMBITS, OPT_ALLOW_WEAK_HASH, + OPT_ALLOW_SHA1, OPT_HASH_ALG, OPT_COREDUMPS, OPT_PASSWDFD, diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py index f3f2a22408..b679f8b6aa 100755 --- a/src/tests/cli_tests.py +++ b/src/tests/cli_tests.py @@ -102,6 +102,7 @@ def escape_regex(str): KEYRING_DIR_1 = 'keyrings/1' KEYRING_DIR_2 = 'keyrings/2' KEYRING_DIR_3 = 'keyrings/3' +PUBRING_7 = 'keyrings/7/pubring.gpg' SECRING_G10 = 'test_stream_key_load/g10' KEY_ALICE_PUB = 'test_key_validity/alice-pub.asc' KEY_ALICE_SUB_PUB = 'test_key_validity/alice-sub-pub.pgp' @@ -4066,6 +4067,34 @@ def test_allow_weak_hash(self): clear_workfiles() shutil.rmtree(RNP2, ignore_errors=True) + def test_allow_sha1_key_sigs(self): + src, sig = reg_workfiles('cleartext', '.txt', '.sig') + random_text(src, 120) + + ret, out, _ = run_proc(RNPK, ['--keyfile', data_path(PUBRING_7), '--notty', '--list-keys']) + self.assertEqual(ret, 0) + self.assertRegex(out, r'(?s)^.*\[INVALID\].*$') + ret, out, _ = run_proc(RNPK, ['--keyfile', data_path(PUBRING_7), '--notty', '--list-keys', '--allow-sha1-key-sigs']) + self.assertEqual(ret, 0) + self.assertRegex(out, r'(?s)^.*pub.*2024-06-03.*sub.*2024-06-03.*$') + self.assertNotRegex(out, r'(?s)^.*\[INVALID\].*$') + + ret, _, err = run_proc(RNP, ['--keyfile', data_path(PUBRING_7), '--notty', '--password=', '-e', src, '--output', sig]) + self.assertNotEqual(ret, 0) + self.assertRegex(err, r'(?s)^.*Failed to add recipient.*') + ret, _, err = run_proc(RNP, ['--keyfile', data_path(PUBRING_7), '--notty', '--password=', '-e', src, '--output', sig, '--allow-sha1-key-sigs']) + self.assertEqual(ret, 0) + remove_files(sig) + + ret, _, err = run_proc(RNP, ['--keyfile', data_path(PUBRING_7), '--notty', '--password=', '-e', src, '--output', sig, '--hash', 'SHA1']) + self.assertNotEqual(ret, 0) + self.assertRegex(err, r'(?s)^.*Hash algorithm \'SHA1\' is cryptographically weak!.*Weak hash algorithm detected. Pass --allow-weak-hash option if you really want to use it\..*') + ret, _, err = run_proc(RNP, ['--keyfile', data_path(PUBRING_7), '--notty', '--password=', '-e', src, '--output', sig, '--hash', 'SHA1', '--allow-sha1-key-sigs']) + self.assertEqual(ret, 0) + remove_files(sig) + + clear_workfiles() + def test_armored_detection_on_cleartext(self): ret, out, err = run_proc(RNP, ['--keyfile', data_path(SECRING_1), '--password', PASSWORD, '--clearsign'], 'Hello\n') self.assertEqual(ret, 0) @@ -4279,7 +4308,7 @@ def test_sym_encrypted__rnp_aead_botan_crash(self): rnp_decrypt_file(data_path('test_messages/message.aead-windows-issue'), dst) remove_files(dst) rnp_decrypt_file(data_path('test_messages/message.aead-windows-issue2'), dst) - remove_files(dst) + remove_files(dst) def test_aead_chunk_edge_cases(self): if not RNP_AEAD: diff --git a/src/tests/data/keyrings/7/pubring.gpg b/src/tests/data/keyrings/7/pubring.gpg new file mode 100644 index 0000000000..97fed784f3 Binary files /dev/null and b/src/tests/data/keyrings/7/pubring.gpg differ diff --git a/src/tests/data/keyrings/7/secring.gpg b/src/tests/data/keyrings/7/secring.gpg new file mode 100644 index 0000000000..e7d94e9e95 Binary files /dev/null and b/src/tests/data/keyrings/7/secring.gpg differ