sshd side of hostbound public key auth
authordjm <djm@openbsd.org>
Sun, 19 Dec 2021 22:12:30 +0000 (22:12 +0000)
committerdjm <djm@openbsd.org>
Sun, 19 Dec 2021 22:12:30 +0000 (22:12 +0000)
This is identical to the standard "publickey" method, but it also includes
the initial server hostkey in the message signed by the client.

feedback / ok markus@

usr.bin/ssh/auth2-pubkey.c
usr.bin/ssh/monitor.c

index 54f6f04..afde053 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth2-pubkey.c,v 1.111 2021/12/19 22:12:07 djm Exp $ */
+/* $OpenBSD: auth2-pubkey.c,v 1.112 2021/12/19 22:12:30 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  *
@@ -64,6 +64,7 @@
 #include "authfile.h"
 #include "match.h"
 #include "ssherr.h"
+#include "kex.h"
 #include "channels.h" /* XXX for session.h */
 #include "session.h" /* XXX for child_set_env(); refactor? */
 #include "sk-api.h"
@@ -88,19 +89,34 @@ userauth_pubkey(struct ssh *ssh, const char *method)
        Authctxt *authctxt = ssh->authctxt;
        struct passwd *pw = authctxt->pw;
        struct sshbuf *b = NULL;
-       struct sshkey *key = NULL;
+       struct sshkey *key = NULL, *hostkey = NULL;
        char *pkalg = NULL, *userstyle = NULL, *key_s = NULL, *ca_s = NULL;
        u_char *pkblob = NULL, *sig = NULL, have_sig;
        size_t blen, slen;
-       int r, pktype;
+       int hostbound, r, pktype;
        int req_presence = 0, req_verify = 0, authenticated = 0;
        struct sshauthopt *authopts = NULL;
        struct sshkey_sig_details *sig_details = NULL;
 
+       hostbound = strcmp(method, "publickey-hostbound-v00@openssh.com") == 0;
+
        if ((r = sshpkt_get_u8(ssh, &have_sig)) != 0 ||
            (r = sshpkt_get_cstring(ssh, &pkalg, NULL)) != 0 ||
            (r = sshpkt_get_string(ssh, &pkblob, &blen)) != 0)
