From 4b21ae5fcb35329d1a5cc7c3c5859e5dae1b57b0 Mon Sep 17 00:00:00 2001 From: Dmitry Belyavskiy Date: Jul 11 2022 09:55:08 +0000 Subject: Set minimal value of RSA key length via configuration option Related: rhbz#2066882 --- diff --git a/openssh-8.7p1-minrsabits.patch b/openssh-8.7p1-minrsabits.patch new file mode 100644 index 0000000..57019b6 --- /dev/null +++ b/openssh-8.7p1-minrsabits.patch @@ -0,0 +1,424 @@ +diff --git a/auth2-hostbased.c b/auth2-hostbased.c +index 2ab222ed6..4e9437912 100644 +--- a/auth2-hostbased.c ++++ b/auth2-hostbased.c +@@ -118,6 +118,10 @@ userauth_hostbased(struct ssh *ssh, const char *method) + "(null)" : key->cert->signature_type); + goto done; + } ++ if ((r = sshkey_check_rsa_length(key, options.rsa_min_size)) != 0) { ++ logit("refusing %s key", sshkey_type(key)); ++ goto done; ++ } + + if (!authctxt->valid || authctxt->user == NULL) { + debug2_f("disabled because of invalid user"); +diff --git a/auth2-pubkey.c b/auth2-pubkey.c +index daa756a01..68e7dea1f 100644 +--- a/auth2-pubkey.c ++++ b/auth2-pubkey.c +@@ -172,6 +172,10 @@ userauth_pubkey(struct ssh *ssh, const char *method) + "(null)" : key->cert->signature_type); + goto done; + } ++ if ((r = sshkey_check_rsa_length(key, options.rsa_min_size)) != 0) { ++ logit("refusing %s key", sshkey_type(key)); ++ goto done; ++ } + key_s = format_key(key); + if (sshkey_is_cert(key)) + ca_s = format_key(key->cert->signature_key); +diff --git a/readconf.c b/readconf.c +index 5b5afa8e3..5e17abd41 100644 +--- a/readconf.c ++++ b/readconf.c +@@ -160,7 +160,7 @@ typedef enum { + oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys, + oFingerprintHash, oUpdateHostkeys, oHostbasedAcceptedAlgorithms, + oPubkeyAcceptedAlgorithms, oCASignatureAlgorithms, oProxyJump, +- oSecurityKeyProvider, oKnownHostsCommand, ++ oSecurityKeyProvider, oKnownHostsCommand, oRSAMinSize, + oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported + } OpCodes; + +@@ -306,6 +306,7 @@ static struct { + { "proxyjump", oProxyJump }, + { "securitykeyprovider", oSecurityKeyProvider }, + { "knownhostscommand", oKnownHostsCommand }, ++ { "rsaminsize", oRSAMinSize }, + + { NULL, oBadOption } + }; +@@ -2162,6 +2163,10 @@ process_config_line_depth(Options *options, struct passwd *pw, const char *host, + *charptr = xstrdup(arg); + break; + ++ case oRSAMinSize: ++ intptr = &options->rsa_min_size; ++ goto parse_int; ++ + case oDeprecated: + debug("%s line %d: Deprecated option \"%s\"", + filename, linenum, keyword); +@@ -2409,6 +2414,7 @@ initialize_options(Options * options) + options->hostbased_accepted_algos = NULL; + options->pubkey_accepted_algos = NULL; + options->known_hosts_command = NULL; ++ options->rsa_min_size = -1; + } + + /* +@@ -2598,6 +2604,8 @@ fill_default_options(Options * options) + if (options->sk_provider == NULL) + options->sk_provider = xstrdup("$SSH_SK_PROVIDER"); + #endif ++ if (options->rsa_min_size == -1) ++ options->rsa_min_size = SSH_RSA_MINIMUM_MODULUS_SIZE; + + /* Expand KEX name lists */ + all_cipher = cipher_alg_list(',', 0); +@@ -3287,6 +3295,7 @@ dump_client_config(Options *o, const char *host) + dump_cfg_int(oNumberOfPasswordPrompts, o->number_of_password_prompts); + dump_cfg_int(oServerAliveCountMax, o->server_alive_count_max); + dump_cfg_int(oServerAliveInterval, o->server_alive_interval); ++ dump_cfg_int(oRSAMinSize, o->rsa_min_size); + + /* String options */ + dump_cfg_string(oBindAddress, o->bind_address); +diff --git a/readconf.h b/readconf.h +index f647bd42a..29db353ab 100644 +--- a/readconf.h ++++ b/readconf.h +@@ -176,6 +176,8 @@ typedef struct { + + char *known_hosts_command; + ++ int rsa_min_size; /* minimum size of RSA keys */ ++ + char *ignored_unknown; /* Pattern list of unknown tokens to ignore */ + } Options; + +diff --git a/servconf.c b/servconf.c +index f7317a5cb..362ff5b67 100644 +--- a/servconf.c ++++ b/servconf.c +@@ -177,6 +177,7 @@ initialize_server_options(ServerOptions *options) + options->fingerprint_hash = -1; + options->disable_forwarding = -1; + options->expose_userauth_info = -1; ++ options->rsa_min_size = -1; + } + + /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */ +@@ -416,6 +417,8 @@ fill_default_server_options(ServerOptions *options) + options->expose_userauth_info = 0; + if (options->sk_provider == NULL) + options->sk_provider = xstrdup("internal"); ++ if (options->rsa_min_size == -1) ++ options->rsa_min_size = SSH_RSA_MINIMUM_MODULUS_SIZE; + + assemble_algorithms(options); + +@@ -489,6 +492,7 @@ typedef enum { + sStreamLocalBindMask, sStreamLocalBindUnlink, + sAllowStreamLocalForwarding, sFingerprintHash, sDisableForwarding, + sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider, ++ sRSAMinSize, + sDeprecated, sIgnore, sUnsupported + } ServerOpCodes; + +@@ -632,6 +636,7 @@ static struct { + { "rdomain", sRDomain, SSHCFG_ALL }, + { "casignaturealgorithms", sCASignatureAlgorithms, SSHCFG_ALL }, + { "securitykeyprovider", sSecurityKeyProvider, SSHCFG_GLOBAL }, ++ { "rsaminsize", sRSAMinSize, SSHCFG_ALL }, + { NULL, sBadOption, 0 } + }; + +@@ -2377,6 +2382,10 @@ process_server_config_line_depth(ServerOptions *options, char *line, + *charptr = xstrdup(arg); + break; + ++ case sRSAMinSize: ++ intptr = &options->rsa_min_size; ++ goto parse_int; ++ + case sDeprecated: + case sIgnore: + case sUnsupported: +@@ -2549,6 +2558,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth) + M_CP_INTOPT(rekey_limit); + M_CP_INTOPT(rekey_interval); + M_CP_INTOPT(log_level); ++ M_CP_INTOPT(rsa_min_size); + + /* + * The bind_mask is a mode_t that may be unsigned, so we can't use +@@ -2810,6 +2820,7 @@ dump_config(ServerOptions *o) + dump_cfg_int(sMaxSessions, o->max_sessions); + dump_cfg_int(sClientAliveInterval, o->client_alive_interval); + dump_cfg_int(sClientAliveCountMax, o->client_alive_count_max); ++ dump_cfg_int(sRSAMinSize, o->rsa_min_size); + dump_cfg_oct(sStreamLocalBindMask, o->fwd_opts.streamlocal_bind_mask); + + /* formatted integer arguments */ +diff --git a/servconf.h b/servconf.h +index 115db1e79..2e3486906 100644 +--- a/servconf.h ++++ b/servconf.h +@@ -227,6 +227,7 @@ typedef struct { + int expose_userauth_info; + u_int64_t timing_secret; + char *sk_provider; ++ int rsa_min_size; /* minimum size of RSA keys */ + } ServerOptions; + + /* Information about the incoming connection as used by Match */ +diff --git a/ssh.c b/ssh.c +index a926cc007..cd13fb879 100644 +--- a/ssh.c ++++ b/ssh.c +@@ -500,14 +500,22 @@ resolve_canonicalize(char **hostp, int port) + } + + /* +- * Check the result of hostkey loading, ignoring some errors and +- * fatal()ing for others. ++ * Check the result of hostkey loading, ignoring some errors and either ++ * discarding the key or fatal()ing for others. + */ + static void +-check_load(int r, const char *path, const char *message) ++check_load(int r, struct sshkey **k, const char *path, const char *message) + { + switch (r) { + case 0: ++ /* Check RSA keys size and discard if undersized */ ++ if (k != NULL && *k != NULL && ++ (r = sshkey_check_rsa_length(*k, ++ options.rsa_min_size)) != 0) { ++ error_r(r, "load %s \"%s\"", message, path); ++ free(*k); ++ *k = NULL; ++ } + break; + case SSH_ERR_INTERNAL_ERROR: + case SSH_ERR_ALLOC_FAIL: +@@ -1557,12 +1565,13 @@ main(int ac, char **av) + if ((o) >= sensitive_data.nkeys) \ + fatal_f("pubkey out of array bounds"); \ + check_load(sshkey_load_public(p, &(sensitive_data.keys[o]), NULL), \ +- p, "pubkey"); \ ++ &(sensitive_data.keys[o]), p, "pubkey"); \ + } while (0) + #define L_CERT(p,o) do { \ + if ((o) >= sensitive_data.nkeys) \ + fatal_f("cert out of array bounds"); \ +- check_load(sshkey_load_cert(p, &(sensitive_data.keys[o])), p, "cert"); \ ++ check_load(sshkey_load_cert(p, &(sensitive_data.keys[o])), \ ++ &(sensitive_data.keys[o]), p, "cert"); \ + } while (0) + + if (options.hostbased_authentication == 1) { +@@ -2244,7 +2253,7 @@ load_public_identity_files(const struct ssh_conn_info *cinfo) + filename = default_client_percent_dollar_expand(cp, cinfo); + free(cp); + check_load(sshkey_load_public(filename, &public, NULL), +- filename, "pubkey"); ++ &public, filename, "pubkey"); + debug("identity file %s type %d", filename, + public ? public->type : -1); + free(options.identity_files[i]); +@@ -2263,7 +2272,7 @@ load_public_identity_files(const struct ssh_conn_info *cinfo) + continue; + xasprintf(&cp, "%s-cert", filename); + check_load(sshkey_load_public(cp, &public, NULL), +- filename, "pubkey"); ++ &public, filename, "pubkey"); + debug("identity file %s type %d", cp, + public ? public->type : -1); + if (public == NULL) { +@@ -2294,7 +2303,7 @@ load_public_identity_files(const struct ssh_conn_info *cinfo) + free(cp); + + check_load(sshkey_load_public(filename, &public, NULL), +- filename, "certificate"); ++ &public, filename, "certificate"); + debug("certificate file %s type %d", filename, + public ? public->type : -1); + free(options.certificate_files[i]); +diff --git a/sshconnect2.c b/sshconnect2.c +index 67f8e0309..d050c1656 100644 +--- a/sshconnect2.c ++++ b/sshconnect2.c +@@ -91,6 +91,10 @@ static const struct ssh_conn_info *xxx_conn_info; + static int + verify_host_key_callback(struct sshkey *hostkey, struct ssh *ssh) + { ++ int r; ++ ++ if ((r = sshkey_check_rsa_length(hostkey, options.rsa_min_size)) != 0) ++ fatal_r(r, "Bad server host key"); + if (verify_host_key(xxx_host, xxx_hostaddr, hostkey, + xxx_conn_info) == -1) + fatal("Host key verification failed."); +@@ -1747,6 +1751,12 @@ pubkey_prepare(struct ssh *ssh, Authctxt *authctxt) + close(agent_fd); + } else { + for (j = 0; j < idlist->nkeys; j++) { ++ if ((r = sshkey_check_rsa_length(idlist->keys[j], ++ options.rsa_min_size)) != 0) { ++ debug_fr(r, "ignoring %s agent key", ++ sshkey_ssh_name(idlist->keys[j])); ++ continue; ++ } + found = 0; + TAILQ_FOREACH(id, &files, next) { + /* +diff --git a/sshd.c b/sshd.c +index d26eb86ae..5f36905a1 100644 +--- a/sshd.c ++++ b/sshd.c +@@ -1746,6 +1746,13 @@ main(int ac, char **av) + fatal_r(r, "Could not demote key: \"%s\"", + options.host_key_files[i]); + } ++ if (pubkey != NULL && (r = sshkey_check_rsa_length(pubkey, ++ options.rsa_min_size)) != 0) { ++ error_fr(r, "Host key %s", options.host_key_files[i]); ++ sshkey_free(pubkey); ++ sshkey_free(key); ++ continue; ++ } + sensitive_data.host_keys[i] = key; + sensitive_data.host_pubkeys[i] = pubkey; + +diff --git a/sshkey.c b/sshkey.c +index 47864e6d8..8bad6bd99 100644 +--- a/sshkey.c ++++ b/sshkey.c +@@ -2319,18 +2319,24 @@ cert_parse(struct sshbuf *b, struct sshkey *key, struct sshbuf *certbuf) + return ret; + } + +-#ifdef WITH_OPENSSL +-static int +-check_rsa_length(const RSA *rsa) ++int ++sshkey_check_rsa_length(const struct sshkey *k, int min_size) + { ++#ifdef WITH_OPENSSL + const BIGNUM *rsa_n; ++ int nbits; + +- RSA_get0_key(rsa, &rsa_n, NULL, NULL); +- if (BN_num_bits(rsa_n) < SSH_RSA_MINIMUM_MODULUS_SIZE) ++ if (k == NULL || k->rsa == NULL || ++ (k->type != KEY_RSA && k->type != KEY_RSA_CERT)) ++ return 0; ++ RSA_get0_key(k->rsa, &rsa_n, NULL, NULL); ++ nbits = BN_num_bits(rsa_n); ++ if (nbits < SSH_RSA_MINIMUM_MODULUS_SIZE || ++ (min_size > 0 && nbits < min_size)) + return SSH_ERR_KEY_LENGTH; ++#endif /* WITH_OPENSSL */ + return 0; + } +-#endif + + static int + sshkey_from_blob_internal(struct sshbuf *b, struct sshkey **keyp, +@@ -2391,7 +2397,7 @@ sshkey_from_blob_internal(struct sshbuf *b, struct sshkey **keyp, + goto out; + } + rsa_n = rsa_e = NULL; /* transferred */ +- if ((ret = check_rsa_length(key->rsa)) != 0) ++ if ((ret = sshkey_check_rsa_length(key, 0)) != 0) + goto out; + #ifdef DEBUG_PK + RSA_print_fp(stderr, key->rsa, 8); +@@ -3580,7 +3586,7 @@ sshkey_private_deserialize(struct sshbuf *buf, struct sshkey **kp) + goto out; + } + rsa_p = rsa_q = NULL; /* transferred */ +- if ((r = check_rsa_length(k->rsa)) != 0) ++ if ((r = sshkey_check_rsa_length(k, 0)) != 0) + goto out; + if ((r = ssh_rsa_complete_crt_parameters(k, rsa_iqmp)) != 0) + goto out; +@@ -4566,7 +4572,7 @@ sshkey_parse_private_pem_fileblob(struct sshbuf *blob, int type, + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } +- if ((r = check_rsa_length(prv->rsa)) != 0) ++ if ((r = sshkey_check_rsa_length(prv, 0)) != 0) + goto out; + } else if (EVP_PKEY_base_id(pk) == EVP_PKEY_DSA && + (type == KEY_UNSPEC || type == KEY_DSA)) { +diff --git a/sshkey.h b/sshkey.h +index 125cadb64..52e879456 100644 +--- a/sshkey.h ++++ b/sshkey.h +@@ -267,6 +267,7 @@ int sshkey_parse_private_fileblob_type(struct sshbuf *blob, int type, + int sshkey_parse_pubkey_from_private_fileblob_type(struct sshbuf *blob, + int type, struct sshkey **pubkeyp); + ++int sshkey_check_rsa_length(const struct sshkey *, int); + /* XXX should be internal, but used by ssh-keygen */ + int ssh_rsa_complete_crt_parameters(struct sshkey *, const BIGNUM *); + +diff --git a/ssh.1 b/ssh.1 +index b4956aec..b1a40ebd 100644 +--- a/ssh.1 ++++ b/ssh.1 +@@ -554,6 +554,7 @@ For full details of the options listed below, and their possible values, see + .It LogLevel + .It MACs + .It Match ++.It RSAMinSize + .It NoHostAuthenticationForLocalhost + .It NumberOfPasswordPrompts + .It PasswordAuthentication +diff --git a/ssh_config.5 b/ssh_config.5 +index 24a46460..68771e4b 100644 +--- a/ssh_config.5 ++++ b/ssh_config.5 +@@ -1322,6 +1322,10 @@ The argument to this keyword must be + or + .Cm no + (the default). ++.It Cm RSAMinSize ++Provides a minimal bits requirement for RSA keys when used for signature and ++verification but not for the key generation. The default value is 1024 and ++can't be reduced. + .It Cm NumberOfPasswordPrompts + Specifies the number of password prompts before giving up. + The argument to this keyword must be an integer. +diff --git a/sshd_config.5 b/sshd_config.5 +index 867a747d..e08811ca 100644 +--- a/sshd_config.5 ++++ b/sshd_config.5 +@@ -1266,6 +1266,10 @@ will refuse connection attempts with a probability of rate/100 (30%) + if there are currently start (10) unauthenticated connections. + The probability increases linearly and all connection attempts + are refused if the number of unauthenticated connections reaches full (60). ++.It Cm RSAMinSize ++Provides a minimal bits requirement for RSA keys when used for signature and ++verification but not for the key generation. The default value is 1024 and ++can't be reduced. + .It Cm ModuliFile + Specifies the + .Xr moduli 5 +diff --git a/sshkey.h b/sshkey.h +index 094815e0..2bb8cb90 100644 +--- a/sshkey.h ++++ b/sshkey.h +@@ -286,6 +286,8 @@ int sshkey_private_serialize_maxsign(struct sshkey *key, + + void sshkey_sig_details_free(struct sshkey_sig_details *); + ++int ssh_set_rsa_min_bits(int minbits); ++ + #ifdef SSHKEY_INTERNAL + int ssh_rsa_sign(const struct sshkey *key, + u_char **sigp, size_t *lenp, const u_char *data, size_t datalen, diff --git a/openssh.spec b/openssh.spec index feee273..eb5904b 100644 --- a/openssh.spec +++ b/openssh.spec @@ -51,7 +51,7 @@ # Do not forget to bump pam_ssh_agent_auth release if you rewind the main package release to 1 %global openssh_ver 8.7p1 -%global openssh_rel 9 +%global openssh_rel 10 %global pam_ssh_agent_ver 0.10.4 %global pam_ssh_agent_rel 4 @@ -212,6 +212,9 @@ Patch980: openssh-8.7p1-sftpscp-dir-create.patch # https://github.com/openssh/openssh-portable/pull/299 # downstream only Patch981: openssh-8.7p1-recursive-scp.patch +# https://github.com/djmdjm/openssh-wip/pull/13 +Patch982: openssh-8.7p1-minrsabits.patch + # Minimize the use of SHA1 as a proof of possession for RSA key (#2031868) # upstream commits: # 291721bc7c840d113a49518f3fca70e86248b8e8 @@ -398,6 +401,7 @@ popd %patch979 -p1 -b .find-principals %patch980 -p1 -b .sftpdirs %patch981 -p1 -b .scp-sftpdirs +%patch982 -p1 -b .minrsabits %patch200 -p1 -b .audit %patch201 -p1 -b .audit-race @@ -684,6 +688,10 @@ test -f %{sysconfig_anaconda} && \ %endif %changelog +* Wed Jun 29 2022 Dmitry Belyavskiy - 8.7p1-10 +- Set minimal value of RSA key length via configuration option + Related: rhbz#2066882 + * Wed Jun 29 2022 Zoltan Fridrich - 8.7p1-9 - Update minimize-sha1-use.patch to use upstream code Related: rhbz#2031868