| diff -up openssh-8.7p1/compat.c.sshrsacheck openssh-8.7p1/compat.c |
| |
| |
| @@ -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 |
| |
| |
| @@ -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 |
| |
| |
| @@ -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/cert-userkey.sh.sshrsacheck openssh-8.7p1/regress/cert-userkey.sh |
| |
| |
| @@ -7,7 +7,8 @@ rm -f $OBJ/authorized_keys_$USER $OBJ/us |
| cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak |
| cp $OBJ/ssh_proxy $OBJ/ssh_proxy_bak |
| |
| -PLAIN_TYPES=`$SSH -Q key-plain | maybe_filter_sk | sed 's/^ssh-dss/ssh-dsa/;s/^ssh-//'` |
| +#ssh-dss keys are incompatible with DEFAULT crypto policy |
| +PLAIN_TYPES=`$SSH -Q key-plain | maybe_filter_sk | grep -v 'ssh-dss' | sed 's/^ssh-dss/ssh-dsa/;s/^ssh-//'` |
| EXTRA_TYPES="" |
| rsa="" |
| |
| diff -up openssh-8.7p1/regress/Makefile.sshrsacheck openssh-8.7p1/regress/Makefile |
| |
| |
| @@ -2,7 +2,8 @@ |
| |
| tests: prep file-tests t-exec unit |
| |
| -REGRESS_TARGETS= t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 |
| +#ssh-dss tests will not pass on DEFAULT crypto-policy because of SHA1, skipping |
| +REGRESS_TARGETS= t1 t2 t3 t4 t5 t7 t8 t9 t10 t11 t12 |
| |
| # File based tests |
| file-tests: $(REGRESS_TARGETS) |
| diff -up openssh-8.7p1/regress/test-exec.sh.sshrsacheck openssh-8.7p1/regress/test-exec.sh |
| |
| |
| @@ -581,8 +581,9 @@ maybe_filter_sk() { |
| fi |
| } |
| |
| -SSH_KEYTYPES=`$SSH -Q key-plain | maybe_filter_sk` |
| -SSH_HOSTKEY_TYPES=`$SSH -Q key-plain | maybe_filter_sk` |
| +#ssh-dss keys are incompatible with DEFAULT crypto policy |
| +SSH_KEYTYPES=`$SSH -Q key-plain | maybe_filter_sk | grep -v 'ssh-dss'` |
| +SSH_HOSTKEY_TYPES=`$SSH -Q key-plain | maybe_filter_sk | grep -v 'ssh-dss'` |
| |
| for t in ${SSH_KEYTYPES}; do |
| # generate user key |
| diff -up openssh-8.7p1/regress/unittests/kex/test_kex.c.sshrsacheck openssh-8.7p1/regress/unittests/kex/test_kex.c |
| |
| |
| @@ -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); |
| @@ -180,7 +181,7 @@ do_kex(char *kex) |
| { |
| #ifdef WITH_OPENSSL |
| do_kex_with_key(kex, KEY_RSA, 2048); |
| - do_kex_with_key(kex, KEY_DSA, 1024); |
| + /* do_kex_with_key(kex, KEY_DSA, 1024); */ |
| #ifdef OPENSSL_HAS_ECC |
| do_kex_with_key(kex, KEY_ECDSA, 256); |
| #endif /* OPENSSL_HAS_ECC */ |
| diff -up openssh-8.7p1/regress/unittests/sshkey/test_file.c.sshrsacheck openssh-8.7p1/regress/unittests/sshkey/test_file.c |
| |
| |
| @@ -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 |
| |
| |
| @@ -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"); |
| @@ -357,6 +358,7 @@ sshkey_fuzz_tests(void) |
| sshkey_free(k1); |
| TEST_DONE(); |
| |
| + /* Skip this test, SHA1 signatures are not supported |
| TEST_START("fuzz DSA sig"); |
| buf = load_file("dsa_1"); |
| ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0); |
| @@ -364,6 +366,7 @@ sshkey_fuzz_tests(void) |
| sig_fuzz(k1, NULL); |
| sshkey_free(k1); |
| TEST_DONE(); |
| + */ |
| |
| #ifdef OPENSSL_HAS_ECC |
| TEST_START("fuzz ECDSA sig"); |
| diff -up openssh-8.7p1/regress/unittests/sshkey/test_sshkey.c.sshrsacheck openssh-8.7p1/regress/unittests/sshkey/test_sshkey.c |
| |
| |
| @@ -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 */ |
| + int expected = (sig_alg && strcmp(sig_alg, "ssh-rsa") == 0) ? SSH_ERR_LIBCRYPTO_ERROR : 0; |
| + if (k && (sshkey_type_plain(k->type) == KEY_DSA || sshkey_type_plain(k->type) == KEY_DSA_CERT)) |
| + expected = SSH_ERR_LIBCRYPTO_ERROR; |
| |
| 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/regress/unittests/sshsig/tests.c.sshrsacheck openssh-8.7p1/regress/unittests/sshsig/tests.c |
| |
| |
| @@ -102,9 +102,11 @@ tests(void) |
| check_sig("rsa.pub", "rsa.sig", msg, namespace); |
| TEST_DONE(); |
| |
| + /* Skip this test, SHA1 signatures are not supported |
| TEST_START("check DSA signature"); |
| check_sig("dsa.pub", "dsa.sig", msg, namespace); |
| TEST_DONE(); |
| + */ |
| |
| #ifdef OPENSSL_HAS_ECC |
| TEST_START("check ECDSA signature"); |
| diff -up openssh-8.7p1/serverloop.c.sshrsacheck openssh-8.7p1/serverloop.c |
| |
| |
| @@ -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 |
| |
| |
| @@ -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/sshd.c.sshrsacheck openssh-8.7p1/sshd.c |
| |
| |
| @@ -1640,6 +1651,7 @@ main(int ac, char **av) |
| int keytype; |
| Authctxt *authctxt; |
| struct connection_info *connection_info = NULL; |
| + int forbid_ssh_rsa = 0; |
| |
| #ifdef HAVE_SECUREWARE |
| (void)set_auth_parameters(ac, av); |
| @@ -1938,6 +1950,33 @@ main(int ac, char **av) |
| key = NULL; |
| continue; |
| } |
| + if (key && (sshkey_type_plain(key->type) == KEY_RSA || sshkey_type_plain(key->type) == KEY_RSA_CERT)) { |
| + size_t sign_size = 0; |
| + u_char *tmp = NULL; |
| + u_char data[] = "Test SHA1 vector"; |
| + int res; |
| + |
| + res = sshkey_sign(key, &tmp, &sign_size, data, sizeof(data), NULL, NULL, NULL, 0); |
| + free(tmp); |
| + if (res == SSH_ERR_LIBCRYPTO_ERROR) { |
| + verbose_f("sshd: SHA1 in signatures is disabled for RSA keys"); |
| + forbid_ssh_rsa = 1; |
| + } |
| + } |
| + if (key && (sshkey_type_plain(key->type) == KEY_DSA || sshkey_type_plain(key->type) == KEY_DSA_CERT)) { |
| + size_t sign_size = 0; |
| + u_char *tmp = NULL; |
| + u_char data[] = "Test SHA1 vector"; |
| + int res; |
| + |
| + res = sshkey_sign(key, &tmp, &sign_size, data, sizeof(data), NULL, NULL, NULL, 0); |
| + free(tmp); |
| + if (res == SSH_ERR_LIBCRYPTO_ERROR) { |
| + logit_f("sshd: ssh-dss is disabled, skipping key file %s", options.host_key_files[i]); |
| + key = NULL; |
| + continue; |
| + } |
| + } |
| if (sshkey_is_sk(key) && |
| key->sk_flags & SSH_SK_USER_PRESENCE_REQD) { |
| debug("host key %s requires user presence, ignoring", |
| @@ -2275,6 +2306,9 @@ main(int ac, char **av) |
| |
| check_ip_options(ssh); |
| |
| + if (forbid_ssh_rsa) |
| + ssh->compat |= SSH_RH_RSASIGSHA; |
| + |
| /* Prepare the channels layer */ |
| channel_init_channels(ssh); |
| channel_set_af(ssh, options.address_family); |
| diff -Nur openssh-8.7p1/ssh-keygen.c openssh-8.7p1_patched/ssh-keygen.c |
| |
| |
| @@ -491,6 +491,8 @@ |
| BIGNUM *dsa_pub_key = NULL, *dsa_priv_key = NULL; |
| BIGNUM *rsa_n = NULL, *rsa_e = NULL, *rsa_d = NULL; |
| BIGNUM *rsa_p = NULL, *rsa_q = NULL, *rsa_iqmp = NULL; |
| + char rsa_safe_alg[] = "rsa-sha2-256"; |
| + char *alg = NULL; |
| |
| if ((r = sshbuf_get_u32(b, &magic)) != 0) |
| fatal_fr(r, "parse magic"); |
| @@ -590,6 +592,7 @@ do_convert_private_ssh2(struct sshbuf *b |
| if ((r = ssh_rsa_complete_crt_parameters(key, rsa_iqmp)) != 0) |
| fatal_fr(r, "generate RSA parameters"); |
| BN_clear_free(rsa_iqmp); |
| + alg = rsa_safe_alg; |
| break; |
| } |
| rlen = sshbuf_len(b); |
| @@ -598,9 +601,9 @@ do_convert_private_ssh2(struct sshbuf *b |
| |
| /* try the key */ |
| if (sshkey_sign(key, &sig, &slen, data, sizeof(data), |
| - NULL, NULL, NULL, 0) != 0 || |
| + alg, NULL, NULL, 0) != 0 || |
| sshkey_verify(key, sig, slen, data, sizeof(data), |
| - NULL, 0, NULL) != 0) { |
| + alg, 0, NULL) != 0) { |
| sshkey_free(key); |
| free(sig); |
| return NULL; |
| diff -up openssh-8.7p1/ssh-rsa.c.sshrsacheck openssh-8.7p1/ssh-rsa.c |
| |
| |
| @@ -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; |
| } |