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