Line | Count | Source (jump to first uncovered line) |
1 | | /* $OpenBSD: ssh-agent.c,v 1.297 2023/03/09 21:06:24 jcs Exp $ */ |
2 | | /* |
3 | | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
4 | | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
5 | | * All rights reserved |
6 | | * The authentication agent program. |
7 | | * |
8 | | * As far as I am concerned, the code I have written for this software |
9 | | * can be used freely for any purpose. Any derived versions of this |
10 | | * software must be clearly marked as such, and if the derived work is |
11 | | * incompatible with the protocol description in the RFC file, it must be |
12 | | * called by a name other than "ssh" or "Secure Shell". |
13 | | * |
14 | | * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. |
15 | | * |
16 | | * Redistribution and use in source and binary forms, with or without |
17 | | * modification, are permitted provided that the following conditions |
18 | | * are met: |
19 | | * 1. Redistributions of source code must retain the above copyright |
20 | | * notice, this list of conditions and the following disclaimer. |
21 | | * 2. Redistributions in binary form must reproduce the above copyright |
22 | | * notice, this list of conditions and the following disclaimer in the |
23 | | * documentation and/or other materials provided with the distribution. |
24 | | * |
25 | | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
26 | | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
27 | | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
28 | | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
29 | | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
30 | | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
31 | | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
32 | | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
33 | | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
34 | | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
35 | | */ |
36 | | |
37 | | #include "includes.h" |
38 | | |
39 | | #include <sys/types.h> |
40 | | #include <sys/resource.h> |
41 | | #include <sys/stat.h> |
42 | | #include <sys/socket.h> |
43 | | #include <sys/wait.h> |
44 | | #ifdef HAVE_SYS_TIME_H |
45 | | # include <sys/time.h> |
46 | | #endif |
47 | | #ifdef HAVE_SYS_UN_H |
48 | | # include <sys/un.h> |
49 | | #endif |
50 | | #include "openbsd-compat/sys-queue.h" |
51 | | |
52 | | #ifdef WITH_OPENSSL |
53 | | #include <openssl/evp.h> |
54 | | #include "openbsd-compat/openssl-compat.h" |
55 | | #endif |
56 | | |
57 | | #include <errno.h> |
58 | | #include <fcntl.h> |
59 | | #include <limits.h> |
60 | | #ifdef HAVE_PATHS_H |
61 | | # include <paths.h> |
62 | | #endif |
63 | | #ifdef HAVE_POLL_H |
64 | | # include <poll.h> |
65 | | #endif |
66 | | #include <signal.h> |
67 | | #include <stdarg.h> |
68 | | #include <stdio.h> |
69 | | #include <stdlib.h> |
70 | | #include <time.h> |
71 | | #include <string.h> |
72 | | #include <unistd.h> |
73 | | #ifdef HAVE_UTIL_H |
74 | | # include <util.h> |
75 | | #endif |
76 | | |
77 | | #include "xmalloc.h" |
78 | | #include "ssh.h" |
79 | | #include "ssh2.h" |
80 | | #include "sshbuf.h" |
81 | | #include "sshkey.h" |
82 | | #include "authfd.h" |
83 | | #include "log.h" |
84 | | #include "misc.h" |
85 | | #include "digest.h" |
86 | | #include "ssherr.h" |
87 | | #include "match.h" |
88 | | #include "msg.h" |
89 | | #include "pathnames.h" |
90 | | #include "ssh-pkcs11.h" |
91 | | #include "sk-api.h" |
92 | | #include "myproposal.h" |
93 | | |
94 | | #ifndef DEFAULT_ALLOWED_PROVIDERS |
95 | 0 | # define DEFAULT_ALLOWED_PROVIDERS "/usr/lib*/*,/usr/local/lib*/*" |
96 | | #endif |
97 | | |
98 | | /* Maximum accepted message length */ |
99 | 199k | #define AGENT_MAX_LEN (256*1024) |
100 | | /* Maximum bytes to read from client socket */ |
101 | 0 | #define AGENT_RBUF_LEN (4096) |
102 | | /* Maximum number of recorded session IDs/hostkeys per connection */ |
103 | 0 | #define AGENT_MAX_SESSION_IDS 16 |
104 | | /* Maximum size of session ID */ |
105 | | #define AGENT_MAX_SID_LEN 128 |
106 | | /* Maximum number of destination constraints to accept on a key */ |
107 | 0 | #define AGENT_MAX_DEST_CONSTRAINTS 1024 |
108 | | |
109 | | /* XXX store hostkey_sid in a refcounted tree */ |
110 | | |
111 | | typedef enum { |
112 | | AUTH_UNUSED = 0, |
113 | | AUTH_SOCKET = 1, |
114 | | AUTH_CONNECTION = 2, |
115 | | } sock_type; |
116 | | |
117 | | struct hostkey_sid { |
118 | | struct sshkey *key; |
119 | | struct sshbuf *sid; |
120 | | int forwarded; |
121 | | }; |
122 | | |
123 | | typedef struct socket_entry { |
124 | | int fd; |
125 | | sock_type type; |
126 | | struct sshbuf *input; |
127 | | struct sshbuf *output; |
128 | | struct sshbuf *request; |
129 | | size_t nsession_ids; |
130 | | struct hostkey_sid *session_ids; |
131 | | } SocketEntry; |
132 | | |
133 | | u_int sockets_alloc = 0; |
134 | | SocketEntry *sockets = NULL; |
135 | | |
136 | | typedef struct identity { |
137 | | TAILQ_ENTRY(identity) next; |
138 | | struct sshkey *key; |
139 | | char *comment; |
140 | | char *provider; |
141 | | time_t death; |
142 | | u_int confirm; |
143 | | char *sk_provider; |
144 | | struct dest_constraint *dest_constraints; |
145 | | size_t ndest_constraints; |
146 | | } Identity; |
147 | | |
148 | | struct idtable { |
149 | | int nentries; |
150 | | TAILQ_HEAD(idqueue, identity) idlist; |
151 | | }; |
152 | | |
153 | | /* private key table */ |
154 | | struct idtable *idtab; |
155 | | |
156 | | int max_fd = 0; |
157 | | |
158 | | /* pid of shell == parent of agent */ |
159 | | pid_t parent_pid = -1; |
160 | | time_t parent_alive_interval = 0; |
161 | | |
162 | | /* pid of process for which cleanup_socket is applicable */ |
163 | | pid_t cleanup_pid = 0; |
164 | | |
165 | | /* pathname and directory for AUTH_SOCKET */ |
166 | | char socket_name[PATH_MAX]; |
167 | | char socket_dir[PATH_MAX]; |
168 | | |
169 | | /* Pattern-list of allowed PKCS#11/Security key paths */ |
170 | | static char *allowed_providers; |
171 | | |
172 | | /* locking */ |
173 | 6.98k | #define LOCK_SIZE 32 |
174 | | #define LOCK_SALT_SIZE 16 |
175 | 6.98k | #define LOCK_ROUNDS 1 |
176 | | int locked = 0; |
177 | | u_char lock_pwhash[LOCK_SIZE]; |
178 | | u_char lock_salt[LOCK_SALT_SIZE]; |
179 | | |
180 | | extern char *__progname; |
181 | | |
182 | | /* Default lifetime in seconds (0 == forever) */ |
183 | | static int lifetime = 0; |
184 | | |
185 | | static int fingerprint_hash = SSH_FP_HASH_DEFAULT; |
186 | | |
187 | | /* Refuse signing of non-SSH messages for web-origin FIDO keys */ |
188 | | static int restrict_websafe = 1; |
189 | | |
190 | | static void |
191 | | close_socket(SocketEntry *e) |
192 | 2.91k | { |
193 | 2.91k | size_t i; |
194 | | |
195 | 2.91k | close(e->fd); |
196 | 2.91k | sshbuf_free(e->input); |
197 | 2.91k | sshbuf_free(e->output); |
198 | 2.91k | sshbuf_free(e->request); |
199 | 2.91k | for (i = 0; i < e->nsession_ids; i++) { |
200 | 0 | sshkey_free(e->session_ids[i].key); |
201 | 0 | sshbuf_free(e->session_ids[i].sid); |
202 | 0 | } |
203 | 2.91k | free(e->session_ids); |
204 | 2.91k | memset(e, '\0', sizeof(*e)); |
205 | 2.91k | e->fd = -1; |
206 | 2.91k | e->type = AUTH_UNUSED; |
207 | 2.91k | } |
208 | | |
209 | | static void |
210 | | idtab_init(void) |
211 | 2.91k | { |
212 | 2.91k | idtab = xcalloc(1, sizeof(*idtab)); |
213 | 2.91k | TAILQ_INIT(&idtab->idlist); |
214 | 2.91k | idtab->nentries = 0; |
215 | 2.91k | } |
216 | | |
217 | | static void |
218 | | free_dest_constraint_hop(struct dest_constraint_hop *dch) |
219 | 0 | { |
220 | 0 | u_int i; |
221 | |
|
222 | 0 | if (dch == NULL) |
223 | 0 | return; |
224 | 0 | free(dch->user); |
225 | 0 | free(dch->hostname); |
226 | 0 | for (i = 0; i < dch->nkeys; i++) |
227 | 0 | sshkey_free(dch->keys[i]); |
228 | 0 | free(dch->keys); |
229 | 0 | free(dch->key_is_ca); |
230 | 0 | } |
231 | | |
232 | | static void |
233 | | free_dest_constraints(struct dest_constraint *dcs, size_t ndcs) |
234 | 34.9k | { |
235 | 34.9k | size_t i; |
236 | | |
237 | 34.9k | for (i = 0; i < ndcs; i++) { |
238 | 0 | free_dest_constraint_hop(&dcs[i].from); |
239 | 0 | free_dest_constraint_hop(&dcs[i].to); |
240 | 0 | } |
241 | 34.9k | free(dcs); |
242 | 34.9k | } |
243 | | |
244 | | static void |
245 | | free_identity(Identity *id) |
246 | 34.9k | { |
247 | 34.9k | sshkey_free(id->key); |
248 | 34.9k | free(id->provider); |
249 | 34.9k | free(id->comment); |
250 | 34.9k | free(id->sk_provider); |
251 | 34.9k | free_dest_constraints(id->dest_constraints, id->ndest_constraints); |
252 | 34.9k | free(id); |
253 | 34.9k | } |
254 | | |
255 | | /* |
256 | | * Match 'key' against the key/CA list in a destination constraint hop |
257 | | * Returns 0 on success or -1 otherwise. |
258 | | */ |
259 | | static int |
260 | | match_key_hop(const char *tag, const struct sshkey *key, |
261 | | const struct dest_constraint_hop *dch) |
262 | 0 | { |
263 | 0 | const char *reason = NULL; |
264 | 0 | const char *hostname = dch->hostname ? dch->hostname : "(ORIGIN)"; |
265 | 0 | u_int i; |
266 | 0 | char *fp; |
267 | |
|
268 | 0 | if (key == NULL) |
269 | 0 | return -1; |
270 | | /* XXX logspam */ |
271 | 0 | if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT, |
272 | 0 | SSH_FP_DEFAULT)) == NULL) |
273 | 0 | fatal_f("fingerprint failed"); |
274 | 0 | debug3_f("%s: entering hostname %s, requested key %s %s, %u keys avail", |
275 | 0 | tag, hostname, sshkey_type(key), fp, dch->nkeys); |
276 | 0 | free(fp); |
277 | 0 | for (i = 0; i < dch->nkeys; i++) { |
278 | 0 | if (dch->keys[i] == NULL) |
279 | 0 | return -1; |
280 | | /* XXX logspam */ |
281 | 0 | if ((fp = sshkey_fingerprint(dch->keys[i], SSH_FP_HASH_DEFAULT, |
282 | 0 | SSH_FP_DEFAULT)) == NULL) |
283 | 0 | fatal_f("fingerprint failed"); |
284 | 0 | debug3_f("%s: key %u: %s%s %s", tag, i, |
285 | 0 | dch->key_is_ca[i] ? "CA " : "", |
286 | 0 | sshkey_type(dch->keys[i]), fp); |
287 | 0 | free(fp); |
288 | 0 | if (!sshkey_is_cert(key)) { |
289 | | /* plain key */ |
290 | 0 | if (dch->key_is_ca[i] || |
291 | 0 | !sshkey_equal(key, dch->keys[i])) |
292 | 0 | continue; |
293 | 0 | return 0; |
294 | 0 | } |
295 | | /* certificate */ |
296 | 0 | if (!dch->key_is_ca[i]) |
297 | 0 | continue; |
298 | 0 | if (key->cert == NULL || key->cert->signature_key == NULL) |
299 | 0 | return -1; /* shouldn't happen */ |
300 | 0 | if (!sshkey_equal(key->cert->signature_key, dch->keys[i])) |
301 | 0 | continue; |
302 | 0 | if (sshkey_cert_check_host(key, hostname, 1, |
303 | 0 | SSH_ALLOWED_CA_SIGALGS, &reason) != 0) { |
304 | 0 | debug_f("cert %s / hostname %s rejected: %s", |
305 | 0 | key->cert->key_id, hostname, reason); |
306 | 0 | continue; |
307 | 0 | } |
308 | 0 | return 0; |
309 | 0 | } |
310 | 0 | return -1; |
311 | 0 | } |
312 | | |
313 | | /* Check destination constraints on an identity against the hostkey/user */ |
314 | | static int |
315 | | permitted_by_dest_constraints(const struct sshkey *fromkey, |
316 | | const struct sshkey *tokey, Identity *id, const char *user, |
317 | | const char **hostnamep) |
318 | 0 | { |
319 | 0 | size_t i; |
320 | 0 | struct dest_constraint *d; |
321 | |
|
322 | 0 | if (hostnamep != NULL) |
323 | 0 | *hostnamep = NULL; |
324 | 0 | for (i = 0; i < id->ndest_constraints; i++) { |
325 | 0 | d = id->dest_constraints + i; |
326 | | /* XXX remove logspam */ |
327 | 0 | debug2_f("constraint %zu %s%s%s (%u keys) > %s%s%s (%u keys)", |
328 | 0 | i, d->from.user ? d->from.user : "", |
329 | 0 | d->from.user ? "@" : "", |
330 | 0 | d->from.hostname ? d->from.hostname : "(ORIGIN)", |
331 | 0 | d->from.nkeys, |
332 | 0 | d->to.user ? d->to.user : "", d->to.user ? "@" : "", |
333 | 0 | d->to.hostname ? d->to.hostname : "(ANY)", d->to.nkeys); |
334 | | |
335 | | /* Match 'from' key */ |
336 | 0 | if (fromkey == NULL) { |
337 | | /* We are matching the first hop */ |
338 | 0 | if (d->from.hostname != NULL || d->from.nkeys != 0) |
339 | 0 | continue; |
340 | 0 | } else if (match_key_hop("from", fromkey, &d->from) != 0) |
341 | 0 | continue; |
342 | | |
343 | | /* Match 'to' key */ |
344 | 0 | if (tokey != NULL && match_key_hop("to", tokey, &d->to) != 0) |
345 | 0 | continue; |
346 | | |
347 | | /* Match user if specified */ |
348 | 0 | if (d->to.user != NULL && user != NULL && |
349 | 0 | !match_pattern(user, d->to.user)) |
350 | 0 | continue; |
351 | | |
352 | | /* successfully matched this constraint */ |
353 | 0 | if (hostnamep != NULL) |
354 | 0 | *hostnamep = d->to.hostname; |
355 | 0 | debug2_f("allowed for hostname %s", |
356 | 0 | d->to.hostname == NULL ? "*" : d->to.hostname); |
357 | 0 | return 0; |
358 | 0 | } |
359 | | /* no match */ |
360 | 0 | debug2_f("%s identity \"%s\" not permitted for this destination", |
361 | 0 | sshkey_type(id->key), id->comment); |
362 | 0 | return -1; |
363 | 0 | } |
364 | | |
365 | | /* |
366 | | * Check whether hostkeys on a SocketEntry and the optionally specified user |
367 | | * are permitted by the destination constraints on the Identity. |
368 | | * Returns 0 on success or -1 otherwise. |
369 | | */ |
370 | | static int |
371 | | identity_permitted(Identity *id, SocketEntry *e, char *user, |
372 | | const char **forward_hostnamep, const char **last_hostnamep) |
373 | 48 | { |
374 | 48 | size_t i; |
375 | 48 | const char **hp; |
376 | 48 | struct hostkey_sid *hks; |
377 | 48 | const struct sshkey *fromkey = NULL; |
378 | 48 | const char *test_user; |
379 | 48 | char *fp1, *fp2; |
380 | | |
381 | | /* XXX remove logspam */ |
382 | 48 | debug3_f("entering: key %s comment \"%s\", %zu socket bindings, " |
383 | 48 | "%zu constraints", sshkey_type(id->key), id->comment, |
384 | 48 | e->nsession_ids, id->ndest_constraints); |
385 | 48 | if (id->ndest_constraints == 0) |
386 | 48 | return 0; /* unconstrained */ |
387 | 0 | if (e->nsession_ids == 0) |
388 | 0 | return 0; /* local use */ |
389 | | /* |
390 | | * Walk through the hops recorded by session_id and try to find a |
391 | | * constraint that satisfies each. |
392 | | */ |
393 | 0 | for (i = 0; i < e->nsession_ids; i++) { |
394 | 0 | hks = e->session_ids + i; |
395 | 0 | if (hks->key == NULL) |
396 | 0 | fatal_f("internal error: no bound key"); |
397 | | /* XXX remove logspam */ |
398 | 0 | fp1 = fp2 = NULL; |
399 | 0 | if (fromkey != NULL && |
400 | 0 | (fp1 = sshkey_fingerprint(fromkey, SSH_FP_HASH_DEFAULT, |
401 | 0 | SSH_FP_DEFAULT)) == NULL) |
402 | 0 | fatal_f("fingerprint failed"); |
403 | 0 | if ((fp2 = sshkey_fingerprint(hks->key, SSH_FP_HASH_DEFAULT, |
404 | 0 | SSH_FP_DEFAULT)) == NULL) |
405 | 0 | fatal_f("fingerprint failed"); |
406 | 0 | debug3_f("socketentry fd=%d, entry %zu %s, " |
407 | 0 | "from hostkey %s %s to user %s hostkey %s %s", |
408 | 0 | e->fd, i, hks->forwarded ? "FORWARD" : "AUTH", |
409 | 0 | fromkey ? sshkey_type(fromkey) : "(ORIGIN)", |
410 | 0 | fromkey ? fp1 : "", user ? user : "(ANY)", |
411 | 0 | sshkey_type(hks->key), fp2); |
412 | 0 | free(fp1); |
413 | 0 | free(fp2); |
414 | | /* |
415 | | * Record the hostnames for the initial forwarding and |
416 | | * the final destination. |
417 | | */ |
418 | 0 | hp = NULL; |
419 | 0 | if (i == e->nsession_ids - 1) |
420 | 0 | hp = last_hostnamep; |
421 | 0 | else if (i == 0) |
422 | 0 | hp = forward_hostnamep; |
423 | | /* Special handling for final recorded binding */ |
424 | 0 | test_user = NULL; |
425 | 0 | if (i == e->nsession_ids - 1) { |
426 | | /* Can only check user at final hop */ |
427 | 0 | test_user = user; |
428 | | /* |
429 | | * user is only presented for signature requests. |
430 | | * If this is the case, make sure last binding is not |
431 | | * for a forwarding. |
432 | | */ |
433 | 0 | if (hks->forwarded && user != NULL) { |
434 | 0 | error_f("tried to sign on forwarding hop"); |
435 | 0 | return -1; |
436 | 0 | } |
437 | 0 | } else if (!hks->forwarded) { |
438 | 0 | error_f("tried to forward though signing bind"); |
439 | 0 | return -1; |
440 | 0 | } |
441 | 0 | if (permitted_by_dest_constraints(fromkey, hks->key, id, |
442 | 0 | test_user, hp) != 0) |
443 | 0 | return -1; |
444 | 0 | fromkey = hks->key; |
445 | 0 | } |
446 | | /* |
447 | | * Another special case: if the last bound session ID was for a |
448 | | * forwarding, and this function is not being called to check a sign |
449 | | * request (i.e. no 'user' supplied), then only permit the key if |
450 | | * there is a permission that would allow it to be used at another |
451 | | * destination. This hides keys that are allowed to be used to |
452 | | * authenticate *to* a host but not permitted for *use* beyond it. |
453 | | */ |
454 | 0 | hks = &e->session_ids[e->nsession_ids - 1]; |
455 | 0 | if (hks->forwarded && user == NULL && |
456 | 0 | permitted_by_dest_constraints(hks->key, NULL, id, |
457 | 0 | NULL, NULL) != 0) { |
458 | 0 | debug3_f("key permitted at host but not after"); |
459 | 0 | return -1; |
460 | 0 | } |
461 | | |
462 | | /* success */ |
463 | 0 | return 0; |
464 | 0 | } |
465 | | |
466 | | /* return matching private key for given public key */ |
467 | | static Identity * |
468 | | lookup_identity(struct sshkey *key) |
469 | 0 | { |
470 | 0 | Identity *id; |
471 | |
|
472 | 0 | TAILQ_FOREACH(id, &idtab->idlist, next) { |
473 | 0 | if (sshkey_equal(key, id->key)) |
474 | 0 | return (id); |
475 | 0 | } |
476 | 0 | return (NULL); |
477 | 0 | } |
478 | | |
479 | | /* Check confirmation of keysign request */ |
480 | | static int |
481 | | confirm_key(Identity *id, const char *extra) |
482 | 0 | { |
483 | 0 | char *p; |
484 | 0 | int ret = -1; |
485 | |
|
486 | 0 | p = sshkey_fingerprint(id->key, fingerprint_hash, SSH_FP_DEFAULT); |
487 | 0 | if (p != NULL && |
488 | 0 | ask_permission("Allow use of key %s?\nKey fingerprint %s.%s%s", |
489 | 0 | id->comment, p, |
490 | 0 | extra == NULL ? "" : "\n", extra == NULL ? "" : extra)) |
491 | 0 | ret = 0; |
492 | 0 | free(p); |
493 | |
|
494 | 0 | return (ret); |
495 | 0 | } |
496 | | |
497 | | static void |
498 | | send_status(SocketEntry *e, int success) |
499 | 90.8k | { |
500 | 90.8k | int r; |
501 | | |
502 | 90.8k | if ((r = sshbuf_put_u32(e->output, 1)) != 0 || |
503 | 90.8k | (r = sshbuf_put_u8(e->output, success ? |
504 | 90.8k | SSH_AGENT_SUCCESS : SSH_AGENT_FAILURE)) != 0) |
505 | 0 | fatal_fr(r, "compose"); |
506 | 90.8k | } |
507 | | |
508 | | /* send list of supported public keys to 'client' */ |
509 | | static void |
510 | | process_request_identities(SocketEntry *e) |
511 | 5 | { |
512 | 5 | Identity *id; |
513 | 5 | struct sshbuf *msg, *keys; |
514 | 5 | int r; |
515 | 5 | u_int nentries = 0; |
516 | | |
517 | 5 | debug2_f("entering"); |
518 | | |
519 | 5 | if ((msg = sshbuf_new()) == NULL || (keys = sshbuf_new()) == NULL) |
520 | 0 | fatal_f("sshbuf_new failed"); |
521 | 48 | TAILQ_FOREACH(id, &idtab->idlist, next) { |
522 | | /* identity not visible, don't include in response */ |
523 | 48 | if (identity_permitted(id, e, NULL, NULL, NULL) != 0) |
524 | 0 | continue; |
525 | 48 | if ((r = sshkey_puts_opts(id->key, keys, |
526 | 48 | SSHKEY_SERIALIZE_INFO)) != 0 || |
527 | 48 | (r = sshbuf_put_cstring(keys, id->comment)) != 0) { |
528 | 0 | error_fr(r, "compose key/comment"); |
529 | 0 | continue; |
530 | 0 | } |
531 | 48 | nentries++; |
532 | 48 | } |
533 | 5 | debug2_f("replying with %u allowed of %u available keys", |
534 | 5 | nentries, idtab->nentries); |
535 | 5 | if ((r = sshbuf_put_u8(msg, SSH2_AGENT_IDENTITIES_ANSWER)) != 0 || |
536 | 5 | (r = sshbuf_put_u32(msg, nentries)) != 0 || |
537 | 5 | (r = sshbuf_putb(msg, keys)) != 0) |
538 | 0 | fatal_fr(r, "compose"); |
539 | 5 | if ((r = sshbuf_put_stringb(e->output, msg)) != 0) |
540 | 0 | fatal_fr(r, "enqueue"); |
541 | 5 | sshbuf_free(msg); |
542 | 5 | sshbuf_free(keys); |
543 | 5 | } |
544 | | |
545 | | |
546 | | static char * |
547 | | agent_decode_alg(struct sshkey *key, u_int flags) |
548 | 0 | { |
549 | 0 | if (key->type == KEY_RSA) { |
550 | 0 | if (flags & SSH_AGENT_RSA_SHA2_256) |
551 | 0 | return "rsa-sha2-256"; |
552 | 0 | else if (flags & SSH_AGENT_RSA_SHA2_512) |
553 | 0 | return "rsa-sha2-512"; |
554 | 0 | } else if (key->type == KEY_RSA_CERT) { |
555 | 0 | if (flags & SSH_AGENT_RSA_SHA2_256) |
556 | 0 | return "rsa-sha2-256-cert-v01@openssh.com"; |
557 | 0 | else if (flags & SSH_AGENT_RSA_SHA2_512) |
558 | 0 | return "rsa-sha2-512-cert-v01@openssh.com"; |
559 | 0 | } |
560 | 0 | return NULL; |
561 | 0 | } |
562 | | |
563 | | /* |
564 | | * Attempt to parse the contents of a buffer as a SSH publickey userauth |
565 | | * request, checking its contents for consistency and matching the embedded |
566 | | * key against the one that is being used for signing. |
567 | | * Note: does not modify msg buffer. |
568 | | * Optionally extract the username, session ID and/or hostkey from the request. |
569 | | */ |
570 | | static int |
571 | | parse_userauth_request(struct sshbuf *msg, const struct sshkey *expected_key, |
572 | | char **userp, struct sshbuf **sess_idp, struct sshkey **hostkeyp) |
573 | 0 | { |
574 | 0 | struct sshbuf *b = NULL, *sess_id = NULL; |
575 | 0 | char *user = NULL, *service = NULL, *method = NULL, *pkalg = NULL; |
576 | 0 | int r; |
577 | 0 | u_char t, sig_follows; |
578 | 0 | struct sshkey *mkey = NULL, *hostkey = NULL; |
579 | |
|
580 | 0 | if (userp != NULL) |
581 | 0 | *userp = NULL; |
582 | 0 | if (sess_idp != NULL) |
583 | 0 | *sess_idp = NULL; |
584 | 0 | if (hostkeyp != NULL) |
585 | 0 | *hostkeyp = NULL; |
586 | 0 | if ((b = sshbuf_fromb(msg)) == NULL) |
587 | 0 | fatal_f("sshbuf_fromb"); |
588 | | |
589 | | /* SSH userauth request */ |
590 | 0 | if ((r = sshbuf_froms(b, &sess_id)) != 0) |
591 | 0 | goto out; |
592 | 0 | if (sshbuf_len(sess_id) == 0) { |
593 | 0 | r = SSH_ERR_INVALID_FORMAT; |
594 | 0 | goto out; |
595 | 0 | } |
596 | 0 | if ((r = sshbuf_get_u8(b, &t)) != 0 || /* SSH2_MSG_USERAUTH_REQUEST */ |
597 | 0 | (r = sshbuf_get_cstring(b, &user, NULL)) != 0 || /* server user */ |
598 | 0 | (r = sshbuf_get_cstring(b, &service, NULL)) != 0 || /* service */ |
599 | 0 | (r = sshbuf_get_cstring(b, &method, NULL)) != 0 || /* method */ |
600 | 0 | (r = sshbuf_get_u8(b, &sig_follows)) != 0 || /* sig-follows */ |
601 | 0 | (r = sshbuf_get_cstring(b, &pkalg, NULL)) != 0 || /* alg */ |
602 | 0 | (r = sshkey_froms(b, &mkey)) != 0) /* key */ |
603 | 0 | goto out; |
604 | 0 | if (t != SSH2_MSG_USERAUTH_REQUEST || |
605 | 0 | sig_follows != 1 || |
606 | 0 | strcmp(service, "ssh-connection") != 0 || |
607 | 0 | !sshkey_equal(expected_key, mkey) || |
608 | 0 | sshkey_type_from_name(pkalg) != expected_key->type) { |
609 | 0 | r = SSH_ERR_INVALID_FORMAT; |
610 | 0 | goto out; |
611 | 0 | } |
612 | 0 | if (strcmp(method, "publickey-hostbound-v00@openssh.com") == 0) { |
613 | 0 | if ((r = sshkey_froms(b, &hostkey)) != 0) |
614 | 0 | goto out; |
615 | 0 | } else if (strcmp(method, "publickey") != 0) { |
616 | 0 | r = SSH_ERR_INVALID_FORMAT; |
617 | 0 | goto out; |
618 | 0 | } |
619 | 0 | if (sshbuf_len(b) != 0) { |
620 | 0 | r = SSH_ERR_INVALID_FORMAT; |
621 | 0 | goto out; |
622 | 0 | } |
623 | | /* success */ |
624 | 0 | r = 0; |
625 | 0 | debug3_f("well formed userauth"); |
626 | 0 | if (userp != NULL) { |
627 | 0 | *userp = user; |
628 | 0 | user = NULL; |
629 | 0 | } |
630 | 0 | if (sess_idp != NULL) { |
631 | 0 | *sess_idp = sess_id; |
632 | 0 | sess_id = NULL; |
633 | 0 | } |
634 | 0 | if (hostkeyp != NULL) { |
635 | 0 | *hostkeyp = hostkey; |
636 | 0 | hostkey = NULL; |
637 | 0 | } |
638 | 0 | out: |
639 | 0 | sshbuf_free(b); |
640 | 0 | sshbuf_free(sess_id); |
641 | 0 | free(user); |
642 | 0 | free(service); |
643 | 0 | free(method); |
644 | 0 | free(pkalg); |
645 | 0 | sshkey_free(mkey); |
646 | 0 | sshkey_free(hostkey); |
647 | 0 | return r; |
648 | 0 | } |
649 | | |
650 | | /* |
651 | | * Attempt to parse the contents of a buffer as a SSHSIG signature request. |
652 | | * Note: does not modify buffer. |
653 | | */ |
654 | | static int |
655 | | parse_sshsig_request(struct sshbuf *msg) |
656 | 0 | { |
657 | 0 | int r; |
658 | 0 | struct sshbuf *b; |
659 | |
|
660 | 0 | if ((b = sshbuf_fromb(msg)) == NULL) |
661 | 0 | fatal_f("sshbuf_fromb"); |
662 | | |
663 | 0 | if ((r = sshbuf_cmp(b, 0, "SSHSIG", 6)) != 0 || |
664 | 0 | (r = sshbuf_consume(b, 6)) != 0 || |
665 | 0 | (r = sshbuf_get_cstring(b, NULL, NULL)) != 0 || /* namespace */ |
666 | 0 | (r = sshbuf_get_string_direct(b, NULL, NULL)) != 0 || /* reserved */ |
667 | 0 | (r = sshbuf_get_cstring(b, NULL, NULL)) != 0 || /* hashalg */ |
668 | 0 | (r = sshbuf_get_string_direct(b, NULL, NULL)) != 0) /* H(msg) */ |
669 | 0 | goto out; |
670 | 0 | if (sshbuf_len(b) != 0) { |
671 | 0 | r = SSH_ERR_INVALID_FORMAT; |
672 | 0 | goto out; |
673 | 0 | } |
674 | | /* success */ |
675 | 0 | r = 0; |
676 | 0 | out: |
677 | 0 | sshbuf_free(b); |
678 | 0 | return r; |
679 | 0 | } |
680 | | |
681 | | /* |
682 | | * This function inspects a message to be signed by a FIDO key that has a |
683 | | * web-like application string (i.e. one that does not begin with "ssh:". |
684 | | * It checks that the message is one of those expected for SSH operations |
685 | | * (pubkey userauth, sshsig, CA key signing) to exclude signing challenges |
686 | | * for the web. |
687 | | */ |
688 | | static int |
689 | | check_websafe_message_contents(struct sshkey *key, struct sshbuf *data) |
690 | 0 | { |
691 | 0 | if (parse_userauth_request(data, key, NULL, NULL, NULL) == 0) { |
692 | 0 | debug_f("signed data matches public key userauth request"); |
693 | 0 | return 1; |
694 | 0 | } |
695 | 0 | if (parse_sshsig_request(data) == 0) { |
696 | 0 | debug_f("signed data matches SSHSIG signature request"); |
697 | 0 | return 1; |
698 | 0 | } |
699 | | |
700 | | /* XXX check CA signature operation */ |
701 | | |
702 | 0 | error("web-origin key attempting to sign non-SSH message"); |
703 | 0 | return 0; |
704 | 0 | } |
705 | | |
706 | | static int |
707 | | buf_equal(const struct sshbuf *a, const struct sshbuf *b) |
708 | 0 | { |
709 | 0 | if (sshbuf_ptr(a) == NULL || sshbuf_ptr(b) == NULL) |
710 | 0 | return SSH_ERR_INVALID_ARGUMENT; |
711 | 0 | if (sshbuf_len(a) != sshbuf_len(b)) |
712 | 0 | return SSH_ERR_INVALID_FORMAT; |
713 | 0 | if (timingsafe_bcmp(sshbuf_ptr(a), sshbuf_ptr(b), sshbuf_len(a)) != 0) |
714 | 0 | return SSH_ERR_INVALID_FORMAT; |
715 | 0 | return 0; |
716 | 0 | } |
717 | | |
718 | | /* ssh2 only */ |
719 | | static void |
720 | | process_sign_request2(SocketEntry *e) |
721 | 26 | { |
722 | 26 | u_char *signature = NULL; |
723 | 26 | size_t slen = 0; |
724 | 26 | u_int compat = 0, flags; |
725 | 26 | int r, ok = -1, retried = 0; |
726 | 26 | char *fp = NULL, *pin = NULL, *prompt = NULL; |
727 | 26 | char *user = NULL, *sig_dest = NULL; |
728 | 26 | const char *fwd_host = NULL, *dest_host = NULL; |
729 | 26 | struct sshbuf *msg = NULL, *data = NULL, *sid = NULL; |
730 | 26 | struct sshkey *key = NULL, *hostkey = NULL; |
731 | 26 | struct identity *id; |
732 | 26 | struct notifier_ctx *notifier = NULL; |
733 | | |
734 | 26 | debug_f("entering"); |
735 | | |
736 | 26 | if ((msg = sshbuf_new()) == NULL || (data = sshbuf_new()) == NULL) |
737 | 0 | fatal_f("sshbuf_new failed"); |
738 | 26 | if ((r = sshkey_froms(e->request, &key)) != 0 || |
739 | 26 | (r = sshbuf_get_stringb(e->request, data)) != 0 || |
740 | 26 | (r = sshbuf_get_u32(e->request, &flags)) != 0) { |
741 | 26 | error_fr(r, "parse"); |
742 | 26 | goto send; |
743 | 26 | } |
744 | | |
745 | 0 | if ((id = lookup_identity(key)) == NULL) { |
746 | 0 | verbose_f("%s key not found", sshkey_type(key)); |
747 | 0 | goto send; |
748 | 0 | } |
749 | 0 | if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT, |
750 | 0 | SSH_FP_DEFAULT)) == NULL) |
751 | 0 | fatal_f("fingerprint failed"); |
752 | | |
753 | 0 | if (id->ndest_constraints != 0) { |
754 | 0 | if (e->nsession_ids == 0) { |
755 | 0 | logit_f("refusing use of destination-constrained key " |
756 | 0 | "to sign on unbound connection"); |
757 | 0 | goto send; |
758 | 0 | } |
759 | 0 | if (parse_userauth_request(data, key, &user, &sid, |
760 | 0 | &hostkey) != 0) { |
761 | 0 | logit_f("refusing use of destination-constrained key " |
762 | 0 | "to sign an unidentified signature"); |
763 | 0 | goto send; |
764 | 0 | } |
765 | | /* XXX logspam */ |
766 | 0 | debug_f("user=%s", user); |
767 | 0 | if (identity_permitted(id, e, user, &fwd_host, &dest_host) != 0) |
768 | 0 | goto send; |
769 | | /* XXX display fwd_host/dest_host in askpass UI */ |
770 | | /* |
771 | | * Ensure that the session ID is the most recent one |
772 | | * registered on the socket - it should have been bound by |
773 | | * ssh immediately before userauth. |
774 | | */ |
775 | 0 | if (buf_equal(sid, |
776 | 0 | e->session_ids[e->nsession_ids - 1].sid) != 0) { |
777 | 0 | error_f("unexpected session ID (%zu listed) on " |
778 | 0 | "signature request for target user %s with " |
779 | 0 | "key %s %s", e->nsession_ids, user, |
780 | 0 | sshkey_type(id->key), fp); |
781 | 0 | goto send; |
782 | 0 | } |
783 | | /* |
784 | | * Ensure that the hostkey embedded in the signature matches |
785 | | * the one most recently bound to the socket. An exception is |
786 | | * made for the initial forwarding hop. |
787 | | */ |
788 | 0 | if (e->nsession_ids > 1 && hostkey == NULL) { |
789 | 0 | error_f("refusing use of destination-constrained key: " |
790 | 0 | "no hostkey recorded in signature for forwarded " |
791 | 0 | "connection"); |
792 | 0 | goto send; |
793 | 0 | } |
794 | 0 | if (hostkey != NULL && !sshkey_equal(hostkey, |
795 | 0 | e->session_ids[e->nsession_ids - 1].key)) { |
796 | 0 | error_f("refusing use of destination-constrained key: " |
797 | 0 | "mismatch between hostkey in request and most " |
798 | 0 | "recently bound session"); |
799 | 0 | goto send; |
800 | 0 | } |
801 | 0 | xasprintf(&sig_dest, "public key authentication request for " |
802 | 0 | "user \"%s\" to listed host", user); |
803 | 0 | } |
804 | 0 | if (id->confirm && confirm_key(id, sig_dest) != 0) { |
805 | 0 | verbose_f("user refused key"); |
806 | 0 | goto send; |
807 | 0 | } |
808 | 0 | if (sshkey_is_sk(id->key)) { |
809 | 0 | if (restrict_websafe && |
810 | 0 | strncmp(id->key->sk_application, "ssh:", 4) != 0 && |
811 | 0 | !check_websafe_message_contents(key, data)) { |
812 | | /* error already logged */ |
813 | 0 | goto send; |
814 | 0 | } |
815 | 0 | if (id->key->sk_flags & SSH_SK_USER_PRESENCE_REQD) { |
816 | 0 | notifier = notify_start(0, |
817 | 0 | "Confirm user presence for key %s %s%s%s", |
818 | 0 | sshkey_type(id->key), fp, |
819 | 0 | sig_dest == NULL ? "" : "\n", |
820 | 0 | sig_dest == NULL ? "" : sig_dest); |
821 | 0 | } |
822 | 0 | } |
823 | 0 | retry_pin: |
824 | 0 | if ((r = sshkey_sign(id->key, &signature, &slen, |
825 | 0 | sshbuf_ptr(data), sshbuf_len(data), agent_decode_alg(key, flags), |
826 | 0 | id->sk_provider, pin, compat)) != 0) { |
827 | 0 | debug_fr(r, "sshkey_sign"); |
828 | 0 | if (pin == NULL && !retried && sshkey_is_sk(id->key) && |
829 | 0 | r == SSH_ERR_KEY_WRONG_PASSPHRASE) { |
830 | 0 | notify_complete(notifier, NULL); |
831 | 0 | notifier = NULL; |
832 | | /* XXX include sig_dest */ |
833 | 0 | xasprintf(&prompt, "Enter PIN%sfor %s key %s: ", |
834 | 0 | (id->key->sk_flags & SSH_SK_USER_PRESENCE_REQD) ? |
835 | 0 | " and confirm user presence " : " ", |
836 | 0 | sshkey_type(id->key), fp); |
837 | 0 | pin = read_passphrase(prompt, RP_USE_ASKPASS); |
838 | 0 | retried = 1; |
839 | 0 | goto retry_pin; |
840 | 0 | } |
841 | 0 | error_fr(r, "sshkey_sign"); |
842 | 0 | goto send; |
843 | 0 | } |
844 | | /* Success */ |
845 | 0 | ok = 0; |
846 | 26 | send: |
847 | 26 | debug_f("good signature"); |
848 | 26 | notify_complete(notifier, "User presence confirmed"); |
849 | | |
850 | 26 | if (ok == 0) { |
851 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_AGENT_SIGN_RESPONSE)) != 0 || |
852 | 0 | (r = sshbuf_put_string(msg, signature, slen)) != 0) |
853 | 0 | fatal_fr(r, "compose"); |
854 | 26 | } else if ((r = sshbuf_put_u8(msg, SSH_AGENT_FAILURE)) != 0) |
855 | 0 | fatal_fr(r, "compose failure"); |
856 | | |
857 | 26 | if ((r = sshbuf_put_stringb(e->output, msg)) != 0) |
858 | 0 | fatal_fr(r, "enqueue"); |
859 | | |
860 | 26 | sshbuf_free(sid); |
861 | 26 | sshbuf_free(data); |
862 | 26 | sshbuf_free(msg); |
863 | 26 | sshkey_free(key); |
864 | 26 | sshkey_free(hostkey); |
865 | 26 | free(fp); |
866 | 26 | free(signature); |
867 | 26 | free(sig_dest); |
868 | 26 | free(user); |
869 | 26 | free(prompt); |
870 | 26 | if (pin != NULL) |
871 | 0 | freezero(pin, strlen(pin)); |
872 | 26 | } |
873 | | |
874 | | /* shared */ |
875 | | static void |
876 | | process_remove_identity(SocketEntry *e) |
877 | 7 | { |
878 | 7 | int r, success = 0; |
879 | 7 | struct sshkey *key = NULL; |
880 | 7 | Identity *id; |
881 | | |
882 | 7 | debug2_f("entering"); |
883 | 7 | if ((r = sshkey_froms(e->request, &key)) != 0) { |
884 | 7 | error_fr(r, "parse key"); |
885 | 7 | goto done; |
886 | 7 | } |
887 | 0 | if ((id = lookup_identity(key)) == NULL) { |
888 | 0 | debug_f("key not found"); |
889 | 0 | goto done; |
890 | 0 | } |
891 | | /* identity not visible, cannot be removed */ |
892 | 0 | if (identity_permitted(id, e, NULL, NULL, NULL) != 0) |
893 | 0 | goto done; /* error already logged */ |
894 | | /* We have this key, free it. */ |
895 | 0 | if (idtab->nentries < 1) |
896 | 0 | fatal_f("internal error: nentries %d", idtab->nentries); |
897 | 0 | TAILQ_REMOVE(&idtab->idlist, id, next); |
898 | 0 | free_identity(id); |
899 | 0 | idtab->nentries--; |
900 | 0 | success = 1; |
901 | 7 | done: |
902 | 7 | sshkey_free(key); |
903 | 7 | send_status(e, success); |
904 | 7 | } |
905 | | |
906 | | static void |
907 | | process_remove_all_identities(SocketEntry *e) |
908 | 8 | { |
909 | 8 | Identity *id; |
910 | | |
911 | 8 | debug2_f("entering"); |
912 | | /* Loop over all identities and clear the keys. */ |
913 | 80 | for (id = TAILQ_FIRST(&idtab->idlist); id; |
914 | 72 | id = TAILQ_FIRST(&idtab->idlist)) { |
915 | 72 | TAILQ_REMOVE(&idtab->idlist, id, next); |
916 | 72 | free_identity(id); |
917 | 72 | } |
918 | | |
919 | | /* Mark that there are no identities. */ |
920 | 8 | idtab->nentries = 0; |
921 | | |
922 | | /* Send success. */ |
923 | 8 | send_status(e, 1); |
924 | 8 | } |
925 | | |
926 | | /* removes expired keys and returns number of seconds until the next expiry */ |
927 | | static time_t |
928 | | reaper(void) |
929 | 0 | { |
930 | 0 | time_t deadline = 0, now = monotime(); |
931 | 0 | Identity *id, *nxt; |
932 | |
|
933 | 0 | for (id = TAILQ_FIRST(&idtab->idlist); id; id = nxt) { |
934 | 0 | nxt = TAILQ_NEXT(id, next); |
935 | 0 | if (id->death == 0) |
936 | 0 | continue; |
937 | 0 | if (now >= id->death) { |
938 | 0 | debug("expiring key '%s'", id->comment); |
939 | 0 | TAILQ_REMOVE(&idtab->idlist, id, next); |
940 | 0 | free_identity(id); |
941 | 0 | idtab->nentries--; |
942 | 0 | } else |
943 | 0 | deadline = (deadline == 0) ? id->death : |
944 | 0 | MINIMUM(deadline, id->death); |
945 | 0 | } |
946 | 0 | if (deadline == 0 || deadline <= now) |
947 | 0 | return 0; |
948 | 0 | else |
949 | 0 | return (deadline - now); |
950 | 0 | } |
951 | | |
952 | | static int |
953 | | parse_dest_constraint_hop(struct sshbuf *b, struct dest_constraint_hop *dch) |
954 | 0 | { |
955 | 0 | u_char key_is_ca; |
956 | 0 | size_t elen = 0; |
957 | 0 | int r; |
958 | 0 | struct sshkey *k = NULL; |
959 | 0 | char *fp; |
960 | |
|
961 | 0 | memset(dch, '\0', sizeof(*dch)); |
962 | 0 | if ((r = sshbuf_get_cstring(b, &dch->user, NULL)) != 0 || |
963 | 0 | (r = sshbuf_get_cstring(b, &dch->hostname, NULL)) != 0 || |
964 | 0 | (r = sshbuf_get_string_direct(b, NULL, &elen)) != 0) { |
965 | 0 | error_fr(r, "parse"); |
966 | 0 | goto out; |
967 | 0 | } |
968 | 0 | if (elen != 0) { |
969 | 0 | error_f("unsupported extensions (len %zu)", elen); |
970 | 0 | r = SSH_ERR_FEATURE_UNSUPPORTED; |
971 | 0 | goto out; |
972 | 0 | } |
973 | 0 | if (*dch->hostname == '\0') { |
974 | 0 | free(dch->hostname); |
975 | 0 | dch->hostname = NULL; |
976 | 0 | } |
977 | 0 | if (*dch->user == '\0') { |
978 | 0 | free(dch->user); |
979 | 0 | dch->user = NULL; |
980 | 0 | } |
981 | 0 | while (sshbuf_len(b) != 0) { |
982 | 0 | dch->keys = xrecallocarray(dch->keys, dch->nkeys, |
983 | 0 | dch->nkeys + 1, sizeof(*dch->keys)); |
984 | 0 | dch->key_is_ca = xrecallocarray(dch->key_is_ca, dch->nkeys, |
985 | 0 | dch->nkeys + 1, sizeof(*dch->key_is_ca)); |
986 | 0 | if ((r = sshkey_froms(b, &k)) != 0 || |
987 | 0 | (r = sshbuf_get_u8(b, &key_is_ca)) != 0) |
988 | 0 | goto out; |
989 | 0 | if ((fp = sshkey_fingerprint(k, SSH_FP_HASH_DEFAULT, |
990 | 0 | SSH_FP_DEFAULT)) == NULL) |
991 | 0 | fatal_f("fingerprint failed"); |
992 | 0 | debug3_f("%s%s%s: adding %skey %s %s", |
993 | 0 | dch->user == NULL ? "" : dch->user, |
994 | 0 | dch->user == NULL ? "" : "@", |
995 | 0 | dch->hostname, key_is_ca ? "CA " : "", sshkey_type(k), fp); |
996 | 0 | free(fp); |
997 | 0 | dch->keys[dch->nkeys] = k; |
998 | 0 | dch->key_is_ca[dch->nkeys] = key_is_ca != 0; |
999 | 0 | dch->nkeys++; |
1000 | 0 | k = NULL; /* transferred */ |
1001 | 0 | } |
1002 | | /* success */ |
1003 | 0 | r = 0; |
1004 | 0 | out: |
1005 | 0 | sshkey_free(k); |
1006 | 0 | return r; |
1007 | 0 | } |
1008 | | |
1009 | | static int |
1010 | | parse_dest_constraint(struct sshbuf *m, struct dest_constraint *dc) |
1011 | 0 | { |
1012 | 0 | struct sshbuf *b = NULL, *frombuf = NULL, *tobuf = NULL; |
1013 | 0 | int r; |
1014 | 0 | size_t elen = 0; |
1015 | |
|
1016 | 0 | debug3_f("entering"); |
1017 | |
|
1018 | 0 | memset(dc, '\0', sizeof(*dc)); |
1019 | 0 | if ((r = sshbuf_froms(m, &b)) != 0 || |
1020 | 0 | (r = sshbuf_froms(b, &frombuf)) != 0 || |
1021 | 0 | (r = sshbuf_froms(b, &tobuf)) != 0 || |
1022 | 0 | (r = sshbuf_get_string_direct(b, NULL, &elen)) != 0) { |
1023 | 0 | error_fr(r, "parse"); |
1024 | 0 | goto out; |
1025 | 0 | } |
1026 | 0 | if ((r = parse_dest_constraint_hop(frombuf, &dc->from)) != 0 || |
1027 | 0 | (r = parse_dest_constraint_hop(tobuf, &dc->to)) != 0) |
1028 | 0 | goto out; /* already logged */ |
1029 | 0 | if (elen != 0) { |
1030 | 0 | error_f("unsupported extensions (len %zu)", elen); |
1031 | 0 | r = SSH_ERR_FEATURE_UNSUPPORTED; |
1032 | 0 | goto out; |
1033 | 0 | } |
1034 | 0 | debug2_f("parsed %s (%u keys) > %s%s%s (%u keys)", |
1035 | 0 | dc->from.hostname ? dc->from.hostname : "(ORIGIN)", dc->from.nkeys, |
1036 | 0 | dc->to.user ? dc->to.user : "", dc->to.user ? "@" : "", |
1037 | 0 | dc->to.hostname ? dc->to.hostname : "(ANY)", dc->to.nkeys); |
1038 | | /* check consistency */ |
1039 | 0 | if ((dc->from.hostname == NULL) != (dc->from.nkeys == 0) || |
1040 | 0 | dc->from.user != NULL) { |
1041 | 0 | error_f("inconsistent \"from\" specification"); |
1042 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1043 | 0 | goto out; |
1044 | 0 | } |
1045 | 0 | if (dc->to.hostname == NULL || dc->to.nkeys == 0) { |
1046 | 0 | error_f("incomplete \"to\" specification"); |
1047 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1048 | 0 | goto out; |
1049 | 0 | } |
1050 | | /* success */ |
1051 | 0 | r = 0; |
1052 | 0 | out: |
1053 | 0 | sshbuf_free(b); |
1054 | 0 | sshbuf_free(frombuf); |
1055 | 0 | sshbuf_free(tobuf); |
1056 | 0 | return r; |
1057 | 0 | } |
1058 | | |
1059 | | static int |
1060 | | parse_key_constraint_extension(struct sshbuf *m, char **sk_providerp, |
1061 | | struct dest_constraint **dcsp, size_t *ndcsp) |
1062 | 0 | { |
1063 | 0 | char *ext_name = NULL; |
1064 | 0 | int r; |
1065 | 0 | struct sshbuf *b = NULL; |
1066 | |
|
1067 | 0 | if ((r = sshbuf_get_cstring(m, &ext_name, NULL)) != 0) { |
1068 | 0 | error_fr(r, "parse constraint extension"); |
1069 | 0 | goto out; |
1070 | 0 | } |
1071 | 0 | debug_f("constraint ext %s", ext_name); |
1072 | 0 | if (strcmp(ext_name, "sk-provider@openssh.com") == 0) { |
1073 | 0 | if (sk_providerp == NULL) { |
1074 | 0 | error_f("%s not valid here", ext_name); |
1075 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1076 | 0 | goto out; |
1077 | 0 | } |
1078 | 0 | if (*sk_providerp != NULL) { |
1079 | 0 | error_f("%s already set", ext_name); |
1080 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1081 | 0 | goto out; |
1082 | 0 | } |
1083 | 0 | if ((r = sshbuf_get_cstring(m, sk_providerp, NULL)) != 0) { |
1084 | 0 | error_fr(r, "parse %s", ext_name); |
1085 | 0 | goto out; |
1086 | 0 | } |
1087 | 0 | } else if (strcmp(ext_name, |
1088 | 0 | "restrict-destination-v00@openssh.com") == 0) { |
1089 | 0 | if (*dcsp != NULL) { |
1090 | 0 | error_f("%s already set", ext_name); |
1091 | 0 | goto out; |
1092 | 0 | } |
1093 | 0 | if ((r = sshbuf_froms(m, &b)) != 0) { |
1094 | 0 | error_fr(r, "parse %s outer", ext_name); |
1095 | 0 | goto out; |
1096 | 0 | } |
1097 | 0 | while (sshbuf_len(b) != 0) { |
1098 | 0 | if (*ndcsp >= AGENT_MAX_DEST_CONSTRAINTS) { |
1099 | 0 | error_f("too many %s constraints", ext_name); |
1100 | 0 | goto out; |
1101 | 0 | } |
1102 | 0 | *dcsp = xrecallocarray(*dcsp, *ndcsp, *ndcsp + 1, |
1103 | 0 | sizeof(**dcsp)); |
1104 | 0 | if ((r = parse_dest_constraint(b, |
1105 | 0 | *dcsp + (*ndcsp)++)) != 0) |
1106 | 0 | goto out; /* error already logged */ |
1107 | 0 | } |
1108 | 0 | } else { |
1109 | 0 | error_f("unsupported constraint \"%s\"", ext_name); |
1110 | 0 | r = SSH_ERR_FEATURE_UNSUPPORTED; |
1111 | 0 | goto out; |
1112 | 0 | } |
1113 | | /* success */ |
1114 | 0 | r = 0; |
1115 | 0 | out: |
1116 | 0 | free(ext_name); |
1117 | 0 | sshbuf_free(b); |
1118 | 0 | return r; |
1119 | 0 | } |
1120 | | |
1121 | | static int |
1122 | | parse_key_constraints(struct sshbuf *m, struct sshkey *k, time_t *deathp, |
1123 | | u_int *secondsp, int *confirmp, char **sk_providerp, |
1124 | | struct dest_constraint **dcsp, size_t *ndcsp) |
1125 | 0 | { |
1126 | 0 | u_char ctype; |
1127 | 0 | int r; |
1128 | 0 | u_int seconds, maxsign = 0; |
1129 | |
|
1130 | 0 | while (sshbuf_len(m)) { |
1131 | 0 | if ((r = sshbuf_get_u8(m, &ctype)) != 0) { |
1132 | 0 | error_fr(r, "parse constraint type"); |
1133 | 0 | goto out; |
1134 | 0 | } |
1135 | 0 | switch (ctype) { |
1136 | 0 | case SSH_AGENT_CONSTRAIN_LIFETIME: |
1137 | 0 | if (*deathp != 0) { |
1138 | 0 | error_f("lifetime already set"); |
1139 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1140 | 0 | goto out; |
1141 | 0 | } |
1142 | 0 | if ((r = sshbuf_get_u32(m, &seconds)) != 0) { |
1143 | 0 | error_fr(r, "parse lifetime constraint"); |
1144 | 0 | goto out; |
1145 | 0 | } |
1146 | 0 | *deathp = monotime() + seconds; |
1147 | 0 | *secondsp = seconds; |
1148 | 0 | break; |
1149 | 0 | case SSH_AGENT_CONSTRAIN_CONFIRM: |
1150 | 0 | if (*confirmp != 0) { |
1151 | 0 | error_f("confirm already set"); |
1152 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1153 | 0 | goto out; |
1154 | 0 | } |
1155 | 0 | *confirmp = 1; |
1156 | 0 | break; |
1157 | 0 | case SSH_AGENT_CONSTRAIN_MAXSIGN: |
1158 | 0 | if (k == NULL) { |
1159 | 0 | error_f("maxsign not valid here"); |
1160 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1161 | 0 | goto out; |
1162 | 0 | } |
1163 | 0 | if (maxsign != 0) { |
1164 | 0 | error_f("maxsign already set"); |
1165 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1166 | 0 | goto out; |
1167 | 0 | } |
1168 | 0 | if ((r = sshbuf_get_u32(m, &maxsign)) != 0) { |
1169 | 0 | error_fr(r, "parse maxsign constraint"); |
1170 | 0 | goto out; |
1171 | 0 | } |
1172 | 0 | if ((r = sshkey_enable_maxsign(k, maxsign)) != 0) { |
1173 | 0 | error_fr(r, "enable maxsign"); |
1174 | 0 | goto out; |
1175 | 0 | } |
1176 | 0 | break; |
1177 | 0 | case SSH_AGENT_CONSTRAIN_EXTENSION: |
1178 | 0 | if ((r = parse_key_constraint_extension(m, |
1179 | 0 | sk_providerp, dcsp, ndcsp)) != 0) |
1180 | 0 | goto out; /* error already logged */ |
1181 | 0 | break; |
1182 | 0 | default: |
1183 | 0 | error_f("Unknown constraint %d", ctype); |
1184 | 0 | r = SSH_ERR_FEATURE_UNSUPPORTED; |
1185 | 0 | goto out; |
1186 | 0 | } |
1187 | 0 | } |
1188 | | /* success */ |
1189 | 0 | r = 0; |
1190 | 0 | out: |
1191 | 0 | return r; |
1192 | 0 | } |
1193 | | |
1194 | | static void |
1195 | | process_add_identity(SocketEntry *e) |
1196 | 15 | { |
1197 | 15 | Identity *id; |
1198 | 15 | int success = 0, confirm = 0; |
1199 | 15 | char *fp, *comment = NULL, *sk_provider = NULL; |
1200 | 15 | char canonical_provider[PATH_MAX]; |
1201 | 15 | time_t death = 0; |
1202 | 15 | u_int seconds = 0; |
1203 | 15 | struct dest_constraint *dest_constraints = NULL; |
1204 | 15 | size_t ndest_constraints = 0; |
1205 | 15 | struct sshkey *k = NULL; |
1206 | 15 | int r = SSH_ERR_INTERNAL_ERROR; |
1207 | | |
1208 | 15 | debug2_f("entering"); |
1209 | 15 | if ((r = sshkey_private_deserialize(e->request, &k)) != 0 || |
1210 | 15 | k == NULL || |
1211 | 15 | (r = sshbuf_get_cstring(e->request, &comment, NULL)) != 0) { |
1212 | 15 | error_fr(r, "parse"); |
1213 | 15 | goto out; |
1214 | 15 | } |
1215 | 0 | if (parse_key_constraints(e->request, k, &death, &seconds, &confirm, |
1216 | 0 | &sk_provider, &dest_constraints, &ndest_constraints) != 0) { |
1217 | 0 | error_f("failed to parse constraints"); |
1218 | 0 | sshbuf_reset(e->request); |
1219 | 0 | goto out; |
1220 | 0 | } |
1221 | | |
1222 | 0 | if (sk_provider != NULL) { |
1223 | 0 | if (!sshkey_is_sk(k)) { |
1224 | 0 | error("Cannot add provider: %s is not an " |
1225 | 0 | "authenticator-hosted key", sshkey_type(k)); |
1226 | 0 | goto out; |
1227 | 0 | } |
1228 | 0 | if (strcasecmp(sk_provider, "internal") == 0) { |
1229 | 0 | debug_f("internal provider"); |
1230 | 0 | } else { |
1231 | 0 | if (realpath(sk_provider, canonical_provider) == NULL) { |
1232 | 0 | verbose("failed provider \"%.100s\": " |
1233 | 0 | "realpath: %s", sk_provider, |
1234 | 0 | strerror(errno)); |
1235 | 0 | goto out; |
1236 | 0 | } |
1237 | 0 | free(sk_provider); |
1238 | 0 | sk_provider = xstrdup(canonical_provider); |
1239 | 0 | if (match_pattern_list(sk_provider, |
1240 | 0 | allowed_providers, 0) != 1) { |
1241 | 0 | error("Refusing add key: " |
1242 | 0 | "provider %s not allowed", sk_provider); |
1243 | 0 | goto out; |
1244 | 0 | } |
1245 | 0 | } |
1246 | 0 | } |
1247 | 0 | if ((r = sshkey_shield_private(k)) != 0) { |
1248 | 0 | error_fr(r, "shield private"); |
1249 | 0 | goto out; |
1250 | 0 | } |
1251 | 0 | if (lifetime && !death) |
1252 | 0 | death = monotime() + lifetime; |
1253 | 0 | if ((id = lookup_identity(k)) == NULL) { |
1254 | 0 | id = xcalloc(1, sizeof(Identity)); |
1255 | 0 | TAILQ_INSERT_TAIL(&idtab->idlist, id, next); |
1256 | | /* Increment the number of identities. */ |
1257 | 0 | idtab->nentries++; |
1258 | 0 | } else { |
1259 | | /* identity not visible, do not update */ |
1260 | 0 | if (identity_permitted(id, e, NULL, NULL, NULL) != 0) |
1261 | 0 | goto out; /* error already logged */ |
1262 | | /* key state might have been updated */ |
1263 | 0 | sshkey_free(id->key); |
1264 | 0 | free(id->comment); |
1265 | 0 | free(id->sk_provider); |
1266 | 0 | free_dest_constraints(id->dest_constraints, |
1267 | 0 | id->ndest_constraints); |
1268 | 0 | } |
1269 | | /* success */ |
1270 | 0 | id->key = k; |
1271 | 0 | id->comment = comment; |
1272 | 0 | id->death = death; |
1273 | 0 | id->confirm = confirm; |
1274 | 0 | id->sk_provider = sk_provider; |
1275 | 0 | id->dest_constraints = dest_constraints; |
1276 | 0 | id->ndest_constraints = ndest_constraints; |
1277 | |
|
1278 | 0 | if ((fp = sshkey_fingerprint(k, SSH_FP_HASH_DEFAULT, |
1279 | 0 | SSH_FP_DEFAULT)) == NULL) |
1280 | 0 | fatal_f("sshkey_fingerprint failed"); |
1281 | 0 | debug_f("add %s %s \"%.100s\" (life: %u) (confirm: %u) " |
1282 | 0 | "(provider: %s) (destination constraints: %zu)", |
1283 | 0 | sshkey_ssh_name(k), fp, comment, seconds, confirm, |
1284 | 0 | sk_provider == NULL ? "none" : sk_provider, ndest_constraints); |
1285 | 0 | free(fp); |
1286 | | /* transferred */ |
1287 | 0 | k = NULL; |
1288 | 0 | comment = NULL; |
1289 | 0 | sk_provider = NULL; |
1290 | 0 | dest_constraints = NULL; |
1291 | 0 | ndest_constraints = 0; |
1292 | 0 | success = 1; |
1293 | 15 | out: |
1294 | 15 | free(sk_provider); |
1295 | 15 | free(comment); |
1296 | 15 | sshkey_free(k); |
1297 | 15 | free_dest_constraints(dest_constraints, ndest_constraints); |
1298 | 15 | send_status(e, success); |
1299 | 15 | } |
1300 | | |
1301 | | /* XXX todo: encrypt sensitive data with passphrase */ |
1302 | | static void |
1303 | | process_lock_agent(SocketEntry *e, int lock) |
1304 | 10.9k | { |
1305 | 10.9k | int r, success = 0, delay; |
1306 | 10.9k | char *passwd; |
1307 | 10.9k | u_char passwdhash[LOCK_SIZE]; |
1308 | 10.9k | static u_int fail_count = 0; |
1309 | 10.9k | size_t pwlen; |
1310 | | |
1311 | 10.9k | debug2_f("entering"); |
1312 | | /* |
1313 | | * This is deliberately fatal: the user has requested that we lock, |
1314 | | * but we can't parse their request properly. The only safe thing to |
1315 | | * do is abort. |
1316 | | */ |
1317 | 10.9k | if ((r = sshbuf_get_cstring(e->request, &passwd, &pwlen)) != 0) |
1318 | 0 | fatal_fr(r, "parse"); |
1319 | 10.9k | if (pwlen == 0) { |
1320 | 3.96k | debug("empty password not supported"); |
1321 | 6.98k | } else if (locked && !lock) { |
1322 | 6.98k | if (bcrypt_pbkdf(passwd, pwlen, lock_salt, sizeof(lock_salt), |
1323 | 6.98k | passwdhash, sizeof(passwdhash), LOCK_ROUNDS) < 0) |
1324 | 0 | fatal("bcrypt_pbkdf"); |
1325 | 6.98k | if (timingsafe_bcmp(passwdhash, lock_pwhash, LOCK_SIZE) == 0) { |
1326 | 0 | debug("agent unlocked"); |
1327 | 0 | locked = 0; |
1328 | 0 | fail_count = 0; |
1329 | 0 | explicit_bzero(lock_pwhash, sizeof(lock_pwhash)); |
1330 | 0 | success = 1; |
1331 | 6.98k | } else { |
1332 | | /* delay in 0.1s increments up to 10s */ |
1333 | 6.98k | if (fail_count < 100) |
1334 | 100 | fail_count++; |
1335 | 6.98k | delay = 100000 * fail_count; |
1336 | 6.98k | debug("unlock failed, delaying %0.1lf seconds", |
1337 | 6.98k | (double)delay/1000000); |
1338 | | // usleep(delay); |
1339 | 6.98k | } |
1340 | 6.98k | explicit_bzero(passwdhash, sizeof(passwdhash)); |
1341 | 6.98k | } else if (!locked && lock) { |
1342 | 1 | debug("agent locked"); |
1343 | 1 | locked = 1; |
1344 | 1 | arc4random_buf(lock_salt, sizeof(lock_salt)); |
1345 | 1 | if (bcrypt_pbkdf(passwd, pwlen, lock_salt, sizeof(lock_salt), |
1346 | 1 | lock_pwhash, sizeof(lock_pwhash), LOCK_ROUNDS) < 0) |
1347 | 0 | fatal("bcrypt_pbkdf"); |
1348 | 1 | success = 1; |
1349 | 1 | } |
1350 | 10.9k | freezero(passwd, pwlen); |
1351 | 10.9k | send_status(e, success); |
1352 | 10.9k | } |
1353 | | |
1354 | | static void |
1355 | | no_identities(SocketEntry *e) |
1356 | 19.5k | { |
1357 | 19.5k | struct sshbuf *msg; |
1358 | 19.5k | int r; |
1359 | | |
1360 | 19.5k | if ((msg = sshbuf_new()) == NULL) |
1361 | 0 | fatal_f("sshbuf_new failed"); |
1362 | 19.5k | if ((r = sshbuf_put_u8(msg, SSH2_AGENT_IDENTITIES_ANSWER)) != 0 || |
1363 | 19.5k | (r = sshbuf_put_u32(msg, 0)) != 0 || |
1364 | 19.5k | (r = sshbuf_put_stringb(e->output, msg)) != 0) |
1365 | 0 | fatal_fr(r, "compose"); |
1366 | 19.5k | sshbuf_free(msg); |
1367 | 19.5k | } |
1368 | | |
1369 | | #ifdef ENABLE_PKCS11 |
1370 | | static void |
1371 | | process_add_smartcard_key(SocketEntry *e) |
1372 | 15 | { |
1373 | 15 | char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX]; |
1374 | 15 | char **comments = NULL; |
1375 | 15 | int r, i, count = 0, success = 0, confirm = 0; |
1376 | 15 | u_int seconds = 0; |
1377 | 15 | time_t death = 0; |
1378 | 15 | struct sshkey **keys = NULL, *k; |
1379 | 15 | Identity *id; |
1380 | 15 | struct dest_constraint *dest_constraints = NULL; |
1381 | 15 | size_t ndest_constraints = 0; |
1382 | | |
1383 | 15 | debug2_f("entering"); |
1384 | 15 | if ((r = sshbuf_get_cstring(e->request, &provider, NULL)) != 0 || |
1385 | 15 | (r = sshbuf_get_cstring(e->request, &pin, NULL)) != 0) { |
1386 | 15 | error_fr(r, "parse"); |
1387 | 15 | goto send; |
1388 | 15 | } |
1389 | 0 | if (parse_key_constraints(e->request, NULL, &death, &seconds, &confirm, |
1390 | 0 | NULL, &dest_constraints, &ndest_constraints) != 0) { |
1391 | 0 | error_f("failed to parse constraints"); |
1392 | 0 | goto send; |
1393 | 0 | } |
1394 | 0 | if (realpath(provider, canonical_provider) == NULL) { |
1395 | 0 | verbose("failed PKCS#11 add of \"%.100s\": realpath: %s", |
1396 | 0 | provider, strerror(errno)); |
1397 | 0 | goto send; |
1398 | 0 | } |
1399 | 0 | if (match_pattern_list(canonical_provider, allowed_providers, 0) != 1) { |
1400 | 0 | verbose("refusing PKCS#11 add of \"%.100s\": " |
1401 | 0 | "provider not allowed", canonical_provider); |
1402 | 0 | goto send; |
1403 | 0 | } |
1404 | 0 | debug_f("add %.100s", canonical_provider); |
1405 | 0 | if (lifetime && !death) |
1406 | 0 | death = monotime() + lifetime; |
1407 | |
|
1408 | 0 | count = pkcs11_add_provider(canonical_provider, pin, &keys, &comments); |
1409 | 0 | for (i = 0; i < count; i++) { |
1410 | 0 | k = keys[i]; |
1411 | 0 | if (lookup_identity(k) == NULL) { |
1412 | 0 | id = xcalloc(1, sizeof(Identity)); |
1413 | 0 | id->key = k; |
1414 | 0 | keys[i] = NULL; /* transferred */ |
1415 | 0 | id->provider = xstrdup(canonical_provider); |
1416 | 0 | if (*comments[i] != '\0') { |
1417 | 0 | id->comment = comments[i]; |
1418 | 0 | comments[i] = NULL; /* transferred */ |
1419 | 0 | } else { |
1420 | 0 | id->comment = xstrdup(canonical_provider); |
1421 | 0 | } |
1422 | 0 | id->death = death; |
1423 | 0 | id->confirm = confirm; |
1424 | 0 | id->dest_constraints = dest_constraints; |
1425 | 0 | id->ndest_constraints = ndest_constraints; |
1426 | 0 | dest_constraints = NULL; /* transferred */ |
1427 | 0 | ndest_constraints = 0; |
1428 | 0 | TAILQ_INSERT_TAIL(&idtab->idlist, id, next); |
1429 | 0 | idtab->nentries++; |
1430 | 0 | success = 1; |
1431 | 0 | } |
1432 | | /* XXX update constraints for existing keys */ |
1433 | 0 | sshkey_free(keys[i]); |
1434 | 0 | free(comments[i]); |
1435 | 0 | } |
1436 | 15 | send: |
1437 | 15 | free(pin); |
1438 | 15 | free(provider); |
1439 | 15 | free(keys); |
1440 | 15 | free(comments); |
1441 | 15 | free_dest_constraints(dest_constraints, ndest_constraints); |
1442 | 15 | send_status(e, success); |
1443 | 15 | } |
1444 | | |
1445 | | static void |
1446 | | process_remove_smartcard_key(SocketEntry *e) |
1447 | 31 | { |
1448 | 31 | char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX]; |
1449 | 31 | int r, success = 0; |
1450 | 31 | Identity *id, *nxt; |
1451 | | |
1452 | 31 | debug2_f("entering"); |
1453 | 31 | if ((r = sshbuf_get_cstring(e->request, &provider, NULL)) != 0 || |
1454 | 31 | (r = sshbuf_get_cstring(e->request, &pin, NULL)) != 0) { |
1455 | 31 | error_fr(r, "parse"); |
1456 | 31 | goto send; |
1457 | 31 | } |
1458 | 0 | free(pin); |
1459 | |
|
1460 | 0 | if (realpath(provider, canonical_provider) == NULL) { |
1461 | 0 | verbose("failed PKCS#11 add of \"%.100s\": realpath: %s", |
1462 | 0 | provider, strerror(errno)); |
1463 | 0 | goto send; |
1464 | 0 | } |
1465 | | |
1466 | 0 | debug_f("remove %.100s", canonical_provider); |
1467 | 0 | for (id = TAILQ_FIRST(&idtab->idlist); id; id = nxt) { |
1468 | 0 | nxt = TAILQ_NEXT(id, next); |
1469 | | /* Skip file--based keys */ |
1470 | 0 | if (id->provider == NULL) |
1471 | 0 | continue; |
1472 | 0 | if (!strcmp(canonical_provider, id->provider)) { |
1473 | 0 | TAILQ_REMOVE(&idtab->idlist, id, next); |
1474 | 0 | free_identity(id); |
1475 | 0 | idtab->nentries--; |
1476 | 0 | } |
1477 | 0 | } |
1478 | 0 | if (pkcs11_del_provider(canonical_provider) == 0) |
1479 | 0 | success = 1; |
1480 | 0 | else |
1481 | 0 | error_f("pkcs11_del_provider failed"); |
1482 | 31 | send: |
1483 | 31 | free(provider); |
1484 | 31 | send_status(e, success); |
1485 | 31 | } |
1486 | | #endif /* ENABLE_PKCS11 */ |
1487 | | |
1488 | | static int |
1489 | | process_ext_session_bind(SocketEntry *e) |
1490 | 0 | { |
1491 | 0 | int r, sid_match, key_match; |
1492 | 0 | struct sshkey *key = NULL; |
1493 | 0 | struct sshbuf *sid = NULL, *sig = NULL; |
1494 | 0 | char *fp = NULL; |
1495 | 0 | size_t i; |
1496 | 0 | u_char fwd = 0; |
1497 | |
|
1498 | 0 | debug2_f("entering"); |
1499 | 0 | if ((r = sshkey_froms(e->request, &key)) != 0 || |
1500 | 0 | (r = sshbuf_froms(e->request, &sid)) != 0 || |
1501 | 0 | (r = sshbuf_froms(e->request, &sig)) != 0 || |
1502 | 0 | (r = sshbuf_get_u8(e->request, &fwd)) != 0) { |
1503 | 0 | error_fr(r, "parse"); |
1504 | 0 | goto out; |
1505 | 0 | } |
1506 | 0 | if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT, |
1507 | 0 | SSH_FP_DEFAULT)) == NULL) |
1508 | 0 | fatal_f("fingerprint failed"); |
1509 | | /* check signature with hostkey on session ID */ |
1510 | 0 | if ((r = sshkey_verify(key, sshbuf_ptr(sig), sshbuf_len(sig), |
1511 | 0 | sshbuf_ptr(sid), sshbuf_len(sid), NULL, 0, NULL)) != 0) { |
1512 | 0 | error_fr(r, "sshkey_verify for %s %s", sshkey_type(key), fp); |
1513 | 0 | goto out; |
1514 | 0 | } |
1515 | | /* check whether sid/key already recorded */ |
1516 | 0 | for (i = 0; i < e->nsession_ids; i++) { |
1517 | 0 | if (!e->session_ids[i].forwarded) { |
1518 | 0 | error_f("attempt to bind session ID to socket " |
1519 | 0 | "previously bound for authentication attempt"); |
1520 | 0 | r = -1; |
1521 | 0 | goto out; |
1522 | 0 | } |
1523 | 0 | sid_match = buf_equal(sid, e->session_ids[i].sid) == 0; |
1524 | 0 | key_match = sshkey_equal(key, e->session_ids[i].key); |
1525 | 0 | if (sid_match && key_match) { |
1526 | 0 | debug_f("session ID already recorded for %s %s", |
1527 | 0 | sshkey_type(key), fp); |
1528 | 0 | r = 0; |
1529 | 0 | goto out; |
1530 | 0 | } else if (sid_match) { |
1531 | 0 | error_f("session ID recorded against different key " |
1532 | 0 | "for %s %s", sshkey_type(key), fp); |
1533 | 0 | r = -1; |
1534 | 0 | goto out; |
1535 | 0 | } |
1536 | | /* |
1537 | | * new sid with previously-seen key can happen, e.g. multiple |
1538 | | * connections to the same host. |
1539 | | */ |
1540 | 0 | } |
1541 | | /* record new key/sid */ |
1542 | 0 | if (e->nsession_ids >= AGENT_MAX_SESSION_IDS) { |
1543 | 0 | error_f("too many session IDs recorded"); |
1544 | 0 | goto out; |
1545 | 0 | } |
1546 | 0 | e->session_ids = xrecallocarray(e->session_ids, e->nsession_ids, |
1547 | 0 | e->nsession_ids + 1, sizeof(*e->session_ids)); |
1548 | 0 | i = e->nsession_ids++; |
1549 | 0 | debug_f("recorded %s %s (slot %zu of %d)", sshkey_type(key), fp, i, |
1550 | 0 | AGENT_MAX_SESSION_IDS); |
1551 | 0 | e->session_ids[i].key = key; |
1552 | 0 | e->session_ids[i].forwarded = fwd != 0; |
1553 | 0 | key = NULL; /* transferred */ |
1554 | | /* can't transfer sid; it's refcounted and scoped to request's life */ |
1555 | 0 | if ((e->session_ids[i].sid = sshbuf_new()) == NULL) |
1556 | 0 | fatal_f("sshbuf_new"); |
1557 | 0 | if ((r = sshbuf_putb(e->session_ids[i].sid, sid)) != 0) |
1558 | 0 | fatal_fr(r, "sshbuf_putb session ID"); |
1559 | | /* success */ |
1560 | 0 | r = 0; |
1561 | 0 | out: |
1562 | 0 | free(fp); |
1563 | 0 | sshkey_free(key); |
1564 | 0 | sshbuf_free(sid); |
1565 | 0 | sshbuf_free(sig); |
1566 | 0 | return r == 0 ? 1 : 0; |
1567 | 0 | } |
1568 | | |
1569 | | static void |
1570 | | process_extension(SocketEntry *e) |
1571 | 7 | { |
1572 | 7 | int r, success = 0; |
1573 | 7 | char *name; |
1574 | | |
1575 | 7 | debug2_f("entering"); |
1576 | 7 | if ((r = sshbuf_get_cstring(e->request, &name, NULL)) != 0) { |
1577 | 6 | error_fr(r, "parse"); |
1578 | 6 | goto send; |
1579 | 6 | } |
1580 | 1 | if (strcmp(name, "session-bind@openssh.com") == 0) |
1581 | 0 | success = process_ext_session_bind(e); |
1582 | 1 | else |
1583 | 1 | debug_f("unsupported extension \"%s\"", name); |
1584 | 1 | free(name); |
1585 | 7 | send: |
1586 | 7 | send_status(e, success); |
1587 | 7 | } |
1588 | | /* |
1589 | | * dispatch incoming message. |
1590 | | * returns 1 on success, 0 for incomplete messages or -1 on error. |
1591 | | */ |
1592 | | static int |
1593 | | process_message(u_int socknum) |
1594 | 199k | { |
1595 | 199k | u_int msg_len; |
1596 | 199k | u_char type; |
1597 | 199k | const u_char *cp; |
1598 | 199k | int r; |
1599 | 199k | SocketEntry *e; |
1600 | | |
1601 | 199k | if (socknum >= sockets_alloc) |
1602 | 0 | fatal_f("sock %u >= allocated %u", socknum, sockets_alloc); |
1603 | 199k | e = &sockets[socknum]; |
1604 | | |
1605 | 199k | if (sshbuf_len(e->input) < 5) |
1606 | 225 | return 0; /* Incomplete message header. */ |
1607 | 199k | cp = sshbuf_ptr(e->input); |
1608 | 199k | msg_len = PEEK_U32(cp); |
1609 | 199k | if (msg_len > AGENT_MAX_LEN) { |
1610 | 1.27k | debug_f("socket %u (fd=%d) message too long %u > %u", |
1611 | 1.27k | socknum, e->fd, msg_len, AGENT_MAX_LEN); |
1612 | 1.27k | return -1; |
1613 | 1.27k | } |
1614 | 197k | if (sshbuf_len(e->input) < msg_len + 4) |
1615 | 1.03k | return 0; /* Incomplete message body. */ |
1616 | | |
1617 | | /* move the current input to e->request */ |
1618 | 196k | sshbuf_reset(e->request); |
1619 | 196k | if ((r = sshbuf_get_stringb(e->input, e->request)) != 0 || |
1620 | 196k | (r = sshbuf_get_u8(e->request, &type)) != 0) { |
1621 | 86.4k | if (r == SSH_ERR_MESSAGE_INCOMPLETE || |
1622 | 86.4k | r == SSH_ERR_STRING_TOO_LARGE) { |
1623 | 86.4k | error_fr(r, "parse"); |
1624 | 86.4k | return -1; |
1625 | 86.4k | } |
1626 | 86.4k | fatal_fr(r, "parse"); |
1627 | 86.4k | } |
1628 | | |
1629 | 110k | debug_f("socket %u (fd=%d) type %d", socknum, e->fd, type); |
1630 | | |
1631 | | /* check whether agent is locked */ |
1632 | 110k | if (locked && type != SSH_AGENTC_UNLOCK) { |
1633 | 99.3k | sshbuf_reset(e->request); |
1634 | 99.3k | switch (type) { |
1635 | 19.5k | case SSH2_AGENTC_REQUEST_IDENTITIES: |
1636 | | /* send empty lists */ |
1637 | 19.5k | no_identities(e); |
1638 | 19.5k | break; |
1639 | 79.8k | default: |
1640 | | /* send a fail message for all other request types */ |
1641 | 79.8k | send_status(e, 0); |
1642 | 99.3k | } |
1643 | 99.3k | return 1; |
1644 | 99.3k | } |
1645 | | |
1646 | 11.0k | switch (type) { |
1647 | 2 | case SSH_AGENTC_LOCK: |
1648 | 10.9k | case SSH_AGENTC_UNLOCK: |
1649 | 10.9k | process_lock_agent(e, type == SSH_AGENTC_LOCK); |
1650 | 10.9k | break; |
1651 | 4 | case SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES: |
1652 | 4 | process_remove_all_identities(e); /* safe for !WITH_SSH1 */ |
1653 | 4 | break; |
1654 | | /* ssh2 */ |
1655 | 26 | case SSH2_AGENTC_SIGN_REQUEST: |
1656 | 26 | process_sign_request2(e); |
1657 | 26 | break; |
1658 | 5 | case SSH2_AGENTC_REQUEST_IDENTITIES: |
1659 | 5 | process_request_identities(e); |
1660 | 5 | break; |
1661 | 7 | case SSH2_AGENTC_ADD_IDENTITY: |
1662 | 15 | case SSH2_AGENTC_ADD_ID_CONSTRAINED: |
1663 | 15 | process_add_identity(e); |
1664 | 15 | break; |
1665 | 7 | case SSH2_AGENTC_REMOVE_IDENTITY: |
1666 | 7 | process_remove_identity(e); |
1667 | 7 | break; |
1668 | 4 | case SSH2_AGENTC_REMOVE_ALL_IDENTITIES: |
1669 | 4 | process_remove_all_identities(e); |
1670 | 4 | break; |
1671 | 0 | #ifdef ENABLE_PKCS11 |
1672 | 12 | case SSH_AGENTC_ADD_SMARTCARD_KEY: |
1673 | 15 | case SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED: |
1674 | 15 | process_add_smartcard_key(e); |
1675 | 15 | break; |
1676 | 31 | case SSH_AGENTC_REMOVE_SMARTCARD_KEY: |
1677 | 31 | process_remove_smartcard_key(e); |
1678 | 31 | break; |
1679 | 0 | #endif /* ENABLE_PKCS11 */ |
1680 | 7 | case SSH_AGENTC_EXTENSION: |
1681 | 7 | process_extension(e); |
1682 | 7 | break; |
1683 | 10 | default: |
1684 | | /* Unknown message. Respond with failure. */ |
1685 | 10 | error("Unknown message %d", type); |
1686 | 10 | sshbuf_reset(e->request); |
1687 | 10 | send_status(e, 0); |
1688 | 10 | break; |
1689 | 11.0k | } |
1690 | 11.0k | return 1; |
1691 | 11.0k | } |
1692 | | |
1693 | | static void |
1694 | | new_socket(sock_type type, int fd) |
1695 | 2.91k | { |
1696 | 2.91k | u_int i, old_alloc, new_alloc; |
1697 | | |
1698 | 2.91k | debug_f("type = %s", type == AUTH_CONNECTION ? "CONNECTION" : |
1699 | 2.91k | (type == AUTH_SOCKET ? "SOCKET" : "UNKNOWN")); |
1700 | 2.91k | set_nonblock(fd); |
1701 | | |
1702 | 2.91k | if (fd > max_fd) |
1703 | 1 | max_fd = fd; |
1704 | | |
1705 | 2.91k | for (i = 0; i < sockets_alloc; i++) |
1706 | 0 | if (sockets[i].type == AUTH_UNUSED) { |
1707 | 0 | sockets[i].fd = fd; |
1708 | 0 | if ((sockets[i].input = sshbuf_new()) == NULL || |
1709 | 0 | (sockets[i].output = sshbuf_new()) == NULL || |
1710 | 0 | (sockets[i].request = sshbuf_new()) == NULL) |
1711 | 0 | fatal_f("sshbuf_new failed"); |
1712 | 0 | sockets[i].type = type; |
1713 | 0 | return; |
1714 | 0 | } |
1715 | 2.91k | old_alloc = sockets_alloc; |
1716 | 2.91k | new_alloc = sockets_alloc + 10; |
1717 | 2.91k | sockets = xrecallocarray(sockets, old_alloc, new_alloc, |
1718 | 2.91k | sizeof(sockets[0])); |
1719 | 32.0k | for (i = old_alloc; i < new_alloc; i++) |
1720 | 29.1k | sockets[i].type = AUTH_UNUSED; |
1721 | 2.91k | sockets_alloc = new_alloc; |
1722 | 2.91k | sockets[old_alloc].fd = fd; |
1723 | 2.91k | if ((sockets[old_alloc].input = sshbuf_new()) == NULL || |
1724 | 2.91k | (sockets[old_alloc].output = sshbuf_new()) == NULL || |
1725 | 2.91k | (sockets[old_alloc].request = sshbuf_new()) == NULL) |
1726 | 0 | fatal_f("sshbuf_new failed"); |
1727 | 2.91k | sockets[old_alloc].type = type; |
1728 | 2.91k | } |
1729 | | |
1730 | | static int |
1731 | | handle_socket_read(u_int socknum) |
1732 | 0 | { |
1733 | 0 | struct sockaddr_un sunaddr; |
1734 | 0 | socklen_t slen; |
1735 | 0 | uid_t euid; |
1736 | 0 | gid_t egid; |
1737 | 0 | int fd; |
1738 | |
|
1739 | 0 | slen = sizeof(sunaddr); |
1740 | 0 | fd = accept(sockets[socknum].fd, (struct sockaddr *)&sunaddr, &slen); |
1741 | 0 | if (fd == -1) { |
1742 | 0 | error("accept from AUTH_SOCKET: %s", strerror(errno)); |
1743 | 0 | return -1; |
1744 | 0 | } |
1745 | 0 | if (getpeereid(fd, &euid, &egid) == -1) { |
1746 | 0 | error("getpeereid %d failed: %s", fd, strerror(errno)); |
1747 | 0 | close(fd); |
1748 | 0 | return -1; |
1749 | 0 | } |
1750 | 0 | if ((euid != 0) && (getuid() != euid)) { |
1751 | 0 | error("uid mismatch: peer euid %u != uid %u", |
1752 | 0 | (u_int) euid, (u_int) getuid()); |
1753 | 0 | close(fd); |
1754 | 0 | return -1; |
1755 | 0 | } |
1756 | 0 | new_socket(AUTH_CONNECTION, fd); |
1757 | 0 | return 0; |
1758 | 0 | } |
1759 | | |
1760 | | static int |
1761 | | handle_conn_read(u_int socknum) |
1762 | 0 | { |
1763 | 0 | char buf[AGENT_RBUF_LEN]; |
1764 | 0 | ssize_t len; |
1765 | 0 | int r; |
1766 | |
|
1767 | 0 | if ((len = read(sockets[socknum].fd, buf, sizeof(buf))) <= 0) { |
1768 | 0 | if (len == -1) { |
1769 | 0 | if (errno == EAGAIN || errno == EINTR) |
1770 | 0 | return 0; |
1771 | 0 | error_f("read error on socket %u (fd %d): %s", |
1772 | 0 | socknum, sockets[socknum].fd, strerror(errno)); |
1773 | 0 | } |
1774 | 0 | return -1; |
1775 | 0 | } |
1776 | 0 | if ((r = sshbuf_put(sockets[socknum].input, buf, len)) != 0) |
1777 | 0 | fatal_fr(r, "compose"); |
1778 | 0 | explicit_bzero(buf, sizeof(buf)); |
1779 | 0 | for (;;) { |
1780 | 0 | if ((r = process_message(socknum)) == -1) |
1781 | 0 | return -1; |
1782 | 0 | else if (r == 0) |
1783 | 0 | break; |
1784 | 0 | } |
1785 | 0 | return 0; |
1786 | 0 | } |
1787 | | |
1788 | | static int |
1789 | | handle_conn_write(u_int socknum) |
1790 | 0 | { |
1791 | 0 | ssize_t len; |
1792 | 0 | int r; |
1793 | |
|
1794 | 0 | if (sshbuf_len(sockets[socknum].output) == 0) |
1795 | 0 | return 0; /* shouldn't happen */ |
1796 | 0 | if ((len = write(sockets[socknum].fd, |
1797 | 0 | sshbuf_ptr(sockets[socknum].output), |
1798 | 0 | sshbuf_len(sockets[socknum].output))) <= 0) { |
1799 | 0 | if (len == -1) { |
1800 | 0 | if (errno == EAGAIN || errno == EINTR) |
1801 | 0 | return 0; |
1802 | 0 | error_f("read error on socket %u (fd %d): %s", |
1803 | 0 | socknum, sockets[socknum].fd, strerror(errno)); |
1804 | 0 | } |
1805 | 0 | return -1; |
1806 | 0 | } |
1807 | 0 | if ((r = sshbuf_consume(sockets[socknum].output, len)) != 0) |
1808 | 0 | fatal_fr(r, "consume"); |
1809 | 0 | return 0; |
1810 | 0 | } |
1811 | | |
1812 | | static void |
1813 | | after_poll(struct pollfd *pfd, size_t npfd, u_int maxfds) |
1814 | 0 | { |
1815 | 0 | size_t i; |
1816 | 0 | u_int socknum, activefds = npfd; |
1817 | |
|
1818 | 0 | for (i = 0; i < npfd; i++) { |
1819 | 0 | if (pfd[i].revents == 0) |
1820 | 0 | continue; |
1821 | | /* Find sockets entry */ |
1822 | 0 | for (socknum = 0; socknum < sockets_alloc; socknum++) { |
1823 | 0 | if (sockets[socknum].type != AUTH_SOCKET && |
1824 | 0 | sockets[socknum].type != AUTH_CONNECTION) |
1825 | 0 | continue; |
1826 | 0 | if (pfd[i].fd == sockets[socknum].fd) |
1827 | 0 | break; |
1828 | 0 | } |
1829 | 0 | if (socknum >= sockets_alloc) { |
1830 | 0 | error_f("no socket for fd %d", pfd[i].fd); |
1831 | 0 | continue; |
1832 | 0 | } |
1833 | | /* Process events */ |
1834 | 0 | switch (sockets[socknum].type) { |
1835 | 0 | case AUTH_SOCKET: |
1836 | 0 | if ((pfd[i].revents & (POLLIN|POLLERR)) == 0) |
1837 | 0 | break; |
1838 | 0 | if (npfd > maxfds) { |
1839 | 0 | debug3("out of fds (active %u >= limit %u); " |
1840 | 0 | "skipping accept", activefds, maxfds); |
1841 | 0 | break; |
1842 | 0 | } |
1843 | 0 | if (handle_socket_read(socknum) == 0) |
1844 | 0 | activefds++; |
1845 | 0 | break; |
1846 | 0 | case AUTH_CONNECTION: |
1847 | 0 | if ((pfd[i].revents & (POLLIN|POLLHUP|POLLERR)) != 0 && |
1848 | 0 | handle_conn_read(socknum) != 0) |
1849 | 0 | goto close_sock; |
1850 | 0 | if ((pfd[i].revents & (POLLOUT|POLLHUP)) != 0 && |
1851 | 0 | handle_conn_write(socknum) != 0) { |
1852 | 0 | close_sock: |
1853 | 0 | if (activefds == 0) |
1854 | 0 | fatal("activefds == 0 at close_sock"); |
1855 | 0 | close_socket(&sockets[socknum]); |
1856 | 0 | activefds--; |
1857 | 0 | break; |
1858 | 0 | } |
1859 | 0 | break; |
1860 | 0 | default: |
1861 | 0 | break; |
1862 | 0 | } |
1863 | 0 | } |
1864 | 0 | } |
1865 | | |
1866 | | static int |
1867 | | prepare_poll(struct pollfd **pfdp, size_t *npfdp, int *timeoutp, u_int maxfds) |
1868 | 0 | { |
1869 | 0 | struct pollfd *pfd = *pfdp; |
1870 | 0 | size_t i, j, npfd = 0; |
1871 | 0 | time_t deadline; |
1872 | 0 | int r; |
1873 | | |
1874 | | /* Count active sockets */ |
1875 | 0 | for (i = 0; i < sockets_alloc; i++) { |
1876 | 0 | switch (sockets[i].type) { |
1877 | 0 | case AUTH_SOCKET: |
1878 | 0 | case AUTH_CONNECTION: |
1879 | 0 | npfd++; |
1880 | 0 | break; |
1881 | 0 | case AUTH_UNUSED: |
1882 | 0 | break; |
1883 | 0 | default: |
1884 | 0 | fatal("Unknown socket type %d", sockets[i].type); |
1885 | 0 | break; |
1886 | 0 | } |
1887 | 0 | } |
1888 | 0 | if (npfd != *npfdp && |
1889 | 0 | (pfd = recallocarray(pfd, *npfdp, npfd, sizeof(*pfd))) == NULL) |
1890 | 0 | fatal_f("recallocarray failed"); |
1891 | 0 | *pfdp = pfd; |
1892 | 0 | *npfdp = npfd; |
1893 | |
|
1894 | 0 | for (i = j = 0; i < sockets_alloc; i++) { |
1895 | 0 | switch (sockets[i].type) { |
1896 | 0 | case AUTH_SOCKET: |
1897 | 0 | if (npfd > maxfds) { |
1898 | 0 | debug3("out of fds (active %zu >= limit %u); " |
1899 | 0 | "skipping arming listener", npfd, maxfds); |
1900 | 0 | break; |
1901 | 0 | } |
1902 | 0 | pfd[j].fd = sockets[i].fd; |
1903 | 0 | pfd[j].revents = 0; |
1904 | 0 | pfd[j].events = POLLIN; |
1905 | 0 | j++; |
1906 | 0 | break; |
1907 | 0 | case AUTH_CONNECTION: |
1908 | 0 | pfd[j].fd = sockets[i].fd; |
1909 | 0 | pfd[j].revents = 0; |
1910 | | /* |
1911 | | * Only prepare to read if we can handle a full-size |
1912 | | * input read buffer and enqueue a max size reply.. |
1913 | | */ |
1914 | 0 | if ((r = sshbuf_check_reserve(sockets[i].input, |
1915 | 0 | AGENT_RBUF_LEN)) == 0 && |
1916 | 0 | (r = sshbuf_check_reserve(sockets[i].output, |
1917 | 0 | AGENT_MAX_LEN)) == 0) |
1918 | 0 | pfd[j].events = POLLIN; |
1919 | 0 | else if (r != SSH_ERR_NO_BUFFER_SPACE) |
1920 | 0 | fatal_fr(r, "reserve"); |
1921 | 0 | if (sshbuf_len(sockets[i].output) > 0) |
1922 | 0 | pfd[j].events |= POLLOUT; |
1923 | 0 | j++; |
1924 | 0 | break; |
1925 | 0 | default: |
1926 | 0 | break; |
1927 | 0 | } |
1928 | 0 | } |
1929 | 0 | deadline = reaper(); |
1930 | 0 | if (parent_alive_interval != 0) |
1931 | 0 | deadline = (deadline == 0) ? parent_alive_interval : |
1932 | 0 | MINIMUM(deadline, parent_alive_interval); |
1933 | 0 | if (deadline == 0) { |
1934 | 0 | *timeoutp = -1; /* INFTIM */ |
1935 | 0 | } else { |
1936 | 0 | if (deadline > INT_MAX / 1000) |
1937 | 0 | *timeoutp = INT_MAX / 1000; |
1938 | 0 | else |
1939 | 0 | *timeoutp = deadline * 1000; |
1940 | 0 | } |
1941 | 0 | return (1); |
1942 | 0 | } |
1943 | | |
1944 | | static void |
1945 | | cleanup_socket(void) |
1946 | 0 | { |
1947 | 0 | if (cleanup_pid != 0 && getpid() != cleanup_pid) |
1948 | 0 | return; |
1949 | 0 | debug_f("cleanup"); |
1950 | 0 | if (socket_name[0]) |
1951 | 0 | unlink(socket_name); |
1952 | 0 | if (socket_dir[0]) |
1953 | 0 | rmdir(socket_dir); |
1954 | 0 | } |
1955 | | |
1956 | | void |
1957 | | cleanup_exit(int i) |
1958 | 0 | { |
1959 | 0 | cleanup_socket(); |
1960 | 0 | _exit(i); |
1961 | 0 | } |
1962 | | |
1963 | | static void |
1964 | | cleanup_handler(int sig) |
1965 | 0 | { |
1966 | 0 | cleanup_socket(); |
1967 | 0 | #ifdef ENABLE_PKCS11 |
1968 | 0 | pkcs11_terminate(); |
1969 | 0 | #endif |
1970 | 0 | _exit(2); |
1971 | 0 | } |
1972 | | |
1973 | | static void |
1974 | | check_parent_exists(void) |
1975 | 0 | { |
1976 | | /* |
1977 | | * If our parent has exited then getppid() will return (pid_t)1, |
1978 | | * so testing for that should be safe. |
1979 | | */ |
1980 | 0 | if (parent_pid != -1 && getppid() != parent_pid) { |
1981 | | /* printf("Parent has died - Authentication agent exiting.\n"); */ |
1982 | 0 | cleanup_socket(); |
1983 | 0 | _exit(2); |
1984 | 0 | } |
1985 | 0 | } |
1986 | | |
1987 | | static void |
1988 | | usage(void) |
1989 | 0 | { |
1990 | 0 | fprintf(stderr, |
1991 | 0 | "usage: ssh-agent [-c | -s] [-Dd] [-a bind_address] [-E fingerprint_hash]\n" |
1992 | 0 | " [-O option] [-P allowed_providers] [-t life]\n" |
1993 | 0 | " ssh-agent [-a bind_address] [-E fingerprint_hash] [-O option]\n" |
1994 | 0 | " [-P allowed_providers] [-t life] command [arg ...]\n" |
1995 | 0 | " ssh-agent [-c | -s] -k\n"); |
1996 | 0 | exit(1); |
1997 | 0 | } |
1998 | | |
1999 | | int |
2000 | | main(int ac, char **av) |
2001 | 0 | { |
2002 | 0 | int c_flag = 0, d_flag = 0, D_flag = 0, k_flag = 0, s_flag = 0; |
2003 | 0 | int sock, ch, result, saved_errno; |
2004 | 0 | char *shell, *format, *pidstr, *agentsocket = NULL; |
2005 | 0 | #ifdef HAVE_SETRLIMIT |
2006 | 0 | struct rlimit rlim; |
2007 | 0 | #endif |
2008 | 0 | extern int optind; |
2009 | 0 | extern char *optarg; |
2010 | 0 | pid_t pid; |
2011 | 0 | char pidstrbuf[1 + 3 * sizeof pid]; |
2012 | 0 | size_t len; |
2013 | 0 | mode_t prev_mask; |
2014 | 0 | int timeout = -1; /* INFTIM */ |
2015 | 0 | struct pollfd *pfd = NULL; |
2016 | 0 | size_t npfd = 0; |
2017 | 0 | u_int maxfds; |
2018 | | |
2019 | | /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ |
2020 | 0 | sanitise_stdfd(); |
2021 | | |
2022 | | /* drop */ |
2023 | 0 | setegid(getgid()); |
2024 | 0 | setgid(getgid()); |
2025 | |
|
2026 | 0 | platform_disable_tracing(0); /* strict=no */ |
2027 | |
|
2028 | 0 | #ifdef RLIMIT_NOFILE |
2029 | 0 | if (getrlimit(RLIMIT_NOFILE, &rlim) == -1) |
2030 | 0 | fatal("%s: getrlimit: %s", __progname, strerror(errno)); |
2031 | 0 | #endif |
2032 | | |
2033 | 0 | __progname = ssh_get_progname(av[0]); |
2034 | 0 | seed_rng(); |
2035 | |
|
2036 | 0 | while ((ch = getopt(ac, av, "cDdksE:a:O:P:t:")) != -1) { |
2037 | 0 | switch (ch) { |
2038 | 0 | case 'E': |
2039 | 0 | fingerprint_hash = ssh_digest_alg_by_name(optarg); |
2040 | 0 | if (fingerprint_hash == -1) |
2041 | 0 | fatal("Invalid hash algorithm \"%s\"", optarg); |
2042 | 0 | break; |
2043 | 0 | case 'c': |
2044 | 0 | if (s_flag) |
2045 | 0 | usage(); |
2046 | 0 | c_flag++; |
2047 | 0 | break; |
2048 | 0 | case 'k': |
2049 | 0 | k_flag++; |
2050 | 0 | break; |
2051 | 0 | case 'O': |
2052 | 0 | if (strcmp(optarg, "no-restrict-websafe") == 0) |
2053 | 0 | restrict_websafe = 0; |
2054 | 0 | else |
2055 | 0 | fatal("Unknown -O option"); |
2056 | 0 | break; |
2057 | 0 | case 'P': |
2058 | 0 | if (allowed_providers != NULL) |
2059 | 0 | fatal("-P option already specified"); |
2060 | 0 | allowed_providers = xstrdup(optarg); |
2061 | 0 | break; |
2062 | 0 | case 's': |
2063 | 0 | if (c_flag) |
2064 | 0 | usage(); |
2065 | 0 | s_flag++; |
2066 | 0 | break; |
2067 | 0 | case 'd': |
2068 | 0 | if (d_flag || D_flag) |
2069 | 0 | usage(); |
2070 | 0 | d_flag++; |
2071 | 0 | break; |
2072 | 0 | case 'D': |
2073 | 0 | if (d_flag || D_flag) |
2074 | 0 | usage(); |
2075 | 0 | D_flag++; |
2076 | 0 | break; |
2077 | 0 | case 'a': |
2078 | 0 | agentsocket = optarg; |
2079 | 0 | break; |
2080 | 0 | case 't': |
2081 | 0 | if ((lifetime = convtime(optarg)) == -1) { |
2082 | 0 | fprintf(stderr, "Invalid lifetime\n"); |
2083 | 0 | usage(); |
2084 | 0 | } |
2085 | 0 | break; |
2086 | 0 | default: |
2087 | 0 | usage(); |
2088 | 0 | } |
2089 | 0 | } |
2090 | 0 | ac -= optind; |
2091 | 0 | av += optind; |
2092 | |
|
2093 | 0 | if (ac > 0 && (c_flag || k_flag || s_flag || d_flag || D_flag)) |
2094 | 0 | usage(); |
2095 | |
|
2096 | 0 | if (allowed_providers == NULL) |
2097 | 0 | allowed_providers = xstrdup(DEFAULT_ALLOWED_PROVIDERS); |
2098 | |
|
2099 | 0 | if (ac == 0 && !c_flag && !s_flag) { |
2100 | 0 | shell = getenv("SHELL"); |
2101 | 0 | if (shell != NULL && (len = strlen(shell)) > 2 && |
2102 | 0 | strncmp(shell + len - 3, "csh", 3) == 0) |
2103 | 0 | c_flag = 1; |
2104 | 0 | } |
2105 | 0 | if (k_flag) { |
2106 | 0 | const char *errstr = NULL; |
2107 | |
|
2108 | 0 | pidstr = getenv(SSH_AGENTPID_ENV_NAME); |
2109 | 0 | if (pidstr == NULL) { |
2110 | 0 | fprintf(stderr, "%s not set, cannot kill agent\n", |
2111 | 0 | SSH_AGENTPID_ENV_NAME); |
2112 | 0 | exit(1); |
2113 | 0 | } |
2114 | 0 | pid = (int)strtonum(pidstr, 2, INT_MAX, &errstr); |
2115 | 0 | if (errstr) { |
2116 | 0 | fprintf(stderr, |
2117 | 0 | "%s=\"%s\", which is not a good PID: %s\n", |
2118 | 0 | SSH_AGENTPID_ENV_NAME, pidstr, errstr); |
2119 | 0 | exit(1); |
2120 | 0 | } |
2121 | 0 | if (kill(pid, SIGTERM) == -1) { |
2122 | 0 | perror("kill"); |
2123 | 0 | exit(1); |
2124 | 0 | } |
2125 | 0 | format = c_flag ? "unsetenv %s;\n" : "unset %s;\n"; |
2126 | 0 | printf(format, SSH_AUTHSOCKET_ENV_NAME); |
2127 | 0 | printf(format, SSH_AGENTPID_ENV_NAME); |
2128 | 0 | printf("echo Agent pid %ld killed;\n", (long)pid); |
2129 | 0 | exit(0); |
2130 | 0 | } |
2131 | | |
2132 | | /* |
2133 | | * Minimum file descriptors: |
2134 | | * stdio (3) + listener (1) + syslog (1 maybe) + connection (1) + |
2135 | | * a few spare for libc / stack protectors / sanitisers, etc. |
2136 | | */ |
2137 | 0 | #define SSH_AGENT_MIN_FDS (3+1+1+1+4) |
2138 | 0 | if (rlim.rlim_cur < SSH_AGENT_MIN_FDS) |
2139 | 0 | fatal("%s: file descriptor rlimit %lld too low (minimum %u)", |
2140 | 0 | __progname, (long long)rlim.rlim_cur, SSH_AGENT_MIN_FDS); |
2141 | 0 | maxfds = rlim.rlim_cur - SSH_AGENT_MIN_FDS; |
2142 | |
|
2143 | 0 | parent_pid = getpid(); |
2144 | |
|
2145 | 0 | if (agentsocket == NULL) { |
2146 | | /* Create private directory for agent socket */ |
2147 | 0 | mktemp_proto(socket_dir, sizeof(socket_dir)); |
2148 | 0 | if (mkdtemp(socket_dir) == NULL) { |
2149 | 0 | perror("mkdtemp: private socket dir"); |
2150 | 0 | exit(1); |
2151 | 0 | } |
2152 | 0 | snprintf(socket_name, sizeof socket_name, "%s/agent.%ld", socket_dir, |
2153 | 0 | (long)parent_pid); |
2154 | 0 | } else { |
2155 | | /* Try to use specified agent socket */ |
2156 | 0 | socket_dir[0] = '\0'; |
2157 | 0 | strlcpy(socket_name, agentsocket, sizeof socket_name); |
2158 | 0 | } |
2159 | | |
2160 | | /* |
2161 | | * Create socket early so it will exist before command gets run from |
2162 | | * the parent. |
2163 | | */ |
2164 | 0 | prev_mask = umask(0177); |
2165 | 0 | sock = unix_listener(socket_name, SSH_LISTEN_BACKLOG, 0); |
2166 | 0 | if (sock < 0) { |
2167 | | /* XXX - unix_listener() calls error() not perror() */ |
2168 | 0 | *socket_name = '\0'; /* Don't unlink any existing file */ |
2169 | 0 | cleanup_exit(1); |
2170 | 0 | } |
2171 | 0 | umask(prev_mask); |
2172 | | |
2173 | | /* |
2174 | | * Fork, and have the parent execute the command, if any, or present |
2175 | | * the socket data. The child continues as the authentication agent. |
2176 | | */ |
2177 | 0 | if (D_flag || d_flag) { |
2178 | 0 | log_init(__progname, |
2179 | 0 | d_flag ? SYSLOG_LEVEL_DEBUG3 : SYSLOG_LEVEL_INFO, |
2180 | 0 | SYSLOG_FACILITY_AUTH, 1); |
2181 | 0 | format = c_flag ? "setenv %s %s;\n" : "%s=%s; export %s;\n"; |
2182 | 0 | printf(format, SSH_AUTHSOCKET_ENV_NAME, socket_name, |
2183 | 0 | SSH_AUTHSOCKET_ENV_NAME); |
2184 | 0 | printf("echo Agent pid %ld;\n", (long)parent_pid); |
2185 | 0 | fflush(stdout); |
2186 | 0 | goto skip; |
2187 | 0 | } |
2188 | 0 | pid = fork(); |
2189 | 0 | if (pid == -1) { |
2190 | 0 | perror("fork"); |
2191 | 0 | cleanup_exit(1); |
2192 | 0 | } |
2193 | 0 | if (pid != 0) { /* Parent - execute the given command. */ |
2194 | 0 | close(sock); |
2195 | 0 | snprintf(pidstrbuf, sizeof pidstrbuf, "%ld", (long)pid); |
2196 | 0 | if (ac == 0) { |
2197 | 0 | format = c_flag ? "setenv %s %s;\n" : "%s=%s; export %s;\n"; |
2198 | 0 | printf(format, SSH_AUTHSOCKET_ENV_NAME, socket_name, |
2199 | 0 | SSH_AUTHSOCKET_ENV_NAME); |
2200 | 0 | printf(format, SSH_AGENTPID_ENV_NAME, pidstrbuf, |
2201 | 0 | SSH_AGENTPID_ENV_NAME); |
2202 | 0 | printf("echo Agent pid %ld;\n", (long)pid); |
2203 | 0 | exit(0); |
2204 | 0 | } |
2205 | 0 | if (setenv(SSH_AUTHSOCKET_ENV_NAME, socket_name, 1) == -1 || |
2206 | 0 | setenv(SSH_AGENTPID_ENV_NAME, pidstrbuf, 1) == -1) { |
2207 | 0 | perror("setenv"); |
2208 | 0 | exit(1); |
2209 | 0 | } |
2210 | 0 | execvp(av[0], av); |
2211 | 0 | perror(av[0]); |
2212 | 0 | exit(1); |
2213 | 0 | } |
2214 | | /* child */ |
2215 | 0 | log_init(__progname, SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_AUTH, 0); |
2216 | |
|
2217 | 0 | if (setsid() == -1) { |
2218 | 0 | error("setsid: %s", strerror(errno)); |
2219 | 0 | cleanup_exit(1); |
2220 | 0 | } |
2221 | | |
2222 | 0 | (void)chdir("/"); |
2223 | 0 | if (stdfd_devnull(1, 1, 1) == -1) |
2224 | 0 | error_f("stdfd_devnull failed"); |
2225 | |
|
2226 | 0 | #ifdef HAVE_SETRLIMIT |
2227 | | /* deny core dumps, since memory contains unencrypted private keys */ |
2228 | 0 | rlim.rlim_cur = rlim.rlim_max = 0; |
2229 | 0 | if (setrlimit(RLIMIT_CORE, &rlim) == -1) { |
2230 | 0 | error("setrlimit RLIMIT_CORE: %s", strerror(errno)); |
2231 | 0 | cleanup_exit(1); |
2232 | 0 | } |
2233 | 0 | #endif |
2234 | | |
2235 | 0 | skip: |
2236 | |
|
2237 | 0 | cleanup_pid = getpid(); |
2238 | |
|
2239 | 0 | #ifdef ENABLE_PKCS11 |
2240 | 0 | pkcs11_init(0); |
2241 | 0 | #endif |
2242 | 0 | new_socket(AUTH_SOCKET, sock); |
2243 | 0 | if (ac > 0) |
2244 | 0 | parent_alive_interval = 10; |
2245 | 0 | idtab_init(); |
2246 | 0 | ssh_signal(SIGPIPE, SIG_IGN); |
2247 | 0 | ssh_signal(SIGINT, (d_flag | D_flag) ? cleanup_handler : SIG_IGN); |
2248 | 0 | ssh_signal(SIGHUP, cleanup_handler); |
2249 | 0 | ssh_signal(SIGTERM, cleanup_handler); |
2250 | |
|
2251 | 0 | if (pledge("stdio rpath cpath unix id proc exec", NULL) == -1) |
2252 | 0 | fatal("%s: pledge: %s", __progname, strerror(errno)); |
2253 | 0 | platform_pledge_agent(); |
2254 | |
|
2255 | 0 | while (1) { |
2256 | 0 | prepare_poll(&pfd, &npfd, &timeout, maxfds); |
2257 | 0 | result = poll(pfd, npfd, timeout); |
2258 | 0 | saved_errno = errno; |
2259 | 0 | if (parent_alive_interval != 0) |
2260 | 0 | check_parent_exists(); |
2261 | 0 | (void) reaper(); /* remove expired keys */ |
2262 | 0 | if (result == -1) { |
2263 | 0 | if (saved_errno == EINTR) |
2264 | 0 | continue; |
2265 | 0 | fatal("poll: %s", strerror(saved_errno)); |
2266 | 0 | } else if (result > 0) |
2267 | 0 | after_poll(pfd, npfd, maxfds); |
2268 | 0 | } |
2269 | | /* NOTREACHED */ |
2270 | 0 | } |