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