-               fatal_fr(r, "parse packet");
+               fatal_fr(r, "parse %s packet", method);
+
+       /* hostbound auth includes the hostkey offered at initial KEX */
+       if (hostbound) {
+               if ((r = sshpkt_getb_froms(ssh, &b)) != 0 ||
+                   (r = sshkey_fromb(b, &hostkey)) != 0)
+                       fatal_fr(r, "parse %s hostkey", method);
+               if (ssh->kex->initial_hostkey == NULL)
+                       fatal_f("internal error: initial hostkey not recorded");
+               if (!sshkey_equal(hostkey, ssh->kex->initial_hostkey))
+                       fatal_f("%s packet contained wrong host key", method);
+               sshbuf_free(b);
+               b = NULL;
+       }
 
        if (log_level_get() >= SYSLOG_LEVEL_DEBUG2) {
                char *keystring;
@@ -163,7 +179,8 @@ userauth_pubkey(struct ssh *ssh, const char *method)
                ca_s = format_key(key->cert->signature_key);
 
        if (have_sig) {
-               debug3_f("have %s signature for %s%s%s", pkalg, key_s,
+               debug3_f("%s have %s signature for %s%s%s",
+                   method, pkalg, key_s,
                    ca_s == NULL ? "" : " CA ", ca_s == NULL ? "" : ca_s);
                if ((r = sshpkt_get_string(ssh, &sig, &slen)) != 0 ||
                    (r = sshpkt_get_end(ssh)) != 0)
@@ -193,7 +210,10 @@ userauth_pubkey(struct ssh *ssh, const char *method)
                    (r = sshbuf_put_u8(b, have_sig)) != 0 ||
                    (r = sshbuf_put_cstring(b, pkalg)) != 0 ||
                    (r = sshbuf_put_string(b, pkblob, blen)) != 0)
-                       fatal_fr(r, "reconstruct packet");
+                       fatal_fr(r, "reconstruct %s packet", method);
+               if (hostbound &&
+                   (r = sshkey_puts(ssh->kex->initial_hostkey, b)) != 0)
+                       fatal_fr(r, "reconstruct %s packet", method);
 #ifdef DEBUG_PK
                sshbuf_dump(b, stderr);
 #endif
@@ -243,7 +263,7 @@ userauth_pubkey(struct ssh *ssh, const char *method)
                }
                auth2_record_key(authctxt, authenticated, key);
        } else {
-               debug_f("test pkalg %s pkblob %s%s%s", pkalg, key_s,
+               debug_f("%s test pkalg %s pkblob %s%s%s", method, pkalg, key_s,
                    ca_s == NULL ? "" : " CA ", ca_s == NULL ? "" : ca_s);
 
                if ((r = sshpkt_get_end(ssh)) != 0)
@@ -282,6 +302,7 @@ done:
        sshbuf_free(b);
        sshauthopt_free(authopts);
        sshkey_free(key);
+       sshkey_free(hostkey);
        free(userstyle);
        free(pkalg);
        free(pkblob);
@@ -1064,7 +1085,7 @@ user_key_allowed(struct ssh *ssh, struct passwd *pw, struct sshkey *key,
 
 Authmethod method_pubkey = {
        "publickey",
-       NULL,
+       "publickey-hostbound-v00@openssh.com",
        userauth_pubkey,
        &options.pubkey_authentication
 };
index 3318e12..f44e054 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: monitor.c,v 1.228 2021/08/11 05:20:17 djm Exp $ */
+/* $OpenBSD: monitor.c,v 1.229 2021/12/19 22:12:30 djm Exp $ */
 /*
  * Copyright 2002 Niels Provos <provos@citi.umich.edu>
  * Copyright 2002 Markus Friedl <markus@openbsd.org>
@@ -997,11 +997,12 @@ static int
 monitor_valid_userblob(struct ssh *ssh, const u_char *data, u_int datalen)
 {
        struct sshbuf *b;
+       struct sshkey *hostkey = NULL;
        const u_char *p;
        char *userstyle, *cp;
        size_t len;
        u_char type;
-       int r, fail = 0;
+       int hostbound = 0, r, fail = 0;
 
        if ((b = sshbuf_from(data, datalen)) == NULL)
                fatal_f("sshbuf_from");
@@ -1042,19 +1043,34 @@ monitor_valid_userblob(struct ssh *ssh, const u_char *data, u_int datalen)
        if ((r = sshbuf_skip_string(b)) != 0 || /* service */
            (r = sshbuf_get_cstring(b, &cp, NULL)) != 0)
                fatal_fr(r, "parse method");
-       if (strcmp("publickey", cp) != 0)
-               fail++;
+       if (strcmp("publickey", cp) != 0) {
+               if (strcmp("publickey-hostbound-v00@openssh.com", cp) == 0)
+                       hostbound = 1;
+               else
+                       fail++;
+       }
        free(cp);
        if ((r = sshbuf_get_u8(b, &type)) != 0)
                fatal_fr(r, "parse pktype");
        if (type == 0)
                fail++;
        if ((r = sshbuf_skip_string(b)) != 0 || /* pkalg */
-           (r = sshbuf_skip_string(b)) != 0)   /* pkblob */
+           (r = sshbuf_skip_string(b)) != 0 || /* pkblob */
+           (hostbound && (r = sshkey_froms(b, &hostkey)) != 0))
                fatal_fr(r, "parse pk");
        if (sshbuf_len(b) != 0)
                fail++;
        sshbuf_free(b);
+       if (hostkey != NULL) {
+               /*
+                * Ensure this is actually one of our hostkeys; unfortunately
+                * can't check ssh->kex->initial_hostkey directly at this point
+                * as packet state has not yet been exported to monitor.
+                */
+               if (get_hostkey_index(hostkey, 1, ssh) == -1)
+                       fatal_f("hostbound hostkey does not match");
+               sshkey_free(hostkey);
+       }
        return (fail == 0);
 }