> Sent: Monday, February 03, 2020 at 2:28 AM
> From: "Damien Miller" <[email protected]>
> To: "Aham Brahmasmi" <[email protected]>
> Cc: Misc <[email protected]>
> Subject: Re: ssh: probable bug in ssh -current
>
> On Fri, 31 Jan 2020, Aham Brahmasmi wrote:
>
> > Bug:
> > When the client connects to the server, they use the ed25519-cert to
> > establish the connection. After the ssh session is established, the
> > server sends the "[email protected]" message with the server's
> > ed25519 host public key.
> >
> > This results in the client looping over the keys in known hosts file,
> > and deciding that the @cert-authority host certificate authority public
> > key is "deprecated", because it was not sent by the server [1]. The
> > client then informs the user:
> > "
> > The server has updated its host keys.
> > These changes were verified by the server's existing trusted key.
> > Deprecating obsolete hostkey: ED25519
> > SHA256:<host_ca_public_key_fingerprint>
> > Accept updated hostkeys? (yes/no):
> > "
>
> Could you plesse try this patch?
>
>
> Index: clientloop.c
> ===================================================================
> RCS file: /cvs/src/usr.bin/ssh/clientloop.c,v
> retrieving revision 1.338
> diff -u -p -r1.338 clientloop.c
> --- clientloop.c 30 Jan 2020 07:20:57 -0000 1.338
> +++ clientloop.c 3 Feb 2020 02:27:18 -0000
> @@ -1856,13 +1859,25 @@ hostkeys_find(struct hostkey_foreach_lin
>
> /* Mark off keys we've already seen for this host */
> for (i = 0; i < ctx->nkeys; i++) {
> - if (sshkey_equal(l->key, ctx->keys[i])) {
> + if ((l->marker & MRK_CA) != 0) {
> + if (!sshkey_is_cert(ctx->keys[i]))
> + continue;
> + if (!sshkey_equal(ctx->keys[i]->cert->signature_key,
> + l->key))
> + continue;
> + debug3("%s: found %s CA key at %s:%ld", __func__,
> + sshkey_ssh_name(ctx->keys[i]), l->path, l->linenum);
> + ctx->keys_seen[i] = 1;
> + return 0;
> + } else if (sshkey_equal(l->key, ctx->keys[i])) {
> debug3("%s: found %s key at %s:%ld", __func__,
> sshkey_ssh_name(ctx->keys[i]), l->path, l->linenum);
> ctx->keys_seen[i] = 1;
> return 0;
> }
> }
> + if ((l->marker & MRK_REVOKE) != 0)
> + return 0;
> /* This line contained a key that not offered by the server */
> debug3("%s: deprecated %s key at %s:%ld", __func__,
> sshkey_ssh_name(l->key), l->path, l->linenum);
> @@ -1961,10 +1976,11 @@ update_known_hosts(struct hostkeys_updat
> if (stat(options.user_hostfiles[i], &sb) != 0) {
> if (errno == ENOENT) {
> debug("%s: known hosts file %s does not exist",
> - __func__, strerror(errno));
> + __func__, options.user_hostfiles[i]);
> } else {
> - error("%s: known hosts file %s inaccessible",
> - __func__, strerror(errno));
> + error("%s: known hosts file %s inaccessible: "
> + "%s", __func__, options.user_hostfiles[i],
> + strerror(errno));
> }
> continue;
> }
>
Namaste Damien,
Thank you very much for the patch. I apologize for the delay in my
response.
Applying the patch as is on -current (clientloop.c, v 1.340) did not
resolve the problem. In fact, the patched ssh client tried to
simultaneously add as well as delete the ssh-ed25519 key present in line
2.
As a result, I tried to read and execute the code. Here is what I
understood:
1) The changes to the strings in the error and debug functions work
correctly.
2) I think we may benefit from changing the lines in the patch:
if ((l->marker & MRK_CA) != 0) {
to
if (l->marker == MRK_CA) {
and
if ((l->marker & MRK_REVOKE) != 0)
to
if (l->marker == MRK_REVOKE)
I base this on what I saw in record_hostkey function in hostfile.c [1].
In case we do intend to perform bit-wise and operations, could you
please share the rationale behind it?
3) Now, with the above change, given that the known_hosts contains:
@cert-authority <server> ssh-ed25519 <host_ca_public_key>
<server> ssh-ed25519 <server_ed25519_host_public_key>
the following is what happens during the first iteration of
hostkeys_find function in the patched clientloop.c (1.340 + patch +
above change):
the execution enters the following if block and the continue is
executed:
if (!sshkey_is_cert(ctx->keys[i]))
continue;
The sshkey_is_cert function is in the sshkey.c file [2].
...
int
sshkey_type_is_cert(int type)
{
const struct keytype *kt;
for (kt = keytypes; kt->type != -1; kt++) {
if (kt->type == type)
return kt->cert;
}
return 0;
}
...
int
sshkey_is_cert(const struct sshkey *k)
{
if (k == NULL)
return 0;
return sshkey_type_is_cert(k->type);
}
...
During the execution, this sshkey_is_cert function returns 0. This is
because the argument key is of type=3 which implies "KEY_ED25519"
according to the enum sshkey_types in file sshkey.h [3].
...
/* Key types */
enum sshkey_types {
KEY_RSA,
KEY_DSA,
KEY_ECDSA,
KEY_ED25519,
KEY_RSA_CERT,
KEY_DSA_CERT,
KEY_ECDSA_CERT,
KEY_ED25519_CERT,
KEY_XMSS,
KEY_XMSS_CERT,
KEY_ECDSA_SK,
KEY_ECDSA_SK_CERT,
KEY_ED25519_SK,
KEY_ED25519_SK_CERT,
KEY_UNSPEC
};
...
The enum keytypes is defined in file sshkey.c [2] as
...
/* Supported key types */
struct keytype {
const char *name;
const char *shortname;
const char *sigalg;
int type;
int nid;
int cert;
int sigonly;
};
static const struct keytype keytypes[] = {
{ "ssh-ed25519", "ED25519", NULL, KEY_ED25519, 0, 0, 0 },
{ "[email protected]", "ED25519-CERT", NULL,
KEY_ED25519_CERT, 0, 1, 0 },
...
Since "ssh-ed25519" (KEY_ED25519) has cert=0, the sshkey_is_cert
function returns 0.
Now, the notify_hostkeys function in the file sshd.c [4] contains
...
for (i = nkeys = 0; i < options.num_host_key_files; i++) {
key = get_hostkey_public_by_index(i, ssh);
if (key == NULL || key->type == KEY_UNSPEC ||
sshkey_is_cert(key))
continue;
...
which implies that the ssh daemon does not seem to send the ssh
host certificate keys as part of the hostkeys-00 message.
Further, the client_input_hostkeys function in the file clientloop.c [5]
contains
...
/* Skip certs */
if (sshkey_is_cert(key)) {
debug3("%s: %s key is a certificate; skipping",
__func__, sshkey_ssh_name(key));
continue;
}
...
which implies that the ssh client seems to be skipping the ssh host
certificate keys.
So, there does not seem to be any way to satisfy the sshkey_is_cert
function check in the patch, because from what I understand, the server
does not seem to send the certificate keys and the client skips the
certificate keys before iterating through the known_hosts file.
I am out of ideas beyond this. I am sorry. If there is a stupid mistake
that I am making here, please let me know.
Dhanyavaad,
ab
[1] -
https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/hostfile.c?rev=1.77&content-type=text/x-cvsweb-markup
[2] -
https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/sshkey.c?rev=1.99&content-type=text/x-cvsweb-markup
[3] -
https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/sshkey.h?rev=1.44&content-type=text/x-cvsweb-markup
[4] -
https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/sshd.c?rev=1.549&content-type=text/x-cvsweb-markup
[5] -
https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/clientloop.c?rev=1.340&content-type=text/x-cvsweb-markup
---------|---------|---------|---------|---------|---------|---------|--