39b801
diff --git a/auth2-hostbased.c b/auth2-hostbased.c
2b784c
index 36b9d2f5..6b517db4 100644
39b801
--- a/auth2-hostbased.c
39b801
+++ b/auth2-hostbased.c
2b784c
@@ -119,6 +119,11 @@ userauth_hostbased(struct ssh *ssh, const char *method)
39b801
 		    "(null)" : key->cert->signature_type);
39b801
 		goto done;
39b801
 	}
2b784c
+	if ((r = sshkey_check_rsa_length(key,
2b784c
+	    options.required_rsa_size)) != 0) {
2b784c
+		logit_r(r, "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
2b784c
index 962fd342..5d59febc 100644
39b801
--- a/auth2-pubkey.c
39b801
+++ b/auth2-pubkey.c
2b784c
@@ -175,6 +175,11 @@ userauth_pubkey(struct ssh *ssh, const char *method)
39b801
 		    "(null)" : key->cert->signature_type);
39b801
 		goto done;
39b801
 	}
2b784c
+	if ((r = sshkey_check_rsa_length(key,
2b784c
+	    options.required_rsa_size)) != 0) {
2b784c
+		logit_r(r, "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
2b784c
index 7f26c680..42be690b 100644
39b801
--- a/readconf.c
39b801
+++ b/readconf.c
2b784c
@@ -174,7 +174,7 @@ typedef enum {
39b801
 	oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys,
39b801
 	oFingerprintHash, oUpdateHostkeys, oHostbasedAcceptedAlgorithms,
39b801
 	oPubkeyAcceptedAlgorithms, oCASignatureAlgorithms, oProxyJump,
39b801
-	oSecurityKeyProvider, oKnownHostsCommand,
2b784c
+	oSecurityKeyProvider, oKnownHostsCommand, oRequiredRSASize,
39b801
 	oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported
39b801
 } OpCodes;
39b801
 
2b784c
@@ -320,6 +320,8 @@ static struct {
39b801
 	{ "proxyjump", oProxyJump },
39b801
 	{ "securitykeyprovider", oSecurityKeyProvider },
39b801
 	{ "knownhostscommand", oKnownHostsCommand },
2b784c
+	{ "requiredrsasize", oRequiredRSASize },
2b784c
+	{ "rsaminsize", oRequiredRSASize }, /* alias */
39b801
 
39b801
 	{ NULL, oBadOption }
39b801
 };
2b784c
@@ -2176,6 +2177,10 @@ parse_pubkey_algos:
39b801
 			*charptr = xstrdup(arg);
39b801
 		break;
39b801
 
2b784c
+	case oRequiredRSASize:
2b784c
+		intptr = &options->required_rsa_size;
39b801
+		goto parse_int;
39b801
+
39b801
 	case oDeprecated:
39b801
 		debug("%s line %d: Deprecated option \"%s\"",
39b801
 		    filename, linenum, keyword);
2b784c
@@ -2423,6 +2428,7 @@ initialize_options(Options * options)
39b801
 	options->hostbased_accepted_algos = NULL;
39b801
 	options->pubkey_accepted_algos = NULL;
39b801
 	options->known_hosts_command = NULL;
2b784c
+	options->required_rsa_size = -1;
39b801
 }
39b801
 
39b801
 /*
2b784c
@@ -2619,6 +2625,8 @@ fill_default_options(Options * options)
39b801
 	if (options->sk_provider == NULL)
39b801
 		options->sk_provider = xstrdup("$SSH_SK_PROVIDER");
39b801
 #endif
2b784c
+	if (options->required_rsa_size == -1)
2b784c
+		options->required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE;
39b801
 
39b801
 	/* Expand KEX name lists */
39b801
 	all_cipher = cipher_alg_list(',', 0);
2b784c
@@ -3308,6 +3316,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);
2b784c
+	dump_cfg_int(oRequiredRSASize, o->required_rsa_size);
39b801
 
39b801
 	/* String options */
39b801
 	dump_cfg_string(oBindAddress, o->bind_address);
