]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - crypto/openssh/ssh-sk-client.c
zfs: merge openzfs/zfs@9198de8f1
[FreeBSD/FreeBSD.git] / crypto / openssh / ssh-sk-client.c
1 /* $OpenBSD: ssh-sk-client.c,v 1.12 2022/01/14 03:34:00 djm Exp $ */
2 /*
3  * Copyright (c) 2019 Google LLC
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17
18 #include "includes.h"
19
20 #include <sys/types.h>
21 #include <sys/socket.h>
22 #include <sys/wait.h>
23
24 #include <fcntl.h>
25 #include <limits.h>
26 #include <errno.h>
27 #include <signal.h>
28 #include <stdarg.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33
34 #include "log.h"
35 #include "ssherr.h"
36 #include "sshbuf.h"
37 #include "sshkey.h"
38 #include "msg.h"
39 #include "digest.h"
40 #include "pathnames.h"
41 #include "ssh-sk.h"
42 #include "misc.h"
43
44 /* #define DEBUG_SK 1 */
45
46 static int
47 start_helper(int *fdp, pid_t *pidp, void (**osigchldp)(int))
48 {
49         void (*osigchld)(int);
50         int oerrno, pair[2];
51         pid_t pid;
52         char *helper, *verbosity = NULL;
53
54         *fdp = -1;
55         *pidp = 0;
56         *osigchldp = SIG_DFL;
57
58         helper = getenv("SSH_SK_HELPER");
59         if (helper == NULL || strlen(helper) == 0)
60                 helper = _PATH_SSH_SK_HELPER;
61         if (access(helper, X_OK) != 0) {
62                 oerrno = errno;
63                 error_f("helper \"%s\" unusable: %s", helper, strerror(errno));
64                 errno = oerrno;
65                 return SSH_ERR_SYSTEM_ERROR;
66         }
67 #ifdef DEBUG_SK
68         verbosity = "-vvv";
69 #endif
70
71         /* Start helper */
72         if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) {
73                 error("socketpair: %s", strerror(errno));
74                 return SSH_ERR_SYSTEM_ERROR;
75         }
76         osigchld = ssh_signal(SIGCHLD, SIG_DFL);
77         if ((pid = fork()) == -1) {
78                 oerrno = errno;
79                 error("fork: %s", strerror(errno));
80                 close(pair[0]);
81                 close(pair[1]);
82                 ssh_signal(SIGCHLD, osigchld);
83                 errno = oerrno;
84                 return SSH_ERR_SYSTEM_ERROR;
85         }
86         if (pid == 0) {
87                 if ((dup2(pair[1], STDIN_FILENO) == -1) ||
88                     (dup2(pair[1], STDOUT_FILENO) == -1)) {
89                         error_f("dup2: %s", strerror(errno));
90                         _exit(1);
91                 }
92                 close(pair[0]);
93                 close(pair[1]);
94                 closefrom(STDERR_FILENO + 1);
95                 debug_f("starting %s %s", helper,
96                     verbosity == NULL ? "" : verbosity);
97                 execlp(helper, helper, verbosity, (char *)NULL);
98                 error_f("execlp: %s", strerror(errno));
99                 _exit(1);
100         }
101         close(pair[1]);
102
103         /* success */
104         debug3_f("started pid=%ld", (long)pid);
105         *fdp = pair[0];
106         *pidp = pid;
107         *osigchldp = osigchld;
108         return 0;
109 }
110
111 static int
112 reap_helper(pid_t pid)
113 {
114         int status, oerrno;
115
116         debug3_f("pid=%ld", (long)pid);
117
118         errno = 0;
119         while (waitpid(pid, &status, 0) == -1) {
120                 if (errno == EINTR) {
121                         errno = 0;
122                         continue;
123                 }
124                 oerrno = errno;
125                 error_f("waitpid: %s", strerror(errno));
126                 errno = oerrno;
127                 return SSH_ERR_SYSTEM_ERROR;
128         }
129         if (!WIFEXITED(status)) {
130                 error_f("helper exited abnormally");
131                 return SSH_ERR_AGENT_FAILURE;
132         } else if (WEXITSTATUS(status) != 0) {
133                 error_f("helper exited with non-zero exit status");
134                 return SSH_ERR_AGENT_FAILURE;
135         }
136         return 0;
137 }
138
139 static int
140 client_converse(struct sshbuf *msg, struct sshbuf **respp, u_int type)
141 {
142         int oerrno, fd, r2, ll, r = SSH_ERR_INTERNAL_ERROR;
143         u_int rtype, rerr;
144         pid_t pid;
145         u_char version;
146         void (*osigchld)(int);
147         struct sshbuf *req = NULL, *resp = NULL;
148         *respp = NULL;
149
150         if ((r = start_helper(&fd, &pid, &osigchld)) != 0)
151                 return r;
152
153         if ((req = sshbuf_new()) == NULL || (resp = sshbuf_new()) == NULL) {
154                 r = SSH_ERR_ALLOC_FAIL;
155                 goto out;
156         }
157         /* Request preamble: type, log_on_stderr, log_level */
158         ll = log_level_get();
159         if ((r = sshbuf_put_u32(req, type)) != 0 ||
160             (r = sshbuf_put_u8(req, log_is_on_stderr() != 0)) != 0 ||
161             (r = sshbuf_put_u32(req, ll < 0 ? 0 : ll)) != 0 ||
162             (r = sshbuf_putb(req, msg)) != 0) {
163                 error_fr(r, "compose");
164                 goto out;
165         }
166         if ((r = ssh_msg_send(fd, SSH_SK_HELPER_VERSION, req)) != 0) {
167                 error_fr(r, "send");
168                 goto out;
169         }
170         if ((r = ssh_msg_recv(fd, resp)) != 0) {
171                 error_fr(r, "receive");
172                 goto out;
173         }
174         if ((r = sshbuf_get_u8(resp, &version)) != 0) {
175                 error_fr(r, "parse version");
176                 goto out;
177         }
178         if (version != SSH_SK_HELPER_VERSION) {
179                 error_f("unsupported version: got %u, expected %u",
180                     version, SSH_SK_HELPER_VERSION);
181                 r = SSH_ERR_INVALID_FORMAT;
182                 goto out;
183         }
184         if ((r = sshbuf_get_u32(resp, &rtype)) != 0) {
185                 error_fr(r, "parse message type");
186                 goto out;
187         }
188         if (rtype == SSH_SK_HELPER_ERROR) {
189                 if ((r = sshbuf_get_u32(resp, &rerr)) != 0) {
190                         error_fr(r, "parse");
191                         goto out;
192                 }
193                 debug_f("helper returned error -%u", rerr);
194                 /* OpenSSH error values are negative; encoded as -err on wire */
195                 if (rerr == 0 || rerr >= INT_MAX)
196                         r = SSH_ERR_INTERNAL_ERROR;
197                 else
198                         r = -(int)rerr;
199                 goto out;
200         } else if (rtype != type) {
201                 error_f("helper returned incorrect message type %u, "
202                     "expecting %u", rtype, type);
203                 r = SSH_ERR_INTERNAL_ERROR;
204                 goto out;
205         }
206         /* success */
207         r = 0;
208  out:
209         oerrno = errno;
210         close(fd);
211         if ((r2 = reap_helper(pid)) != 0) {
212                 if (r == 0) {
213                         r = r2;
214                         oerrno = errno;
215                 }
216         }
217         if (r == 0) {
218                 *respp = resp;
219                 resp = NULL;
220         }
221         sshbuf_free(req);
222         sshbuf_free(resp);
223         ssh_signal(SIGCHLD, osigchld);
224         errno = oerrno;
225         return r;
226
227 }
228
229 int
230 sshsk_sign(const char *provider, struct sshkey *key,
231     u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
232     u_int compat, const char *pin)
233 {
234         int oerrno, r = SSH_ERR_INTERNAL_ERROR;
235         struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL;
236
237         *sigp = NULL;
238         *lenp = 0;
239
240 #ifndef ENABLE_SK
241         return SSH_ERR_KEY_TYPE_UNKNOWN;
242 #endif
243
244         if ((kbuf = sshbuf_new()) == NULL ||
245             (req = sshbuf_new()) == NULL) {
246                 r = SSH_ERR_ALLOC_FAIL;
247                 goto out;
248         }
249
250         if ((r = sshkey_private_serialize(key, kbuf)) != 0) {
251                 error_fr(r, "encode key");
252                 goto out;
253         }
254         if ((r = sshbuf_put_stringb(req, kbuf)) != 0 ||
255             (r = sshbuf_put_cstring(req, provider)) != 0 ||
256             (r = sshbuf_put_string(req, data, datalen)) != 0 ||
257             (r = sshbuf_put_cstring(req, NULL)) != 0 || /* alg */
258             (r = sshbuf_put_u32(req, compat)) != 0 ||
259             (r = sshbuf_put_cstring(req, pin)) != 0) {
260                 error_fr(r, "compose");
261                 goto out;
262         }
263
264         if ((r = client_converse(req, &resp, SSH_SK_HELPER_SIGN)) != 0)
265                 goto out;
266
267         if ((r = sshbuf_get_string(resp, sigp, lenp)) != 0) {
268                 error_fr(r, "parse signature");
269                 r = SSH_ERR_INVALID_FORMAT;
270                 goto out;
271         }
272         if (sshbuf_len(resp) != 0) {
273                 error_f("trailing data in response");
274                 r = SSH_ERR_INVALID_FORMAT;
275                 goto out;
276         }
277         /* success */
278         r = 0;
279  out:
280         oerrno = errno;
281         if (r != 0) {
282                 freezero(*sigp, *lenp);
283                 *sigp = NULL;
284                 *lenp = 0;
285         }
286         sshbuf_free(kbuf);
287         sshbuf_free(req);
288         sshbuf_free(resp);
289         errno = oerrno;
290         return r;
291 }
292
293 int
294 sshsk_enroll(int type, const char *provider_path, const char *device,
295     const char *application, const char *userid, uint8_t flags,
296     const char *pin, struct sshbuf *challenge_buf,
297     struct sshkey **keyp, struct sshbuf *attest)
298 {
299         int oerrno, r = SSH_ERR_INTERNAL_ERROR;
300         struct sshbuf *kbuf = NULL, *abuf = NULL, *req = NULL, *resp = NULL;
301         struct sshkey *key = NULL;
302
303         *keyp = NULL;
304         if (attest != NULL)
305                 sshbuf_reset(attest);
306
307 #ifndef ENABLE_SK
308         return SSH_ERR_KEY_TYPE_UNKNOWN;
309 #endif
310
311         if (type < 0)
312                 return SSH_ERR_INVALID_ARGUMENT;
313
314         if ((abuf = sshbuf_new()) == NULL ||
315             (kbuf = sshbuf_new()) == NULL ||
316             (req = sshbuf_new()) == NULL) {
317                 r = SSH_ERR_ALLOC_FAIL;
318                 goto out;
319         }
320
321         if ((r = sshbuf_put_u32(req, (u_int)type)) != 0 ||
322             (r = sshbuf_put_cstring(req, provider_path)) != 0 ||
323             (r = sshbuf_put_cstring(req, device)) != 0 ||
324             (r = sshbuf_put_cstring(req, application)) != 0 ||
325             (r = sshbuf_put_cstring(req, userid)) != 0 ||
326             (r = sshbuf_put_u8(req, flags)) != 0 ||
327             (r = sshbuf_put_cstring(req, pin)) != 0 ||
328             (r = sshbuf_put_stringb(req, challenge_buf)) != 0) {
329                 error_fr(r, "compose");
330                 goto out;
331         }
332
333         if ((r = client_converse(req, &resp, SSH_SK_HELPER_ENROLL)) != 0)
334                 goto out;
335
336         if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 ||
337             (r = sshbuf_get_stringb(resp, abuf)) != 0) {
338                 error_fr(r, "parse");
339                 r = SSH_ERR_INVALID_FORMAT;
340                 goto out;
341         }
342         if (sshbuf_len(resp) != 0) {
343                 error_f("trailing data in response");
344                 r = SSH_ERR_INVALID_FORMAT;
345                 goto out;
346         }
347         if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) {
348                 error_fr(r, "encode");
349                 goto out;
350         }
351         if (attest != NULL && (r = sshbuf_putb(attest, abuf)) != 0) {
352                 error_fr(r, "encode attestation information");
353                 goto out;
354         }
355
356         /* success */
357         r = 0;
358         *keyp = key;
359         key = NULL;
360  out:
361         oerrno = errno;
362         sshkey_free(key);
363         sshbuf_free(kbuf);
364         sshbuf_free(abuf);
365         sshbuf_free(req);
366         sshbuf_free(resp);
367         errno = oerrno;
368         return r;
369 }
370
371 static void
372 sshsk_free_resident_key(struct sshsk_resident_key *srk)
373 {
374         if (srk == NULL)
375                 return;
376         sshkey_free(srk->key);
377         freezero(srk->user_id, srk->user_id_len);
378         free(srk);
379 }
380
381
382 void
383 sshsk_free_resident_keys(struct sshsk_resident_key **srks, size_t nsrks)
384 {
385         size_t i;
386
387         if (srks == NULL || nsrks == 0)
388                 return;
389
390         for (i = 0; i < nsrks; i++)
391                 sshsk_free_resident_key(srks[i]);
392         free(srks);
393 }
394
395 int
396 sshsk_load_resident(const char *provider_path, const char *device,
397     const char *pin, u_int flags, struct sshsk_resident_key ***srksp,
398     size_t *nsrksp)
399 {
400         int oerrno, r = SSH_ERR_INTERNAL_ERROR;
401         struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL;
402         struct sshkey *key = NULL;
403         struct sshsk_resident_key *srk = NULL, **srks = NULL, **tmp;
404         u_char *userid = NULL;
405         size_t userid_len = 0, nsrks = 0;
406
407         *srksp = NULL;
408         *nsrksp = 0;
409
410         if ((kbuf = sshbuf_new()) == NULL ||
411             (req = sshbuf_new()) == NULL) {
412                 r = SSH_ERR_ALLOC_FAIL;
413                 goto out;
414         }
415
416         if ((r = sshbuf_put_cstring(req, provider_path)) != 0 ||
417             (r = sshbuf_put_cstring(req, device)) != 0 ||
418             (r = sshbuf_put_cstring(req, pin)) != 0 ||
419             (r = sshbuf_put_u32(req, flags)) != 0) {
420                 error_fr(r, "compose");
421                 goto out;
422         }
423
424         if ((r = client_converse(req, &resp, SSH_SK_HELPER_LOAD_RESIDENT)) != 0)
425                 goto out;
426
427         while (sshbuf_len(resp) != 0) {
428                 /* key, comment, user_id */
429                 if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 ||
430                     (r = sshbuf_get_cstring(resp, NULL, NULL)) != 0 ||
431                     (r = sshbuf_get_string(resp, &userid, &userid_len)) != 0) {
432                         error_fr(r, "parse");
433                         r = SSH_ERR_INVALID_FORMAT;
434                         goto out;
435                 }
436                 if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) {
437                         error_fr(r, "decode key");
438                         goto out;
439                 }
440                 if ((srk = calloc(1, sizeof(*srk))) == NULL) {
441                         error_f("calloc failed");
442                         goto out;
443                 }
444                 srk->key = key;
445                 key = NULL;
446                 srk->user_id = userid;
447                 srk->user_id_len = userid_len;
448                 userid = NULL;
449                 userid_len = 0;
450                 if ((tmp = recallocarray(srks, nsrks, nsrks + 1,
451                     sizeof(*srks))) == NULL) {
452                         error_f("recallocarray keys failed");
453                         goto out;
454                 }
455                 debug_f("srks[%zu]: %s %s uidlen %zu", nsrks,
456                     sshkey_type(srk->key), srk->key->sk_application,
457                     srk->user_id_len);
458                 srks = tmp;
459                 srks[nsrks++] = srk;
460                 srk = NULL;
461         }
462
463         /* success */
464         r = 0;
465         *srksp = srks;
466         *nsrksp = nsrks;
467         srks = NULL;
468         nsrks = 0;
469  out:
470         oerrno = errno;
471         sshsk_free_resident_key(srk);
472         sshsk_free_resident_keys(srks, nsrks);
473         freezero(userid, userid_len);
474         sshkey_free(key);
475         sshbuf_free(kbuf);
476         sshbuf_free(req);
477         sshbuf_free(resp);
478         errno = oerrno;
479         return r;
480 }