1*38a52bd3SEd Maste /* $OpenBSD: ssh-sk-helper.c,v 1.13 2022/04/29 03:16:48 dtucker Exp $ */ 219261079SEd Maste /* 319261079SEd Maste * Copyright (c) 2019 Google LLC 419261079SEd Maste * 519261079SEd Maste * Permission to use, copy, modify, and distribute this software for any 619261079SEd Maste * purpose with or without fee is hereby granted, provided that the above 719261079SEd Maste * copyright notice and this permission notice appear in all copies. 819261079SEd Maste * 919261079SEd Maste * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 1019261079SEd Maste * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 1119261079SEd Maste * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 1219261079SEd Maste * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 1319261079SEd Maste * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 1419261079SEd Maste * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 1519261079SEd Maste * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1619261079SEd Maste */ 1719261079SEd Maste 1819261079SEd Maste /* 1919261079SEd Maste * This is a tiny program used to isolate the address space used for 2019261079SEd Maste * security key middleware signing operations from ssh-agent. It is similar 2119261079SEd Maste * to ssh-pkcs11-helper.c but considerably simpler as the operations for 2219261079SEd Maste * security keys are stateless. 2319261079SEd Maste * 2419261079SEd Maste * Please crank SSH_SK_HELPER_VERSION in sshkey.h for any incompatible 2519261079SEd Maste * protocol changes. 2619261079SEd Maste */ 2719261079SEd Maste 2819261079SEd Maste #include "includes.h" 2919261079SEd Maste 3019261079SEd Maste #include <limits.h> 3119261079SEd Maste #include <stdarg.h> 3219261079SEd Maste #include <stdio.h> 3319261079SEd Maste #include <stdlib.h> 3419261079SEd Maste #include <string.h> 3519261079SEd Maste #include <unistd.h> 3619261079SEd Maste #include <errno.h> 3719261079SEd Maste 3819261079SEd Maste #include "xmalloc.h" 3919261079SEd Maste #include "log.h" 4019261079SEd Maste #include "sshkey.h" 4119261079SEd Maste #include "authfd.h" 4219261079SEd Maste #include "misc.h" 4319261079SEd Maste #include "sshbuf.h" 4419261079SEd Maste #include "msg.h" 4519261079SEd Maste #include "uidswap.h" 4619261079SEd Maste #include "sshkey.h" 4719261079SEd Maste #include "ssherr.h" 4819261079SEd Maste #include "ssh-sk.h" 4919261079SEd Maste 5019261079SEd Maste #ifdef ENABLE_SK 5119261079SEd Maste extern char *__progname; 5219261079SEd Maste 5319261079SEd Maste static struct sshbuf *reply_error(int r, char *fmt, ...) 5419261079SEd Maste __attribute__((__format__ (printf, 2, 3))); 5519261079SEd Maste 5619261079SEd Maste static struct sshbuf * 5719261079SEd Maste reply_error(int r, char *fmt, ...) 5819261079SEd Maste { 5919261079SEd Maste char *msg; 6019261079SEd Maste va_list ap; 6119261079SEd Maste struct sshbuf *resp; 6219261079SEd Maste 6319261079SEd Maste va_start(ap, fmt); 6419261079SEd Maste xvasprintf(&msg, fmt, ap); 6519261079SEd Maste va_end(ap); 6619261079SEd Maste debug("%s: %s", __progname, msg); 6719261079SEd Maste free(msg); 6819261079SEd Maste 6919261079SEd Maste if (r >= 0) 7019261079SEd Maste fatal_f("invalid error code %d", r); 7119261079SEd Maste 7219261079SEd Maste if ((resp = sshbuf_new()) == NULL) 7319261079SEd Maste fatal("%s: sshbuf_new failed", __progname); 7419261079SEd Maste if (sshbuf_put_u32(resp, SSH_SK_HELPER_ERROR) != 0 || 7519261079SEd Maste sshbuf_put_u32(resp, (u_int)-r) != 0) 7619261079SEd Maste fatal("%s: buffer error", __progname); 7719261079SEd Maste return resp; 7819261079SEd Maste } 7919261079SEd Maste 8019261079SEd Maste /* If the specified string is zero length, then free it and replace with NULL */ 8119261079SEd Maste static void 8219261079SEd Maste null_empty(char **s) 8319261079SEd Maste { 8419261079SEd Maste if (s == NULL || *s == NULL || **s != '\0') 8519261079SEd Maste return; 8619261079SEd Maste 8719261079SEd Maste free(*s); 8819261079SEd Maste *s = NULL; 8919261079SEd Maste } 9019261079SEd Maste 9119261079SEd Maste static struct sshbuf * 9219261079SEd Maste process_sign(struct sshbuf *req) 9319261079SEd Maste { 9419261079SEd Maste int r = SSH_ERR_INTERNAL_ERROR; 9519261079SEd Maste struct sshbuf *resp, *kbuf; 9619261079SEd Maste struct sshkey *key = NULL; 9719261079SEd Maste uint32_t compat; 9819261079SEd Maste const u_char *message; 9919261079SEd Maste u_char *sig = NULL; 10019261079SEd Maste size_t msglen, siglen = 0; 10119261079SEd Maste char *provider = NULL, *pin = NULL; 10219261079SEd Maste 10319261079SEd Maste if ((r = sshbuf_froms(req, &kbuf)) != 0 || 10419261079SEd Maste (r = sshbuf_get_cstring(req, &provider, NULL)) != 0 || 10519261079SEd Maste (r = sshbuf_get_string_direct(req, &message, &msglen)) != 0 || 10619261079SEd Maste (r = sshbuf_get_cstring(req, NULL, NULL)) != 0 || /* alg */ 10719261079SEd Maste (r = sshbuf_get_u32(req, &compat)) != 0 || 10819261079SEd Maste (r = sshbuf_get_cstring(req, &pin, NULL)) != 0) 10919261079SEd Maste fatal_r(r, "%s: parse", __progname); 11019261079SEd Maste if (sshbuf_len(req) != 0) 11119261079SEd Maste fatal("%s: trailing data in request", __progname); 11219261079SEd Maste 11319261079SEd Maste if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) 11419261079SEd Maste fatal_r(r, "%s: Unable to parse private key", __progname); 11519261079SEd Maste if (!sshkey_is_sk(key)) { 11619261079SEd Maste fatal("%s: Unsupported key type %s", 11719261079SEd Maste __progname, sshkey_ssh_name(key)); 11819261079SEd Maste } 11919261079SEd Maste 12019261079SEd Maste debug_f("ready to sign with key %s, provider %s: " 12119261079SEd Maste "msg len %zu, compat 0x%lx", sshkey_type(key), 12219261079SEd Maste provider, msglen, (u_long)compat); 12319261079SEd Maste 12419261079SEd Maste null_empty(&pin); 12519261079SEd Maste 12619261079SEd Maste if ((r = sshsk_sign(provider, key, &sig, &siglen, 12719261079SEd Maste message, msglen, compat, pin)) != 0) { 12819261079SEd Maste resp = reply_error(r, "Signing failed: %s", ssh_err(r)); 12919261079SEd Maste goto out; 13019261079SEd Maste } 13119261079SEd Maste 13219261079SEd Maste if ((resp = sshbuf_new()) == NULL) 13319261079SEd Maste fatal("%s: sshbuf_new failed", __progname); 13419261079SEd Maste 13519261079SEd Maste if ((r = sshbuf_put_u32(resp, SSH_SK_HELPER_SIGN)) != 0 || 13619261079SEd Maste (r = sshbuf_put_string(resp, sig, siglen)) != 0) 13719261079SEd Maste fatal_r(r, "%s: compose", __progname); 13819261079SEd Maste out: 13919261079SEd Maste sshkey_free(key); 14019261079SEd Maste sshbuf_free(kbuf); 14119261079SEd Maste free(provider); 14219261079SEd Maste if (sig != NULL) 14319261079SEd Maste freezero(sig, siglen); 14419261079SEd Maste if (pin != NULL) 14519261079SEd Maste freezero(pin, strlen(pin)); 14619261079SEd Maste return resp; 14719261079SEd Maste } 14819261079SEd Maste 14919261079SEd Maste static struct sshbuf * 15019261079SEd Maste process_enroll(struct sshbuf *req) 15119261079SEd Maste { 15219261079SEd Maste int r; 15319261079SEd Maste u_int type; 15419261079SEd Maste char *provider, *application, *pin, *device, *userid; 15519261079SEd Maste uint8_t flags; 15619261079SEd Maste struct sshbuf *challenge, *attest, *kbuf, *resp; 15719261079SEd Maste struct sshkey *key; 15819261079SEd Maste 15919261079SEd Maste if ((attest = sshbuf_new()) == NULL || 16019261079SEd Maste (kbuf = sshbuf_new()) == NULL) 16119261079SEd Maste fatal("%s: sshbuf_new failed", __progname); 16219261079SEd Maste 16319261079SEd Maste if ((r = sshbuf_get_u32(req, &type)) != 0 || 16419261079SEd Maste (r = sshbuf_get_cstring(req, &provider, NULL)) != 0 || 16519261079SEd Maste (r = sshbuf_get_cstring(req, &device, NULL)) != 0 || 16619261079SEd Maste (r = sshbuf_get_cstring(req, &application, NULL)) != 0 || 16719261079SEd Maste (r = sshbuf_get_cstring(req, &userid, NULL)) != 0 || 16819261079SEd Maste (r = sshbuf_get_u8(req, &flags)) != 0 || 16919261079SEd Maste (r = sshbuf_get_cstring(req, &pin, NULL)) != 0 || 17019261079SEd Maste (r = sshbuf_froms(req, &challenge)) != 0) 17119261079SEd Maste fatal_r(r, "%s: parse", __progname); 17219261079SEd Maste if (sshbuf_len(req) != 0) 17319261079SEd Maste fatal("%s: trailing data in request", __progname); 17419261079SEd Maste 17519261079SEd Maste if (type > INT_MAX) 17619261079SEd Maste fatal("%s: bad type %u", __progname, type); 17719261079SEd Maste if (sshbuf_len(challenge) == 0) { 17819261079SEd Maste sshbuf_free(challenge); 17919261079SEd Maste challenge = NULL; 18019261079SEd Maste } 18119261079SEd Maste null_empty(&device); 18219261079SEd Maste null_empty(&userid); 18319261079SEd Maste null_empty(&pin); 18419261079SEd Maste 18519261079SEd Maste if ((r = sshsk_enroll((int)type, provider, device, application, userid, 18619261079SEd Maste flags, pin, challenge, &key, attest)) != 0) { 18719261079SEd Maste resp = reply_error(r, "Enrollment failed: %s", ssh_err(r)); 18819261079SEd Maste goto out; 18919261079SEd Maste } 19019261079SEd Maste 19119261079SEd Maste if ((resp = sshbuf_new()) == NULL) 19219261079SEd Maste fatal("%s: sshbuf_new failed", __progname); 19319261079SEd Maste if ((r = sshkey_private_serialize(key, kbuf)) != 0) 19419261079SEd Maste fatal_r(r, "%s: encode key", __progname); 19519261079SEd Maste if ((r = sshbuf_put_u32(resp, SSH_SK_HELPER_ENROLL)) != 0 || 19619261079SEd Maste (r = sshbuf_put_stringb(resp, kbuf)) != 0 || 19719261079SEd Maste (r = sshbuf_put_stringb(resp, attest)) != 0) 19819261079SEd Maste fatal_r(r, "%s: compose", __progname); 19919261079SEd Maste 20019261079SEd Maste out: 20119261079SEd Maste sshkey_free(key); 20219261079SEd Maste sshbuf_free(kbuf); 20319261079SEd Maste sshbuf_free(attest); 20419261079SEd Maste sshbuf_free(challenge); 20519261079SEd Maste free(provider); 20619261079SEd Maste free(application); 20719261079SEd Maste if (pin != NULL) 20819261079SEd Maste freezero(pin, strlen(pin)); 20919261079SEd Maste 21019261079SEd Maste return resp; 21119261079SEd Maste } 21219261079SEd Maste 21319261079SEd Maste static struct sshbuf * 21419261079SEd Maste process_load_resident(struct sshbuf *req) 21519261079SEd Maste { 21619261079SEd Maste int r; 21719261079SEd Maste char *provider, *pin, *device; 21819261079SEd Maste struct sshbuf *kbuf, *resp; 2191323ec57SEd Maste struct sshsk_resident_key **srks = NULL; 2201323ec57SEd Maste size_t nsrks = 0, i; 2211323ec57SEd Maste u_int flags; 22219261079SEd Maste 22319261079SEd Maste if ((kbuf = sshbuf_new()) == NULL) 22419261079SEd Maste fatal("%s: sshbuf_new failed", __progname); 22519261079SEd Maste 22619261079SEd Maste if ((r = sshbuf_get_cstring(req, &provider, NULL)) != 0 || 22719261079SEd Maste (r = sshbuf_get_cstring(req, &device, NULL)) != 0 || 2281323ec57SEd Maste (r = sshbuf_get_cstring(req, &pin, NULL)) != 0 || 2291323ec57SEd Maste (r = sshbuf_get_u32(req, &flags)) != 0) 23019261079SEd Maste fatal_r(r, "%s: parse", __progname); 23119261079SEd Maste if (sshbuf_len(req) != 0) 23219261079SEd Maste fatal("%s: trailing data in request", __progname); 23319261079SEd Maste 23419261079SEd Maste null_empty(&device); 23519261079SEd Maste null_empty(&pin); 23619261079SEd Maste 2371323ec57SEd Maste if ((r = sshsk_load_resident(provider, device, pin, flags, 2381323ec57SEd Maste &srks, &nsrks)) != 0) { 23919261079SEd Maste resp = reply_error(r, "sshsk_load_resident failed: %s", 24019261079SEd Maste ssh_err(r)); 24119261079SEd Maste goto out; 24219261079SEd Maste } 24319261079SEd Maste 24419261079SEd Maste if ((resp = sshbuf_new()) == NULL) 24519261079SEd Maste fatal("%s: sshbuf_new failed", __progname); 24619261079SEd Maste 24719261079SEd Maste if ((r = sshbuf_put_u32(resp, SSH_SK_HELPER_LOAD_RESIDENT)) != 0) 24819261079SEd Maste fatal_r(r, "%s: compose", __progname); 24919261079SEd Maste 2501323ec57SEd Maste for (i = 0; i < nsrks; i++) { 2511323ec57SEd Maste debug_f("key %zu %s %s uidlen %zu", i, 2521323ec57SEd Maste sshkey_type(srks[i]->key), srks[i]->key->sk_application, 2531323ec57SEd Maste srks[i]->user_id_len); 25419261079SEd Maste sshbuf_reset(kbuf); 2551323ec57SEd Maste if ((r = sshkey_private_serialize(srks[i]->key, kbuf)) != 0) 25619261079SEd Maste fatal_r(r, "%s: encode key", __progname); 25719261079SEd Maste if ((r = sshbuf_put_stringb(resp, kbuf)) != 0 || 2581323ec57SEd Maste (r = sshbuf_put_cstring(resp, "")) != 0 || /* comment */ 2591323ec57SEd Maste (r = sshbuf_put_string(resp, srks[i]->user_id, 2601323ec57SEd Maste srks[i]->user_id_len)) != 0) 26119261079SEd Maste fatal_r(r, "%s: compose key", __progname); 26219261079SEd Maste } 26319261079SEd Maste 26419261079SEd Maste out: 2651323ec57SEd Maste sshsk_free_resident_keys(srks, nsrks); 26619261079SEd Maste sshbuf_free(kbuf); 26719261079SEd Maste free(provider); 268*38a52bd3SEd Maste free(device); 26919261079SEd Maste if (pin != NULL) 27019261079SEd Maste freezero(pin, strlen(pin)); 27119261079SEd Maste return resp; 27219261079SEd Maste } 27319261079SEd Maste 27419261079SEd Maste int 27519261079SEd Maste main(int argc, char **argv) 27619261079SEd Maste { 27719261079SEd Maste SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; 27819261079SEd Maste LogLevel log_level = SYSLOG_LEVEL_ERROR; 27919261079SEd Maste struct sshbuf *req, *resp; 28019261079SEd Maste int in, out, ch, r, vflag = 0; 28119261079SEd Maste u_int rtype, ll = 0; 28219261079SEd Maste uint8_t version, log_stderr = 0; 28319261079SEd Maste 28419261079SEd Maste sanitise_stdfd(); 28519261079SEd Maste log_init(__progname, log_level, log_facility, log_stderr); 28619261079SEd Maste 28719261079SEd Maste while ((ch = getopt(argc, argv, "v")) != -1) { 28819261079SEd Maste switch (ch) { 28919261079SEd Maste case 'v': 29019261079SEd Maste vflag = 1; 29119261079SEd Maste if (log_level == SYSLOG_LEVEL_ERROR) 29219261079SEd Maste log_level = SYSLOG_LEVEL_DEBUG1; 29319261079SEd Maste else if (log_level < SYSLOG_LEVEL_DEBUG3) 29419261079SEd Maste log_level++; 29519261079SEd Maste break; 29619261079SEd Maste default: 29719261079SEd Maste fprintf(stderr, "usage: %s [-v]\n", __progname); 29819261079SEd Maste exit(1); 29919261079SEd Maste } 30019261079SEd Maste } 30119261079SEd Maste log_init(__progname, log_level, log_facility, vflag); 30219261079SEd Maste 30319261079SEd Maste /* 30419261079SEd Maste * Rearrange our file descriptors a little; we don't trust the 30519261079SEd Maste * providers not to fiddle with stdin/out. 30619261079SEd Maste */ 30719261079SEd Maste closefrom(STDERR_FILENO + 1); 30819261079SEd Maste if ((in = dup(STDIN_FILENO)) == -1 || (out = dup(STDOUT_FILENO)) == -1) 30919261079SEd Maste fatal("%s: dup: %s", __progname, strerror(errno)); 31019261079SEd Maste close(STDIN_FILENO); 31119261079SEd Maste close(STDOUT_FILENO); 31219261079SEd Maste sanitise_stdfd(); /* resets to /dev/null */ 31319261079SEd Maste 31419261079SEd Maste if ((req = sshbuf_new()) == NULL) 31519261079SEd Maste fatal("%s: sshbuf_new failed", __progname); 31619261079SEd Maste if (ssh_msg_recv(in, req) < 0) 31719261079SEd Maste fatal("ssh_msg_recv failed"); 31819261079SEd Maste close(in); 31919261079SEd Maste debug_f("received message len %zu", sshbuf_len(req)); 32019261079SEd Maste 32119261079SEd Maste if ((r = sshbuf_get_u8(req, &version)) != 0) 32219261079SEd Maste fatal_r(r, "%s: parse version", __progname); 32319261079SEd Maste if (version != SSH_SK_HELPER_VERSION) { 32419261079SEd Maste fatal("unsupported version: received %d, expected %d", 32519261079SEd Maste version, SSH_SK_HELPER_VERSION); 32619261079SEd Maste } 32719261079SEd Maste 32819261079SEd Maste if ((r = sshbuf_get_u32(req, &rtype)) != 0 || 32919261079SEd Maste (r = sshbuf_get_u8(req, &log_stderr)) != 0 || 33019261079SEd Maste (r = sshbuf_get_u32(req, &ll)) != 0) 33119261079SEd Maste fatal_r(r, "%s: parse", __progname); 33219261079SEd Maste 33319261079SEd Maste if (!vflag && log_level_name((LogLevel)ll) != NULL) 33419261079SEd Maste log_init(__progname, (LogLevel)ll, log_facility, log_stderr); 33519261079SEd Maste 33619261079SEd Maste switch (rtype) { 33719261079SEd Maste case SSH_SK_HELPER_SIGN: 33819261079SEd Maste resp = process_sign(req); 33919261079SEd Maste break; 34019261079SEd Maste case SSH_SK_HELPER_ENROLL: 34119261079SEd Maste resp = process_enroll(req); 34219261079SEd Maste break; 34319261079SEd Maste case SSH_SK_HELPER_LOAD_RESIDENT: 34419261079SEd Maste resp = process_load_resident(req); 34519261079SEd Maste break; 34619261079SEd Maste default: 34719261079SEd Maste fatal("%s: unsupported request type %u", __progname, rtype); 34819261079SEd Maste } 34919261079SEd Maste sshbuf_free(req); 35019261079SEd Maste debug_f("reply len %zu", sshbuf_len(resp)); 35119261079SEd Maste 35219261079SEd Maste if (ssh_msg_send(out, SSH_SK_HELPER_VERSION, resp) == -1) 35319261079SEd Maste fatal("ssh_msg_send failed"); 35419261079SEd Maste sshbuf_free(resp); 35519261079SEd Maste close(out); 35619261079SEd Maste 35719261079SEd Maste return (0); 35819261079SEd Maste } 35919261079SEd Maste #else /* ENABLE_SK */ 36019261079SEd Maste #include <stdio.h> 36119261079SEd Maste 36219261079SEd Maste int 36319261079SEd Maste main(int argc, char **argv) 36419261079SEd Maste { 36519261079SEd Maste fprintf(stderr, "ssh-sk-helper: disabled at compile time\n"); 36619261079SEd Maste return -1; 36719261079SEd Maste } 36819261079SEd Maste #endif /* ENABLE_SK */ 369