39b801
diff --git a/readconf.h b/readconf.h
2b784c
index f647bd42..ffb5ec4f 100644
39b801
--- a/readconf.h
39b801
+++ b/readconf.h
39b801
@@ -176,6 +176,8 @@ typedef struct {
39b801
 
39b801
 	char   *known_hosts_command;
39b801
 
2b784c
+	int	required_rsa_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
2b784c
index 29df0463..423772b1 100644
39b801
--- a/servconf.c
39b801
+++ b/servconf.c
2b784c
@@ -195,6 +195,7 @@ initialize_server_options(ServerOptions *options)
39b801
 	options->fingerprint_hash = -1;
39b801
 	options->disable_forwarding = -1;
39b801
 	options->expose_userauth_info = -1;
2b784c
+	options->required_rsa_size = -1;
39b801
 }
39b801
 
39b801
 /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */
2b784c
@@ -441,6 +442,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");
2b784c
+	if (options->required_rsa_size == -1)
2b784c
+		options->required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE;
39b801
 
39b801
 	assemble_algorithms(options);
39b801
 
2b784c
@@ -517,6 +520,7 @@ typedef enum {
39b801
 	sStreamLocalBindMask, sStreamLocalBindUnlink,
39b801
 	sAllowStreamLocalForwarding, sFingerprintHash, sDisableForwarding,
39b801
 	sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider,
2b784c
+	sRequiredRSASize,
39b801
 	sDeprecated, sIgnore, sUnsupported
39b801
 } ServerOpCodes;
39b801
 
2b784c
@@ -676,6 +680,8 @@ static struct {
39b801
 	{ "rdomain", sRDomain, SSHCFG_ALL },
39b801
 	{ "casignaturealgorithms", sCASignatureAlgorithms, SSHCFG_ALL },
39b801
 	{ "securitykeyprovider", sSecurityKeyProvider, SSHCFG_GLOBAL },
2b784c
+	{ "requiredrsasize", sRequiredRSASize, SSHCFG_ALL },
2b784c
+	{ "rsaminsize", sRequiredRSASize, SSHCFG_ALL }, /* alias */
39b801
 	{ NULL, sBadOption, 0 }
39b801
 };
39b801
 
2b784c
@@ -2438,6 +2443,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
39b801
 			*charptr = xstrdup(arg);
39b801
 		break;
39b801
 
2b784c
+	case sRequiredRSASize:
2b784c
+		intptr = &options->required_rsa_size;
39b801
+		goto parse_int;
39b801
+
39b801
 	case sDeprecated:
39b801
 	case sIgnore:
39b801
 	case sUnsupported:
2b784c
@@ -2610,6 +2619,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);
2b784c
+	M_CP_INTOPT(required_rsa_size);
39b801
 
