diff -up openssh-8.7p1/compat.c.sshrsacheck openssh-8.7p1/compat.c --- openssh-8.7p1/compat.c.sshrsacheck 2023-01-12 13:29:06.338710923 +0100 +++ openssh-8.7p1/compat.c 2023-01-12 13:29:06.357711165 +0100 @@ -43,6 +43,7 @@ void compat_banner(struct ssh *ssh, const char *version) { int i; + int forbid_ssh_rsa = 0; static struct { char *pat; int bugs; @@ -145,16 +146,21 @@ compat_banner(struct ssh *ssh, const cha }; /* process table, return first match */ + forbid_ssh_rsa = (ssh->compat & SSH_RH_RSASIGSHA); ssh->compat = 0; for (i = 0; check[i].pat; i++) { if (match_pattern_list(version, check[i].pat, 0) == 1) { debug_f("match: %s pat %s compat 0x%08x", version, check[i].pat, check[i].bugs); ssh->compat = check[i].bugs; + if (forbid_ssh_rsa) + ssh->compat |= SSH_RH_RSASIGSHA; return; } } debug_f("no match: %s", version); + if (forbid_ssh_rsa) + ssh->compat |= SSH_RH_RSASIGSHA; } /* Always returns pointer to allocated memory, caller must free. */ diff -up openssh-8.7p1/compat.h.sshrsacheck openssh-8.7p1/compat.h --- openssh-8.7p1/compat.h.sshrsacheck 2021-08-20 06:03:49.000000000 +0200 +++ openssh-8.7p1/compat.h 2023-01-12 13:29:06.358711178 +0100 @@ -30,7 +30,7 @@ #define SSH_BUG_UTF8TTYMODE 0x00000001 #define SSH_BUG_SIGTYPE 0x00000002 #define SSH_BUG_SIGTYPE74 0x00000004 -/* #define unused 0x00000008 */ +#define SSH_RH_RSASIGSHA 0x00000008 #define SSH_OLD_SESSIONID 0x00000010 /* #define unused 0x00000020 */ #define SSH_BUG_DEBUG 0x00000040 diff -up openssh-8.7p1/monitor.c.sshrsacheck openssh-8.7p1/monitor.c --- openssh-8.7p1/monitor.c.sshrsacheck 2023-01-20 13:07:54.279676981 +0100 +++ openssh-8.7p1/monitor.c 2023-01-20 15:01:07.007821379 +0100 @@ -660,11 +660,12 @@ mm_answer_sign(struct ssh *ssh, int sock struct sshkey *key; struct sshbuf *sigbuf = NULL; u_char *p = NULL, *signature = NULL; - char *alg = NULL; + char *alg = NULL, *effective_alg; size_t datlen, siglen, alglen; int r, is_proof = 0; u_int keyid, compat; const char proof_req[] = "hostkeys-prove-00@openssh.com"; + const char safe_rsa[] = "rsa-sha2-256"; debug3_f("entering"); @@ -719,18 +720,30 @@ mm_answer_sign(struct ssh *ssh, int sock } if ((key = get_hostkey_by_index(keyid)) != NULL) { - if ((r = sshkey_sign(key, &signature, &siglen, p, datlen, alg, + if (ssh->compat & SSH_RH_RSASIGSHA && strcmp(alg, "ssh-rsa") == 0 + && (sshkey_type_plain(key->type) == KEY_RSA)) { + effective_alg = safe_rsa; + } else { + effective_alg = alg; + } + if ((r = sshkey_sign(key, &signature, &siglen, p, datlen, effective_alg, options.sk_provider, NULL, compat)) != 0) fatal_fr(r, "sign"); } else if ((key = get_hostkey_public_by_index(keyid, ssh)) != NULL && auth_sock > 0) { + if (ssh->compat & SSH_RH_RSASIGSHA && strcmp(alg, "ssh-rsa") == 0 + && (sshkey_type_plain(key->type) == KEY_RSA)) { + effective_alg = safe_rsa; + } else { + effective_alg = alg; + } if ((r = ssh_agent_sign(auth_sock, key, &signature, &siglen, - p, datlen, alg, compat)) != 0) + p, datlen, effective_alg, compat)) != 0) fatal_fr(r, "agent sign"); } else fatal_f("no hostkey from index %d", keyid); - debug3_f("%s %s signature len=%zu", alg, + debug3_f("%s (effective: %s) %s signature len=%zu", alg, effective_alg, is_proof ? "hostkey proof" : "KEX", siglen); sshbuf_reset(m); diff -up openssh-8.7p1/regress/unittests/kex/test_kex.c.sshrsacheck openssh-8.7p1/regress/unittests/kex/test_kex.c --- openssh-8.7p1/regress/unittests/kex/test_kex.c.sshrsacheck 2023-01-26 13:34:52.645743677 +0100 +++ openssh-8.7p1/regress/unittests/kex/test_kex.c 2023-01-26 13:36:56.220745823 +0100 @@ -97,7 +97,8 @@ do_kex_with_key(char *kex, int keytype, memcpy(kex_params.proposal, myproposal, sizeof(myproposal)); if (kex != NULL) kex_params.proposal[PROPOSAL_KEX_ALGS] = kex; - keyname = strdup(sshkey_ssh_name(private)); + keyname = (strcmp(sshkey_ssh_name(private), "ssh-rsa")) ? + strdup(sshkey_ssh_name(private)) : strdup("rsa-sha2-256"); ASSERT_PTR_NE(keyname, NULL); kex_params.proposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = keyname; ASSERT_INT_EQ(ssh_init(&client, 0, &kex_params), 0); diff -up openssh-8.7p1/regress/unittests/sshkey/test_file.c.sshrsacheck openssh-8.7p1/regress/unittests/sshkey/test_file.c --- openssh-8.7p1/regress/unittests/sshkey/test_file.c.sshrsacheck 2023-01-26 12:04:55.946343408 +0100 +++ openssh-8.7p1/regress/unittests/sshkey/test_file.c 2023-01-26 12:06:35.235164432 +0100 @@ -110,6 +110,7 @@ sshkey_file_tests(void) sshkey_free(k2); TEST_DONE(); + /* Skip this test, SHA1 signatures are not supported TEST_START("load RSA cert with SHA1 signature"); ASSERT_INT_EQ(sshkey_load_cert(test_data_file("rsa_1_sha1"), &k2), 0); ASSERT_PTR_NE(k2, NULL); @@ -117,7 +118,7 @@ sshkey_file_tests(void) ASSERT_INT_EQ(sshkey_equal_public(k1, k2), 1); ASSERT_STRING_EQ(k2->cert->signature_type, "ssh-rsa"); sshkey_free(k2); - TEST_DONE(); + TEST_DONE(); */ TEST_START("load RSA cert with SHA512 signature"); ASSERT_INT_EQ(sshkey_load_cert(test_data_file("rsa_1_sha512"), &k2), 0); diff -up openssh-8.7p1/regress/unittests/sshkey/test_fuzz.c.sshrsacheck openssh-8.7p1/regress/unittests/sshkey/test_fuzz.c --- openssh-8.7p1/regress/unittests/sshkey/test_fuzz.c.sshrsacheck 2023-01-26 12:10:37.533168013 +0100 +++ openssh-8.7p1/regress/unittests/sshkey/test_fuzz.c 2023-01-26 12:15:35.637631860 +0100 @@ -333,13 +333,14 @@ sshkey_fuzz_tests(void) TEST_DONE(); #ifdef WITH_OPENSSL + /* Skip this test, SHA1 signatures are not supported TEST_START("fuzz RSA sig"); buf = load_file("rsa_1"); ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0); sshbuf_free(buf); sig_fuzz(k1, "ssh-rsa"); sshkey_free(k1); - TEST_DONE(); + TEST_DONE();*/ TEST_START("fuzz RSA SHA256 sig"); buf = load_file("rsa_1"); diff -up openssh-8.7p1/regress/unittests/sshkey/test_sshkey.c.sshrsacheck openssh-8.7p1/regress/unittests/sshkey/test_sshkey.c --- openssh-8.7p1/regress/unittests/sshkey/test_sshkey.c.sshrsacheck 2023-01-26 11:02:52.339413463 +0100 +++ openssh-8.7p1/regress/unittests/sshkey/test_sshkey.c 2023-01-26 11:58:42.324253896 +0100 @@ -60,6 +60,9 @@ build_cert(struct sshbuf *b, struct sshk u_char *sigblob; size_t siglen; + /* ssh-rsa implies SHA1, forbidden in DEFAULT cp */ + int expected = (sig_alg == NULL || strcmp(sig_alg, "ssh-rsa") == 0) ? SSH_ERR_LIBCRYPTO_ERROR : 0; + ca_buf = sshbuf_new(); ASSERT_PTR_NE(ca_buf, NULL); ASSERT_INT_EQ(sshkey_putb(ca_key, ca_buf), 0); @@ -101,8 +104,9 @@ build_cert(struct sshbuf *b, struct sshk ASSERT_INT_EQ(sshbuf_put_string(b, NULL, 0), 0); /* reserved */ ASSERT_INT_EQ(sshbuf_put_stringb(b, ca_buf), 0); /* signature key */ ASSERT_INT_EQ(sshkey_sign(sign_key, &sigblob, &siglen, - sshbuf_ptr(b), sshbuf_len(b), sig_alg, NULL, NULL, 0), 0); - ASSERT_INT_EQ(sshbuf_put_string(b, sigblob, siglen), 0); /* signature */ + sshbuf_ptr(b), sshbuf_len(b), sig_alg, NULL, NULL, 0), expected); + if (expected == 0) + ASSERT_INT_EQ(sshbuf_put_string(b, sigblob, siglen), 0); /* signature */ free(sigblob); sshbuf_free(ca_buf); @@ -119,16 +123,22 @@ signature_test(struct sshkey *k, struct { size_t len; u_char *sig; + /* ssh-rsa implies SHA1, forbidden in DEFAULT cp in RHEL, permitted in Fedora */ + int expected = (sig_alg && strcmp(sig_alg, "ssh-rsa") == 0) ? sshkey_sign(k, &sig, &len, d, l, sig_alg, NULL, NULL, 0) : 0; + if (k && (sshkey_type_plain(k->type) == KEY_DSA || sshkey_type_plain(k->type) == KEY_DSA_CERT)) + expected = sshkey_sign(k, &sig, &len, d, l, sig_alg, NULL, NULL, 0); ASSERT_INT_EQ(sshkey_sign(k, &sig, &len, d, l, sig_alg, - NULL, NULL, 0), 0); - ASSERT_SIZE_T_GT(len, 8); - ASSERT_PTR_NE(sig, NULL); - ASSERT_INT_EQ(sshkey_verify(k, sig, len, d, l, NULL, 0, NULL), 0); - ASSERT_INT_NE(sshkey_verify(bad, sig, len, d, l, NULL, 0, NULL), 0); - /* Fuzz test is more comprehensive, this is just a smoke test */ - sig[len - 5] ^= 0x10; - ASSERT_INT_NE(sshkey_verify(k, sig, len, d, l, NULL, 0, NULL), 0); + NULL, NULL, 0), expected); + if (expected == 0) { + ASSERT_SIZE_T_GT(len, 8); + ASSERT_PTR_NE(sig, NULL); + ASSERT_INT_EQ(sshkey_verify(k, sig, len, d, l, NULL, 0, NULL), 0); + ASSERT_INT_NE(sshkey_verify(bad, sig, len, d, l, NULL, 0, NULL), 0); + /* Fuzz test is more comprehensive, this is just a smoke test */ + sig[len - 5] ^= 0x10; + ASSERT_INT_NE(sshkey_verify(k, sig, len, d, l, NULL, 0, NULL), 0); + } free(sig); } @@ -514,7 +524,7 @@ sshkey_tests(void) ASSERT_INT_EQ(sshkey_load_public(test_data_file("rsa_1.pub"), &k2, NULL), 0); k3 = get_private("rsa_1"); - build_cert(b, k2, "ssh-rsa-cert-v01@openssh.com", k3, k1, NULL); + build_cert(b, k2, "ssh-rsa-cert-v01@openssh.com", k3, k1, "rsa-sha2-256"); ASSERT_INT_EQ(sshkey_from_blob(sshbuf_ptr(b), sshbuf_len(b), &k4), SSH_ERR_KEY_CERT_INVALID_SIGN_KEY); ASSERT_PTR_EQ(k4, NULL); diff -up openssh-8.7p1/serverloop.c.sshrsacheck openssh-8.7p1/serverloop.c --- openssh-8.7p1/serverloop.c.sshrsacheck 2023-01-12 14:57:08.118400073 +0100 +++ openssh-8.7p1/serverloop.c 2023-01-12 14:59:17.330470518 +0100 @@ -80,6 +80,7 @@ #include "auth-options.h" #include "serverloop.h" #include "ssherr.h" +#include "compat.h" extern ServerOptions options; @@ -737,6 +737,10 @@ server_input_hostkeys_prove(struct ssh * else if (ssh->kex->flags & KEX_RSA_SHA2_256_SUPPORTED) sigalg = "rsa-sha2-256"; } + if (ssh->compat & SSH_RH_RSASIGSHA && sigalg == NULL) { + sigalg = "rsa-sha2-512"; + debug3_f("SHA1 signature is not supported, falling back to %s", sigalg); + } debug3_f("sign %s key (index %d) using sigalg %s", sshkey_type(key), ndx, sigalg == NULL ? "default" : sigalg); if ((r = sshbuf_put_cstring(sigbuf, diff -up openssh-8.7p1/sshconnect2.c.sshrsacheck openssh-8.7p1/sshconnect2.c --- openssh-8.7p1/sshconnect2.c.sshrsacheck 2023-01-25 15:33:29.140353651 +0100 +++ openssh-8.7p1/sshconnect2.c 2023-01-25 15:59:34.225364883 +0100 @@ -1461,6 +1464,14 @@ identity_sign(struct identity *id, u_cha retried = 1; goto retry_pin; } + if ((r == SSH_ERR_LIBCRYPTO_ERROR) && strcmp("ssh-rsa", alg)) { + char rsa_safe_alg[] = "rsa-sha2-512"; + debug3_f("trying to fallback to algorithm %s", rsa_safe_alg); + + if ((r = sshkey_sign(sign_key, sigp, lenp, data, datalen, + rsa_safe_alg, options.sk_provider, pin, compat)) != 0) + debug_fr(r, "sshkey_sign - RSA fallback"); + } goto out; } diff -up openssh-8.7p1/ssh-rsa.c.sshrsacheck openssh-8.7p1/ssh-rsa.c --- openssh-8.7p1/ssh-rsa.c.sshrsacheck 2023-01-20 13:07:54.180676144 +0100 +++ openssh-8.7p1/ssh-rsa.c 2023-01-20 13:07:54.290677074 +0100 @@ -254,7 +254,8 @@ ssh_rsa_verify(const struct sshkey *key, ret = SSH_ERR_INVALID_ARGUMENT; goto out; } - if (hash_alg != want_alg) { + if (hash_alg != want_alg && want_alg != SSH_DIGEST_SHA1) { + debug_f("Unexpected digest algorithm: got %d, wanted %d", hash_alg, want_alg); ret = SSH_ERR_SIGNATURE_INVALID; goto out; } diff -up openssh-9.8p1/sshd-session.c.xxx openssh-9.8p1/sshd-session.c --- openssh-9.8p1/sshd-session.c.xxx 2024-07-23 15:08:14.794350818 +0200 +++ openssh-9.8p1/sshd-session.c 2024-07-23 15:40:21.658456636 +0200 @@ -1305,6 +1305,27 @@ main(int ac, char **av) check_ip_options(ssh); + { + struct sshkey *rsakey = NULL; + rsakey = get_hostkey_private_by_type(KEY_RSA, 0, ssh); + if (rsakey == NULL) + rsakey = get_hostkey_private_by_type(KEY_RSA_CERT, 0, ssh); + + if (rsakey != NULL) { + size_t sign_size = 0; + u_char *tmp = NULL; + u_char data[] = "Test SHA1 vector"; + int res; + + res = sshkey_sign(rsakey, &tmp, &sign_size, data, sizeof(data), NULL, NULL, NULL, 0); + free(tmp); + if (res == SSH_ERR_LIBCRYPTO_ERROR) { + verbose_f("SHA1 in signatures is disabled for RSA keys"); + ssh->compat |= SSH_RH_RSASIGSHA; + } + } + } + /* Prepare the channels layer */ channel_init_channels(ssh); channel_set_af(ssh, options.address_family);