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."); @@ -1762,6 +1762,12 @@ load_identity_file(Identity *id) private = NULL; quit = 1; } + if (r = sshkey_check_rsa_length(private, options.rsa_min_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, @@ -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,