39b801
 	/*
39b801
 	 * The bind_mask is a mode_t that may be unsigned, so we can't use
2b784c
@@ -2874,6 +2884,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);
2b784c
+	dump_cfg_int(sRequiredRSASize, o->required_rsa_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
2b784c
index 8a04463e..9346155c 100644
39b801
--- a/servconf.h
39b801
+++ b/servconf.h
2b784c
@@ -229,6 +229,7 @@ typedef struct {
39b801
 	int	expose_userauth_info;
39b801
 	u_int64_t timing_secret;
39b801
 	char   *sk_provider;
2b784c
+	int	required_rsa_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
2b784c
index 559bf2af..25be53d5 100644
39b801
--- a/ssh.c
39b801
+++ b/ssh.c
2b784c
@@ -516,14 +516,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,
2b784c
+		    options.required_rsa_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:
2b784c
@@ -1578,7 +1586,7 @@ 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) \
2b784c
@@ -1586,7 +1594,8 @@ main(int ac, char **av)
2b784c
 #define L_CERT(p,o) do { \
2b784c
 	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]);
2b784c
@@ -2284,7 +2293,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) {
2b784c
@@ -2315,7 +2324,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
2b784c
index f9bd19ea..58fe98db 100644
39b801
--- a/sshconnect2.c
39b801
+++ b/sshconnect2.c
2b784c
@@ -96,6 +96,11 @@ 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
+
2b784c
+	if ((r = sshkey_check_rsa_length(hostkey,
2b784c
+	    options.required_rsa_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.");
2b784c
@@ -1606,6 +1611,13 @@ load_identity_file(Identity *id)
39b801
 			private = NULL;
39b801
 			quit = 1;
39b801
 		}
2b784c
+		if (!quit && (r = sshkey_check_rsa_length(private,
2b784c
+		    options.required_rsa_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,
2b784c
@@ -1752,6 +1764,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],
2b784c
+			    options.required_rsa_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
2b784c
index 17eee9d8..395ef493 100644
39b801
--- a/sshd.c
39b801
+++ b/sshd.c
2b784c
@@ -1870,6 +1870,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,
2b784c
+		    options.required_rsa_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
2b784c
index ed2b5dff..77093235 100644
39b801
--- a/sshkey.c
39b801
+++ b/sshkey.c
2b784c
@@ -2365,18 +2365,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,
2b784c
@@ -2439,7 +2445,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);
2b784c
@@ -3642,7 +3648,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;
2b784c
@@ -4644,7 +4650,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
2b784c
index 094815e0..be254e6b 100644
39b801
--- a/sshkey.h
39b801
+++ b/sshkey.h
2b784c
@@ -273,6 +273,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
2b784c
index b4956aec..e255b9b9 100644
39b801
--- a/ssh.1
39b801
+++ b/ssh.1
2b784c
@@ -571,6 +571,7 @@ For full details of the options listed below, and their possible values, see
2b784c
 .It RemoteCommand
2b784c
 .It RemoteForward
2b784c
 .It RequestTTY
2b784c
+.It RequiredRSASize
2b784c
 .It SendEnv
2b784c
 .It ServerAliveInterval
2b784c
 .It ServerAliveCountMax
39b801
diff --git a/ssh_config.5 b/ssh_config.5
2b784c
index 24a46460..d1ede18e 100644
39b801
--- a/ssh_config.5
39b801
+++ b/ssh_config.5
2b784c
@@ -1634,6 +1634,17 @@ and
2b784c
 .Fl T
2b784c
 flags for
2b784c
 .Xr ssh 1 .
2b784c
+.It Cm RequiredRSASize
2b784c
+Specifies the minimum RSA key size (in bits) that
2b784c
+.Xr ssh 1
2b784c
+will accept.
2b784c
+User authentication keys smaller than this limit will be ignored.
2b784c
+Servers that present host keys smaller than this limit will cause the
2b784c
+connection to be terminated.
2b784c
+The default is
2b784c
+.Cm 1024
2b784c
+bits.
2b784c
+Note that this limit may only be raised from the default.
2b784c
 .It Cm RevokedHostKeys
2b784c
 Specifies revoked host public keys.
2b784c
 Keys listed in this file will be refused for host authentication.
39b801
diff --git a/sshd_config.5 b/sshd_config.5
2b784c
index 867a747d..f5a06637 100644
39b801
--- a/sshd_config.5
39b801
+++ b/sshd_config.5
2b784c
@@ -1596,6 +1596,16 @@ is
2b784c
 .Cm default none ,
2b784c
 which means that rekeying is performed after the cipher's default amount
2b784c
 of data has been sent or received and no time based rekeying is done.
2b784c
+.It Cm RequiredRSASize
2b784c
+Specifies the minimum RSA key size (in bits) that
2b784c
+.Xr sshd 8
2b784c
+will accept.
2b784c
+User and host-based authentication keys smaller than this limit will be
2b784c
+refused.
2b784c
+The default is
2b784c
+.Cm 1024
2b784c
+bits.
2b784c
+Note that this limit may only be raised from the default.
2b784c
 .It Cm RevokedKeys
2b784c
 Specifies revoked public keys file, or
2b784c
 .Cm none