From: "Omar Polo" Subject: Re: gotsysd: gotwebd ssh host key fingerprints To: Stefan Sperling Cc: gameoftrees@openbsd.org Date: Fri, 06 Feb 2026 18:32:07 +0100 Stefan Sperling wrote: > Have gotsysd activate the SSH key fingerprint feature in gotwebd. > Depends on the previous patch I sent for gotwebd. > > ok? sure, ok op@ > make gotsys-write-conf add ssh host key fingerprints to /etc/gotwebd.conf > > M gotsysd/libexec/gotsys-write-conf/gotsys-write-conf.c | 194+ 0- > M regress/gotsysd/test_gotwebd.sh | 25+ 0- > > 2 files changed, 219 insertions(+), 0 deletions(-) > > commit - 896f2e7025bdfa07698b31229ccfdd7de5984ba4 > commit + dfc1f484212cc1f9f103669c27aa655112b738eb > blob - 72e14ac4a5e991c08b11d9829c9912bdc689c94d > blob + da235fcbeb5a5685f3a0f08e020159821903eb27 > --- gotsysd/libexec/gotsys-write-conf/gotsys-write-conf.c > +++ gotsysd/libexec/gotsys-write-conf/gotsys-write-conf.c > @@ -20,9 +20,13 @@ > #include > #include > > +#include > + > #include > +#include > #include > #include > +#include > #include > #include > #include > @@ -64,6 +68,21 @@ static size_t *num_notif_refs_cur; > static size_t num_notif_refs_needed; > static size_t num_notif_refs_received; > > +static char ssh_hostkeys[GOTWEBD_NUM_SSHFP][GOTWEBD_MAX_SSHFP]; > +static const char *ssh_hostkey_paths[GOTWEBD_NUM_SSHFP]; > + > +#ifndef GOTSYS_SSHFP_ECDSA_PATH > +#define GOTSYS_SSHFP_ECDSA_PATH "/etc/ssh/ssh_host_ecdsa_key.pub" > +#endif > + > +#ifndef GOTSYS_SSHFP_ED25519_PATH > +#define GOTSYS_SSHFP_ED25519_PATH "/etc/ssh/ssh_host_ed25519_key.pub" > +#endif > + > +#ifndef GOTSYS_SSHFP_RSA_PATH > +#define GOTSYS_SSHFP_RSA_PATH "/etc/ssh/ssh_host_rsa_key.pub" > +#endif > + > enum writeconf_state { > WRITECONF_STATE_EXPECT_GOTWEB_CFG, > WRITECONF_STATE_EXPECT_GOTWEB_ADDRS, > @@ -1069,6 +1088,48 @@ write_webrepo(int *show_repo_description, int *login_h > "short write to %s", path); > } > free(clone_url); > + > + if (ssh_hostkeys[GOTWEBD_SSHFP_ECDSA][0]) { > + ret = dprintf(fd, "\t\tssh_hostkey_ecdsa \"%s\"\n", > + ssh_hostkeys[GOTWEBD_SSHFP_ECDSA]); > + if (ret == -1) { > + return got_error_from_errno2("dprintf", path); > + } > + if (ret != 22 + > + strlen(ssh_hostkeys[GOTWEBD_SSHFP_ECDSA]) + 1) { > + return got_error_fmt(GOT_ERR_IO, > + "short write to %s", path); > + } > + > + } > + > + if (ssh_hostkeys[GOTWEBD_SSHFP_ED25519][0]) { > + ret = dprintf(fd, "\t\tssh_hostkey_ed25519 \"%s\"\n", > + ssh_hostkeys[GOTWEBD_SSHFP_ED25519]); > + if (ret == -1) { > + return got_error_from_errno2("dprintf", path); > + } > + if (ret != 24 + > + strlen(ssh_hostkeys[GOTWEBD_SSHFP_ED25519]) + 1) { > + return got_error_fmt(GOT_ERR_IO, > + "short write to %s", path); > + } > + > + } > + > + if (ssh_hostkeys[GOTWEBD_SSHFP_RSA][0]) { > + ret = dprintf(fd, "\t\tssh_hostkey_rsa \"%s\"\n", > + ssh_hostkeys[GOTWEBD_SSHFP_RSA]); > + if (ret == -1) { > + return got_error_from_errno2("dprintf", path); > + } > + if (ret != 20 + > + strlen(ssh_hostkeys[GOTWEBD_SSHFP_RSA]) + 1) { > + return got_error_fmt(GOT_ERR_IO, > + "short write to %s", path); > + } > + > + } > } > > ret = dprintf(fd, "\t}\n"); > @@ -2456,12 +2517,123 @@ fatal: > } > } > > +static const struct got_error * > +parse_hostkey(const char *line, char *outbuf, size_t outsize) > +{ > + const struct got_error *err = NULL; > + char *space; > + char *b64; > + uint8_t *hostkey = NULL; > + size_t hostkey_len; > + size_t len; > + uint8_t digest[SHA256_DIGEST_LENGTH]; > + SHA2_CTX ctx; > + uint8_t *b64digest = NULL; > + size_t b64digest_len; > + int ret; > + > + space = strchr(line, ' '); > + if (space == NULL) > + return got_error(GOT_ERR_NOT_FOUND); > + > + b64 = space + 1; > + space = strchr(b64, ' '); > + if (space == NULL) > + return got_error(GOT_ERR_NOT_FOUND); > + > + *space = '\0'; > + > + len = strlen(b64); > + if (len == 0) > + return got_error(GOT_ERR_NOT_FOUND); > + > + hostkey = calloc(1, len); > + if (hostkey == NULL) > + return got_error_from_errno("calloc"); > + > + hostkey_len = b64_pton(b64, hostkey, len); > + if (hostkey_len <= 0) { > + err = got_error(GOT_ERR_NOT_FOUND); > + goto done; > + } > + > + SHA256Init(&ctx); > + SHA256Update(&ctx, hostkey, hostkey_len); > + SHA256Final(digest, &ctx); > + > + b64digest_len = ((len + 2) / 3) * 4 + 1; > + b64digest = calloc(1, b64digest_len); > + if (b64digest == NULL) { > + err = got_error_from_errno("calloc"); > + goto done; > + } > + > + if (b64_ntop(digest, SHA256_DIGEST_LENGTH, > + b64digest, b64digest_len) == -1) { > + err = got_error_from_errno("b64_ntop"); > + goto done; > + } > + > + b64digest[strcspn(b64digest, "=")] = '\0'; > + ret = snprintf(outbuf, outsize, "SHA256:%s\n", b64digest); > + if (ret == -1) { > + err = got_error_from_errno("snprintf"); > + goto done; > + } > + if ((size_t)ret >= outsize) { > + err = got_error_fmt(GOT_ERR_NO_SPACE, "SSH host key " > + "fingerprint too long, exceeds %zu bytes: SHA256:%s", > + outsize, b64digest); > + goto done; > + } > +done: > + free(hostkey); > + free(b64digest); > + return err; > +} > + > +static const struct got_error * > +load_ssh_hostkey(int key_type) > +{ > + const struct got_error *err = NULL; > + const char *path = ssh_hostkey_paths[key_type]; > + FILE *f; > + char *line = NULL; > + size_t linesize = 0; > + ssize_t linelen; > + > + f = fopen(path, "r"); > + if (f == NULL) { > + /* Don't care why it failed. Skip this file. */ > + return NULL; > + } > + > + while ((linelen = getline(&line, &linesize, f)) != -1) { > + if (linelen == 0) > + continue; > + > + err = parse_hostkey(line, ssh_hostkeys[key_type], > + sizeof(ssh_hostkeys[key_type])); > + if (err == NULL || err->code != GOT_ERR_NOT_FOUND) > + break; > + err = NULL; > + } > + > + free(line); > + > + if (fclose(f) == EOF && ferror(f) && err == NULL) > + err = got_ferror(f, GOT_ERR_IO); > + > + return err; > +} > + > int > main(int argc, char *argv[]) > { > const struct got_error *err = NULL; > struct gotsysd_imsgev iev; > struct event evsigint, evsigterm, evsighup, evsigusr1; > + size_t i; > #if 0 > static int attached; > > @@ -2539,11 +2711,33 @@ main(int argc, char *argv[]) > goto done; > } > > + ssh_hostkey_paths[GOTWEBD_SSHFP_ECDSA] = GOTSYS_SSHFP_ECDSA_PATH; > + ssh_hostkey_paths[GOTWEBD_SSHFP_ED25519] = GOTSYS_SSHFP_ED25519_PATH; > + ssh_hostkey_paths[GOTWEBD_SSHFP_RSA] = GOTSYS_SSHFP_RSA_PATH; > + > + for (i = 0; i < nitems(ssh_hostkey_paths); i++) { > + if (unveil(ssh_hostkey_paths[i], "r") == -1) { > + err = got_error_from_errno2("unveil r", > + ssh_hostkey_paths[i]); > + goto done; > + } > + } > + > if (unveil(NULL, NULL) == -1) { > err = got_error_from_errno("unveil"); > goto done; > } > > + err = load_ssh_hostkey(GOTWEBD_SSHFP_ECDSA); > + if (err) > + goto done; > + err = load_ssh_hostkey(GOTWEBD_SSHFP_ED25519); > + if (err) > + goto done; > + err = load_ssh_hostkey(GOTWEBD_SSHFP_RSA); > + if (err) > + goto done; > + > iev.handler = dispatch_event; > iev.events = EV_READ; > iev.handler_arg = NULL; > blob - 937cf1405022eebf9e32c315d2eeb5823f51d4db > blob + b8c0500e50a21e234c5a8ec5f7dd2688296be03e > --- regress/gotsysd/test_gotwebd.sh > +++ regress/gotsysd/test_gotwebd.sh > @@ -645,6 +645,19 @@ EOF > test_summary_page() { > local testroot=`test_init summary_page 1` > > + GOTSYS_ECDSA_HOST_FP=$(ssh -i ${GOTSYSD_SSH_KEY} \ > + ${GOTSYSD_TEST_USER}@${VMIP} \ > + ssh-keygen -lf /etc/ssh/ssh_host_ecdsa_key.pub | \ > + cut -d' ' -f2) > + GOTSYS_ED25519_HOST_FP=$(ssh -i ${GOTSYSD_SSH_KEY} \ > + ${GOTSYSD_TEST_USER}@${VMIP} \ > + ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub | \ > + cut -d' ' -f2) > + GOTSYS_RSA_HOST_FP=$(ssh -i ${GOTSYSD_SSH_KEY} \ > + ${GOTSYSD_TEST_USER}@${VMIP} \ > + ssh-keygen -lf /etc/ssh/ssh_host_rsa_key.pub | \ > + cut -d' ' -f2) > + > got checkout -q $testroot/${GOTSYS_REPO} $testroot/wt >/dev/null > ret=$? > if [ $ret -ne 0 ]; then > @@ -779,6 +792,18 @@ Clone URL: > > ssh://${GOTSYSD_TEST_USER}@${VMIP}/public.git > > +ECDSA: > + > + ${GOTSYS_ECDSA_HOST_FP} > + > +ED25519: > + > + ${GOTSYS_ED25519_HOST_FP} > + > +RSA: > + > + ${GOTSYS_RSA_HOST_FP} > + > Commit Briefs > > right now $short_commit_id Flan Hacker