1 | /* $NetBSD: authfd.c,v 1.18 2019/04/20 17:16:40 christos Exp $ */ |
2 | /* $OpenBSD: authfd.c,v 1.113 2018/12/27 23:02:11 djm Exp $ */ |
3 | /* |
4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
5 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
6 | * All rights reserved |
7 | * Functions for connecting the local authentication agent. |
8 | * |
9 | * As far as I am concerned, the code I have written for this software |
10 | * can be used freely for any purpose. Any derived versions of this |
11 | * software must be clearly marked as such, and if the derived work is |
12 | * incompatible with the protocol description in the RFC file, it must be |
13 | * called by a name other than "ssh" or "Secure Shell". |
14 | * |
15 | * SSH2 implementation, |
16 | * Copyright (c) 2000 Markus Friedl. All rights reserved. |
17 | * |
18 | * Redistribution and use in source and binary forms, with or without |
19 | * modification, are permitted provided that the following conditions |
20 | * are met: |
21 | * 1. Redistributions of source code must retain the above copyright |
22 | * notice, this list of conditions and the following disclaimer. |
23 | * 2. Redistributions in binary form must reproduce the above copyright |
24 | * notice, this list of conditions and the following disclaimer in the |
25 | * documentation and/or other materials provided with the distribution. |
26 | * |
27 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
28 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
29 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
30 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
31 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
32 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
33 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
34 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
35 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
36 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
37 | */ |
38 | |
39 | #include "includes.h" |
40 | __RCSID("$NetBSD: authfd.c,v 1.18 2019/04/20 17:16:40 christos Exp $" ); |
41 | #include <sys/types.h> |
42 | #include <sys/un.h> |
43 | #include <sys/socket.h> |
44 | |
45 | #include <fcntl.h> |
46 | #include <stdlib.h> |
47 | #include <signal.h> |
48 | #include <string.h> |
49 | #include <unistd.h> |
50 | #include <errno.h> |
51 | |
52 | #include "xmalloc.h" |
53 | #include "ssh.h" |
54 | #include "sshbuf.h" |
55 | #include "sshkey.h" |
56 | #include "authfd.h" |
57 | #include "cipher.h" |
58 | #include "compat.h" |
59 | #include "log.h" |
60 | #include "atomicio.h" |
61 | #include "misc.h" |
62 | #include "ssherr.h" |
63 | |
64 | #define MAX_AGENT_IDENTITIES 2048 /* Max keys in agent reply */ |
65 | #define MAX_AGENT_REPLY_LEN (256 * 1024) /* Max bytes in agent reply */ |
66 | |
67 | /* macro to check for "agent failure" message */ |
68 | #define agent_failed(x) \ |
69 | ((x == SSH_AGENT_FAILURE) || \ |
70 | (x == SSH_COM_AGENT2_FAILURE) || \ |
71 | (x == SSH2_AGENT_FAILURE)) |
72 | |
73 | /* Convert success/failure response from agent to a err.h status */ |
74 | static int |
75 | decode_reply(u_char type) |
76 | { |
77 | if (agent_failed(type)) |
78 | return SSH_ERR_AGENT_FAILURE; |
79 | else if (type == SSH_AGENT_SUCCESS) |
80 | return 0; |
81 | else |
82 | return SSH_ERR_INVALID_FORMAT; |
83 | } |
84 | |
85 | /* Returns the number of the authentication fd, or -1 if there is none. */ |
86 | int |
87 | ssh_get_authentication_socket(int *fdp) |
88 | { |
89 | const char *authsocket; |
90 | int sock, oerrno; |
91 | struct sockaddr_un sunaddr; |
92 | |
93 | if (fdp != NULL) |
94 | *fdp = -1; |
95 | |
96 | authsocket = getenv(SSH_AUTHSOCKET_ENV_NAME); |
97 | if (authsocket == NULL || *authsocket == '\0') |
98 | return SSH_ERR_AGENT_NOT_PRESENT; |
99 | |
100 | memset(&sunaddr, 0, sizeof(sunaddr)); |
101 | sunaddr.sun_family = AF_UNIX; |
102 | strlcpy(sunaddr.sun_path, authsocket, sizeof(sunaddr.sun_path)); |
103 | |
104 | if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) |
105 | return SSH_ERR_SYSTEM_ERROR; |
106 | |
107 | /* close on exec */ |
108 | if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1 || |
109 | connect(sock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) < 0) { |
110 | oerrno = errno; |
111 | close(sock); |
112 | errno = oerrno; |
113 | return SSH_ERR_SYSTEM_ERROR; |
114 | } |
115 | if (fdp != NULL) |
116 | *fdp = sock; |
117 | else |
118 | close(sock); |
119 | return 0; |
120 | } |
121 | |
122 | /* Communicate with agent: send request and read reply */ |
123 | static int |
124 | ssh_request_reply(int sock, struct sshbuf *request, struct sshbuf *reply) |
125 | { |
126 | int r; |
127 | size_t l, len; |
128 | char buf[1024]; |
129 | |
130 | /* Get the length of the message, and format it in the buffer. */ |
131 | len = sshbuf_len(request); |
132 | POKE_U32(buf, len); |
133 | |
134 | /* Send the length and then the packet to the agent. */ |
135 | if (atomicio(vwrite, sock, buf, 4) != 4 || |
136 | atomicio(vwrite, sock, sshbuf_mutable_ptr(request), |
137 | sshbuf_len(request)) != sshbuf_len(request)) |
138 | return SSH_ERR_AGENT_COMMUNICATION; |
139 | /* |
140 | * Wait for response from the agent. First read the length of the |
141 | * response packet. |
142 | */ |
143 | if (atomicio(read, sock, buf, 4) != 4) |
144 | return SSH_ERR_AGENT_COMMUNICATION; |
145 | |
146 | /* Extract the length, and check it for sanity. */ |
147 | len = PEEK_U32(buf); |
148 | if (len > MAX_AGENT_REPLY_LEN) |
149 | return SSH_ERR_INVALID_FORMAT; |
150 | |
151 | /* Read the rest of the response in to the buffer. */ |
152 | sshbuf_reset(reply); |
153 | while (len > 0) { |
154 | l = len; |
155 | if (l > sizeof(buf)) |
156 | l = sizeof(buf); |
157 | if (atomicio(read, sock, buf, l) != l) |
158 | return SSH_ERR_AGENT_COMMUNICATION; |
159 | if ((r = sshbuf_put(reply, buf, l)) != 0) |
160 | return r; |
161 | len -= l; |
162 | } |
163 | return 0; |
164 | } |
165 | |
166 | /* |
167 | * Closes the agent socket if it should be closed (depends on how it was |
168 | * obtained). The argument must have been returned by |
169 | * ssh_get_authentication_socket(). |
170 | */ |
171 | void |
172 | ssh_close_authentication_socket(int sock) |
173 | { |
174 | if (getenv(SSH_AUTHSOCKET_ENV_NAME)) |
175 | close(sock); |
176 | } |
177 | |
178 | /* Lock/unlock agent */ |
179 | int |
180 | ssh_lock_agent(int sock, int lock, const char *password) |
181 | { |
182 | int r; |
183 | u_char type = lock ? SSH_AGENTC_LOCK : SSH_AGENTC_UNLOCK; |
184 | struct sshbuf *msg; |
185 | |
186 | if ((msg = sshbuf_new()) == NULL) |
187 | return SSH_ERR_ALLOC_FAIL; |
188 | if ((r = sshbuf_put_u8(msg, type)) != 0 || |
189 | (r = sshbuf_put_cstring(msg, password)) != 0) |
190 | goto out; |
191 | if ((r = ssh_request_reply(sock, msg, msg)) != 0) |
192 | goto out; |
193 | if ((r = sshbuf_get_u8(msg, &type)) != 0) |
194 | goto out; |
195 | r = decode_reply(type); |
196 | out: |
197 | sshbuf_free(msg); |
198 | return r; |
199 | } |
200 | |
201 | |
202 | static int |
203 | deserialise_identity2(struct sshbuf *ids, struct sshkey **keyp, char **) |
204 | { |
205 | int r; |
206 | char * = NULL; |
207 | const u_char *blob; |
208 | size_t blen; |
209 | |
210 | if ((r = sshbuf_get_string_direct(ids, &blob, &blen)) != 0 || |
211 | (r = sshbuf_get_cstring(ids, &comment, NULL)) != 0) |
212 | goto out; |
213 | if ((r = sshkey_from_blob(blob, blen, keyp)) != 0) |
214 | goto out; |
215 | if (commentp != NULL) { |
216 | *commentp = comment; |
217 | comment = NULL; |
218 | } |
219 | r = 0; |
220 | out: |
221 | free(comment); |
222 | return r; |
223 | } |
224 | |
225 | /* |
226 | * Fetch list of identities held by the agent. |
227 | */ |
228 | int |
229 | ssh_fetch_identitylist(int sock, struct ssh_identitylist **idlp) |
230 | { |
231 | u_char type; |
232 | u_int32_t num, i; |
233 | struct sshbuf *msg; |
234 | struct ssh_identitylist *idl = NULL; |
235 | int r; |
236 | |
237 | /* |
238 | * Send a message to the agent requesting for a list of the |
239 | * identities it can represent. |
240 | */ |
241 | if ((msg = sshbuf_new()) == NULL) |
242 | return SSH_ERR_ALLOC_FAIL; |
243 | if ((r = sshbuf_put_u8(msg, SSH2_AGENTC_REQUEST_IDENTITIES)) != 0) |
244 | goto out; |
245 | |
246 | if ((r = ssh_request_reply(sock, msg, msg)) != 0) |
247 | goto out; |
248 | |
249 | /* Get message type, and verify that we got a proper answer. */ |
250 | if ((r = sshbuf_get_u8(msg, &type)) != 0) |
251 | goto out; |
252 | if (agent_failed(type)) { |
253 | r = SSH_ERR_AGENT_FAILURE; |
254 | goto out; |
255 | } else if (type != SSH2_AGENT_IDENTITIES_ANSWER) { |
256 | r = SSH_ERR_INVALID_FORMAT; |
257 | goto out; |
258 | } |
259 | |
260 | /* Get the number of entries in the response and check it for sanity. */ |
261 | if ((r = sshbuf_get_u32(msg, &num)) != 0) |
262 | goto out; |
263 | if (num > MAX_AGENT_IDENTITIES) { |
264 | r = SSH_ERR_INVALID_FORMAT; |
265 | goto out; |
266 | } |
267 | if (num == 0) { |
268 | r = SSH_ERR_AGENT_NO_IDENTITIES; |
269 | goto out; |
270 | } |
271 | |
272 | /* Deserialise the response into a list of keys/comments */ |
273 | if ((idl = calloc(1, sizeof(*idl))) == NULL || |
274 | (idl->keys = calloc(num, sizeof(*idl->keys))) == NULL || |
275 | (idl->comments = calloc(num, sizeof(*idl->comments))) == NULL) { |
276 | r = SSH_ERR_ALLOC_FAIL; |
277 | goto out; |
278 | } |
279 | for (i = 0; i < num;) { |
280 | if ((r = deserialise_identity2(msg, &(idl->keys[i]), |
281 | &(idl->comments[i]))) != 0) { |
282 | if (r == SSH_ERR_KEY_TYPE_UNKNOWN) { |
283 | /* Gracefully skip unknown key types */ |
284 | num--; |
285 | continue; |
286 | } else |
287 | goto out; |
288 | } |
289 | i++; |
290 | } |
291 | idl->nkeys = num; |
292 | *idlp = idl; |
293 | idl = NULL; |
294 | r = 0; |
295 | out: |
296 | sshbuf_free(msg); |
297 | if (idl != NULL) |
298 | ssh_free_identitylist(idl); |
299 | return r; |
300 | } |
301 | |
302 | void |
303 | ssh_free_identitylist(struct ssh_identitylist *idl) |
304 | { |
305 | size_t i; |
306 | |
307 | if (idl == NULL) |
308 | return; |
309 | for (i = 0; i < idl->nkeys; i++) { |
310 | if (idl->keys != NULL) |
311 | sshkey_free(idl->keys[i]); |
312 | if (idl->comments != NULL) |
313 | free(idl->comments[i]); |
314 | } |
315 | free(idl); |
316 | } |
317 | |
318 | /* |
319 | * Sends a challenge (typically from a server via ssh(1)) to the agent, |
320 | * and waits for a response from the agent. |
321 | * Returns true (non-zero) if the agent gave the correct answer, zero |
322 | * otherwise. |
323 | */ |
324 | |
325 | |
326 | /* encode signature algorithm in flag bits, so we can keep the msg format */ |
327 | static u_int |
328 | agent_encode_alg(const struct sshkey *key, const char *alg) |
329 | { |
330 | if (alg != NULL && sshkey_type_plain(key->type) == KEY_RSA) { |
331 | if (strcmp(alg, "rsa-sha2-256" ) == 0 || |
332 | strcmp(alg, "rsa-sha2-256-cert-v01@openssh.com" ) == 0) |
333 | return SSH_AGENT_RSA_SHA2_256; |
334 | if (strcmp(alg, "rsa-sha2-512" ) == 0 || |
335 | strcmp(alg, "rsa-sha2-512-cert-v01@openssh.com" ) == 0) |
336 | return SSH_AGENT_RSA_SHA2_512; |
337 | } |
338 | return 0; |
339 | } |
340 | |
341 | /* ask agent to sign data, returns err.h code on error, 0 on success */ |
342 | int |
343 | ssh_agent_sign(int sock, const struct sshkey *key, |
344 | u_char **sigp, size_t *lenp, |
345 | const u_char *data, size_t datalen, const char *alg, u_int compat) |
346 | { |
347 | struct sshbuf *msg; |
348 | u_char *sig = NULL, type = 0; |
349 | size_t len = 0; |
350 | u_int flags = 0; |
351 | int r = SSH_ERR_INTERNAL_ERROR; |
352 | |
353 | *sigp = NULL; |
354 | *lenp = 0; |
355 | |
356 | if (datalen > SSH_KEY_MAX_SIGN_DATA_SIZE) |
357 | return SSH_ERR_INVALID_ARGUMENT; |
358 | if ((msg = sshbuf_new()) == NULL) |
359 | return SSH_ERR_ALLOC_FAIL; |
360 | flags |= agent_encode_alg(key, alg); |
361 | if ((r = sshbuf_put_u8(msg, SSH2_AGENTC_SIGN_REQUEST)) != 0 || |
362 | (r = sshkey_puts(key, msg)) != 0 || |
363 | (r = sshbuf_put_string(msg, data, datalen)) != 0 || |
364 | (r = sshbuf_put_u32(msg, flags)) != 0) |
365 | goto out; |
366 | if ((r = ssh_request_reply(sock, msg, msg)) != 0) |
367 | goto out; |
368 | if ((r = sshbuf_get_u8(msg, &type)) != 0) |
369 | goto out; |
370 | if (agent_failed(type)) { |
371 | r = SSH_ERR_AGENT_FAILURE; |
372 | goto out; |
373 | } else if (type != SSH2_AGENT_SIGN_RESPONSE) { |
374 | r = SSH_ERR_INVALID_FORMAT; |
375 | goto out; |
376 | } |
377 | if ((r = sshbuf_get_string(msg, &sig, &len)) != 0) |
378 | goto out; |
379 | /* Check what we actually got back from the agent. */ |
380 | if ((r = sshkey_check_sigtype(sig, len, alg)) != 0) |
381 | goto out; |
382 | /* success */ |
383 | *sigp = sig; |
384 | *lenp = len; |
385 | sig = NULL; |
386 | len = 0; |
387 | r = 0; |
388 | out: |
389 | freezero(sig, len); |
390 | sshbuf_free(msg); |
391 | return r; |
392 | } |
393 | |
394 | /* Encode key for a message to the agent. */ |
395 | |
396 | |
397 | static int |
398 | encode_constraints(struct sshbuf *m, u_int life, u_int confirm, u_int maxsign) |
399 | { |
400 | int r; |
401 | |
402 | if (life != 0) { |
403 | if ((r = sshbuf_put_u8(m, SSH_AGENT_CONSTRAIN_LIFETIME)) != 0 || |
404 | (r = sshbuf_put_u32(m, life)) != 0) |
405 | goto out; |
406 | } |
407 | if (confirm != 0) { |
408 | if ((r = sshbuf_put_u8(m, SSH_AGENT_CONSTRAIN_CONFIRM)) != 0) |
409 | goto out; |
410 | } |
411 | if (maxsign != 0) { |
412 | if ((r = sshbuf_put_u8(m, SSH_AGENT_CONSTRAIN_MAXSIGN)) != 0 || |
413 | (r = sshbuf_put_u32(m, maxsign)) != 0) |
414 | goto out; |
415 | } |
416 | r = 0; |
417 | out: |
418 | return r; |
419 | } |
420 | |
421 | /* |
422 | * Adds an identity to the authentication server. |
423 | * This call is intended only for use by ssh-add(1) and like applications. |
424 | */ |
425 | int |
426 | ssh_add_identity_constrained(int sock, const struct sshkey *key, |
427 | const char *, u_int life, u_int confirm, u_int maxsign) |
428 | { |
429 | struct sshbuf *msg; |
430 | int r, constrained = (life || confirm || maxsign); |
431 | u_char type; |
432 | |
433 | if ((msg = sshbuf_new()) == NULL) |
434 | return SSH_ERR_ALLOC_FAIL; |
435 | |
436 | switch (key->type) { |
437 | #ifdef WITH_OPENSSL |
438 | case KEY_RSA: |
439 | case KEY_RSA_CERT: |
440 | case KEY_DSA: |
441 | case KEY_DSA_CERT: |
442 | case KEY_ECDSA: |
443 | case KEY_ECDSA_CERT: |
444 | #endif |
445 | case KEY_ED25519: |
446 | case KEY_ED25519_CERT: |
447 | case KEY_XMSS: |
448 | case KEY_XMSS_CERT: |
449 | type = constrained ? |
450 | SSH2_AGENTC_ADD_ID_CONSTRAINED : |
451 | SSH2_AGENTC_ADD_IDENTITY; |
452 | if ((r = sshbuf_put_u8(msg, type)) != 0 || |
453 | (r = sshkey_private_serialize_maxsign(key, msg, maxsign, |
454 | NULL)) != 0 || |
455 | (r = sshbuf_put_cstring(msg, comment)) != 0) |
456 | goto out; |
457 | break; |
458 | default: |
459 | r = SSH_ERR_INVALID_ARGUMENT; |
460 | goto out; |
461 | } |
462 | if (constrained && |
463 | (r = encode_constraints(msg, life, confirm, maxsign)) != 0) |
464 | goto out; |
465 | if ((r = ssh_request_reply(sock, msg, msg)) != 0) |
466 | goto out; |
467 | if ((r = sshbuf_get_u8(msg, &type)) != 0) |
468 | goto out; |
469 | r = decode_reply(type); |
470 | out: |
471 | sshbuf_free(msg); |
472 | return r; |
473 | } |
474 | |
475 | /* |
476 | * Removes an identity from the authentication server. |
477 | * This call is intended only for use by ssh-add(1) and like applications. |
478 | */ |
479 | int |
480 | ssh_remove_identity(int sock, struct sshkey *key) |
481 | { |
482 | struct sshbuf *msg; |
483 | int r; |
484 | u_char type, *blob = NULL; |
485 | size_t blen; |
486 | |
487 | if ((msg = sshbuf_new()) == NULL) |
488 | return SSH_ERR_ALLOC_FAIL; |
489 | |
490 | if (key->type != KEY_UNSPEC) { |
491 | if ((r = sshkey_to_blob(key, &blob, &blen)) != 0) |
492 | goto out; |
493 | if ((r = sshbuf_put_u8(msg, |
494 | SSH2_AGENTC_REMOVE_IDENTITY)) != 0 || |
495 | (r = sshbuf_put_string(msg, blob, blen)) != 0) |
496 | goto out; |
497 | } else { |
498 | r = SSH_ERR_INVALID_ARGUMENT; |
499 | goto out; |
500 | } |
501 | if ((r = ssh_request_reply(sock, msg, msg)) != 0) |
502 | goto out; |
503 | if ((r = sshbuf_get_u8(msg, &type)) != 0) |
504 | goto out; |
505 | r = decode_reply(type); |
506 | out: |
507 | if (blob != NULL) { |
508 | explicit_bzero(blob, blen); |
509 | free(blob); |
510 | } |
511 | sshbuf_free(msg); |
512 | return r; |
513 | } |
514 | |
515 | /* |
516 | * Add/remove an token-based identity from the authentication server. |
517 | * This call is intended only for use by ssh-add(1) and like applications. |
518 | */ |
519 | int |
520 | ssh_update_card(int sock, int add, const char *reader_id, const char *pin, |
521 | u_int life, u_int confirm) |
522 | { |
523 | struct sshbuf *msg; |
524 | int r, constrained = (life || confirm); |
525 | u_char type; |
526 | |
527 | if (add) { |
528 | type = constrained ? |
529 | SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED : |
530 | SSH_AGENTC_ADD_SMARTCARD_KEY; |
531 | } else |
532 | type = SSH_AGENTC_REMOVE_SMARTCARD_KEY; |
533 | |
534 | if ((msg = sshbuf_new()) == NULL) |
535 | return SSH_ERR_ALLOC_FAIL; |
536 | if ((r = sshbuf_put_u8(msg, type)) != 0 || |
537 | (r = sshbuf_put_cstring(msg, reader_id)) != 0 || |
538 | (r = sshbuf_put_cstring(msg, pin)) != 0) |
539 | goto out; |
540 | if (constrained && |
541 | (r = encode_constraints(msg, life, confirm, 0)) != 0) |
542 | goto out; |
543 | if ((r = ssh_request_reply(sock, msg, msg)) != 0) |
544 | goto out; |
545 | if ((r = sshbuf_get_u8(msg, &type)) != 0) |
546 | goto out; |
547 | r = decode_reply(type); |
548 | out: |
549 | sshbuf_free(msg); |
550 | return r; |
551 | } |
552 | |
553 | /* |
554 | * Removes all identities from the agent. |
555 | * This call is intended only for use by ssh-add(1) and like applications. |
556 | * |
557 | * This supports the SSH protocol 1 message to because, when clearing all |
558 | * keys from an agent, we generally want to clear both protocol v1 and v2 |
559 | * keys. |
560 | */ |
561 | int |
562 | ssh_remove_all_identities(int sock, int version) |
563 | { |
564 | struct sshbuf *msg; |
565 | u_char type = (version == 1) ? |
566 | SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES : |
567 | SSH2_AGENTC_REMOVE_ALL_IDENTITIES; |
568 | int r; |
569 | |
570 | if ((msg = sshbuf_new()) == NULL) |
571 | return SSH_ERR_ALLOC_FAIL; |
572 | if ((r = sshbuf_put_u8(msg, type)) != 0) |
573 | goto out; |
574 | if ((r = ssh_request_reply(sock, msg, msg)) != 0) |
575 | goto out; |
576 | if ((r = sshbuf_get_u8(msg, &type)) != 0) |
577 | goto out; |
578 | r = decode_reply(type); |
579 | out: |
580 | sshbuf_free(msg); |
581 | return r; |
582 | } |
583 | |